Связывание WPF не обновляется, возможно, из-за преобразователя значений или свойства зависимости - PullRequest
0 голосов
/ 02 мая 2018

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

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

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

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

Я попытался разбрызгать немного «Mode = TwoWay», но безуспешно (удалено, поскольку они не дали эффекта). Я пытался использовать SelectedValue против 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());
    }
}

1 Ответ

0 голосов
/ 02 мая 2018

Вы не предоставляете 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}}" />
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...