Intro
Previously I did a post on the “Control Local Values bug” and how a subtle bug can be introduced when setting dependency properties of controls to local values. In WPF 4.0 (dev10), a mechanism was added to the property engine to solve this problem. Here are the new APIs in dev10:
public class DependencyObject
{
// Sets the value of a dependency property without changing its value source.
public void SetCurrentValue(DependencyProperty dp, object value);
}
public struct ValueSource
{
public bool IsCurrent { get; }
}
Basically, when using DependencyObject.SetCurrentValue instead of DependencyObject.SetValue you set its effective value but its ValueSource remains the same. This is very similar to coercion.
Sample Test App
To make it more clear, I wrote a sample test app that shows the BaseValueSource and ValueSource properties when changing a DependencyProperty (DP) via SetCurrentValue. I created a custom control with a DP, TitleProperty, where I track its values. In addition, I setup several scenarios where the TitleProperty is initially set by a particular BaseValueSource. I’ll go through one scenario here.
I setup the custom control where the TitleProperty is set from a Style with a binding.
<Style x:Key="StyleBindingCustomControlStyle" TargetType="{x:Type local:CustomControl}">
<Setter Property="Title"
Value="{Binding RelativeSource={RelativeSource Self},
Path=WorkingTag,
StringFormat='Set via Style w/ Binding: {0}'}" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:CustomControl}">
<Button Content="{TemplateBinding Title}"
Background="{TemplateBinding Background}" />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<GroupBox Header="Style Binding">
<local:CustomControl x:Name="CustomControl_Style_Binding"
Style="{StaticResource StyleBindingCustomControlStyle}"/>
</GroupBox>
Initially it will look like this:
Notice the BaseValueSource is ‘Style’, IsExpression is ‘True’, and IsCurrent is ‘False’ as expected. After pressing the “Set Current Value” button the values will update accordingly:
Now the local value has updated to a new string that I set and IsCurrent has updated to ‘True’. Also notice that BaseValueSource and IsExpression have not changed which is also expected. If you press “Clear Local Value” the values will update back to their initial values. Now to bring back the original issue with the control local values bug, if you were to set or clear the local value any previous BaseValueSource would be overwritten and any expression lost. I have created other scenarios in the sample with different BaseValueSource behavior so you can understand how setting the current value behaves in those scenarios. Please be sure to check it out below.
Usage Recommendation
For a control developer, the general recommendation is to always use DependencyObject.SetCurrentValue over DependencyObject.SetValue in Control code. You’ll notice that our stock controls in the 4.0 framework have all been updated to use this API instead of setting the properties with local values.
Here is the sample app which works with VS 2010 beta 1.
As many of you probably already know, dev10 beta1 is available for public download. Some of the additions to Beta1 on the WPF Controls side include:
· MultiTouch
· EasingFunctions for animations
· DataGrid, Calendar, DatePicker, and VisualStateManager in PresentationFramework.dll
· New APIs to support the control local values bug
· And tons of bug fixes on the new controls as well as existing control and element services areas
I’ll be going over some of these new features in more depth so stay tuned!
The WPF team has just released an MVVM Toolkit which contains an overview and walkthrough of the MVVM pattern, a full Messenger sample app using MVVM, as well as a Visual Studio template for MVVM. The template creates a WPF application with folders for Views, ViewModels, Models, and Commands. What I really like about it is the DelegateCommand class which is similar to
RelayCommand. One addition to this version of the Command implementation is the ability to manually control when OnCanExecuteChanged is called or let the CommandManager control it. The common scenario will be the latter, but allowing the option for the former will allow greater control over application performance due to the many calls to OnCanExecuteChanged. In the future, the plan is to get this as well as other MVVM concepts into the WPF framework. You can learn more about it
here and download it
here. Check it out and we would love to hear your feedback.
The WPF test team is hard at work to provide value through a simple and componetized library of public test APIs. The WPF TestApi library has just released a v0.2 which includes new visual verification features, command line parsing features, an auomated application controller, and new unit test samples.
Ivo has a great post about the visual verification APIs and usage that you can view here. Please try it out and let us know your feedback!
1. DataGridColumn.SortDirection does not actually sort the column.
DataGridColumn.SortDirection is used to queue the visual arrow in the DataGridColumnHeader to point up, down, or to not show. To actually sort the columns other than clicking on the DataGridColumnHeader, you can set the DataGrid.Items.SortDescriptions programmatically.
Here is an example of an ICommand used to trigger grouping of a particular column in the DataGrid. In addition to grouping it, I also sort it on the same column name that I grouped it with. Note that this code will programmatically sort the column but will not update the sorting arrow since I did not update SortDirection.
public ICommand GroupColumn
{
get
{
if (_groupColumn == null)
{
_groupColumn = new RelayCommand<object>(
(param) =>
{
// the delegate is passed in the column header
// name which matches the Binding on the column
string header = param as string;
//
// SearchResults is the ItemsSource that DataGrid
// is bound to. Below I programmatically group and
// sort the collection based on the header name
//
SearchResults.GroupDescriptions.Add(
new PropertyGroupDescription(header));
SearchResults.SortDescriptions.Add(
new SortDescription(header, ListSortDirection.Ascending));
});
}
return _groupColumn;
}
}
2. DataGridColumns cannot be styled as they are not FrameworkElements.
DataGridColumn derives from DependencyObject, not FrameworkElement, and therefore cannot be styled like many of the other elements on DataGrid. You can think of a DataGridColumn as data storage for what each cell’s content will be template with and a couple other properties like the width of each cell and how it is sorted. They are not meant to be visual elements like DataGridCell or DataGridRow.
3. While editing a row, the row is committed by pressing Enter, pressing tab on the last cell of the row, losing focus, clicking the column header to sort the column, programmatically calling DataGrid.CommitEdit, updating DataGrid.CurrentCell to a cell outside the current row, or updating DataGrid.CurrentItem.
The actions described above are the default actions to commit a row. One action that deserves a little more clarification is the commit on losing focus. Each DataGridCell listens for changes to its IsKeyboardFocusWithin property and when it changes it updates the CurrentCell accordingly (which causes the row to commit).
4. Disable commit behavior through the DataGrid.RowEditEnding event
In the previous gotcha I explain all the ways that a row can be committed. If you want to customize that behavior, the best way to do it is through DataGrid.RowEditEnding. This event is fired at the start of any and all row commits and you have the ability to cancel the commit behavior in this event through DataGridRowEditEndingEventArgs.Cancel. See more about editing behavior here.
5. Customize focus behavior after a row commit through the DataGrid.RowEditEnding event
While there isn’t a DataGrid.RowEditEnded at this time (March 2009 Release), you can use the Dispatcher trick to get custom behavior after a row commit has occurred. In DataGrid.RowEditEnding, you can specify a Dispatcher.BeginInvoke so that the code in the delegate is executed after the row commit has completed. Here is some example code which sets focus to the second cell of the next row and puts it in edit mode but only when I press tab on the last cell of the row before the NewItemPlaceholder.
private void OnRowEditEnding(object sender, DataGridRowEditEndingEventArgs e)
{
if (e.EditAction == DataGridEditAction.Commit)
{
//
// custom commit action:
// tab specific behavior for editing workflow.
// moves to the next row and opens the second cell for edit
// if the next row is the NewItemPlaceholder
//
if (_isTabPressed &&
e.Row.Item == dataGrid.Items[dataGrid.Items.Count - 2])
{
// set the new cell to be the last row and the second column
int colIndex = 1;
var rowToSelect = dataGrid.Items[dataGrid.Items.Count - 1];
var colToSelect = dataGrid.Columns[colIndex];
int rowIndex = dataGrid.Items.IndexOf(rowToSelect);
// select the new cell
dataGrid.SelectedCells.Clear();
dataGrid.SelectedCells.Add(
new DataGridCellInfo(rowToSelect, colToSelect));
this.Dispatcher.BeginInvoke(new DispatcherOperationCallback((param) =>
{
// get the new cell, set focus, then open for edit
var cell = Helper.GetCell(dataGrid, rowIndex, colIndex);
cell.Focus();
dataGrid.BeginEdit();
return null;
}), DispatcherPriority.Background, new object[] { null });
}
}
}
See more WPF DataGrid gotchas.
Launching a custom dialog for editing on the DataGrid is another somewhat common request that I see from the discussion list. I thought I would provide a sample but at the same time implement it with the MVVM pattern. The requirement that I will use on the sample is that the editing mechanism can only come from the editing dialog that I provide.
I decided that the way to launch the custom dialog will be through double-clicking on the row. To get that behavior, I will use the attached behavior pattern on DataGridRow. The behavior is defined like so:
public class DataGridRowBehavior
{
public static DependencyProperty OnDoubleClickProperty = DependencyProperty.RegisterAttached(
"OnDoubleClick",
typeof(ICommand),
typeof(DataGridRowBehavior),
new UIPropertyMetadata(DataGridRowBehavior.OnDoubleClick));
public static void SetOnDoubleClick(DependencyObject target, ICommand value)
{
target.SetValue(DataGridRowBehavior.OnDoubleClickProperty, value);
}
private static void OnDoubleClick(DependencyObject target, DependencyPropertyChangedEventArgs e)
{
var element = target as DataGridRow;
if ((e.NewValue != null) && (e.OldValue == null))
{
element.PreviewMouseDoubleClick += OnPreviewMouseDoubleClick;
}
else if ((e.NewValue == null) && (e.OldValue != null))
{
element.PreviewMouseDoubleClick -= OnPreviewMouseDoubleClick;
}
}
private static void OnPreviewMouseDoubleClick(object sender, MouseButtonEventArgs e)
{
UIElement element = (UIElement)sender;
ICommand command = (ICommand)element.GetValue(DataGridRowBehavior.OnDoubleClickProperty);
command.Execute(e);
}
}
The behavior is then used on the DataGridRow like this:
<Style TargetType="{x:Type toolkit:DataGridRow}">
<Setter Property="local:DataGridRowBehavior.OnDoubleClick"
Value="{Binding ShowEditItemDialogCommand}" />
</Style>
What’s happening is the OnDoubleClick attached property listens for a property change on itself. I define an ICommand, ShowEditItemDialogCommand, on OnDoubleClick which then hooks up the actual event handler, PreviewMouseDoubleClick, on the target element. In that event handler, OnPreviewMouseDoubleClick, I retrieve the value of OnDoubleClick which is my ICommand and execute it. So now we have the behavior to launch a dialog through a double-click action. The next thing is to define the ICommand ShowEditItemDialogCommand.
I have defined a DataGridRow ViewModel which hosts the actual item data as well as any properties that will drive the view (the DataGridRow). It also will host any commands on a DataGridRow which for our purposes will be the ShowEditItemDialogCommand. My commands are also implemented using the RelayCommand mechanism that is discussed on this MSDN article. So here is the basic implementation:
public class ItemViewModel : ViewModelBase
{
private ICommand _editItemCommand;
public ICommand ShowEditItemDialogCommand
{
get
{
if (_editItemCommand == null)
{
_editItemCommand = new RelayCommand(
() =>
{
// pass the item to the dialog
var dlg = new EditItemDialog(Item);
if (dlg.ShowDialog() == true)
{
// retrieve the updated item from the dialog
Item = dlg.Result.Item;
}
});
}
return _editItemCommand;
}
}
}
So when DataGridRowBehavior.OnPreviewMouseDoubleClick executes the command, my ShowEditItemDialogCommand will initiate the launch of the new dialog. The dialog that is being launched is basically a new Window which is considered a View. From a MVVM purist perspective this does not belong in a ViewModel. To handle this you can use the mediator design pattern to host the communication between the ViewModel and the View. There are a couple ways to implement this pattern which include implementing it as an observer pattern or using a specialized communication mechanism through interfaces and references. I’ve chosen the latter and in the sample code you can see I’ve named it EditDialogDirector to be clear on its behavior. I won’t show all the details here.
Now, I said earlier that I set a requirement that editing can only come from the dialog that I provide. While there are a lot of ways to customize the editing behavior, I decided to keep it really simple and set DataGrid.IsReadOnly to true. It does mean that I will lose the default editing capabilities that DataGrid provides, but I won’t have to keep state on DataGrid begin and commit edits. There are some other details that I’m leaving out but the full sample code below will give you a good idea of how everything is pieced together. Also, there is a lot of great reference material and samples on MVVM to further enhance your knowledge on the subject.
http://blogs.msdn.com/johngossman/archive/2005/10/08/478683.aspx
http://karlshifflett.wordpress.com/mvvm/
http://www.codeproject.com/KB/WPF/InternationalizedWizard.aspx
Download the full sample here.
Since the release of the WPF DataGrid there have been several common patterns of questions that developers were asking on the discussion list. I thought that I would capture some of that here so it would be easy to find. I'll also try to keep it as short as possible and refer to other links for more information. These gotchas are in no particular order.
1. DataGridColumns are not part of the visual tree.
Since a DataGridColumn has a Binding DP it is easy to think that bindings to other DataGridColumn DPs will work just the same but they don't. Below is an example showing a DataGridCheckBoxColumn setting the Binding DP and CanUserSort through data binding. While Binding will bind correctly, CanUserSort will not.
<dg:DataGridCheckBoxColumn Header="Selection"
Binding="{Binding IsSelected}"
CanUserSort="{Binding CanSort}"/>
Note that the reason Binding does work is because the internal code dynamically sets the binding of DataGridColumn.Binding to the DataGridCell.Content. It does not do so for the other DataGridColumn DPs.
Workaround => Check out Jaime’s post on how to get the DataGridColumns to use the same DataContext as the DataGrid.
2. DataGridColumn.Binding will automatically coerce the binding to BindingMode.TwoWay and UpdateSourceTrigger.Explicit
No matter how you declare the binding for DataGridColumn.Binding, internally it will coerce the BindingMode to TwoWay and UpdateSourceTrigger to Explicit (even if you set them explicitly yourself). This is because the DataGrid manages the binding operations itself for editing functionality. Now, you might run into an issue when you have a read-only property with a TwoWay binding mode. If you set DataGridColumn.IsReadOnly to true, then the BindingMode will not be coerced to TwoWay.
3. You manage the bindings when using a DataGridTemplateColumn.
The DataGrid internal code does not coerce the bindings set on DataGridTemplateColumn because DataGridTemplateColumn doesn’t even have a Binding DP. Since you are setting the bindings up yourself, you have to tell it when to commit. One common way to do this is to setup a OneWay binding on the UIElement in the CellTemplate and a TwoWay binding on the CellEditingTemplate. See this previous post for example code (under “Template Columns”). The example code uses an UpdateSourceTrigger of PropertyChanged. If that does not suite your needs for committing the data, you can mimic what the DataGrid internal code does and update the source yourself. Set the UpdateSourceTrigger to Explicit and listen to the DataGrid.CellEditEnding event. You can then update the source explicitly in that event.
4. Use DataGridColumn.ElementStyle and DataGridColumn.EditingElementStyle to set properties on a cell’s Content.
Each stock DataGridColumn type produces a specific type of Control as the content for each DataGridCell in the column. See this previous post for more info on that. It seems that the first instinct to set properties on the cell’s content is to set them through the DataGridCell Style when they should instead be set on DataGridColumn.ElementStyle and/or DataGridColumn.EditingElementStyle. ElementStyle is the Style applied to the non-editing element. EditingElementStyle is for the editing element.
In a previous gotcha, I mention that DataGridColumns are not part of the visual tree. With that said, you can still create bindings that map to the same DataContext as DataGridColumn.Binding on DPs from a Style set on DataGridColumn.ElementStyle and DataGridColumn.EditingElementStyle. That is because these styles are applied dynamically to the cell’s content which has the correct DataContext. Below I am setting a DataGridTextBoxColumn’s ElementStyle. Notice that I have a binding setup on Background with a “MyBrush” property path. I am assuming that property is coming from the DataContext of the DataGridCell (which is the same DataContext of the DataGridRow).
<dg:DataGridTextColumn Header="First Name"
Binding="{Binding Path=FirstName}">
<dg:DataGridTextColumn.ElementStyle>
<Style TargetType="{x:Type TextBlock}">
<Setter Property="ToolTip" Value="{StaticResource TextToolTip}" />
<Setter Property="Background" Value="{Binding MyBrush}" />
<Setter Property="TextWrapping" Value="Wrap" />
</Style>
</dg:DataGridTextColumn.ElementStyle>
</dg:DataGridTextColumn>
5. Data source items should implement IEditableObject to get transactional editing functionality.
If you are not familiar with IEditableObject, see this MSDN article which has a good explanation and code sample. The DataGrid has baked in functionality for transactional editing via the IEditableObject interface. When you begin editing a cell, the DataGrid gets into cell editing mode as well as row editing mode. What this means is that you can cancel/commit cells as well as cancel/commit rows. For example, I edit cell 0 and press tab to the next cell. Cell 0 is committed when pressing tab. I start typing in cell 1 and realize I want to cancel the operation. I press ‘Esc’ which reverts cell 1. I now realize I want to cancel the whole operation so I press ‘Esc’ again and now cell 0 is reverted back to its original value.
See more WPF DataGrid gotchas.
Intro
WPF dependency properties are properties that are registered through the WPF property system. By registering with the property system, the property will be provided a set of services such as data binding, styling, change notifications, animation, expressions, invalidation, and coercion. What I will be discussing here is dynamic value resolution and the issue of setting local values in controls.
Background
A dependency property’s value is dependent on multiple providers where there is a level of precedence for each provider. The BaseValueSource enum captures these providers (with the first being the lowest precedence and last being the highest)
· Unknown
· Default
· Inherited
· ThemeStyle
· ThemeStyleTrigger
· Style
· TemplateTrigger
· StyleTrigger
· ImplicitStyleReference
· ParentTemplate
· PatentTemplateTrigger
· Local
In addition to these base value sources, the dependency property can also come from an expression, animation, or coercion (which the internal WPF code represents them as ModifiedValues). Note that ModifiedValues are mutually exclusive from BaseValueSource. For example you can have a value that is set locally and coerced at the same time.
So how does this relate to the control local values bug?
As a control author, dependency properties are the general mechanism to communicate the state and behavior of the control. They will be used in data binding scenarios, visual representation, interaction, etc. This means that you will possibly and most likely be setting values on your dependency properties internally as state changes about your control. So as you are setting new values to a dependency property that you own, you may not realize that you are introducing a very subtle bug relating to dynamic value resolution precedence. I’ll use an example to clarify.
The DatePicker control from WPFToolkit has a DP called IsDropDownOpen. It is used by the DatePicker control to update when the calendar popup opens and closes. It listens for a change event and sets the Popup.IsOpen accordingly.
private static void OnIsDropDownOpenChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
DatePicker dp = d as DatePicker;
Debug.Assert(dp != null);
bool newValue = (bool)e.NewValue;
if (dp._popUp != null && dp._popUp.IsOpen != newValue)
{
dp._popUp.IsOpen = newValue;
}
}
The Popup also listens for when it opens and closes and makes sure IsDropDownOpen is toggled to the correct state.
private void PopUp_Opened(object sender, EventArgs e)
{
if (!this.IsDropDownOpen)
{
this.IsDropDownOpen = true;
}
…
}
Notice the bug? The bug lies in PopUp_Opened when setting IsDropDownOpen to a local value. This really appears harmless and you probably won’t actually see the bug in action until you hand it over to the app developer who will actually set different value providers on your control DPs. The problem is that setting a DP locally as an internal operation in your control will trump any and all value providers set on that DP (as local is the highest precedence). That means that the app developer who uses your control and decides to put some fancy styles, binding, triggers, and all that goodness will find that it will stop working for some reason and not immediately know what caused it.
To further describe how this can occur, here is an example using that same DatePicker (note that this has already been fixed in later version of the toolkit).
<Window.Resources>
<Style x:Key="DatePickerStyle" TargetType="{x:Type toolkit:DatePicker}">
<Setter Property="IsDropDownOpen" Value="{Binding ElementName=cb_IsDropDownOpen, Path=IsChecked}" />
</Style>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<CheckBox Content="IsDropDownOpen" Name="cb_IsDropDownOpen" />
<toolkit:DatePicker Grid.Row="1"
Name="datePicker"
Style="{StaticResource DatePickerStyle}" />
</Grid>
I’ve setup a style on the DatePicker where I’ve bound a CheckBox.IsChecked DP to the DatePicker.IsDropDownOpen DP. If you click on the CheckBox, the popup opens as expected. If you take a look at the BaseValueSource on IsDropDownOpen it will read Style. However, as soon as you close the popup, the binding does not work anymore which is expected since a local value is set on IsDropDownOpen.
Summary
To make it really short and sweet, setting dependency properties to local values in internal operations of a control can lead to the dreaded control local values bug. I’m sure you’re next question is, how can this be prevented? Well, I will be doing a follow-up post on what has been added in dev10 to easily prevent this bug so stay tuned!
UPDATE: Check out the follow up post here.
An updated version of the WPFToolkit has been released today. This includes some high pri bug fixes which are briefly described in the downloads tab. As a reminder, if you have any questions or find any issues please use the Discussion list and the Issue Tracker. Enjoy!
Also, a new set of WPF Themes has also been released in the WPF Futures section. This is a collection of 9 themes that are also found in the Silverlight Toolkit.
Model-View-ViewModel (MVVM) is one of those really interesting design patterns that are used in WPF. It provides a separation between the UI and business logic and uses data binding techniques to connect them together. Karl Shifflett has some great material on MVVM that you can read about here.
Anyway, I thought I’d dig into some of the details starting with grouping items in a DataGrid. I was originally building a test app for modeling animations of DependencyProperties and ended up using a DataGrid to show the DPs, their current values, and animation properties. I found that I really needed all the sorting, grouping, and filtering capabilities to analyze test values so I wanted to update the DataGrid and use the MVVM pattern (as much as possible).
Grouping by column
To group by column I decided to use a context menu on the column header. In the context menu, the MenuItems are hooked up to commands on my ViewModel like so:
<ContextMenu x:Key="cm_columnHeaderMenu">
<MenuItem Name="mi_group"
Header="Group by this column"
Command="{Binding RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type local:Window1}},
Path=DataContext.GroupColumn}"
CommandParameter="{Binding RelativeSource={RelativeSource Self},
Path=DataContext}"/>
<MenuItem Name="mi_clearGroups"
Header="Clear grouping"
Command="{Binding RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type local:Window1}},
Path=DataContext.UngroupColumns}" />
</ContextMenu>
My ViewModel is the DataContext for the main window and implements these commands with a MVVM commanding style introduced by Josh Smith called RelayCommand. You can find it in his Crack.NET solution as well as other MVVM samples here and here. Here is the code for grouping:
public ICommand GroupColumn
{
get
{
if (_groupColumn == null)
{
_groupColumn = new RelayCommand<object>(
(param) =>
{
string header = param as string;
DPCollection.GroupDescriptions.Add(new PropertyGroupDescription("TargetProperty." + header));
DPCollection.SortDescriptions.Add(new SortDescription("TargetProperty.Name", ListSortDirection.Ascending));
});
}
return _groupColumn;
}
}
DPCollection is the CollectionView that I set on the DataGrid.ItemsSource. So triggering this command will set a group on a particular property and sort by its name. The important part is really how the command is being implemented in the ViewModel and through a delegate style of commanding such as RelayCommand.
Expanding and Collapsing Groups
I really wanted to follow a pattern similar to Josh’s and Bea’s TreeView examples but unfortunately the grouping implementation is not so extensible. When you setup GroupDescriptions, the ItemContainerGenerator of the ItemsControl will create these GroupItem visuals and will set the DataContext of each GroupItem to a CollectionViewGroupInternal object. This CollectionViewGroupInternal object holds information about the items in the group, its name, count, etc. The xaml for my GroupStyle without custom expanding or collapsing looks like this:
<GroupStyle x:Key="gs_Default">
<GroupStyle.HeaderTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Path=Name}" />
</StackPanel>
</DataTemplate>
</GroupStyle.HeaderTemplate>
<GroupStyle.ContainerStyle>
<Style TargetType="{x:Type GroupItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type GroupItem}">
<Expander IsExpanded="{Binding Path=??}">
<Expander.Header>
<DockPanel TextBlock.FontWeight="Bold">
<TextBlock Text="{Binding Path=Name}" />
<TextBlock Text="{Binding Path=ItemCount}"/>
</DockPanel>
</Expander.Header>
<ItemsPresenter />
</Expander>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</GroupStyle.ContainerStyle>
</GroupStyle>
Notice the properties “Name” and “ItemCount” which are properties on the CollectionViewGroup which is a base class of CollectionViewGroupInternal. Also notice the Expander.IsExpanded property. I needed some way to bind some kind of ViewModel property to Expander.IsExpanded.
The problem is that CollectionViewGroupInternal is Internal as you might have suspected and I was unsuccessful in applying a class adaptor to the base class object without affecting the grouping functionality. So I decided to put an IsExpanded property directly on each of the items instead. While this is more of a hack as IsExpanded means nothing to the item by itself, it is relatively cheaper and really may be a more realistic solution than re-implementing grouping on a custom CollectionView. Here is the updated xaml:
<GroupStyle x:Key="gs_Default">
<GroupStyle.HeaderTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Path=Name}" />
</StackPanel>
</DataTemplate>
</GroupStyle.HeaderTemplate>
<GroupStyle.ContainerStyle>
<Style TargetType="{x:Type GroupItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type GroupItem}">
<Expander IsExpanded="{Binding Path=Items[0].IsExpanded}">
<Expander.Header>
<DockPanel TextBlock.FontWeight="Bold">
<TextBlock Text="{Binding Path=Name}" />
<TextBlock Text="{Binding Path=ItemCount}"/>
</DockPanel>
</Expander.Header>
<ItemsPresenter />
</Expander>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</GroupStyle.ContainerStyle>
</GroupStyle>
I have made the assumption that it will check the first item in the group but that is ok as I only care able expanding or collapsing all groups. When I clear grouping or group a different column, all IsExpanded properties are reset so there are no side effects on the next group.
Finally, the command for expanding looks like this:
public ICommand ExpandAllGroups
{
get
{
if (_expandAllGroups == null)
_expandAllGroups = new RelayCommand(
() =>
{
if (DPCollection.Groups != null)
{
foreach (object groupItem in DPCollection.Groups)
{
var group = groupItem as CollectionViewGroup;
group.Items[0] as ElementViewModel).IsExpanded = true;
}
}
});
return _expandAllGroups;
}
}
I have attached a full sample here, which is a stripped down version of the test app I was building.
Scenario
By default the WPF DataGrid will commit a row when focus is lost on the row, the ‘Enter’ key is pressed, tabbing to the next row, or programmatically calling commit on the row. Let’s say I want to control how the user commits a row edit by locking input to just that row being edited and having a filtered set of input or a single input of my choosing that will commit that row.
Implementation
There are no configuration APIs on the DataGrid that allow you to tell it what input will commit a row, but there are events that allow you to cancel editing operations as you so choose. So we can work around that. Another thing we will have to do is keep track of the editing state of the DataGrid. With this extra flag, we can also use it to disable other rows while the current row is being edited.
You can subclass DataGrid and add functionality that may be more generalized but for this sample I’m just going to use a UserControl. Here is the flag that I will use which is a DP so it can be used in the DataGridRow style.
public partial class CustomDataGrid : UserControl
{
public bool IsEditingRow
{
get { return (bool)GetValue(EditingRowProperty); }
set { SetValue(EditingRowProperty, value); }
}
public static readonly DependencyProperty EditingRowProperty =
DependencyProperty.Register(
"IsEditingRow",
typeof(bool),
typeof(CustomDataGrid),
new FrameworkPropertyMetadata(false, null, null));
}
This flag is going to be used such that anytime a row is being edited, other rows will be disabled and you can only commit through an input that I choose. To disable the rows we can add something like this in the row’s style:
<Style TargetType="{x:Type dg:DataGridRow}">
<Style.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:CustomDataGrid}}, Path=IsEditingRow}" Value="True" />
<Condition Binding="{Binding RelativeSource={RelativeSource Self}, Path=IsEditing}" Value="False" />
</MultiDataTrigger.Conditions>
<Setter Property="IsEnabled" Value="False"/>
<Setter Property="Foreground" Value="LightGray"/>
</MultiDataTrigger>
</Style.Triggers>
</Style>
For naming purposes I probably should have called it IsInEditMode to differentiate but please indulge me for now. So anyway, if the DataGrid is editing a row and the particular row is not in edit mode it will be disabled.
Now, for the logic, two important events for this scenario are:
· DataGrid.BeginningEdit
· DataGrid.RowEditEnding
BeginninigEdit will be used to set the IsEditingRow flag as well as cancel a BeginEdit operation when trying to open a different row for edit while still editing the current row.
private void DataGrid_Standard_BeginningEdit(object sender, DataGridBeginningEditEventArgs e)
{
if (!IsEditingRow)
{
// set edit mode state
IsEditingRow = true;
_currentEditingRow = e.Row;
}
else if (e.Row != _currentEditingRow)
{
// cancel all new edits for different rows
e.Cancel = true;
}
}
RowEditEnding will be used to cancel a commit if IsEditingRow is true. It also resets when the row edit is cancelled.
private void DataGrid_Standard_RowEditEnding(object sender, DataGridRowEditEndingEventArgs e)
{
if (e.EditAction == DataGridEditAction.Cancel)
{
// cancelling the entire row will reset the state
IsEditingRow = false;
}
else if (e.EditAction == DataGridEditAction.Commit)
{
e.Cancel = IsEditingRow;
}
}
So for this workaround, it is really based on how you want to manipulate IsEditingRow. For this sample, I want to commit a row only when the ‘Enter’ key is pressed. I can do that by listening to the PreviewKeyDown event and setting IsEditingRow to false for the ‘Enter’ key.
private void DataGrid_Standard_PreviewKeyDown(object sender, KeyEventArgs e)
{
DataGridRow row = GetRow(DataGrid_Standard, DataGrid_Standard.Items.IndexOf(DataGrid_Standard.CurrentItem));
if (row.IsEditing && e.Key == Key.Enter)
{
// only 'Enter' key on an editing row will allow a commit to occur
IsEditingRow = false;
}
}
That’s basically it. If you try out the sample you will see that when you open a row for edit, the rest of the rows will be disabled and the only way to commit is through the ‘Enter’ key.
Let’s say ‘Enter’ to commit is not the functionality you want. Instead you want to commit using a specific button. In that case you can listen to the click event or setup a command and set IsEditingRow accordingly based on your specific conditions.
You can download the full sample here.
The Scenario
I want to do a master detail like scenario where the selection in one ComboBox cell will update the list of items in the next ComboBox cell and so on.
Setting up the DataSource and ViewModel
I will use the Northwind database for this example and will use the first column to select a Category within the Categories table.
The second column will have a ComboBox to select all the Products within the selected Category.
And the third column will have a ComboBox to select all the orders that were placed on the selected Product.
The ViewModel that I will use on each DataGrid item will have the properties:
· CurrentCategory
· CurrentProduct
· CurrentOrder
· ProductsInCategory
· OrdersFromProduct
Each time CurrentCategory is updated, ProductsInCategory will be updated as well to create the new list of products. When CurrentProduct is updated, OrdersFromProduct will be updated in a similar fashion. So for example, CurrentCategory will look like this:
public int CurrentCategory
{
get { return _currentCategory; }
set
{
_currentCategory = value;
ProductsInCategory = DBAccess.GetProductsInCategory(_currentCategory).Tables["Products"].DefaultView;
OnPropertyChanged("CurrentCategory");
}
}
GetProductsInCategory() does a query on the database passing in the category id. I will not go over the database querying implementation here.
Hooking up to the UI
For the first DataGridComboBoxColumn, the ComboBox.ItemsSource will bind to the Categories DataTable through a StaticResource. The binding on the column will be set to the CurrentCategory.
<dg:DataGridComboBoxColumn Header="Current Category"
SelectedValueBinding="{Binding Path=CurrentCategory}"
SelectedValuePath="CategoryID"
DisplayMemberPath="CategoryName"
ItemsSource="{Binding Source={StaticResource categoriesDataProvider}}">
</dg:DataGridComboBoxColumn>
The other DataGridComboBoxColumns will have to take a slightly different approach. Let’s say we do something like this:
<!--NOT CORRECT-->
<dg:DataGridComboBoxColumn Header="Current Product"
SelectedValueBinding="{Binding Path=CurrentProduct}"
SelectedValuePath="ProductID"
DisplayMemberPath="ProductName"
ItemsSource="{Binding Path=ProductsInCategory}">
</dg:DataGridComboBoxColumn>
It does seem more intuitive to take this approach however DataGridColumns are not actually part of the visual tree and only the Binding DPs will actually be set on generated cell elements to inherit DataGrid’s DataContext. That means that the binding for ItemsSource will fail as it won’t be able to find path, ProductsInCategory, that it is current set.
Aside: Jaime wrote this nice post on how to set the DataContext for the columns to DataGrid’s DataContext, but I’m going to show an implementation here with just the default implementation.
So what we want is for the ItemsSource to bind to the DataContext of the row on the DataGrid. We can do that by setting the binding on the actual generated element of the column through ElementStyle and EditingElementStyle.
<!—now itemssource will find the correct DataContext-->
<dg:DataGridComboBoxColumn Header="Current Product"
SelectedValueBinding="{Binding Path=CurrentProduct}"
SelectedValuePath="ProductID"
DisplayMemberPath="ProductName">
<dg:DataGridComboBoxColumn.ElementStyle>
<Style TargetType="ComboBox">
<Setter Property="ItemsSource" Value="{Binding Path=ProductsInCategory}" />
</Style>
</dg:DataGridComboBoxColumn.ElementStyle>
<dg:DataGridComboBoxColumn.EditingElementStyle>
<Style TargetType="ComboBox">
<Setter Property="ItemsSource" Value="{Binding Path=ProductsInCategory}" />
</Style>
</dg:DataGridComboBoxColumn.EditingElementStyle>
</dg:DataGridComboBoxColumn>
This works because internally when the cell is being generated, the Content of the cell will have its layout updated which will make it part of the visual tree and it will have access to the correct DataContext. Now the ItemsSource for the ComboBox will dynamically update each time CurrentCategory is changed. The DataGridComboBoxColumn for the CurrentOrder is similar to CurrentProduct so I will not show that here.
You can download the sample here.
Our WPF Test team has just released a library of tools that can be leveraged to test WPF applications. What's really cool is that these are utilities that we use internally and now we can push them out to external customers for feedback and usage. The library currently lives here on codeplex, http://www.codeplex.com/TestApi. There are a couple more blog posts where you can get more information about it.
http://blogs.msdn.com/llobo/archive/2008/12/08/wpf-test-helper-library.aspx
http://blogs.msdn.com/wpfsdk/archive/2008/12/09/testapi-released.aspx
The download contains some samples but I thought I would show a sample with one of the DataGrid applications instead.
I made some small modifications to the SampleTestApp to get it working with one of my DataGrid samples. In this sample, I am doing a visual verification test to check that the rows and cells update to the correct visual state when in edit mode. I am using the in-proc, out of thread strategy which is similar to the strategy used in the visual verification in the SampleTestApp.
To get the tests to work with the DataGridSample application, I needed to add automation id’s to some of the elements. In particular, the Window and the DataGrid:
<Window x:Class="DataGridSample.DataGridBasicSample"
AutomationProperties.AutomationId="DataGridBasicSample">
…
<dg:DataGrid x:Name="DataGrid_Standard"
AutomationProperties.AutomationId="DataGrid_Standard">
</dg:DataGrid>
…
</Window>
These IDs will be queried by my test in order to find the correct AutomationElements. And here is what the test will basically look like (left out logging and some error checking to make it more concise):
// retrieve the necessary AutomationElements
AutomationElement window = AutomationUtilities.FindElementsById(AutomationElement.RootElement, "DataGridBasicSample")[0];
AutomationElement dataGrid = AutomationUtilities.FindElementsById(window, "DataGrid_Standard")[0];
GridPattern grid = dataGrid.GetCurrentPattern(GridPattern.Pattern) as GridPattern;
AutomationElement cell = grid.GetItem(0, 0);
// do action for making cell editable
AutomationHelpers.MoveToAndDoubleClick(cell);
// Capture the actual pixels from the bounds of the screen rectangle
Snapshot actual = Snapshot.FromRectangle(AutomationHelpers.GetElementSize(dataGrid));
// Load the reference/master data from a previously saved file
Snapshot master = Snapshot.FromFile(Path.Combine(TestContext.TestDeploymentDir, "Master_EditTest1.png"));
if (CompareImages(actual, master) == VerificationResult.Fail)
{
Assert.Fail("Initial State test failed. Actual should look like Master image. Refer to logged images under:" + TestContext.TestLogsDir);
}
Download the full sample here. We would love to hear your feedback.
The DataGrid walkthrough on windowsclient.net/wpf and the Tips & Tricks section on codeplex talk briefly about the Design-time support for DataGrid. I thought I’d expand on that just a little more so you get a good idea of what you have available.
Installation
So to get it to work you need to install the toolkit using the WPFToolkit.msi (download from here). This will put the dlls under your “C:\Program Files\WPF Toolkit\<version number>\” directory as well as set a reg key in Visual Studio to recognize the controls in the toolbox. In your application, you will need to reference the wpftoolkit.dll from this install directory location to get the design-time support to work.
Walkthrough
Ok, I’ll start from the very beginning.
0. Install and set a reference to wpftoolkit.dll
1. Open the Toolbox. You should see a group with DataGrid, DatePicker, and Calendar.
a. If you don’t, you can add them by right-clicking in the Toolbox, selecting “Choose Toolbox Items”, tab to the WPF Components tab, then selecting DataGrid, DatePicker, and Calendar.
2. Drag & drop support: Drag the DataGrid item from the Toolbox to the window on the design surface.
a. Note: You may need to resize the window as well as work with the layout so the DataGrid fits as you would like.
3. Setup the ItemsSource for the DataGrid
a. In my example I’m going to use a DataTable that retrieves the Customers Table from the Northwind database. The code for that is in the sample on this post.
<Window.Resources>
<ObjectDataProvider x:Key="CustomerDataProvider"
ObjectType="{x:Type local:Customers}"
MethodName="GetCustomers" />
</Window.Resources>
<Grid>
<my:DataGrid Name="dataGrid1"
ItemsSource="{Binding Source={StaticResource CustomerDataProvider}}" />
</Grid>
b. As soon as the ItemsSource is set, the design surface should update to show you the data on the DataGrid. This is because DataGrid.AutoGenerateColumns is set to true by default.
4. Properties View grouping support: Now, click on the DataGrid in the design window and open the Properties View. Set the grouping to “Categorized”. You will find some groups that are specific to DataGrid such as the Columns group, GridLines group, Headers group, and Rows group.

a. In my example, from the Properties View I set EnableColumnVirtualization to true and AlternationRowBackground to LightGray
5. Now, right-click on the DataGrid in design view. You should see a DataGrid menu item with additional sub-items. The sub-items will be a little different depending if you have an ItemsSource set or not. Here is what it looks like with an ItemsSource set but no columns explicitly added:
When no ItemsSource is set, some sub-items will not appear. When explicit columns are set on the DataGrid, additional sub-items will be added. Now, let’s try out each of the options.
6. Generating columns all at once: Right-click on the DataGrid in design view. Select the DataGrid menu item, then select the “Generate Columns” sub-menu item. What just happened?
a. That may be a little confusing at first since the design view didn’t change. The design-time sets DataGrid.AutoGenerateColumns to false, then generates a list of columns with a one-to-one mapping of the data source’s properties and adds them to the columns collection in the xaml. Here is what my DataGrid looks like now in the xaml:
<my:DataGrid Name="dataGrid1" ItemsSource="{Binding Source={StaticResource CustomerDataProvider}}" EnableColumnVirtualization="True" AlternatingRowBackground="LightGray" AutoGenerateColumns="False">
<my:DataGrid.Columns>
<my:DataGridTextColumn Binding="{Binding Path=CustomerID}" Header="CustomerID" />
<my:DataGridTextColumn Binding="{Binding Path=CompanyName}" Header="CompanyName" />
<my:DataGridTextColumn Binding="{Binding Path=ContactName}" Header="ContactName" />
<my:DataGridTextColumn Binding="{Binding Path=ContactTitle}" Header="ContactTitle" />
<my:DataGridTextColumn Binding="{Binding Path=Address}" Header="Address" />
<my:DataGridTextColumn Binding="{Binding Path=City}" Header="City" />
<my:DataGridTextColumn Binding="{Binding Path=Region}" Header="Region" />
<my:DataGridTextColumn Binding="{Binding Path=PostalCode}" Header="PostalCode" />
<my:DataGridTextColumn Binding="{Binding Path=Country}" Header="Country" />
<my:DataGridTextColumn Binding="{Binding Path=Phone}" Header="Phone" />
<my:DataGridTextColumn Binding="{Binding Path=Fax}" Header="Fax" />
</my:DataGrid.Columns>
</my:DataGrid>
7. Removing all columns: Now that I have explicit columns set, if I wanted to clear them I can right-click on the DataGrid in design view, select DataGrid -> Remove Columns. This will remove all the columns from xaml but it will not change the AutoGenerateColumns property back to true.
8. Creating a column with the Add/Edit Columns editor: With all columns removed now, select DataGrid -> Add/Edit Columns… You should be presented with a column editor where the data sources properties are mapped on the left panel. The middle area is where you can choose the type of DataGridColumn to create and the right panel shows the columns that will be created.
a. Leave Type as Default and press the Add All button. You should see all the properties on the left panel moved to the right panel and mapped to columns. The default column is DataGridTextColumn.
b. Now, press the “Edit…” button on the first column in the right panel. An “Edit Column” editor should pop up with a list of common properties to set on the column.
c. Edit any of the fields of the columns you created and press ok on the “Add/Edit Columns” editor. The xaml should be updated with the set of columns you’ve created.
Summary
To recap, the cider design-time supports toolbox drag and drop, generating all columns, generating specific columns, editing columns, and removing columns. Remember that you have to set the ItemsSource on the DataGrid declaratively in order to get that functionality. The Properties View has also been updated for the DataGrid to support grouping of specific properties on the DataGrid. Please try it out and any feedback would be greatly appreciated.
Other Design-time Resources
Karl Shifflett has created this awesome tool called XAML Power Toys. Included in the tool is a utility for creating and customizing a WPF DataGrid. Check out the video walkthrough of it here.
This sample shows how to create a template for the NewItemPlaceholder to indicate that you can add a new item. This is what it will look like:
First I will need to create a new template specifically for the NewItemPlaceholder row. I’ve followed the general outline of the original DataGridRow control template but I’ve only included the pieces I needed and I’ve also added an element for the “Click here to add new item.” text. My template looks something like this:
<ControlTemplate x:Key="NewRow_ControlTemplate" TargetType="{x:Type dg:DataGridRow}">
<Border x:Name="DGR_Border" Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" SnapsToDevicePixels="True">
<dg:SelectiveScrollingGrid>
…
<TextBlock Text="Click here to add a new item." Grid.Column="1"/>
…
</dg:SelectiveScrollingGrid>
</ControlTemplate>
Then I’m going to need to attach to several events. In particular, I need to hook up to the DataGrid.LoadingRow, DataGrid.UnloadingRow, DataGrid.RowEditEnding, and DataGridRow.MouseLeftButtonDown events.
I attach to the LoadingRow and UnloadingRow events so I can set either the default or new item control template when the row is created. These event handlers will look like this:
private void DataGrid_Standard_LoadingRow(object sender, DataGridRowEventArgs e)
{
if (_defaultRowControlTemplate == null)
{
_defaultRowControlTemplate = e.Row.Template;
}
if (e.Row.Item == CollectionView.NewItemPlaceholder)
{
e.Row.Template = _newRowControlTemplate;
e.Row.UpdateLayout();
}
}
private void DataGrid_Standard_UnloadingRow(object sender, DataGridRowEventArgs e)
{
if (e.Row.Item == CollectionView.NewItemPlaceholder && e.Row.Template != _defaultRowControlTemplate)
{
e.Row.Template = _defaultRowControlTemplate;
e.Row.UpdateLayout();
}
}
I attach to the DataGridRow.MouseLeftButtonDown event so that I can change the template of the NewItemPlaceholder on the first click. For this sample, when first clicking the row of the NewItemPlaceholder it immediate enters edit mode. Here is the handler:
private void Row_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
DataGridRow row = sender as DataGridRow;
if (row.Item == CollectionView.NewItemPlaceholder && row.Template == _newRowControlTemplate)
{
// for a new row update the template and open for edit
row.Template = _defaultRowControlTemplate;
row.UpdateLayout();
DataGrid_Standard.CurrentItem = row.Item;
DataGridCell cell = Helper.GetCell(DataGrid_Standard, DataGrid_Standard.Items.IndexOf(row.Item), 0);
cell.Focus();
DataGrid_Standard.BeginEdit();
}
}
The last event is necessary so that the template is updated back to the new item template after a row is either committed or cancelled. It is a little tricky because what I really want is to perform my operation when the row is already committed or cancelled but the only event available, RowEditEnding, fires while the row is committing or being cancelled. What I can do is use the Dispatcher to make my operation take place after the RowEditEnding event. My handler looks like this:
private void DataGrid_Standard_RowEditEnding(object sender, DataGridRowEditEndingEventArgs e)
{
IEditableCollectionView iecv = CollectionViewSource.GetDefaultView((sender as DataGrid).ItemsSource) as IEditableCollectionView;
if (iecv.IsAddingNew)
{
// need to wait till after the operation as the NewItemPlaceHolder is added after
Dispatcher.Invoke(new DispatcherOperationCallback(ResetNewItemTemplate), DispatcherPriority.ApplicationIdle, DataGrid_Standard);
}
}
private object ResetNewItemTemplate(object arg)
{
DataGridRow row = Helper.GetRow(DataGrid_Standard, DataGrid_Standard.Items.Count - 1);
if (row.Template != _newRowControlTemplate)
{
row.Template = _newRowControlTemplate;
row.UpdateLayout();
}
return null;
}
So when I cancel or commit the edit on the new item, the new item template will be added back to the last row.
You can download the full sample here.