Обновление списка WPF при изменении элемента

У меня есть ListBox WPF, и я добавил некоторые объекты FooBar как элементы (по коду). FooBars не являются объектами WPF, просто тупым классом с перезаписанной функцией ToString().

Теперь, когда я изменяю свойство, влияющее на ToString, я хочу, чтобы ListBox обновлялся.

  • Как я могу сделать это "быстро и грязно" (например, перекрасить).
  • Являются ли свойства зависимостей таким образом?
  • Стоит ли/всегда целесообразно создать класс оболочки wpf для моих FooBars?

Спасибо...

Ответ 1

Ваш тип должен реализовывать 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.

EDIT 1 Я заметил ваш комментарий, что вы используете коллекцию ListBox.Items как свою коллекцию. Это не будет требовать привязки. Вам лучше сделать что-то вроде:

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

_listBox.ItemsSource = collection;

Я не проверял этот код для точности компиляции, но вы получаете суть.

EDIT 2 Использование DataTemplate, которое я дал выше (я отредактировал его в соответствии с вашим кодом), устраняет проблему.

Кажется странным, что запуск PropertyChanged не приводит к обновлению элемента списка, но с использованием метода ToString это не тот способ, которым WPF должен был работать.

Используя этот DataTemplate, пользовательский интерфейс корректно связывается с точным свойством.

Недавно я задал вопрос о выполнении форматирования строк в привязке WPF. Вам может показаться полезным.

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

Ответ 2

Правильное решение здесь - использовать ObservableCollection < gt; для свойства ListBox IetmsSource. WPF автоматически обнаружит изменения в этом содержимом коллекции и заставит соответствующий ListBox обновляться, чтобы отразить изменения.

Вы можете прочитать эту статью MSDN для получения дополнительной информации. Было написано, чтобы конкретно объяснить, как справиться с этим сценарием

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

Ответ 3

Попробуйте реализовать интерфейс INotifyPropertyChanged на объектах FooBar. Когда они меняются, вызывают события PropertyChanged, передавая string.Empty как имя свойства. Это должно сделать трюк.

Ответ 4

Если объект коллекции, который вы используете для хранения элементов, является наблюдаемымcollection < > , то это обрабатывается для вас.

i.e, если коллекция будет изменена, любые элементы управления будут привязаны к ней, и наоборот.

Ответ 5

Вот код С#, для которого я работаю:

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.