WPF Binding DataGridCheckBoxColumn - c#

I am having difficulty binding a DataGridCheckBoxColumn in a DataGrid in WPF.
What I am trying to do is have a "Select All" button to check all the items in the grid.
<Button Grid.Row="1" Grid.Column="0" Content="Select All In List" HorizontalAlignment="Stretch" Command="{Binding SelectAll}"></Button>
In my ViewModel I have a Command that is called from the button.
public ICommand SelectAll { get; set; }
private void OnSelectAll(object obj)
{
foreach (var item in EducationLeaflets)
{
item.Selected = true;
}
OnPropertyChanged("EducationLeaflets");
}
This is my property from my ViewModel that I bind my DataGrid to:
public ObservableCollection<LeafletListModel> EducationLeaflets { get; private set; }
My DataGrid with a DataGridCheckBoxColumn as the first column.
<DataGrid Grid.Row="0" Grid.Column="0"
AutoGenerateColumns="False"
EnableRowVirtualization="True"
ItemsSource="{Binding EducationLeaflets}"
RowDetailsVisibilityMode="VisibleWhenSelected"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Grid.ColumnSpan="3" Background="White" HorizontalGridLinesBrush="#FFF0F0F0" VerticalGridLinesBrush="#FFF0F0F0">
<DataGrid.Columns>
<DataGridCheckBoxColumn
Binding="{Binding Path=Selected, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
</DataGridCheckBoxColumn>
<DataGridTextColumn
Binding="{Binding Id}"
Header="RecordId"
Width="SizeToHeader" />
<DataGridTextColumn
Binding="{Binding Name}"
Header="Name"
Width="*" />
</DataGrid.Columns>
</DataGrid>
Also the model that is displayed in each grid row.
public class LeafletListModel: ListModel
{
public LeafletListModel(int id, string name, DateTime bpsDrugsUpdated):base(id, name)
{
BpsDrugsUpdated = bpsDrugsUpdated;
}
public bool Selected { get; set; }
public DateTime BpsDrugsUpdated { get;private set; }
}
When I click the button the items in the DataGrid are not checked as I would like. Thank you for your help.

It is not EducationLeaflets that changes - it stays the same ObservableCollection as before clicking SelectAll. Even its content does not change (this would be reflected in the CollectionChanged event from the ObservableCollection.
What actually changes are the individual items in the ObservableCollection. And since these do not implement INotifyPropertyChanged, the update will not be reflected in the Views.
So, if you make LeafletListModel implement INotifyPropertyChanged, it should
work as expected.
public class LeafletListModel: ListModel, INotifyPropertyChanged
{
private bool _selected;
public LeafletListModel(int id, string name, DateTime bpsDrugsUpdated):base(id, name)
{
BpsDrugsUpdated = bpsDrugsUpdated;
}
public bool Selected
{
get { return _selected; }
set
{
if (_selected != value)
{
_selected = value;
OnPropertyChanged();
}
}
}
public DateTime BpsDrugsUpdated { get; private set; }
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}

Related

content does not appear in datagrid

Well, I have a data class with some data objects like this:
private ObservableCollection<bool> check = new ObservableCollection<bool>();
public ObservableCollection<bool> Check
{
get { return check; }
set
{
check = value;
Notify("check");
}
}
private ObservableCollection<string> user = new ObservableCollection<string>();
public ObservableCollection<string> User
{
get { return user; }
set
{
user = value;
Notify("user");
}
}
And in the MainWindow I added a DataGrid like this:
<DataGrid AutoGenerateColumns="False"
Name="dataGrid1"
CanUserAddRows="False" CanUserSortColumns="False" CanUserResizeColumns="True" CanUserReorderColumns="False"
ItemsSource="{Binding}">
<DataGrid.Columns >
<DataGridCheckBoxColumn Header = "" Binding="{Binding Check, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" MinWidth="50" />
<DataGridTextColumn Header = "User" Binding="{Binding User, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" MinWidth="50" />
</DataGrid.Columns>
</DataGrid>
For the whole Window the datakontext ist set to the data class. In the constructor I called "DataContext = theData"; I added some values in the constructor of the data class and proofed by running the program the instance of this class. The values are correct added to the ObservableCollection.
But the values are not shown in the datagrid. Why?
The ItemsSource property of the DataGrid should be set or bound to an IEnumerable<T>. And a single column in the DataGrid should be bound to a property of the type T. You are trying to bind a DataGridTextColumn to an ObservableCollection<string> and a DataGridCheckBoxColumn to an ObservableCollection<bool> and this makes no sense. They should be bound to a string and bool property respectively. Please refer to the following sample code.
Model:
public class YourDataObject : INotifyPropertyChanged
{
private bool _check;
public bool Check
{
get { return _check; }
set { _check = value; NotifyPropertyChanged(); }
}
private string _user;
public string User
{
get { return _user; }
set { _user = value; NotifyPropertyChanged(); }
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
View Model:
public class ViewModel
{
public ViewModel()
{
TheDataObjects = new ObservableCollection<YourDataObject>();
TheDataObjects.Add(new YourDataObject());
TheDataObjects.Add(new YourDataObject());
TheDataObjects.Add(new YourDataObject());
}
public ObservableCollection<YourDataObject> TheDataObjects { get; private set; }
}
View:
public MainWindow()
{
InitializeComponent();
DataContext = new ViewModel();
}
<DataGrid AutoGenerateColumns="False"
Name="dataGrid1"
CanUserAddRows="False" CanUserSortColumns="False" CanUserResizeColumns="True" CanUserReorderColumns="False"
ItemsSource="{Binding TheDataObjects}">
<DataGrid.Columns >
<DataGridCheckBoxColumn Header = "" Binding="{Binding Check, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" MinWidth="50" />
<DataGridTextColumn Header = "User" Binding="{Binding User, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" MinWidth="50" />
</DataGrid.Columns>
</DataGrid>
Try setting,
this.DataContext = theData;
You need to set proper property to ItemsSource.
ItemsSource="{Binding User}"
Above line will clear the issue.
Also, you should Notify public properties in Setter.
Notify("Check");
Notify("User");

How to refresh specific DataGrid Column-Cell Value without refreshing the entire table

So I have the following code meant to audit our inventory. Upon editing the actual_quantity cell on the DataGrid, the ItemAuditInstance instance automatically gets updated, setting its actual_quantity as well as calculating for itsdifference. My problem is the DataGrid automatically refreshes the cell for actual_quantity but not difference
I know you can force a refresh by setting the DataGrid's ItemsSource to null then back to the list but it seems like a waste of resources as I don't want to refresh hundreds of rows for only one value. Is there any way to do it this?
ItemAuditInstance:
public class ItemAuditInstance : ReflectionObject {
public int id { get; set; }
public int bfk_item_audit_id { get; set; }
public string item { get; set; }
public int current_quantity { get; set; }
public int actual_quantity { get; set; }
public int difference { get; set; }
public int ds_actual_quantity {
get {
return actual_quantity;
}
set {
actual_quantity = value;
difference = current_quantity - actual_quantity;
}
}
XAML:
<DataGrid x:Name="dgItems" Margin="0,0,0,50" AutoGenerateColumns="False" HorizontalContentAlignment="Right" RowEditEnding="dgItems_RowEditEnding">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding item}" Header="Item" IsReadOnly="True"/>
<DataGridTextColumn Binding="{Binding current_quantity}" Header="Current Quantity" IsReadOnly="True"/>
<DataGridTextColumn Binding="{Binding ds_actual_quantity, UpdateSourceTrigger=PropertyChanged}" Header="Actual Quantity" IsReadOnly="False"/>
<DataGridTextColumn Binding="{Binding difference}" Header="Difference" IsReadOnly="True" Width="*"/>
</DataGrid.Columns>
</DataGrid>
You should implement INotifyPropertyChanged in the ItemAuditInstance class and set UpdateSourceTrigger to PropertyChanged in the cell binding (just like you have in the binding of ds_actual_quantity) for such behavior.
In your example the binding of actual quantity is set right, however without INotifyPropertyChanged it may sometimes not work right.
I suggest using backing private fields to allow silent changes (sometimes needed to avoid infinite loops)
Your implementation of INotifyPropertyChanged should look like:
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(PropertyChangedEventArgs e)
{
if (PropertyChanged != null)
{
PropertyChanged(this, e);
}
}
private int _difference;
public int Difference
{
get
{
return _difference;
}
set
{
_difference = value;
OnPropertyChanged(new PropertyChangedEventArgs("Difference"));
}
}
along with XAML:
<DataGridTextColumn Binding="{Binding Difference, UpdateSourceTrigger=PropertyChanged}" Header="Difference" IsReadOnly="True" Width="*"/>
Now just set
Difference = current_quantity - actual_quantity
and the binding should be automatically updated

DataGridTemplateColumn (ComboBox, DatePicker) Resets/Clears and doesn't fire AddingNewItem

I've narrowed down the problem to the following example that has a DataGrid with three columns.
XAML:
<Window x:Class="DataGridColumnTemplate_NotFiringAddingNewItem.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<DataGrid x:Name="dg" HorizontalAlignment="Left" Margin="10,10,0,0" VerticalAlignment="Top" Height="299" AutoGenerateColumns="False" Width="497" AddingNewItem="dg_AddingNewItem" CanUserAddRows="True">
<DataGrid.Columns>
<DataGridTemplateColumn Header="DateWorks">
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<DatePicker SelectedDate="{Binding InvoiceDate}" />
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="DateDoesn'tWork">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<DatePicker SelectedDate="{Binding InvoiceDate}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<DatePicker SelectedDate="{Binding InvoiceDate}" />
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
<DataGridTextColumn Header="Text" Binding="{Binding Description}"/>
</DataGrid.Columns>
</DataGrid>
</Grid>
</Window>
C#:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
List<JobCostEntity> l = new List<JobCostEntity>()
{
new JobCostEntity() { Id = 0, InvoiceDate = DateTime.Now, Description = "A"},
new JobCostEntity() { Id = 0, InvoiceDate = DateTime.Now, Description = "B"}
};
dg.ItemsSource = l;
}
private void dg_AddingNewItem(object sender, AddingNewItemEventArgs e)
{
MessageBox.Show("AddingNewItem");
}
}
public partial class JobCostEntity
{
public int Id { get; set; }
public int JobId { get; set; }
public Nullable<int> JobItemId { get; set; }
public Nullable<System.DateTime> InvoiceDate { get; set; }
public Nullable<System.DateTime> ProcessedDate { get; set; }
public int PackageId { get; set; }
public int DelegateId { get; set; }
public string Description { get; set; }
public Nullable<decimal> LabourCost { get; set; }
public Nullable<decimal> PlantOrMaterialCost { get; set; }
public Nullable<decimal> SubcontractorCost { get; set; }
public Nullable<decimal> TotalCost { get; set; }
public bool Paid { get; set; }
}
If the first column you click on in the new item row is 'DateWorks' or 'Text', then you will raise the AddingNewItem event.
If instead you click the 'DateDoesntWork' column first, you can select a date, but no new item is added until you move to one of the other columns, at which point the value in the 'DateDoesntWork' DatePicker gets cleared.
What on earth is going on?
It's arguably(!) desirable to have the DatePicker already visible to the user (hence both a CellTemplate and a CellEditingTemplate), rather than them have to click the cell to 'reveal' the control.
Is there some way I have to inform the DataGrid that my DataGridTemplateColumn Control has just set a value on a new row? If so, how so?!
EDIT:
Inspired by this post: https://social.msdn.microsoft.com/Forums/vstudio/en-US/93d66047-1469-4bed-8fc8-fa5f9bdd2166/programmatically-beginning-edit-in-datagrid-cell?forum=wpf
I have tried to hack my way around the problem by adding the following to the 'DateDoesntWork' column DatePicker, which does cause the AddingNewItem event to fire, but the selected date still doesn't get added to the underlying entity.
private void DatePicker_GotFocus(object sender, RoutedEventArgs e)
{
if (dg.SelectedIndex == dg.Items.Count - 1)
{
DataGridCellInfo dgci = dg.SelectedCells[0];
DataGridCell dgc = DataGridHelper.GetCell(dg, GetRowIndex(dg, dgci), GetColIndex(dg, dgci));
dgc.Focus();
dg.BeginEdit();
}
}
It seems like the DatePicker is still trying to target the NewItemPlaceholder, if that makes any sense?!
Stranger still, if you select a date in the DateDoesntWork column on the new row, then start editing the Text column on the new row, then without entering any text, select the row above ... now another new row is added and that newly added row shows the date i selected for the row before!!!
Total. Madness.
As Maxime Tremblay-Savard has metioned, it seems like the CellTemplate is blocking the 'layer' below and stopping the AddingNewItem event firing, though the built in DataGridColumn types don't suffer from this problem.
In case you need a solution for your InvoiceDate, here is a way to have the behaviour you describe for DateWorks by creating a DataGridDateColumn like so:
public class DataGridDateColumn : DataGridBoundColumn
{
public string DateFormatString { get; set; }
protected override void CancelCellEdit(FrameworkElement editingElement, object before)
{
var picker = editingElement as DatePicker;
if (picker != null)
{
picker.SelectedDate = DateTime.Parse(before.ToString());
}
}
protected override FrameworkElement GenerateEditingElement(DataGridCell cell, object dataItem)
{
var element = new DatePicker();
var binding = new Binding(((Binding)this.Binding).Path.Path) {Source = dataItem};
if (DateFormatString != null)
{
binding.Converter = new DateTimeConverter();
binding.ConverterParameter = DateFormatString;
}
element.SetBinding(DatePicker.SelectedDateProperty, this.Binding);
return element;
}
protected override FrameworkElement GenerateElement(DataGridCell cell, object dataItem)
{
var element = new TextBlock();
var b = new Binding(((Binding) Binding).Path.Path) {Source = dataItem};
if (DateFormatString != null)
{
b.Converter = new DateTimeConverter();
b.ConverterParameter = DateFormatString;
}
element.SetBinding(TextBlock.TextProperty, b);
return element;
}
protected override object PrepareCellForEdit(FrameworkElement editingElement, RoutedEventArgs editingEventArgs)
{
var element = editingElement as DatePicker;
if (element != null)
{
if (element.SelectedDate.HasValue ) return element.SelectedDate.Value;
}
return DateTime.Now;
}
}
public class DateTimeConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var date = (DateTime)value;
return date.ToString(parameter.ToString());
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
DateTime resultDateTime;
if (DateTime.TryParse(value.ToString(), out resultDateTime))
{
return resultDateTime;
}
return value;
}
}
I then added two more columns to your grid:
<custom:DataGridDateColumn Header="Custom" Binding="{Binding InvoiceDate, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
<DataGridCheckBoxColumn Header="Paid" Binding="{Binding Paid}"/>
If I click into the Custom field now, I get the Message Box, select a date and then tab out, the value gets cleared until I implement INPC on InvoiceDate:
private Nullable<System.DateTime> _invoiceDate;
public Nullable<System.DateTime> InvoiceDate
{
get { return _invoiceDate; }
set
{
_invoiceDate = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
Now, the date is showing according to the DateFormatString set.
Again, I am aware that does not answer your original question, but after my hasty comment from before, I felt obliged to at least come up with a specific workaround.
My take on the issue. The issue you're having with your second column is with the DataGridTemplateColumn. The DataGridTemplateColumn is the actual column, so it's where you should click to add a new line, when you put a control in a DataTemplate in the DataGridCTemplateColumn.CellTemplate, it becomes a "layer" above it. The controls in this "upper layer" are then usable without actually clicking on the Row, which means it does not create a new line.
I did some testing to prove this, if you create a checkbox column this way:
<DataGridCheckBoxColumn Header="Paid" Binding="{Binding Paid}"/>
If you click on the checkbox, it triggers the event to add a new line because this is the actual column, not a control over it.
But if you do the same but with the DataGridTemplateColumn, like this:
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<CheckBox Content="Paid" IsChecked="{Binding Paid}" Margin="5"></CheckBox>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
Note the margin, to be able to click on the actual cell and not on the control above the cell
With this way, if you click on the cell itself, it will trigger the add a new line event, while if you click on the checkbox that is "above" the cell, it will not trigger the event and will only check/uncheck it.
There is also a remark on the msdn documentation that might help you understand also:
The DataGridTemplateColumn type enables you to create your own column types by specifying the cell templates used to display values and enable editing. Set the CellTemplate property to specify the contents of cells that display values, but do not allow editing. Set the CellEditingTemplate property to specify the contents of cells in editing mode. If you set the column IsReadOnly property to true, the CellEditingTemplate property value is never used.
I hope this gives you a better insight on what's going on with your DataGrid
EDIT
Something like this would permit you to manually add the line when you click "Enter" after selectionning your date.
private void DatePicker_KeyUp(object sender, KeyEventArgs e)
{
if (e.Key == Key.Enter)
{
List<JobCostEntity> tempList = (List<JobCostEntity>)dg.ItemsSource;
tempList.Add(new JobCostEntity() { InvoiceDate = ((DatePicker)sender).DisplayDate });
dg.ItemsSource = tempList;
}
}
EDIT - Added code to make one-click editing possible.
Changed all column bindings with UpdateSourceTrigger=PropertyChanged - This is because the default value of LostFocus works at a row level, not cell level, which means that you have to leave the row completely before the bindings take effect. This works ok for many situations, but not when you have two columns bound to the same property, because the changes done to one of those columns won't show inmediately in the other column.
Set IsHitTestVisible="False" to the non-editing template of the central column - My first approach was to make the column read-only and use only the CellTemplate... But this didn't trigger the AddingNewItem event. It seems you NEED to change from the regular cell to the editing cell for that event to fire, but since your non-editing template is not what you want the user to interact with, disabling hit testing makes all sense. That way you force the user to change to edit mode, hence triggering the event, before being able to enter input.
Handled the CurrentCellChanged event of the DataGrid. In the handler, use the methods CommitEdit() to make sure the previously selected cell leaves editing mode, and an asynchronous call to BeginEdit() to start editing the current cell right away, without having to wait for a second click.
Handled the Loaded event of the DatePickers inside the CellEditingTemplates. In the handler, used Keyboard.Focus() to give focus to the DatePicker as soon as it is loaded, saving the user the need to click a third time to put the focus on the control.
XAML:
<Grid>
<DataGrid x:Name="dg" HorizontalAlignment="Left" Margin="10,10,0,0" VerticalAlignment="Top" Height="299" AutoGenerateColumns="False" Width="497" AddingNewItem="dg_AddingNewItem" CanUserAddRows="True"
CurrentCellChanged="dg_CurrentCellChanged">
<DataGrid.Columns>
<DataGridTemplateColumn Header="DateWorks">
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<DatePicker Loaded="DatePicker_Loaded"
SelectedDate="{Binding InvoiceDate,
UpdateSourceTrigger=PropertyChanged}" />
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="DateDoesn'tWork">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<DatePicker IsHitTestVisible="False"
SelectedDate="{Binding InvoiceDate,
UpdateSourceTrigger=PropertyChanged}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<DatePicker Loaded="DatePicker_Loaded"
SelectedDate="{Binding InvoiceDate,
UpdateSourceTrigger=PropertyChanged}" />
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
<DataGridTextColumn Header="Text" Binding="{Binding Description,
UpdateSourceTrigger=PropertyChanged}"/>
</DataGrid.Columns>
</DataGrid>
</Grid>
Code-behind:
private void dg_CurrentCellChanged(object sender, EventArgs e)
{
var dataGrid = sender as DataGrid;
dataGrid.CommitEdit();
Dispatcher.BeginInvoke(new Action(() => dataGrid.BeginEdit()), System.Windows.Threading.DispatcherPriority.Loaded);
}
private void DatePicker_Loaded(object sender, RoutedEventArgs e)
{
Keyboard.Focus(sender as DatePicker);
}
If you use a control that handles mouse click within CellTemplate, the DataGrid never receive click event that triggers it to switch to Edit mode. So like eoinmullan mentioned, the solution is to set the control IsHitTestVisible=False.
Below is the working code. I added INotifyPropertyChanged so we can actually see the changed value reflected in the UI. I also added red background for DateDoesn'tWork CellTemplate, so you can see when the DataGrid goes from display mode to edit mode.
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<DataGrid x:Name="dg" HorizontalAlignment="Left" Margin="10,10,0,0" VerticalAlignment="Top" Height="299" AutoGenerateColumns="False" Width="497" AddingNewItem="dg_AddingNewItem" CanUserAddRows="True">
<DataGrid.Columns>
<DataGridTemplateColumn Header="DateWorks" >
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding InvoiceDate}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<DatePicker SelectedDate="{Binding InvoiceDate}" />
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="DateDoesn'tWork">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate >
<!--Differentiate visually between CellTemplate and CellEditTemplate by using red background-->
<DatePicker Background="Red" IsHitTestVisible="False" SelectedDate="{Binding InvoiceDate}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<DatePicker SelectedDate="{Binding InvoiceDate}" />
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
<DataGridTextColumn Header="Text" Binding="{Binding Description}"/>
</DataGrid.Columns>
</DataGrid>
</Grid>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
List<JobCostEntity> l = new List<JobCostEntity>()
{
new JobCostEntity() {Id = 0, InvoiceDate = DateTime.Now, Description = "A"},
new JobCostEntity() {Id = 0, InvoiceDate = DateTime.Now, Description = "B"}
};
dg.ItemsSource = l;
}
private void dg_AddingNewItem(object sender, AddingNewItemEventArgs e)
{
//MessageBox.Show("AddingNewItem");
}
}
public partial class JobCostEntity : INotifyPropertyChanged
{
private string _description;
private DateTime? _invoiceDate;
public int Id { get; set; }
public int JobId { get; set; }
public Nullable<int> JobItemId { get; set; }
public Nullable<System.DateTime> InvoiceDate
{
get { return _invoiceDate; }
set
{
if (value.Equals(_invoiceDate)) return;
_invoiceDate = value;
OnPropertyChanged();
}
}
public Nullable<System.DateTime> ProcessedDate { get; set; }
public int PackageId { get; set; }
public int DelegateId { get; set; }
public string Description
{
get { return _description; }
set
{
if (value == _description) return;
_description = value;
OnPropertyChanged();
}
}
public Nullable<decimal> LabourCost { get; set; }
public Nullable<decimal> PlantOrMaterialCost { get; set; }
public Nullable<decimal> SubcontractorCost { get; set; }
public Nullable<decimal> TotalCost { get; set; }
public bool Paid { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
First part of the code is only to show the date in 'Working column".
To fix the click twice to edit, then you can use the helper class.
Hope it helps...
<Window x:Class="WpfApplicationAnswerForStackOverflow.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<DataGrid x:Name="dg" HorizontalAlignment="Left" Margin="10,10,0,0" VerticalAlignment="Top" Height="299" AutoGenerateColumns="False" Width="497" AddingNewItem="dg_AddingNewItem" CanUserAddRows="True">
<DataGrid.Columns>
<DataGridTemplateColumn Header="DateWorks">
<!-- Here -->
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding InvoiceDate, StringFormat='d'}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<DatePicker SelectedDate="{Binding InvoiceDate}" />
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="DateDoesn'tWork">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<DatePicker SelectedDate="{Binding InvoiceDate}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<DatePicker SelectedDate="{Binding InvoiceDate}" />
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
<DataGridTextColumn Header="Text" Binding="{Binding Description}"/>
</DataGrid.Columns>
</DataGrid>
</Grid>
</Window>
Fix for single click edit:
Usage:
<Window ...
xmlns:WpfUtil="clr-namespace:HQ.Util.Wpf.WpfUtil;assembly=WpfUtil">
<DataGrid ... util:DataGridCellHelper.IsSingleClickInCell="True">
Class
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
namespace HQ.Wpf.Util
{
public static class DataGridCellHelper
{
#region IsSingleClickInCell
public static readonly DependencyProperty IsSingleClickInCellProperty =
DependencyProperty.RegisterAttached("IsSingleClickInCell", typeof(bool), typeof(DataGrid), new FrameworkPropertyMetadata(false, OnIsSingleClickInCellSet)); public static void SetIsSingleClickInCell(UIElement element, bool value) { element.SetValue(IsSingleClickInCellProperty, value); }
public static bool GetIsSingleClickInCell(UIElement element)
{
return (bool)element.GetValue(IsSingleClickInCellProperty);
}
private static void OnIsSingleClickInCellSet(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
if (!(bool)(DesignerProperties.IsInDesignModeProperty.GetMetadata(typeof(DependencyObject)).DefaultValue))
{
if ((bool)e.NewValue)
{
var dataGrid = sender as DataGrid;
Debug.Assert(dataGrid != null);
EventManager.RegisterClassHandler(typeof(DataGridCell),
DataGridCell.PreviewMouseLeftButtonUpEvent,
new RoutedEventHandler(OnPreviewMouseLeftButtonDown));
}
}
}
private static void OnPreviewMouseLeftButtonDown(object sender, RoutedEventArgs e)
{
DataGridCell cell = sender as DataGridCell;
if (cell != null && !cell.IsEditing && !cell.IsReadOnly)
{
var checkBoxes = ControlHelper.FindVisualChildren<CheckBox>(cell);
if (checkBoxes != null && checkBoxes.Count() > 0)
{
foreach (var checkBox in checkBoxes)
{
if (checkBox.IsEnabled)
{
checkBox.Focus();
checkBox.IsChecked = !checkBox.IsChecked;
var bindingExpression = checkBox.GetBindingExpression(CheckBox.IsCheckedProperty); if (bindingExpression != null) { bindingExpression.UpdateSource(); }
}
break;
}
}
}
}
#endregion
}
}

Binding Mode=TwoWay only working on "one" way

I'm binding datas from a DbSet to a DataGrid thanks to an observable Collection. The way DbSet=>DataGrid works well, but the way DataGrid=>DbSet doesn't at all.
This is some codes to show you what I've done:
First of all, the Datagrid with the Binding properties:
<DataGrid x:Name="DonneesBrutes" ItemsSource="{Binding Path=.ResultatCollection, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Margin="10,65,0,0" AutoGenerateColumns="False" EnableRowVirtualization="True" RowDetailsVisibilityMode="VisibleWhenSelected">
<DataGrid.Columns>
<DataGridTextColumn x:Name="PMRQ" Width="*" Binding="{Binding Path=.TOTMPMRQ, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Header="PMRQ"></DataGridTextColumn>
<DataGridTextColumn x:Name="LibellePMRQ" Width="*" Binding="{Binding Path=.LibelléTOTApres, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Header="Libellé PMRQ"></DataGridTextColumn>
<DataGridTextColumn x:Name="Ligne" Width="*" Binding="{Binding Path=.Remarque, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Header="Ligne"></DataGridTextColumn>
<DataGridTextColumn x:Name="OTM" Width="*" Binding="{Binding Path=.TOTMPMRQ, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Header="OTM"></DataGridTextColumn>
<DataGridTextColumn x:Name="TOTM" Width="*" Binding="{Binding Path=.SiModifie, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Header="TOTM"></DataGridTextColumn>
<DataGridTextColumn x:Name="LibelleTOTM" Width="*" Binding="{Binding Path=.LibelléTOTApres, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Header="Libellé OTM"></DataGridTextColumn>
<DataGridTextColumn x:Name="GA" Width="*" Binding="{Binding Path=.Groupe_D_alerte, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Header="GA"></DataGridTextColumn>
<DataGridTextColumn x:Name="Discipline" Width="*" Binding="{Binding Path=.NomTable, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Header="Discipline"></DataGridTextColumn>
<DataGridTextColumn x:Name="DisciplineSubstituee" Width="120" Binding="{Binding Path=.NomChamp, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Header="Discipline Substituée"></DataGridTextColumn>
</DataGrid.Columns>
</DataGrid>
This is the ResultatCollection which is the ObservableCollection I made to bind datas from DbSet to DataGrid:
class ViewModel:INotifyPropertyChanged
{
private BDDInterneEntities _BDDInterneEntities;
public ViewModel()
{
_BDDInterneEntities = new BDDInterneEntities();
ResultatCollection = new ObservableCollection<Resultat>(_BDDInterneEntities.Resultat);
}
public ObservableCollection<Resultat> ResultatCollection { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string property)
{
if(PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
}
And then this is what the DbSet Resultat looks like:
public partial class Resultat:INotifyPropertyChanged
{
public string NomTable { get; set; }
public string Groupe_D_alerte { get; set; }
public string NomChamp { get; set; }
public string TOTMPMRQ { get; set; }
public string SiModifie { get; set; }
public string LibelléTOTAvant { get; set; }
public string LibelléTOTApres { get; set; }
public string Remarque { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string property)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
}
I have heard speaking about DataGrid.Items.refresh() but it sounds not working, DataGrid.ItemsSource = null then DataGrid.ItemsSource = myDataSource but it doesn't too.
I have heard too the mode=TwoWay and UpdateSourceTrigger=PropertyChanges were enough, I'm quite lost.
Thanks in advance for the help.
Greetings.
In order to receive binding notifications you have to implement INotifyPropertyChanged correctly. The way you did it made it compilable, but not much else.
Every time a externally visible property changes you have to raise the PropertyChanged event. That's why you normally see code like
private int myProperty;
public int MyProperty
{
get { return myProperty; }
set
{
if (myProperty != value)
{
myProperty = value;
OnPropertyChanged("MyProperty");
}
}
}
Of course you must not write directly to the field but use the property instead if you want to notify users of that class about property changes.
As you can see this gets quite tedious which is why several MVVM frameworks offer simplified versions, but you can also do your own version of it, for example
protected void Set<T>(ref T field, T newValue, string propertyName)
{
if (field != newValue)
{
field = newValue;
OnPropertyChanged(propertyName);
}
}
Then your property would become
public int MyProperty
{
get { return myProperty; }
set { Set(ref myProperty, value, "MyProperty"); }
}

Binding a DataGrid TwoWay to an ObservableList of objects

I have a class to store data:
public enum ColumnType
{
...
}
public class LogColumn
{
public string Name { get; set; }
public ColumnType Type { get; set; }
public bool OrderBy { get; set; }
}
and a ObservableCollection of it in my UserControl:
private ObservableCollection<LogColumn> _logColumns = new ObservableCollection<LogColumn>();
public ObservableCollection<LogColumn> LogColumns
{
get { return _logColumns; }
set
{
_logColumns = value;
OnPropertyChanged("LogColumns");
}
}
This collection is bound to a DataGrid:
<DataGrid Name="dgColumnSelection"
ItemsSource="{Binding LogColumns, UpdateSourceTrigger=LostFocus, Mode=TwoWay, ValidatesOnDataErrors=True}" CanUserAddRows="False" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Header="Name" IsReadOnly="True" Binding="{Binding Name, UpdateSourceTrigger=LostFocus, Mode=TwoWay, ValidatesOnDataErrors=True}" />
<DataGridComboBoxColumn Header="Type" ItemsSource="{Binding Source={StaticResource ColumnType}}" SelectedValueBinding="{Binding Type, UpdateSourceTrigger=LostFocus, Mode=TwoWay, ValidatesOnDataErrors=True}" />
<DataGridCheckBoxColumn Header="Order by" Binding="{Binding OrderBy, UpdateSourceTrigger=LostFocus, Mode=TwoWay, ValidatesOnDataErrors=True}" />
</DataGrid.Columns>
</DataGrid>
But neither PropertyChanged-event nor Validation is fired. Where is my fault?
Thx.
Best regards
The event is never fired because you are never setting the LogColumns variable to something new. What you are doing is changing properties on the LogColumn items contained within the LogColumns collection.
Implementing the INotifyPropertyChanged interface on your LogColumn class will might help do what you want.
ObservableCollection only notify if the items of ObservableCollection get chagned not the items item get changed to do so you need to implement the inotifyproprty chagned in your logcolumns class
as
public class LogColumn : INotifyPropertyChanged
{
private string _Name;
public string Name
{
get { return _Name; }
set { _Name = value; Onchanged("Name"); }
}
private ColumnType _Type;
public ColumnType Type
{
get { return _Type; }
set { _Type = value; Onchanged("Type"); }
}
private bool _OrderBy;
public bool OrderBy
{
get { return _OrderBy; }
set { _OrderBy = value; Onchanged("OrderBy"); }
}
public event PropertyChangedEventHandler PropertyChanged;
public void Onchanged(string name)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
now this will work.

Categories

Resources