Home > OS >  How can I prevent the ObservableCollection from being refreshed for no reason in WPF?
How can I prevent the ObservableCollection from being refreshed for no reason in WPF?

Time:09-07

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 enter image description here

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:

  1. Replace ObservableCollection with List.

  2. 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));
    }
    // ..
}
  1. 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

  • Related