Friday, April 15, 2011

DataGrid Column with mixed ComboBox and TextBox cells.

This turned out to be a bit tricky. I did try a few of should-work solutions that I found through Google, but for whatever various reasons, they just didn't quite do what I needed them to (or in a couple of mysterious cases, not work at all).

The basic issue is that I had a DataGrid with one column for a description and a second column for data. I wanted to the second column to be editable or have a drop down combo box for the user to select an item from a list. I quickly learned that though it is possible to have a built-in DataGridComboColumn, you can't necessarily turn those off, or have a default when there aren't multiple items to chose from. I also wanted two-way binding with these cells. There are some clever attempts out there, and perhaps some of those may work for you with what you actually intend. I came up with my own method. To summarize:

1. Create the columns, in my case just two, in the Xaml definition of the DataGrid, the first one being a TextColumn, the second a TemplateColumn. Any extra columns that will have mixed formatting should be TemplateColumns.
2. Got my list of cells which only exists after the DataGrid has been rendered.
3. Create the two templates *in code* (one ComboBox for a drop-down selection cell, one TextBox for a 'simple' data entry cell).
4. Create a *map* of templates to correspond to the respective cell. This in practice was just a List<DataTemplate> of DataTemplates.
5. Apply the map of templates onto the rendered cells.


FindVisualChild has got to be one of the most popular WPF methods out there that is user-generated. I wouldn't be surprised if some kind of equivalent method makes its way into a future version of .NET. A simple Google search will turn up this method but I'll post yet another copy here for transparency's sake:


Code:
/// <summary> /// Look for a child element /// </summary> /// <typeparam name="childItem">The element being searched for</param> /// <param name="obj">The visual tree container</param> /// <returns>The element being searched for, or null if it is not found.</param> private childItem FindVisualChild<childItem>(DependencyObject obj) where childItem : DependencyObject { if (obj == null) { return null; } int childCount = VisualTreeHelper.GetChildrenCount(obj); for (int] i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++) { DependencyObject child = VisualTreeHelper.GetChild(obj, i); if (child != null && child is childItem) { return (childItem)child; } else { childItem childOfChild = FindVisualChild<childItem>(child); if (childOfChild != null) { return childOfChild; } } } return null; }
So, having this in my toolkit, I've created a private list of DataGrid cells:
private List<DataGridCell> dataCellList = new List<DataGridCell>();

To get my cells one by one using this method:


Code:
/// <summary> /// Get the cell of the datagrid. /// </summary> /// <param name="dataGrid">The data grid in question</param> /// <param name="cellInfo">The cell information for a row of that datagrid</param> /// <param name="cellIndex">The row index of the cell to find if we decide to use this to get the cell</param>    /// <returns>The cell or null</param> private DataGridCell TryToFindGridCell(DataGrid dataGrid, DataGridCellInfo cellInfo, int cellIndex = -1) { DataGridRow row; DataGridCell result = null; if (cellIndex < 0) { row = (DataGridRow)dataGrid.ItemContainerGenerator.ContainerFromItem(cellInfo.Item); } else { row = (DataGridRow)dataGrid.ItemContainerGenerator.ContainerFromIndex(cellIndex); } if (row != null) { int columnIndex = dataGrid.Columns.IndexOf(cellInfo.Column); if (columnIndex > -1) { DataGridCellsPresenter presenter = this.FindVisualChild<DataGridCellsPresenter>(row); result = presenter.ItemContainerGenerator.ContainerFromIndex(columnIndex) as DataGridCell; } } return result; }
This method is called indirectly by the Window_Activated handler. This wonderful handler will keep being called until all the visual elements are validated (as far as I can tell and have experienced). It then stops, but is recalled every time your window is opened (even on a Show(), so keep that in mind). The method I have setup (called from the handler) is below:

Code:
   /// <summary> /// This method sorts through the rendered grid and gets all of the cells. /// Then, depending on whether there are multiple selection in the combobox  /// defined in the template, the cell is either a combo box or a textbox. /// </summary>                  private void PrivateDataGrid_Initialized() { DataGrid dataGrid = this.dataGrid1;
            int rowNumber = 0; 
            
for (int k = 0; k < dataGrid.Columns.Count; k++) { for (int i = 0; i < dataGrid.Items.Count; i++) { if (this.dataCellList.Count < (dataGrid.Columns.Count * dataGrid.Items.Count)) { int j = i + (dataGrid.Items.Count * k); this.dataCellInfoList.Add(new DataGridCellInfo(dataGrid.Items[i], dataGrid.Columns[k])); this.dataCellList.Add(this.TryToFindGridCell(dataGrid, this.dataCellInfoList[j], i)); } } } //// Just clear out the list and let this handler try again until we get non-null cells. if (this.dataCellList[0] == null) { this.dataCellList.Clear(); } else { //// After we start getting valid cells, assign the correct template. this.columnIndex = 1; for (int k = this.columnIndex * dataGrid.Items.Count; k < (this.columnIndex + 1) * (this.dataCellList.Count - this.dataGrid.Items.Count); k++) { this.dataCellList[k].ContentTemplate = this.dataTemplateList[rowNumber]; rowNumber++; } } 
}        }
And now we have our cells. As you can see, I also have a list of cell infos, List<DataGridCellInfo>(), to push as parameters into the TryToFindGridCell method. Here I'm also assigning my templates.

So let's get that list of templates put together. I use a collection (an ObservableCollection in fact) to hold my data lists. If a particular list has more than one string in it, I put a ComboBox template in the DataTemplate list, otherwise if there is just one string in the list, I put a simple TextBox template in the DataTemplate list. I call this method after I have done my data binding for my grid. The code for my list of templates:

Code:
/// <summary>
/// Create a list of DataTemplates to display either a combo box
/// or a plain text box.
/// </summary>
private void CreateDataTemplates()
{
    //// If the list has multiple entries,
    //// use a combo box to diplay the options.
    //// Otherwise, just use a plain text box.
    for (int i = 0; i < this.OptionList.Count; i++)
    {
       if (this.OptionList[i].Count > 1)
       {
          string bindingString = "OptionList" + "[" + i.ToString() + "]";
          Binding comboBinding = new Binding(bindingString);
          comboBinding.Mode = BindingMode.TwoWay;
          comboBinding.RelativeSource = new RelativeSource(RelativeSourceMode.FindAncestor) { AncestorType = typeof(Window) };
                     #region ComboBox Template
          DataTemplate dataCellComboTemplate = new DataTemplate();
          dataCellComboTemplate.RegisterName("comboBoxCellTemplate", dataCellComboTemplate);
          ////Set up the stack panel
          FrameworkElementFactory spFactory = new FrameworkElementFactory(typeof(StackPanel));
          spFactory.Name = "stackPanelFactory";
                     FrameworkElementFactory cbFactory = new FrameworkElementFactory(typeof(ComboBox));
          cbFactory.Name = "comboBoxFactory";
          cbFactory.SetValue(ComboBox.NameProperty, "dataComboBox");
          cbFactory.SetValue(ComboBox.HeightProperty, Double.NaN);
          cbFactory.SetValue(ComboBox.SelectedIndexProperty, 0);
          cbFactory.AddHandler(ComboBox.SelectedEvent, new RoutedEventHandler(this.ComboBox_SelectedItem));
          cbFactory.SetBinding(ComboBox.DataContextProperty, new Binding());
          cbFactory.SetBinding(ComboBox.ItemsSourceProperty, comboBinding);
          spFactory.AppendChild(cbFactory);
          dataCellComboTemplate.VisualTree = spFactory;
          #endregion
                     this.dataTemplateList.Add(dataCellComboTemplate);
    }
    else
    {
          DataTemplate dataCellTextTemplate = new DataTemplate();
          dataCellTextTemplate.RegisterName("textBoxCellTemplate", dataCellTextTemplate);
          string bindingString = "OptionList" + "[" + i.ToString() + "]" + "[" + "0" + "]";
          Binding textBinding = new Binding(bindingString);
          textBinding.Mode = BindingMode.TwoWay;
          textBinding.RelativeSource = new RelativeSource(RelativeSourceMode.FindAncestor) { AncestorType = typeof(Window) };
                     #region Plain TextBox Template
          FrameworkElementFactory plainSpFactory = new FrameworkElementFactory(typeof(StackPanel));
          plainSpFactory.Name = "plainStackPanelFactory";
                     FrameworkElementFactory plainTextBoxFactory = new FrameworkElementFactory(typeof(TextBox));
          plainTextBoxFactory.Name = "cellTextBlockFactory";
          plainTextBoxFactory.SetValue(TextBox.NameProperty, "dataTextBlock");
          plainTextBoxFactory.SetValue(TextBox.HeightProperty, Double.Parse("25"));
          plainTextBoxFactory.SetValue(TextBox.WidthProperty, Double.NaN);
          plainTextBoxFactory.SetBinding(TextBox.DataContextProperty, new Binding());
          plainTextBoxFactory.SetBinding(TextBox.TextProperty, textBinding);
          plainSpFactory.AppendChild(plainTextBoxFactory);
          dataCellTextTemplate.VisualTree = plainSpFactory;
          #endregion
                     this.dataTemplateList.Add(dataCellTextTemplate);
      }
   }
}
I think that is pretty much it. This works. One nice thing about it as well is that, if there are more items added to my DataGrid, I don't have to touch any of this code again. I only have to update my OptionList and all the rest just falls into place. If I have to add columns, it's a bit more work. I will have to update the Xaml, and I will probably go through the trouble of adding some code to sort through the columns and apply the templates accordingly (so then likewise, I don't have to touch it again for either new columns or rows).

One note about separation of concerns: You could go ahead and generate those columns in code as well and apply the Itemsource appropriately. This would have the display only show the base elements while your code handles how they are displayed and what is displayed. Your business logic and data are naturally separated off onto there own concerns. I don't do this View-View separation here as I'm not so concerned about this level of separation for my particular case. But it is something to keep in mind, especially if things get a lot more complicated.

Another thing: In my research for this solution and help with other issues regarding Xaml elements, I've come across the general notion that it is not a good idea to go overwriting templates. I don't subscribe to this idea and I don't think Microsoft does either. Just look at the interfaces they make available and methods they've developed in order to make it so easy for us to do just exactly that.

Tuesday, March 29, 2011

ContentPresenter in a ControlTemplate

This will control the location of the display of all elements on your form. Note that it will even 'offset' the elements inside a StackPanel (perhaps causing them to be cut off). Just a hint for those experiencing the strange effect of having elements appear in strange locations relative to your panel or Grid or whatever container you have of your stack panel.

Friday, March 25, 2011

I... I... I think I may be in love with Observable Collections

     Oh Observable Collection
     You satisfy my selection

     Like a lonely flower, a desert rose
     When there is a new one, everyone knows

     If only we could use properties accessed internal
     Using properties accessed public may lead to an inferno

     Oh Observable Collection, I can't stay mad at you.
     Throughout my meandering code, your notifications are pure and true.

Observable collections are a handy way to get data transferred across multiple windows and XAML documents. I have found them versatile and useful:

XAML Code:
. . . <TextBlock Height="36" Margin="0,6,56,0" Name="textTimeBlock" Width="200" Text="{Binding Path=TimeCollection[0], RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}" FontSize="24" TextAlignment="Center" /> . . .

Corresponding C#.

Code:
. . . private readonly ObservableCollection<string> timeCollection; . . . /// <summary> /// Gets the updating time /// </summary> public ObservableCollection<string> TimeCollection { get { return this.timeCollection; } }
Just set up a simple DispatcherTimer tick to update the collection every second...
Code:
this.dispatcherTimer.Tick += new EventHandler(delegate(object s, EventArgs a) { if (this.timeCollection.Count == 1) { this.timeCollection.RemoveAt(0); } this.timeCollection.Add(DateTime.Now.ToLongTimeString()); });
... and away you go.

Now you may have noticed a slight peculiarity, that the private field 'timeCollection' is readonly. This is necessary because I don't want anybody who comes along and grabs the exe to be able to just assign their own collection over my collection(!). Since the property must be public for the notifications to make it to the XAML code, this is the only way. You still expose, but atleast no one can overwrite. Hopefully in some future version of .NET, Microsoft will allow this notifications process to work with internal accessed properties as well. I can't see any obvious reason why they shouldn't.

Note that despite the fact that your collection is readonly, you will still have access to all of the methods of that collection and, perhaps counter intuitively, the ability to modify the collection outside of the constructor or initialization. The are two ways around this:

1. Use a ReadOnlyObservableCollection, though this does have the issue of a protected CollectionChanged event. I had several issues trying to get this to work with Xaml code. Most notoriously "A TwoWay or OneWayToSource binding cannot work on the read-only property." I tried everything I could find in the top 30 or 40 Google results. No love.

2. Extend the ObservableCollection class and hide all of the methods that can actually modify the collection. Since you can't make methods of the base class less accessible, what I did was just have the public methods perform no operation, and put in the comments that the collection is readonly. I then created equivalent internal methods with a different signature, which then called the respective base methods. This way, nobody can overwrite the collection or overwrite the actual values of the collection. The class itself is marked public.

Tuesday, March 22, 2011

C++, Delphi, Enumerations, Enum, switch, case,

In Borland Delphi, the way to check if a parameter of an enum type has a particular value is:
Code:
enum TGridDrawState {Grids_3, gdFocused, gdFixed}; . . . procedure SomeProcedure(Value: Int;ARect: TRect; AState: TGridDrawState); . . . if (gdFixed in AState) then . . . etc...

The C++ equivalent is:

Code:
void __fastcall SomeMethod(int Value, TRect Rect, TGridDrawState AState) { . . . if (AState.Contains(gdFixed)) { . . . } . . . etc....
Took me far too much time searching through the code and the internet to dig this up.

WPF DataGrid Binding to internal properties.

It seems that you can't bind to internal properties. They *must* be public. Otherwise you receive no notifications.

How to build a Borland equivalent Smooth progress bar

Embarcadero C++ Builder. How to build a Borland equivalent Smooth progress bar. This code is basically the translation of the description given for a SmoothProgress Bar at the Microsoft Support site http://support.microsoft.com/kb/816195.
The SmoothProgressBar replaces the standard windows default progress indicator for custom GUI's and answers several drawbacks to not only the default progressbar, but many other Windows-default components such as lack of customization, even in development environments such as Microsoft's own Visual Studio.

I will only post the the key methods Set_Value and OnPaint as they are the critical sections anyway and provide all the examples needed to code the other simpler sections described in the Microsoft article above. I should note this code, though it will be compilable working C++, will nonetheless still be a bit pseudo codish. This is because I had to do some tweaks and fudges here and there to get some more finesse behavior. Part of this is due to the fact that I derived this progress bar directly from TProgressBar. I had to 'hide' some of its base properties and replace them with my own equivalent. I also had to play around a bit with the drawing of some boundary lines to get this to fit into the default TProgressBar border properly. You could most certainly do this deriving directly from the more generic TComponent as well (similar to the article above).

Set_Value(...):


Code:
/// <summary> /// Set_Value /// </summary> /// <param name="NewValue">The new value of the progressbar.</param> void __fastcall SmoothProgressBar::Set_Value(int NewValue) { int oldValue = value; // Make sure that the value does not stray outside the valid range. if (NewValue < minimum) { value = minimum; } else if (NewValue > maximum - 1) { value = maximum - 1; shrinkVertical = 3; lastLineFlag = true; } else { value = NewValue; } // Invalidate only the changed area. float percent; TRect newValueRect(this->ClientRect); TRect oldValueRect(this->ClientRect); newValueRect.Left = newValueRect.Left; // Use a new value to calculate the rectangle for progress. percent = (float)(value - minimum) / (float)(maximum - minimum); newValueRect.Right = newValueRect.Left + (int)((float)newValueRect.Width() * percent); newValueRect.Top = newValueRect.Top + shrinkVertical; newValueRect.Bottom = newValueRect.Bottom - shrinkVertical; oldValueRect.Left = oldValueRect.Left; // Use an old value to calculate the rectangle for progress. percent = (float)(oldValue - minimum) / (float)(maximum - minimum); oldValueRect.Right = oldValueRect.Left + 2 + (int)((float)oldValueRect.Width() * percent); oldValueRect.Top = oldValueRect.Top + shrinkVertical; oldValueRect.Bottom = oldValueRect.Bottom - shrinkVertical; TRect updateRect; // Find only the part of the screen that must be updated. if (newValueRect.Width() > oldValueRect.Width()) { updateRect.Left = oldValueRect.Width(); updateRect.IntersectRect(newValueRect, oldValueRect); } else { updateRect.Left = newValueRect.Width(); updateRect.IntersectRect(oldValueRect, newValueRect); } InvalidateRect(this->Handle, &updateRect, 1); SmoothProgressBar::OnPaint(this); }
This, on the face of things, looks like (and pretty much is) a line for line translation. Borland clearly has some of its own little ways of doing things, but the equivalents are obvious (though not so easy to find! Hence my post here for others that may be looking). One thing that may jump out is the explicit call to OnPaint. TProgressBar does not seem to inherit this handler from its parent. I probably could have created a custom event without much additional complexity, but this was already simple, so I figured a direct call wouldn't hurt.

OnPaint(...):

Code:
/// <summary> /// On Paint /// </summary> /// <param name="Sender">The sender of this event</param> void __fastcall SmoothProgressBar::OnPaint(TObject *Sender) { HDC dc; HPEN dcPen; dc = GetDC(this->Handle); this->Brush->Color = this->progressBar_Color; float percent = (float)(value - minimum) / (float)(maximum - minimum); TRect clientRect(this->ClientRect); FillRect(dc, &clientRect, this->Brush->Handle); dcPen = CreatePen(PS_SOLID, 1, this->progressBar_Color); SelectObject(dc, dcPen); SetBkColor(dc, this->progressBar_Color); if (lastLineFlag == true) // Global flag { MoveToEx(dc, clientRect.Right - 1, clientRect.Top + 1, NULL); LineTo(dc, clientRect.Right - 1, clientRect.Bottom - 1); MoveToEx(dc, clientRect.Right, clientRect.Top + 1, NULL); LineTo(dc, clientRect.Right, clientRect.Bottom - 1); } lastLineFlag = false; }
Embarcadero clearly relies heavily on the Windows API here for basic graphical functions. What stands out that is different from the Microsoft example is that I don't draw a border. Since I inherited directly from TProgressBar, I got that border for free. However, I did have to do some fudging to get my colored rectangles to fit (not shown) and in particular to not overwrite the base class progress bar boundary at the end (the line logic in the code above).

I hope this saves somebody some time in trudging through Google search results, questionably useful help files, and quite a bit of raw source code looking for similar examples to what I was trying to accomplish.

Just one more little blip. Since I derived this from TProgressBar (which is really just a shell around the Windows Progress bar), I did have to hide and lock-in the defaults of some of the base properties. I will give an example of hiding the Position property:

Code:
public: __property int Position = {read = position, default = 0}; // Hide the base
This is pretty straightforward... and the only direct example you will find on the net. Notice that to override the property, you must override it in the equivalent access section (public or published). Also, I left out the 'write' section, rendering the property read-only.