簡體   English   中英

WPF綁定未更新,可能由於值轉換器或依賴項屬性

[英]WPF binding not updating, possibly because of value converter or dependency property

我已將代碼縮減為盡可能少的測試用例,但仍然很大。 我希望它非常簡單。

Foo具有Bar和Baz類型的ObservableCollection。 Baz在Foo中保留對Bar對象的引用的ObservableCollection。

主窗口有一個Foo中所有Baz對象的ListBox,這些對象通過轉換器使其成為純字符串。 SelectedItem設置為窗口的DependencyProperty,以方便參考。 在窗口的后面,列出了Foo中所有Bar對象的列表,可以通過此DependencyProperty(SelectedBaz)添加/刪除。 出於調試目的,添加了另一個ListBox,其中顯示了SelectedBaz的Bar對象。

發生的情況是,SelectedBaz被更新,Foo持有的ObservableCollection中的Baz被更新,Foo的Baz集合的CollectionChanged事件被觸發,但是帶有轉換器的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());
    }
}

您沒有在Binding提供Path ,因此您要綁定到整個對象,並且WPF不會跟蹤沒有路徑的綁定的屬性更改通知:

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

有兩種方法可以解決此問題(我建議采用第一種方法,因為它更干凈,而且對於WPF而言是典型的):

  1. 您可以將IMultiValueConverterMultiBinding都用於屬性NameBars 例如:

將轉換器更改為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");
}

更改Label Binding

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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM