I have C# WPF project that is save data from DataGrid
into a Table in SQL Server Database
In Database I have two tables as Factor
and Commodity
that related to each other and
The DataGrid ItemsSource
filled by anObservableCollection
is named WHOLE_DATA_FACTOR
that is from Factor
Table
and ItemsSource
of DataGridComboBoxColumn
it is filled only once when loading the program,
After I Insert into Factor
table in RowEditEnding
event I want to Reload data for DataGrid,
The DataGridComboBoxColumn
items were filled once by a query in the SELECT CommodityCode, CommodityName FROM dbo.Commodity
, but I noticed that after LoadDataAgian
, the items that were in the collection started to refresh (as if moving on each item), and this made the program slow down, and also The DataGridComboBoxColumn
ItemsSource
starts to fill again, while I didn't do that
Here is the Video what happened :
mp4: https://ufile.io/p5azjp40
Full Source Code and DB : https://ufile.io/uxneng9r
XAML:
<DataGridComboBoxColumn Width="160" Header="DataGridComboBoxColumn"
SelectedValueBinding="{Binding CommodityID}"
DisplayMemberPath="CommodityName"
SelectedValuePath="CommodityCode"
>
<DataGridComboBoxColumn.ElementStyle>
<Style TargetType="{x:Type ComboBox}">
<Setter Property="ItemsSource" Value="{Binding Path=TheCommodityCombo_DATA, RelativeSource={RelativeSource AncestorType=Window}}" />
</Style>
</DataGridComboBoxColumn.ElementStyle>
<DataGridComboBoxColumn.EditingElementStyle>
<Style TargetType="{x:Type ComboBox}">
<Setter Property="ItemsSource" Value="{Binding Path=TheCommodityCombo_DATA, RelativeSource={RelativeSource AncestorType=Window}}" />
<Setter Property="IsEditable" Value="True"/>
<Setter Property="ScrollViewer.CanContentScroll" Value="True"/>
<Setter Property="VirtualizingStackPanel.IsVirtualizing" Value="True"/>
<Setter Property="VirtualizingStackPanel.VirtualizationMode" Value="Recycling"/>
</Style>
</DataGridComboBoxColumn.EditingElementStyle>
</DataGridComboBoxColumn>
<DataGridComboBoxColumn Width="200" Header="Status ComboBoxColumn"
SelectedValueBinding="{Binding STATUS}"
DisplayMemberPath="STATUS_NAME"
SelectedValuePath="STATUS">
<DataGridComboBoxColumn.ElementStyle>
<Style TargetType="{x:Type ComboBox}">
<Setter Property="ItemsSource" Value="{Binding Path=DataContext.STATUS_COMBO_DATA, RelativeSource={RelativeSource AncestorType={x:Type Window}}}" />
</Style>
</DataGridComboBoxColumn.ElementStyle>
<DataGridComboBoxColumn.EditingElementStyle>
<Style TargetType="{x:Type ComboBox}">
<Setter Property="ItemsSource" Value="{Binding Path=DataContext.STATUS_COMBO_DATA, RelativeSource={RelativeSource AncestorType={x:Type Window}}}" />
<Setter Property="IsEditable" Value="True"/>
<Setter Property="ScrollViewer.CanContentScroll" Value="True"/>
<Setter Property="VirtualizingStackPanel.IsVirtualizing" Value="True"/>
<Setter Property="VirtualizingStackPanel.VirtualizationMode" Value="Recycling"/>
</Style>
</DataGridComboBoxColumn.EditingElementStyle>
</DataGridComboBoxColumn>
</DataGrid.Columns>
</DataGrid>
Code Behind :
public partial class MainWindow : Window
{
/// <summary>
/// For Ensuring the new data is getting in Row end edit
/// </summary>
private bool _handle = true;
MyerEntities dbms = new MyerEntities();
#region Models and Collections
/// <summary>
/// For Factor Table
/// </summary>
public class MyCustomModel_Factor
{
public long? NUMBER { get; set; }
public string CustomerName { get; set; }
public long? CommodityID { get; set; }
public long? STATUS { get; set; }
}
public ObservableCollection<MyCustomModel_Factor> WHOLE_DATA_FACTOR { get; set; } = new ObservableCollection<MyCustomModel_Factor>();
/// <summary>
/// For Commodity Table for ComboBox Items
/// </summary>
public ObservableCollection<MyCustomModel_Commodity> TheCommodityCombo_DATA { get; set; } = new ObservableCollection<MyCustomModel_Commodity>();
public class MyCustomModel_Commodity
{
public long CommodityCode { get; set; }
public string CommodityName { get; set; }
}
//STATUS
public ObservableCollection<CutsomStatus_Model> STATUS_COMBO_DATA { get; set; } = new ObservableCollection<CutsomStatus_Model>();
public class CutsomStatus_Model
{
public long? STATUS { get; set; }
public string STATUS_NAME { get; set; }
}
#endregion
public MainWindow()
{
InitializeComponent();
DataContext = this;
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
//Filling DataGrid by ObservableCollection
WHOLE_DATA_FACTOR.Clear();
var RST = dbms.Database.SqlQuery<MyCustomModel_Factor>("SELECT * FROM Factor").ToList();
foreach (var item in RST)
{ WHOLE_DATA_FACTOR.Add(item); }
//Filling ComboBox from Another Table that Related to Factor Table
TheCommodityCombo_DATA.Clear();
var RST2 = dbms.Database.SqlQuery<MyCustomModel_Commodity>("SELECT CommodityCode, CommodityName FROM dbo.Commodity").ToList();
foreach (var item2 in RST2)
{
TheCommodityCombo_DATA.Add(item2);
}
//STATUS Filling
STATUS_COMBO_DATA.Add(new CutsomStatus_Model { STATUS = 1, STATUS_NAME = "Undone" });
STATUS_COMBO_DATA.Add(new CutsomStatus_Model { STATUS = 2, STATUS_NAME = "Done" });
STATUS_COMBO_DATA.Add(new CutsomStatus_Model { STATUS = 3, STATUS_NAME = "Canceled" });
}
private void MainDataGrid_CellEditEnding(object sender, DataGridCellEditEndingEventArgs e)
{
try
{
var WhatWasRow = e.Row.Item as MyCustomModel_Factor;
var TheCurrentColumnName = MainDataGrid.CurrentColumn.SortMemberPath;
var test = WhatWasRow.GetType().GetProperty(TheCurrentColumnName).GetValue(WhatWasRow);
var Editedrow = (e.EditingElement as ComboBox);
var test3 = ((System.Windows.Controls.Primitives.Selector)e.EditingElement).SelectedValue;
}
catch (Exception)
{ goto one rrorResumeNext; }
one rrorResumeNext:;
}
private void MainDataGrid_RowEditEnding(object sender, DataGridRowEditEndingEventArgs e)
{
if (_handle)
{
_handle = false;
MainDataGrid.CommitEdit();
var ROW_ITM = e.Row.Item as MyCustomModel_Factor;
//...Do Insert
dbms.Database.ExecuteSqlCommand($"INSERT INTO dbo.Factor (CustomerName,CommodityID,STATUS) VALUES (N'{ROW_ITM.CustomerName}',{ROW_ITM.CommodityID},{ROW_ITM.STATUS})");
//I think i need somthing like this ↓_________________________________
//ObservableCollection.WHOLE_DATA_FACTOR.IsEnabled = false;
//ObservableCollection.TheCommodityCombo_DATA.IsEnabled = false;
//ObservableCollection.STATUS_COMBO_DATA.IsEnabled = false;
LoadDataAgian();
_handle = true;
//ObservableCollection.WHOLE_DATA_FACTOR.IsEnabled = true;
//ObservableCollection.TheCommodityCombo_DATA.IsEnabled = true;
//ObservableCollection.STATUS_COMBO_DATA.IsEnabled = true;
//_______________________________________________________________________
return;
//After this line , most stop continue ,
//but it will go for → ObservableCollections property { get; set; } to refreshing every item , it feels Requery
}
}
private void LoadDataAgian()
{
WHOLE_DATA_FACTOR.Clear();
var RST = dbms.Database.SqlQuery<MyCustomModel_Factor>("SELECT * FROM Factor").ToList();
foreach (var item in RST)
{ WHOLE_DATA_FACTOR.Add(item); }
}
}
Related link : Updating an ObservableCollection in WPF causes screen flicker; How can I prevent it?
Can I somehow temporarily disable WPF data binding changes?
Update:
-The ItemsSource
of the DatGrid
is filled from the Factor
table
-The ItemsSource
of the DataGridComboBoxColumn
is filled with Commoditiy
When I want to Realod DataGrid's Data by "select * from Factor"
I Only Reloaded the DataGrid's ItemsSource not The ItemsSource of the DataGridComboBoxColumn
why DataGridComboBoxColumn's ItemsSource will refresh (as if moving on each item)
NOTE: if explanation was not good please check the video and comment in my code
Best Regards
CodePudding user response:
Whenever you modify (add/remove) the ObservableCollection
, the Target
will be triggered and will start calling the getters {set; get;}
of the collections' items to update itself.
If this behaviour does not meet your needs, you can do the following:
Replace
ObservableCollection
withList
.Let the class implement
INotifyPropertyChanged
interface, like so (based on your code):
public partial class MainWindow : Window, INotifyPropertyChanged {
// ..
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
// ..
}
- Whenever you want to update UI from code, you could do it explicitly using
OnPropertyChanged
:
private void Window_Loaded(object sender, RoutedEventArgs e)
{
//Filling DataGrid by ObservableCollection
WHOLE_DATA_FACTOR = new List<MyCustomModel_Commodity>();
var RST = dbms.Database.SqlQuery<MyCustomModel_Factor>("SELECT * FROM Factor").ToList();
foreach (var item in RST)
WHOLE_DATA_FACTOR.Add(item);
OnPropertyChanged(nameof(WHOLE_DATA_FACTOR));
// ..
}
NOTE that I've created a new List<MyCustomModel_Commodity>()
, so to update the Target
that is bound to a List
, you have to give it a new reference, you could also just do WHOLE_DATA_FACTOR = WHOLE_DATA_FACTOR.ToList()
just before calling OnPropertyChanged