问题 项目更改时更新WPF列表


我有一个WPF ListBox,我添加了一些'FooBar'对象作为项目(通过代码)。 FooBars不是WPF对象,只是带有覆盖ToString()函数的哑类。

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

  1. 我该怎么做“快速又脏”(如重画)。
  2. 依赖属性是否可以继续?
  3. 是否值得/总是可取的,为我的FooBars创建一个wpf包装类?

谢谢...


8835
2018-02-16 12:24


起源

我已经更新了我的答案,以展示如何让这个工作。似乎DataTemplate是要走的路。 - Drew Noakes


答案:


你的类型应该实现 INotifyPropertyChanged 这样集合就可以检测到变化。正如萨姆所说,过去了 string.Empty 作为论点。

 需要有 ListBox的数据源是一个提供更改通知的集合。这是通过 INotifyCollectionChanged 接口(或不是WPF IBindingList 接口)。

当然,你需要的 INotifyCollectionChanged 触发任何一个成员的接口 INotifyPropertyChanged 物品发射它的事件。值得庆幸的是,框架中有一些类型可以为您提供此逻辑。可能是最合适的一个 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可以正确绑定到确切的属性。

我在这里问了一个关于做的问题 WPF绑定中的字符串格式。您可能会发现它很有帮助。

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

代码背后:

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

namespace StackOverflow.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="StackOverflow.ListBoxBindingExample.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:StackOverflow.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>

12
2018-02-16 13:25



感谢您的信息,我尝试了您在编辑中所说的内容,但没有快乐。元素被添加到列表框中,但是当我触发PropertyChanged时没有任何更新 - 没有人似乎在听... - Benjol
当属性发生变化时,你的元素会激活INotifyPropertyChanged事件吗?你可以在你的问题中发布一些示例代码吗? - Drew Noakes
我的错,我刚刚更改了代码,而不是xaml。明天我会再试一次,为你加油! - Benjol
它有效,谢谢。我现在只是在做一个原型GUI,所以我不需要任何更时髦的东西,但我正在查看字符串格式以防万一。 - Benjol
在这种特殊情况下你不是 需要 使用 ObservableCollection。尝试替换它 List<T> 在上面的代码中,它将继续正常工作。事实上 ObservableCollection 甚至不知道其物品的属性变化。 ObservableCollection 不会开火 CollectionChanged 也不 PropertyChanged 当你改变财产 FooBar (不能把测试代码放在这里,因为没有足够的地方)。但是,如果您要添加/删除列表中的项目 ObservableCollection 是最正确的方法。 - Lu55


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

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

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


3
2018-02-16 13:57



我完全同意你的意见。 - Blounty


尝试在FooBar对象上实现INotifyPropertyChanged接口。当它们更改时,引发PropertyChanged事件,将string.Empty作为属性名称传递。这应该够了吧。


1
2018-02-16 12:49



是的,但没有。似乎没有人报名参加我的活动:( - Benjol
这不够。你还应该“告诉”ListBox你应该从哪个对象的属性中获取要显示的字符串。 DataTemplate不是唯一的方法。最简单的方法是添加 DisplayMemberPath="PropertyName" 属性到ListBox。 - Lu55


如果用于存储项目的集合对象是observablecollection <>,则会为您处理。

即如果集合被更改,任何控件数据绑定都将被更新,反之亦然。


0
2018-02-16 12:40



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


这是我为此工作的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中。


0
2018-02-16 21:58