I came across your question and was running into the same thing. Here's my situation and solution and I think it should work for you (and others) as well. I have a datagrid that allows the entry of new items, so the possible values go from 0 to infinity. Users should be able to click the checkbox and have it check or uncheck with one button click. In addition, there are other fields that can be updated as well. I had to create a custom datagrid control to automatically focus and begin editing of the checkbox:
public class CheckboxDataGrid : DataGrid {
public CheckboxDataGrid() {
EventManager.RegisterClassHandler(typeof(DataGridCell),
DataGridCell.PreviewMouseLeftButtonDownEvent,
new RoutedEventHandler(this.OnPreviewMouseLeftButtonDown));
}
private void OnPreviewMouseLeftButtonDown(object sender, RoutedEventArgs e) {
DataGridCell cell = sender as DataGridCell;
if (cell != null && !cell.IsEditing && !cell.IsReadOnly) {
var parentRow = cell.Parent as DataGridRow;
if (parentRow != null) {
SelectedIndex = parentRow.GetIndex();
}
CurrentCell = new DataGridCellInfo(cell);
DependencyObject obj = FindVisualChild<CheckBox>(cell);
if (obj != null) {
BeginEdit(e);
System.Windows.Controls.CheckBox cb = (System.Windows.Controls.CheckBox)obj;
cb.Focus();
}
}
}
public static TChild FindVisualChild<TChild>(DependencyObject obj) where TChild : DependencyObject {
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++) {
DependencyObject child = VisualTreeHelper.GetChild(obj, i);
if (child != null && child is TChild) {
return (TChild)child;
} else {
TChild childOfChild = FindVisualChild<TChild>(child);
if (childOfChild != null)
return childOfChild;
}
}
return null;
}
}
I also needed a converter to ignore the new item binding message because of the way that WPF handles new items in a datagrid:
public class IgnoreNewItemPlaceHolderConverter : IValueConverter {
private const string NewItemPlaceholderName = "{NewItemPlaceholder}";
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) {
return value;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) {
if (value != null && value.ToString() == NewItemPlaceholderName)
return DependencyProperty.UnsetValue;
return value;
}
}
Here is the relavent XAML for the datagrid:
<chkDatagrid:CheckboxDataGrid AutoGenerateColumns="False"
ItemsSource="{Binding ElementName=dgPurchaseOrders, Path=SelectedItem.Releases, NotifyOnSourceUpdated=True}" AlternatingRowBackground="#1E000000"
DockPanel.Dock="Bottom" HorizontalAlignment="Left" SelectedItem="{Binding SelectedRelease, Mode=TwoWay, Converter={StaticResource ignoreNewItemPlaceHolderConverter}}"
MinHeight="100">
<b:Interaction.Triggers>
<b:EventTrigger EventName="RowEditEnding">
<b:InvokeCommandAction Command="{Binding ReleaseRowEditEndingCommand}" CommandParameter="{Binding SelectedRelease}"/>
</b:EventTrigger>
</b:Interaction.Triggers>
<DataGrid.Columns>
<DataGridCheckBoxColumn Header="Is Paid" Binding="{Binding IsPaid, UpdateSourceTrigger=PropertyChanged}" />
<DataGridTextColumn Header="Amount" Binding="{Binding Amount, UpdateSourceTrigger=PropertyChanged}" />
<DataGridTextColumn Header="Description" Binding="{Binding Description, UpdateSourceTrigger=PropertyChanged}" />
<DataGridTextColumn Header="Invoice Number" Binding="{Binding InvoiceNumber, UpdateSourceTrigger=PropertyChanged}" />
<DataGridTextColumn Header="Invoice Receive Date" Binding="{Binding InvoiceRecvDate, UpdateSourceTrigger=PropertyChanged}" />
</DataGrid.Columns>
</chkDatagrid:CheckboxDataGrid>
You'll note a few things here. I'm subscribing to the RowEditEnding event for the editing and committing of the columns and the application of the converter to the new row.
Finally, here's the code that updates the attached entity:
private void OnReleaseRowEditEnding(object arg) {
if (arg != null) {
EMService.EMEntities etx = GetEMEntities();
EMService.Release release = (EMService.Release)arg;
if (release.ReleaseID == 0) {
release.EncumbranceID = SelectedPurchaseOrder.EncumbranceID;
release.CreatedOn = DateTime.Now;
release.CreatedBy = CurrentUser.Username;
release.ModifiedOn = DateTime.Now;
release.ModifiedBy = CurrentUser.Username;
etx.AddObject("Releases", release);
} else {
EMService.Release existingRelease = etx.Releases.Where(e => e.ReleaseID == release.ReleaseID).First();
existingRelease.Amount = release.Amount;
existingRelease.Description = release.Description;
existingRelease.InvoiceNumber = release.InvoiceNumber;
existingRelease.InvoiceRecvDate = release.InvoiceRecvDate;
existingRelease.IsPaid = release.IsPaid;
release.ModifiedOn = DateTime.Now;
release.ModifiedBy = CurrentUser.Username;
etx.UpdateObject(existingRelease);
}
etx.SaveChanges();
}
}
It's a lot to digest, but it ended up working in the long run. Who knew the WPF datagrid would be so putzy.