Привязка WPF не обновляется, возможно, из-за преобразователя значений или свойства зависимости

Я сократил свой код до минимального размера тестового примера, но он все еще довольно велик; Надеюсь, это довольно просто.

Foo имеет ObservableCollection типов Bar и Baz. Baz хранит ObservableCollection ссылок на объекты Bar в Foo.

В главном окне есть ListBox всех объектов Baz в Foo, которые проходят через конвертер, чтобы сделать их простой строкой. SelectedItem устанавливается как DependencyProperty окна для удобства использования. Позже в окне отображается список всех объектов Bar в Foo, которые можно добавить / удалить с помощью этого свойства DependencyProperty (SelectedBaz). Для целей отладки добавлен еще один ListBox, который показывает объекты Bar SelectedBaz.

Что происходит: SelectedBaz обновляется, Baz в ObservableCollection, удерживаемом Foo, обновляется, событие CollectionChanged для коллекции Baz Foo запускается, но ListBox с конвертером никогда не обновляется.

Я безуспешно пытался разбрызгать немного «Mode = TwoWay» (удалено, так как они не имели никакого эффекта). Я пробовал использовать SelectedValue vs. SelectedItem (похоже, что SelectedItem - правильный способ сделать это из моих исследований, поэтому я оставил его как таковой). Я попытался вручную запустить обновление цели привязки в Baz ListBox при нажатии кнопки добавления / удаления, но это не повлияло.

Затем я расстроился и попытался взломать его и использовать целое число с SelectedIndex, MultiBinding, MultiValueConverter и т. д. И т. Д. И обнаружил, что у меня такая же проблема; источник обновляется, но не цель в привязке Baz ListBox.

Итак, вот и мы.

Foo.cs

using System;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Globalization;
using System.Runtime.CompilerServices;
using System.Windows.Data;

namespace WpfApp1
{
    public class Foo : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        public Foo()
        {
            bars.CollectionChanged += Bars_CollectionChanged;
            bazes.CollectionChanged += Bazes_CollectionChanged;

            bars.Add(new Bar("Bar 1"));
            bars.Add(new Bar("Bar 2"));
            bars.Add(new Bar("Bar 3"));

            bazes.Add(new Baz("Baz 1")
            {
                Bars = { bars[0] }
            });

            bazes.Add(new Baz("Baz 2")
            {
                Bars = { bars[1] }
            });

            bazes.Add(new Baz("Baz 3")
            {
                Bars = { bars[0], bars[1], bars[2] }
            });
        }

        public ObservableCollection<Bar> Bars
        {
            get
            {
                return bars;
            }
        }

        public ObservableCollection<Baz> Bazes
        {
            get
            {
                return bazes;
            }
        }

        private void Bars_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            NotifyPropertyChanged("Bars");
        }

        private void Bazes_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            NotifyPropertyChanged("Bazes");
        }

        private void NotifyPropertyChanged([CallerMemberName] string caller = "")
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(caller));
        }

        private ObservableCollection<Bar> bars = new ObservableCollection<Bar>();

        private ObservableCollection<Baz> bazes = new ObservableCollection<Baz>();
    }

    public class Bar : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        public Bar(string name)
        {
            this.name = name;
        }

        public string Name
        {
            get
            {
                return name;
            }

            set
            {
                if (name != value)
                {
                    name = value;
                    NotifyPropertyChanged();
                }
            }
        }

        private void NotifyPropertyChanged([CallerMemberName] string caller = "")
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(caller));
        }

        private string name = "";
    }

    public class Baz : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        public Baz(string name)
        {
            this.name = name;
            bars.CollectionChanged += Bars_CollectionChanged;
        }

        public ObservableCollection<Bar> Bars
        {
            get
            {
                return bars;
            }
        }

        public string Name
        {
            get
            {
                return name;
            }

            set
            {
                if (name != value)
                {
                    name = value;
                    NotifyPropertyChanged();
                }
            }
        }

        private void Bars_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            NotifyPropertyChanged("Bars");
        }

        private void NotifyPropertyChanged([CallerMemberName] string caller = "")
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(caller));
        }

        private ObservableCollection<Bar> bars = new ObservableCollection<Bar>();

        private string name = "";
    }

    public class BazToString : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            Baz b = value as Baz;
            string s = "Baz is " + b.Name + " ";

            foreach (Bar bar in b.Bars)
            {
                s += "with a Bar " + bar.Name + " ";
            }

            return s;
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }
}

MainWindow.xaml

<Window x:Class = "WpfApp1.MainWindow"
        x:Name = "Main"
        xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d = "http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc = "http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local = "clr-namespace:WpfApp1"
        mc:Ignorable = "d"
        Title = "MainWindow" Height = "450" Width = "800">
    <Window.DataContext>
        <local:Foo />
    </Window.DataContext>
    <Window.Resources>
        <local:BazToString x:Key = "BazToString" />
    </Window.Resources>
    <Grid>
        <ListBox Width = "300" Height = "150" Margin = "10,10,0,0" HorizontalAlignment = "Left" VerticalAlignment = "Top" ItemsSource = "{Binding Bazes}" SelectedItem = "{Binding ElementName=Main, Path=SelectedBaz}">
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <Label Content = "{Binding Converter = {StaticResource BazToString}}" />
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>
        <ListBox x:Name = "ListBoxBarSelector" Width = "300" Height = "150" Margin = "10,170,0,0" HorizontalAlignment = "Left" VerticalAlignment = "Top" ItemsSource = "{Binding Bars}">
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <Label Content = "{Binding Name}" />
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>
        <ListBox Width = "300" Height = "150" Margin = "320,170,0,0" HorizontalAlignment = "Left" VerticalAlignment = "Top" ItemsSource = "{Binding ElementName=Main, Path=SelectedBaz.Bars}">
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <Label Content = "{Binding Name}" />
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>
        <Button Width = "100" Height = "30" Margin = "10,330,0,0" HorizontalAlignment = "Left" VerticalAlignment = "Top" Click = "ButtonAddBar_Click" Content = "Add Bar" />
        <Button Width = "100" Height = "30" Margin = "120,330,0,0" HorizontalAlignment = "Left" VerticalAlignment = "Top" Click = "ButtonDelBar_Click" Content = "Delete Bar" />
    </Grid>
</Window>

MainWindow.xaml.cs

using System.Windows;

namespace WpfApp1
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        public Baz SelectedBaz
        {
            get
            {
                return (Baz)GetValue(SelectedBazProperty);
            }

            set
            {
                SetValue(SelectedBazProperty, value);
            }
        }

        private void ButtonAddBar_Click(object sender, RoutedEventArgs e)
        {
            Bar bar = ListBoxBarSelector.SelectedItem as Bar;

            if (bar != null && SelectedBaz != null && !SelectedBaz.Bars.Contains(bar))
            {
                SelectedBaz.Bars.Add(bar);
            }
        }

        private void ButtonDelBar_Click(object sender, RoutedEventArgs e)
        {
            Bar bar = ListBoxBarSelector.SelectedItem as Bar;

            if (bar != null && SelectedBaz != null && SelectedBaz.Bars.Contains(bar))
            {
                SelectedBaz.Bars.Remove(bar);
            }
        }

        private static readonly DependencyProperty SelectedBazProperty =
            DependencyProperty.Register(
                "SelectedBaz",
                typeof(Baz),
                typeof(MainWindow),
                new PropertyMetadata());
    }
}
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
0
615
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Вы не предоставляете Path в своем Binding, поэтому вы привязываетесь ко всему объекту, а WPF не отслеживает уведомления об изменении свойств привязок без пути:

<Label Content = "{Binding Converter = {StaticResource BazToString}}" />

Есть два способа решить эту проблему (рекомендую первый, потому что он более чистый и типичный для WPF):

  1. Вы можете использовать IMultiValueConverter и MultiBinding для обоих свойств Name и Bars. Например:

Измените конвертер на IMultiValueConverter (я также рекомендую придумать более подходящее имя для конвертера после этого изменения на многозначный конвертер):

public class BazToString : IMultiValueConverter
{
    public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
    {
        var bazName = (string)values[0];
        var bars = (IEnumerable<Bar>)values[1];


        string s = "Baz is " + bazName + " ";

        foreach (Bar bar in bars)
        {
            s += "with a Bar " + bar.Name + " ";
        }

        return s;
    }

    public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
    {
        return null;
    }
}

Измените привязку Label к MultiBinding с соответствующими привязками свойств:

<Label>
    <Label.Content>
        <MultiBinding Converter = "{StaticResource BazToString}">
            <Binding Path = "Name" />
            <Binding Path = "Bars" />
        </MultiBinding>
    </Label.Content>
</Label>
  1. Более грязным решением было бы добавление свойства в класс Baz для возврата самого объекта и привязки к нему. Вам также необходимо будет вызвать события PropertyChanged для этого свойства. Например:

Добавьте свойство в класс Baz:

public Baz This
{
    get { return this; }
}

Добавляем возникновение события PropertyChanged:

private void Bars_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
    NotifyPropertyChanged("Bars");
    NotifyPropertyChanged("This");
}

Измените Binding из Label:

<Label Content = "{Binding Path=This, Converter = {StaticResource BazToString}}" />

Это именно то! Спасибо!

gjr 02.05.2018 15:11

Другие вопросы по теме