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"); }
}
Related
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));
}
}
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
I have a datagrid where a user can enter some data. Once they have entered the data they click a button to upload the data. However I am having some issues with the data not binding as I expect.
The datagrid is bound to a list called HldLogEQ of type ObservableCollection HoldingLogEQ.
When I debug before the data is uploaded the ISIN and Sedol properties are both null which is incorrect. I thought the line below was the correct way to bind the data? What am I missing?
<DataGridTextColumn Header="Sedol" IsReadOnly="False" Binding="{Binding Security.Sedol, Mode=TwoWay}"/>
HoldingLoq Class
public class HoldingLogEQ : INotifyPropertyChanged
{
public DateTime DateEffective
{
get
{
return _dateEffective;
}
set
{
_dateEffective = value;
OnPropertyChanged("DateEffective");
}
}
public SecruityID Security
{
get
{
return _security;
}
set
{
_security = value;
OnPropertyChanged("Security");
}
}
public List<Fund> Funds
{
get
{
return _funds;
}
set
{
_funds = value;
OnPropertyChanged("Funds");
}
}
private DateTime _dateEffective;
private SecruityID _security = new SecruityDescriptive();
private List<Fund> _funds;
public event PropertyChangedEventHandler PropertyChanged;
void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
Security Id class
public class SecurityID : INotifyPropertyChanged
{
public string ISIN
{
get
{
return _isin;
}
set
{
_isin = value;
OnPropertyChanged("ISIN");
}
}
public string Sedol
{
get
{
return _sedol;
}
set
{
_sedol = value;
OnPropertyChanged("Sedol");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
private string _isin;
private string _sedol;
#endregion
}
Datagrid
<DataGrid Grid.Row="1"
x:Name="dgHldRGHTS"
ItemsSource="{Binding HldLogEQ, UpdateSourceTrigger=PropertyChanged}"
Style="{StaticResource DataGridTemplate1}"
ColumnHeaderStyle="{StaticResource DG_ColumnHeaderCenter1}"
RowStyle="{StaticResource DG_Row1}"
CellStyle="{StaticResource DG_Cell1}"
RowHeaderStyle="{StaticResource DG_RowHeader1}"
RowDetailsTemplate="{StaticResource DG_RowDetail}"
AutoGenerateColumns="False"
HorizontalAlignment="Stretch"
Background="Silver"
Margin="50,0,50,50"
CanUserDeleteRows="True"
CanUserAddRows="True"
RowHeaderWidth="30">
<DataGrid.Columns>
<DataGridTextColumn Header="Date Effective" IsReadOnly="False" Binding="{Binding DateEffective, StringFormat={}\{0:dd-MMM-yy\}, Mode=TwoWay}" MinWidth="75"/>
<DataGridTextColumn Header="ISIN" IsReadOnly="False" Binding="{Binding Security.ISIN, Mode=TwoWay}" MinWidth="75"/>
<DataGridTextColumn Header="Sedol" IsReadOnly="False" Binding="{Binding Security.Sedol, Mode=TwoWay}" MinWidth="75"/>
<DataGridTextColumn Header="Name" IsReadOnly="False" Binding="{Binding Security.Name, Mode=TwoWay}" MinWidth="200"/>
</DataGrid.Columns>
</DataGrid>
Maybe need add Path.
Binding="{Binding Path=Security.Sedol, Mode=TwoWay}"
In my class ResultEntity if I do:
_resultMulti = new List<ResultTaskFund>();
I see the following error message:
"Exception: Items collection must be empty before using ItemSource."
I have seen lots of people also have had this error message and I've read the posts but I don't understand why I'm seeing this message. If I just declare _resultMulti without initialising a new list the application loads. I don't understand why though?
I have the following classes:
ResultSummary
public class ResultEntity : INotifyPropertyChanged
{
public List<ResultTaskFund> ResultsMulti
{
get { return _resultsMulti; }
set { _resultsMulti = value; OnPropertyChanged("ResultsMulti"); }
}
List<ResultTaskFund> _resultMulti;
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
ResultTaskFund
public class ResultTaskFund : INotifyPropertyChanged
{
public string TaskName { get; set; }
public ResultFund ABBC { get; set; }
public ResultFund BBCA { get; set; }
public ResultFund CCCA { get; set; }
}
Result Fund
public class ResultFund
{
public string FundCode { get; set; }
public ErrorAndWarningCodes ErrCode { get; set; }
public bool FundRequried { get; set; }
public bool CheckRequired { get; set; }
public string DisplayString { get; set; }
}
XAML
<DataGrid Grid.Row="0"
DataContext="{Binding ResultSummary}"
x:Name="dataGridResultMulti"
ItemsSource="{Binding ResultsMulti, UpdateSourceTrigger=PropertyChanged}"
Style="{StaticResource DataGridTemplate}"
ColumnHeaderStyle="{StaticResource DG_ColumnHeader}"
RowStyle="{StaticResource DG_Row}"
CellStyle="{StaticResource DG_Cell}"
RowDetailsTemplate="{StaticResource DG_RowDetail}"
RowHeaderStyle="{StaticResource DG_RowHeader}"
AutoGenerateColumns="False"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Background="Silver"
RowHeaderWidth="30"
Margin="25,5,20,15">
<DataGrid.Columns>
<DataGridTextColumn Header="Task" IsReadOnly="True" Binding="{Binding TaskName}"/>
<DataGridTextColumn Header="ABBC" IsReadOnly="True" Binding="{Binding ABBC.DisplayString}"/>
<DataGridTextColumn Header="BBCA" IsReadOnly="True" Binding="{Binding BBCA.DisplayString}"/>
<DataGridTextColumn Header="CCCA" IsReadOnly="True" Binding="{Binding CCCA.DisplayString}"/>
<DataGrid.Columns>
</DataGrid>
You are adding your DataGridTextColumns directly as items of the DataGrid, thus you are setting both the Items and ItemsSource property and you can only use one at the same time. Fix your columns configuration by using the Columns attached property:
<DataGrid.Columns>
<DataGridTextColumn Header="Task" IsReadOnly="True" Binding="{Binding TaskName}"/>
<DataGridTextColumn Header="ABBC" IsReadOnly="True" Binding="{Binding ABBC.DisplayString}"/>
<DataGridTextColumn Header="BBCA" IsReadOnly="True" Binding="{Binding BBCA.DisplayString}"/>
<DataGridTextColumn Header="CCCA" IsReadOnly="True" Binding="{Binding CCCA.DisplayString}"/>
</DataGrid.Columns>
Just wrap your column definitions with DataGrid.Columns. As you are binding it using ItemsSource you can't put any direct content in the definition. It is considering your columns as items of the grid which can't go with ItemsSource collection.
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.