项目更改时更新WPF列表

时间:2023-01-20 19:42:47

I have a WPF ListBox, and I've added some 'FooBar' objects as items (by code). FooBars aren't WPF objects, just dumb class with an overwritten ToString() function.

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

Now, when I change a property which influences the ToString, I'd like to get the ListBox to update.

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

  1. How can I do this 'quick and dirty' (like repaint).
  2. 我该怎么做“快速又脏”(如重画)。
  3. Is dependency properties the way to go on this?
  4. 依赖属性是否可以继续?
  5. Is it worth it/always advisable, to create a wpf wrapper class for my FooBars?
  6. 是否值得/总是可取的,为我的FooBars创建一个wpf包装类?

Thanks...

谢谢...

5 个解决方案

#1


12  

Your type should implement INotifyPropertyChanged so that a collection can detect the changes. As Sam says, pass string.Empty as the argument.

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

You also need to have the ListBox's data source be a collection that provides change notification. This is done via the INotifyCollectionChanged interface (or the not-so-WPF IBindingList interface).

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

Of course, you need the INotifyCollectionChanged interface to fire whenever one of the member INotifyPropertyChanged items fires its event. Thankfully there are a few types in the framework that provide this logic for you. Probably the most suitable one is ObservableCollection<T>. If you bind your ListBox to an ObservableCollection<FooBar> then the event chaining will happen automatically.

当然,只要其中一个成员INotifyPropertyChanged项触发其事件,就需要触发INotifyCollectionChanged接口。值得庆幸的是,框架中有一些类型可以为您提供此逻辑。可能最合适的一个是ObservableCollection 。如果将ListBox绑定到ObservableCollection ,则事件链接将自动发生。

On a related note, you don't have to use a ToString method just to get WPF to render the object in the way that you want. You can use a DataTemplate like this:

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

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

In this way you can control the presentation of the object where it belongs -- in the XAML.

通过这种方式,您可以在XAML中控制对象所在的表示形式。

EDIT 1 I noticed your comment that you're using the ListBox.Items collection as your collection. This won't do the binding required. You're better off doing something like:

编辑1我注意到您的评论是您使用ListBox.Items集合作为您的集合。这不会做必需的绑定。你最好做以下事情:

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

_listBox.ItemsSource = collection;

I haven't checked that code for compilation accuracy, but you get the gist.

我没有检查编译准确性的代码,但你得到了要点。

EDIT 2 Using the DataTemplate I gave above (I edited it to fit your code) fixes the problem.

编辑2使用上面给出的DataTemplate(我编辑它以适合您的代码)修复了问题。

It seems strange that firing PropertyChanged doesn't cause the list item to update, but then using the ToString method isn't the way that WPF was intended to work.

看起来很奇怪,触发PropertyChanged不会导致列表项更新,但是然后使用ToString方法不是WPF的工作方式。

Using this DataTemplate, the UI binds correctly to the exact property.

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

I asked a question on here a while back about doing string formatting in a WPF binding. You might find it helpful.

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

EDIT 3 I'm baffled as to why this is still not working for you. Here's the complete source code for the window I'm using.

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

Code behind:

代码背后:

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:

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>

#2


3  

The correct solution here is to use an ObservableCollection<> for your ListBox IetmsSource property. WPF will automatically detect any changes in this collection's contents and force the corresponding ListBox to update to reflect the changes.

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

You may want to read this MSDN article for more information. It was written to specifically explain how to handle this scenario

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

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

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

#3


1  

Try implementing the INotifyPropertyChanged interface on your FooBar objects. When they change, raise PropertyChanged events, passing string.Empty as the property name. That should do the trick.

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

#4


0  

If the collection object you use to store the items is an observablecollection<> then this is handled for you.

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

i.e if the collection is changed any controls databound to it will be updated and vice versa.

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

#5


0  

Here is the C# code I have working for this:

这是我为此工作的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"));
        }
    }
}

Here is the XAML:

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

Pressing the Button causes the first property of the first FooBar to be updated and for it to show in the ListBox.

按下按钮会更新第一个FooBar的第一个属性,并使其显示在ListBox中。

#1


12  

Your type should implement INotifyPropertyChanged so that a collection can detect the changes. As Sam says, pass string.Empty as the argument.

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

You also need to have the ListBox's data source be a collection that provides change notification. This is done via the INotifyCollectionChanged interface (or the not-so-WPF IBindingList interface).

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

Of course, you need the INotifyCollectionChanged interface to fire whenever one of the member INotifyPropertyChanged items fires its event. Thankfully there are a few types in the framework that provide this logic for you. Probably the most suitable one is ObservableCollection<T>. If you bind your ListBox to an ObservableCollection<FooBar> then the event chaining will happen automatically.

当然,只要其中一个成员INotifyPropertyChanged项触发其事件,就需要触发INotifyCollectionChanged接口。值得庆幸的是,框架中有一些类型可以为您提供此逻辑。可能最合适的一个是ObservableCollection 。如果将ListBox绑定到ObservableCollection ,则事件链接将自动发生。

On a related note, you don't have to use a ToString method just to get WPF to render the object in the way that you want. You can use a DataTemplate like this:

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

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

In this way you can control the presentation of the object where it belongs -- in the XAML.

通过这种方式,您可以在XAML中控制对象所在的表示形式。

EDIT 1 I noticed your comment that you're using the ListBox.Items collection as your collection. This won't do the binding required. You're better off doing something like:

编辑1我注意到您的评论是您使用ListBox.Items集合作为您的集合。这不会做必需的绑定。你最好做以下事情:

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

_listBox.ItemsSource = collection;

I haven't checked that code for compilation accuracy, but you get the gist.

我没有检查编译准确性的代码,但你得到了要点。

EDIT 2 Using the DataTemplate I gave above (I edited it to fit your code) fixes the problem.

编辑2使用上面给出的DataTemplate(我编辑它以适合您的代码)修复了问题。

It seems strange that firing PropertyChanged doesn't cause the list item to update, but then using the ToString method isn't the way that WPF was intended to work.

看起来很奇怪,触发PropertyChanged不会导致列表项更新,但是然后使用ToString方法不是WPF的工作方式。

Using this DataTemplate, the UI binds correctly to the exact property.

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

I asked a question on here a while back about doing string formatting in a WPF binding. You might find it helpful.

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

EDIT 3 I'm baffled as to why this is still not working for you. Here's the complete source code for the window I'm using.

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

Code behind:

代码背后:

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:

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>

#2


3  

The correct solution here is to use an ObservableCollection<> for your ListBox IetmsSource property. WPF will automatically detect any changes in this collection's contents and force the corresponding ListBox to update to reflect the changes.

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

You may want to read this MSDN article for more information. It was written to specifically explain how to handle this scenario

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

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

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

#3


1  

Try implementing the INotifyPropertyChanged interface on your FooBar objects. When they change, raise PropertyChanged events, passing string.Empty as the property name. That should do the trick.

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

#4


0  

If the collection object you use to store the items is an observablecollection<> then this is handled for you.

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

i.e if the collection is changed any controls databound to it will be updated and vice versa.

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

#5


0  

Here is the C# code I have working for this:

这是我为此工作的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"));
        }
    }
}

Here is the XAML:

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

Pressing the Button causes the first property of the first FooBar to be updated and for it to show in the ListBox.

按下按钮会更新第一个FooBar的第一个属性,并使其显示在ListBox中。