Basic use of WPF_ObservableCollection and its precautions


I. Introduction

Item controls are often used in GUI programming, such as ComboBox (drop-down list box), which often has multiple items inside.

When using some graphics frameworks (Qt, WinForm) for original development, the UI control is often accessed directly during the interface initialization phase, and list items are added to it; if there are changes later, the event trigger handler is used to add or delete items.

This approach has certain limitations. If you want to update the data in the list and make it appear on the UI, you have to directly access the control. If it’s just a simple item control like ComboBox, just use comboBox.AddItem directly; but if it’s in a more complex control (such as a grid grid), it’s not so easy to write code if you want to add new items.

The ObservableCollection in WPF can bind the control to the data source, so that the control and the data source are kept in sync. To put it simply, after the control is bound to the data source, you only need to add or delete data to the data source, without worrying about the UI change logic, and realize the separation of the front and back ends.

二、ObservableCollection

As described in Microsoft's official documentation, ObservableCollection is a dynamic data collection that provides notifications (notifying bound controls to make changes) when items in the collection are added, removed, updated, or the entire list is refreshed.

The following is the demo I wrote. On the left is a ComboBox whose ItemSource is bound to a collection object; on the right are some buttons, and the commands clicked on the buttons will modify the collection object. The following is the main XAML code of the interface
insert image description here
:

   <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition/>
            <ColumnDefinition Width="*"/>
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>
        <ComboBox ItemsSource="{Binding Items}" Height="30" x:Name="comboBox"/>
        <StackPanel Grid.Column="1">
            <StackPanel.Resources>
                <Style TargetType="Button">
                    <Setter Property="Margin" Value="8"/>
                </Style>
            </StackPanel.Resources>
            <Button Content="添加一项" Command="{Binding AddItemCommand}"/>
            <Button Content="移除一项" Command="{Binding RemoveItemCommand}"/>
            <Button Content="更新项" Command="{Binding UpdateItemCommand}"/>
            <Button Content="替换整个列表" Command="{Binding ReplaceListCommand}"/>
            <Button Content="查看ItemSource绑定对象" Command="{Binding InspectObjectCommand}" CommandParameter="{Binding ElementName=comboBox}"/>
        </StackPanel>
    </Grid>

The following is the code in the background ViewModel, mainly commands:

		public MainWindowViewModel()
        {
    
    
            _items = new ObservableCollection<string>()
            {
    
    
                "初始项1",
                "初始项2",
                "初始项3"
            };
        }

        private ObservableCollection<string> _items;
        public ObservableCollection<string> Items
        {
    
    
            get => _items;

        }

        private RelayCommand _addItemCommand;
        public RelayCommand AddItemCommand
        {
    
    
            get => _addItemCommand?? (_addItemCommand = new RelayCommand(() =>
            {
    
    
                _items.Add("新加项");
            }));
        }

        private RelayCommand _removeItemCommand = null;
        public RelayCommand RemoveItemCommand
        {
    
    
            get => _removeItemCommand ?? (_removeItemCommand = new RelayCommand(() =>
            {
    
    
                _items.Remove(_items.Last());
            }));
        }

        private RelayCommand _updateItemCommand;
        public RelayCommand UpdateItemCommand
        {
    
    
            get => _updateItemCommand ?? (_updateItemCommand = new RelayCommand(() =>
            {
    
    
                _items[0] = "更新项";
            }));
        }

        private RelayCommand _replaceListCommand;
        public RelayCommand ReplaceListCommand
        {
    
    
            get => _replaceListCommand ?? (_replaceListCommand = new RelayCommand(() =>
            {
    
    
                _items = new ObservableCollection<string>()
                {
    
    
                    "替换后的表"
                };
                MessageBox.Show("替换完毕");
            }));
        }

        private RelayCommand<object> _inspectObjectCommand;
        public RelayCommand<object> InspectObjectCommand
        {
    
    
            get => _inspectObjectCommand ?? (_inspectObjectCommand = new RelayCommand<object>((o) => 
            {
    
    
                ComboBox cb = o as ComboBox;
                MessageBox.Show(string.Format("HashCode:\nItemSource={0}\n_items={1}\n是否相等?{2}", 
                cb.ItemsSource.GetHashCode(), _items.GetHashCode(),cb.ItemsSource.Equals(_items)));
            }));
        }

Among them, the three commands of adding, deleting and changing can be reflected in the UI immediately. And the replace list command doesn't work. Click the View Binding Object button, and you can see that the hashCode of ItemSource and background _items are equal, and they are also equal when compared with equals;

insert image description here
Then, click the button to replace the entire list, and then check the binding object. This time, the hashCode of ItemSource has not changed, and equals also reflects that it is not equal to the background _items.
insert image description here
It can be seen that replacing the bound data source does not modify the ItemSource (it is possible to modify the ItemSource of the access control, although this is nonsense).

The following statement is wrong!
Reference Types vs Primitive Types


The essential problem here is actually the assignment of reference types and basic types.

int a = 1;
int b = a;

The assignment of basic types (such as numbers, strings, Booleans, usually simpler) is to copy the value in the past.
When declaring a basic type, a space will be opened up in the stack memory to store the value of the variable. When the value of a is assigned to b, the value of a will be copied to the space of b. The storage of their two values ​​is independent, in different spaces, as shown in the figure below.
insert image description here
The variable name of the reference type data is stored in the stack memory, and the value is stored in the heap memory. The stack memory will provide a reference address to point to the value in the heap memory. It is very similar to pointers in C language,
insert image description here
so the situation here is that, in fact
insert image description here
, it is to open up another piece of memory in the heap space, and then point the address in the stack memory and space of the _items variable to it. _items = new ObservableCollection(){ "替换后的表"}So the ItemSource doesn't change.

Therefore, if you want to replace the entire list, you should directly replace the value of ItemSource.

3. Conclusion

The basic use of ObservableCollection is shown in the above code example. It should be noted that replacing the binding object in the ViewModel does not actually replace the ItemSource. Also, ObservableCollection is not thread-safe. After ItemSource is bound, ObservableCollection cannot be modified across threads (outside the UI thread). This point will be introduced in another article.

Guess you like

Origin blog.csdn.net/BadAyase/article/details/129119074