项目更改时更新 WPF 列表

Posted

技术标签:

【中文标题】项目更改时更新 WPF 列表【英文标题】:Updating WPF list when item changes 【发布时间】:2009-02-16 12:24:15 【问题描述】:

我有一个 WPF 列表框,并且我添加了一些“FooBar”对象作为项目(通过代码)。 FooBars 不是 WPF 对象,只是具有覆盖 ToString() 函数的哑类。

现在,当我更改影响 ToString 的属性时,我希望更新 ListBox。

    我怎样才能做到“又快又脏”(比如重绘)。 依赖属性是解决这个问题的方法吗? 是否值得/始终建议为我的 FooBars 创建一个 wpf 包装类?

谢谢...

【问题讨论】:

我已经更新了我的答案,以展示你如何让它发挥作用。似乎 DataTemplate 是要走的路。 【参考方案1】:

您的类型应该实现INotifyPropertyChanged 以便集合可以检测到更改。正如 Sam 所说,将 string.Empty 作为参数传递。

需要将ListBox 的数据源设为提供更改通知的集合。这是通过 INotifyCollectionChanged 接口(或非 WPF IBindingList 接口)完成的。

当然,每当INotifyPropertyChanged 成员之一触发其事件时,您都需要触发INotifyCollectionChanged 接口。值得庆幸的是,框架中有一些类型可以为您提供这种逻辑。可能最合适的是ObservableCollection<T>。如果您将ListBox 绑定到ObservableCollection<FooBar>,则事件链接将自动发生。

在相关说明中,您不必使用ToString 方法来让 WPF 以您想要的方式呈现对象。您可以像这样使用DataTemplate

<ListBox x:Name="listBox1">
    <ListBox.Resources>
        <DataTemplate DataType="x:Type local:FooBar">
            <TextBlock Text="Binding Path=Property"/>
        </DataTemplate>
    </ListBox.Resources>
</ListBox>

通过这种方式,您可以控制对象所属的对象的呈现方式——在 XAML 中。

编辑 1 我注意到您的评论说您正在使用 ListBox.Items 集合作为您的集合。这不会进行所需的绑定。你最好做这样的事情:

var collection = new ObservableCollection<FooBar>();
collection.Add(fooBar1);

_listBox.ItemsSource = collection;

我没有检查该代码的编译准确性,但你明白了。

编辑 2 使用我在上面给出的DataTemplate(我对其进行了编辑以适合您的代码)解决了问题。

触发PropertyChanged 不会导致列表项更新似乎很奇怪,但是使用ToString 方法并不是WPF 的预期工作方式。

使用此 DataTemplate,UI 可以正确绑定到确切的属性。

不久前我在这里问了一个关于 string formatting in a WPF binding 的问题。您可能会发现它很有帮助。

编辑 3 我很困惑为什么这仍然不适合你。这是我正在使用的窗口的完整源代码。

后面的代码:

using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Windows;

namespace ***.ListBoxBindingExample

    public partial class Window1
    
        private readonly FooBar _fooBar;

        public Window1()
        
            InitializeComponent();

            _fooBar = new FooBar("Original value");

            listBox1.ItemsSource = new ObservableCollection<FooBar>  _fooBar ;
        

        private void button1_Click(object sender, RoutedEventArgs e)
        
            _fooBar.Property = "Changed value";
        
    

    public sealed class FooBar : INotifyPropertyChanged
    
        public event PropertyChangedEventHandler PropertyChanged;

        private string m_Property;

        public FooBar(string initval)
        
            m_Property = initval;
        

        public string Property
        
            get  return m_Property; 
            set
            
                m_Property = value;
                OnPropertyChanged("Property");
            
        

        private void OnPropertyChanged(string propertyName)
        
            var handler = PropertyChanged;
            if (handler != null)
                handler(this, new PropertyChangedEventArgs(propertyName));
        
    

XAML:

<Window x:Class="***.ListBoxBindingExample.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:***.ListBoxBindingExample"
    Title="Window1" Height="300" Width="300">
    <DockPanel LastChildFill="True">
        <Button Click="button1_Click" DockPanel.Dock="Top">Click Me!</Button>
        <ListBox x:Name="listBox1">
            <ListBox.Resources>
                <DataTemplate DataType="x:Type local:FooBar">
                    <TextBlock Text="Binding Path=Property"/>
                </DataTemplate>
            </ListBox.Resources>
        </ListBox>
    </DockPanel>
</Window>

【讨论】:

成功了,谢谢。我现在只是在做一个原型 GUI,所以我不需要任何更时髦的东西,但我正在查看字符串格式以防万一。 在这种特殊情况下,您不需要使用ObservableCollection。尝试在上面的代码中将其替换为List&lt;T&gt;,它将继续正常工作。事实上ObservableCollection 甚至不知道其项目的属性变化。当您更改FooBar 的属性时,ObservableCollection 既不会触发CollectionChanged 也不会触发PropertyChanged(因为没有足够的地方,所以不能在这里放置测试代码)。但是,如果您要从列表中添加/删除项目,ObservableCollection 是最正确的方法。 只有 4 个问题的解决方案对我有用……尤其是 DataTemplate。【参考方案2】:

这里正确的解决方案是为 ListBox IetmsSource 属性使用ObservableCollection<>。 WPF 将自动检测此集合内容中的任何更改,并强制相应的 ListBox 更新以反映更改。

您可能需要阅读此 MSDN 文章以了解更多信息。它是专门为解释如何处理这种情况而编写的

http://msdn.microsoft.com/en-us/magazine/dd252944.aspx?pr=blog

【讨论】:

我完全同意你的看法。【参考方案3】:

尝试在您的 FooBar 对象上实现 INotifyPropertyChanged 接口。当它们发生变化时,引发 PropertyChanged 事件,将 string.Empty 作为属性名称传递。这应该可以解决问题。

【讨论】:

是的,但不是。似乎没有人注册我的活动:( 这还不够。您还应该“告诉”ListBox 应该从对象的哪个属性中获取要显示的字符串。 DataTemplate 不是这样做的唯一方法。最简单的方法是在 ListBox 中添加DisplayMemberPath="PropertyName" 属性。【参考方案4】:

这是我为此工作的 C# 代码:

using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.ComponentModel;
using System.Collections.ObjectModel;
namespace ListboxOfFoobar

    /// <summary>
    /// Interaction logic for Window1.xaml
    /// </summary>
    public partial class Window1 : Window
    
        public Window1()
        
            InitializeComponent();
        

        private void Button_Click(object sender, RoutedEventArgs e)
        
            ObservableCollection<FooBar> all = (ObservableCollection<FooBar>)FindResource("foobars");
            all[0].P1 = all[0].P1 + "1";
        
    
    public class FooBar : INotifyPropertyChanged
    
        public FooBar(string a1, string a2, string a3, string a4)
        
            P1 = a1;
            P2 = a2;
            P3 = a3;
            P4 = a4;
        

        public event PropertyChangedEventHandler PropertyChanged;
        private void NotifyPropertyChanged(String info)
        
            if (PropertyChanged != null)
            
                PropertyChanged(this, new PropertyChangedEventArgs(info));
            
        

        private String p1;
        public string P1
        
            get  return p1; 
            set
            
                if (value != this.p1)
                
                    this.p1 = value;
                    NotifyPropertyChanged("P1");
                
            
        
        private String p2;
        public string P2
        
            get  return p2; 
            set
            
                if (value != this.p2)
                
                    this.p2 = value;
                    NotifyPropertyChanged("P2");
                
            
        
        private String p3;
        public string P3
        
            get  return p3; 
            set
            
                if (value != this.p3)
                
                    this.p3 = value;
                    NotifyPropertyChanged("P3");
                
            
        
        private String p4;
        public string P4
        
            get  return p4; 
            set
            
                if (value != this.p4)
                
                    this.p4 = value;
                    NotifyPropertyChanged("P4");
                
            
        
        public string X
        
            get  return "Foooooo"; 
        
    
    public class Foos : ObservableCollection<FooBar>
    
        public Foos()
        
            this.Add(new FooBar("a", "b", "c", "d"));
            this.Add(new FooBar("e", "f", "g", "h"));
            this.Add(new FooBar("i", "j", "k", "l"));
            this.Add(new FooBar("m", "n", "o", "p"));
        
    

这是 XAML:

<Window x:Class="ListboxOfFoobar.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:ListboxOfFoobar"
    xmlns:debug="clr-namespace:System.Diagnostics;assembly=System"

    Title="Window1" Height="300" Width="300"        
        >
    <Window.Resources>
        <local:Foos x:Key="foobars" />
        <DataTemplate x:Key="itemTemplate">
            <StackPanel Orientation="Horizontal">
                <TextBlock MinWidth="80" Text="Binding Path=P1"/>
                <TextBlock MinWidth="80" Text="Binding Path=P2"/>
                <TextBlock MinWidth="80" Text="Binding Path=P3"/>
                <TextBlock MinWidth="80" Text="Binding Path=P4"/>
            </StackPanel>
        </DataTemplate>

    </Window.Resources>

    <DockPanel>
        <ListBox DockPanel.Dock="Top"
         ItemsSource="StaticResource foobars"
         ItemTemplate="StaticResource itemTemplate" Height="229" />
        <Button  Content="Modify FooBar" Click="Button_Click" DockPanel.Dock="Bottom" />
    </DockPanel>
</Window>

按下按钮会导致第一个 FooBar 的第一个属性被更新并显示在 ListBox 中。

【讨论】:

【参考方案5】:

如果您用来存储项目的集合对象是一个 observablecollection,那么它会为您处理。

即如果集合发生变化,任何绑定到它的控件都将被更新,反之亦然。

【讨论】:

那么列表框项目就是集合:myList.Items.Add(new FooBar());

以上是关于项目更改时更新 WPF 列表的主要内容,如果未能解决你的问题,请参考以下文章

更改文化时wpf更新验证错误

如何通过Powershell代码更改特定WPF列表框项的背景颜色?

更改集合时 WPF 组合框不更新

为啥更改 ItemsSource 时 DataGrid 不更新?

C# Wpf 如何使用其中的按钮更改 Listviewitem 的高度?

在列表框中选中复选框时更改文本框的颜色 c# wpf