WPF_ObservableCollection基本使用及其注意项


一、引言

在GUI编程中经常会用到条目控件,常见的如ComboBox(下拉列表框),它内部往往有多个项。

在使用一些图形框架(Qt、WinForm)上进行原始开发时,往往在界面初始化阶段直接访问UI控件,往其中添加列表项;若后期有改动,通过事件触发处理程序来再往其中增删项。

这种方式有一定局限性,你想要更新列表中的数据并使之呈现在UI上就得直接访问控件。如果只是ComboBox这种简单的条目控件,那直接 comboBox.AddItem就好;但如果是在更复杂的控件中(如grid网格),要想添加新项,代码就没那么容易写了。

WPF中的ObservableCollection可以将控件与数据源绑定,使得控件与数据源保持同步。简单讲,在控件与数据源绑定后,你只需要往数据源中增删数据,而不需要管UI变更逻辑,且实现了前后端分离。

二、ObservableCollection

微软官方文档中描述,ObservableCollection是一个动态数据集合,当集合中的项增加、移除、更新,或整个列表刷新时,它会提供通知(通知绑定的控件做出改变)。

下面是我写的demo,左边是一个ComboBox,它的ItemSource绑定了集合对象;右边是一些按钮,按钮单击的命令会修改集合对象,
在这里插入图片描述
下面是界面主要的XAML代码:

   <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>

下面是后台ViewModel中的代码,主要是命令:

		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)));
            }));
        }

其中增、删、改这三个命令都能即时反映到UI。而替换列表命令并不能起作用。单击 查看绑定对象 按钮,可以看到,ItemSource和后台_items的hashCode是相等的,并且用equals比较它们也是相等的;

在这里插入图片描述
接着,单击 替换整个列表 按钮,再查看绑定对象,这次ItemSource的hashCode并未发生变化,且equals也反映出与后台_items不等。
在这里插入图片描述
可见,对绑定的数据源做替换并不能修改ItemSource(访问控件的ItemSource做修改是可以的,虽然这是废话)。

扫描二维码关注公众号,回复: 15454764 查看本文章

下面说法有误!
引用类型与基本类型


这里的本质问题其实是引用类型与基本类型的赋值问题。

int a = 1;
int b = a;

基本类型(如 数字、字串、布尔,通常是较简单的)的赋值,是把值拷贝过去。
声明基本类型时,会在栈内存中开辟一块空间,存放变量的值。当a的值赋给b时,a的值会被复制到b的空间中。它们两值的存放是独立的,在不同空间中,如下图。
在这里插入图片描述
而引用类型数据的变量名存放在栈内存中,值存放在堆内存中,栈内存会提供一个引用地址用于指向堆内存中的值。非常类似于C语言中的指针,
在这里插入图片描述
所以这里的情况就是,
在这里插入图片描述
_items = new ObservableCollection(){ "替换后的表"}其实就是在堆空间中再开辟一块内存,然后将_items变量的栈内存啊、空间里的地址指向它。因此ItemSource并不会改变。

因此想替换整个列表,应该直接替换ItemSource的值。

三、结语

ObservableCollection基本使用如上述代码示例所示。其中要注意的是,替换ViewModel中的绑定对象并不能真实替换ItemSource。还有ObservableCollection不是线程安全的,ItemSource绑定其后,不能跨线程(UI线程外)修改ObservableCollection,关于这点会另辟文进行介绍。

猜你喜欢

转载自blog.csdn.net/BadAyase/article/details/129119074
今日推荐