MVVM ListView MultiBinding SelectedItems + SelectedItem (ListView) + SelectedItem (ComboBox) 到 TextBox.Text。没有正确更新

MVVM ListView MultiBinding SelectedItems + SelectedItem (ListView) + SelectedItem (ComboBox) to TextBox.Text. Does not update properly

我正在尝试将我的 ViewModel (ComboBox) 中的 SelectedItems (ListView) 和 SelectedItem 或 SelectedCategory 多重绑定到只读 TextBox。 OutputConverter 仅检查在为 TextBox.

创建文本之前是否至少选择了一项 (ListView) 和 TypeData (ComboBox)

但是,如果我只尝试这样做,TextBox 只会在 ComboBox.SelectedItem 发生变化时更新,而不会在 ListView 内部的 SelectedItems 发生变化时更新。
因此,我还从我的 ViewModel SelectedEntry (ListView)(与 SelectedItem 相同)中包含作为 MultiBinding 的绑定。

现在我得到以下信息:

解释:
Selection 始终落后一步,并使用 ListView 中的前一个 SelectedItems 绑定到 TextBox.Text。即使我通过 CTRLShift + Click 选择多个条目。但是,如果 ComboBox.SelectedItem 更改,它会按预期更新 TextBox

如果 ListView 中的选择发生变化,我如何获得行为 TextBox 立即相应地更新其内容(最好我想使用我的 ViewModel 的 SelectedEntries 而不是 [= ListView 的 16=],如果可能的话,以符合 MVVM 的方式)?

编辑:


代码:

模型类型数据(组合框):

public class TypeData : INotifyPropertyChanged
{
    public enum Type
    {
        NotSet = '0',
        A = 'A',
        B = 'B',
        C = 'C'
    }

    private string name;

    public string Name
    {
        get { return name; }
        set
        {
            name = value;
            //OnPropertyChanged("Name");
            OnPropertyChanged(nameof(Name));
        }
    }
    private Type category;

    public Type Category
    {
        get { return category; }
        set { category = value; }
    }


    public TypeData(string name)
    {
        Name = name;
    }

    public event PropertyChangedEventHandler PropertyChanged;

    private void OnPropertyChanged(string propertyName)
    {
        PropertyChangedEventHandler handler = PropertyChanged;

        if (handler != null)
        {
            handler(this, new PropertyChangedEventArgs(propertyName));
        }
    }

    public override string ToString()
    {
        return Name;
    }
}

模型条目(列表视图):

public class Entry : INotifyPropertyChanged
{
    private string title;
    public string Title
    {
        get { return title; }
        set
        {
            title = value;
            OnPropertyChanged(nameof(Title));
        }
    }

    private string author;

    public string Author
    {
        get { return author; }
        set
        {
            author = value;
            OnPropertyChanged(nameof(Author));
        }
    }

    private string year;

    public string Year
    {
        get { return year; }
        set
        {
            year = value;
            OnPropertyChanged(nameof(Year));
        }
    }


    public Entry(string title, string author, string year)
    {
        Title = title;
        Author = author;
        Year = year;
    }

    public event PropertyChangedEventHandler PropertyChanged;

    private void OnPropertyChanged(string propertyName)
    {
        PropertyChangedEventHandler handler = PropertyChanged;

        if (handler != null)
        {
            handler(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

ViewModel:

public class MainViewModel
{
    public ObservableCollection<Entry> Entries { get; set; }

    public Entry SelectedEntry { get; set; }

    public ObservableCollection<Entry> SelectedEntries { get; set; }

    public ObservableCollection<TypeData> Types { get; set; }

    private TypeData selectedCategory;

    public TypeData SelectedCategory { get; set; }

    public RelayCommand<object> SelectionChangedCommand { get; set; }

    public MainViewModel()
    {
        Entries = new ObservableCollection<Entry>
        {
            new Entry("Title1", "Author1", "Year1"),
            new Entry("Title2", "Author2", "Year2"),
            new Entry("Title3", "Author3", "Year3"),
            new Entry("Title4", "Author4", "Year4"),
        };

        Types = new ObservableCollection<TypeData>
        {
            new TypeData("A"),
            new TypeData("B"),
            new TypeData("C"),
        };

        SelectionChangedCommand = new RelayCommand<object>(items =>
        {
            var selectedEntries = (items as ObservableCollection<object>).Cast<Entry>();
            SelectedEntries = new ObservableCollection<Entry>(selectedEntries);
        });
    }
}

XAML:

<Window x:Class="MvvmMultiBinding.View.MainWindow"
    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:MvvmMultiBinding"
    xmlns:m="clr-namespace:MvvmMultiBinding.Model"
    xmlns:vm="clr-namespace:MvvmMultiBinding.ViewModel"
    xmlns:conv="clr-namespace:MvvmMultiBinding.View.Converter"
    xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
    xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
    xmlns:sys="clr-namespace:System;assembly=mscorlib"
    mc:Ignorable="d"
    Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
    <vm:MainViewModel></vm:MainViewModel>
</Window.DataContext>
<Window.Resources>
    <conv:OutputConverter x:Key="OutputConverter"/>
</Window.Resources>
<Grid>
    <DockPanel>
        <ListView Name="ListViewEntries" ItemsSource="{Binding Entries}" SelectedItem="{Binding SelectedEntry}" DockPanel.Dock="Top">
            <ListView.View>
                <GridView>
                    <GridViewColumn Header="Title" Width="250" DisplayMemberBinding="{Binding Title}" />
                    <GridViewColumn Header="Author" Width="150" DisplayMemberBinding="{Binding Author}" />
                    <GridViewColumn Header="Year" Width="50" DisplayMemberBinding="{Binding Year}" />
                </GridView>
            </ListView.View>
            <i:Interaction.Triggers>
                <i:EventTrigger EventName="SelectionChanged">
                    <i:InvokeCommandAction Command="{Binding DataContext.SelectionChangedCommand, ElementName=ListViewEntries}"
                                       CommandParameter="{Binding SelectedItems, ElementName=ListViewEntries}"/>
                </i:EventTrigger>
            </i:Interaction.Triggers>
        </ListView>
        <ComboBox ItemsSource="{Binding Types}" SelectedItem="{Binding SelectedCategory}" MinWidth="200" DockPanel.Dock="Right"/>
        <TextBox IsReadOnly="True" DockPanel.Dock="Left">
            <TextBox.Text>
                <MultiBinding Converter="{StaticResource OutputConverter}">
                    <Binding ElementName="ListViewEntries" Path="SelectedItems" Mode="OneWay"/>
                    <!--<Binding Path="SelectedEntries" Mode="OneWay"/>-->
                    <Binding Path="SelectedCategory" Mode="OneWay"/>
                    <!-- Without it converter is not called after selection changes -->
                    <Binding Path="SelectedEntry" Mode="OneWay"/>
                </MultiBinding>
            </TextBox.Text>
        </TextBox>
    </DockPanel> 
</Grid>

输出转换器:

public class OutputConverter : IMultiValueConverter
{
    public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
    {
        System.Collections.IList items = (System.Collections.IList)values[0];
        var entries = items.Cast<Entry>();
        TypeData type = values[1] as TypeData;
        List<Entry> selectedEntries = new List<Entry>();

        foreach (var entry in entries)
        {
            selectedEntries.Add(entry);
        }
        StringBuilder sb = new StringBuilder();

        // ComboBox and Selection must not be empty
        if (type != null && selectedEntries.Count > 0)
        {
            foreach (var selectedEntry in selectedEntries)
            {
                sb.AppendFormat("{0} {1}\n\n", selectedEntry.Author, type);
            }
        }

        return sb.ToString();
    }

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

我建议给您的条目 Class 一个 IsSelected 属性(绑定到 ListViewItem 的 IsSelectedProperty)。 When the selection changes you can just iterate through your collection (bound to the ListView) and check if they are selected or not.像这样(请原谅 MvvmLight,RelayCommand = ICommand,ViewModelBase extends ObservableObject):

ViewModel:

public class MainViewModel : ViewModelBase
{
    public MainViewModel()
    {
        TestItemCollection = new ObservableCollection<TestItem>
        {
            new TestItem("Test1"),
             new TestItem("Test2"),
              new TestItem("Test3")
            };
    }

    private TestItem m_selectedItemProperty;
    public TestItem SelectedItemProperty
    {
        get
        {
            return m_selectedItemProperty;
        }
        set
        {
            m_selectedItemProperty = value;
            RaisePropertyChanged("SelectedItemProperty");
        }
    }

    public ObservableCollection<TestItem> TestItemCollection
    {
        get;
        set;
    }

    public RelayCommand SelectionChanged
    {
        get { return new RelayCommand(OnSelectionChanged); }
    }

    private void OnSelectionChanged()
    {
        foreach (var item in TestItemCollection)
        {
            if (item.IsSelected)
                Console.WriteLine("Name: " + item.Name);
        }
    }
}

我在这里打印了名称,但您也可以将它们添加到字符串 属性 并将其绑定到您的文本框或将项目添加到集合(可能绑定到 ListBox 或 ItemsControl 显示所选条目)。

测试项:

public class TestItem : ObservableObject
{
    public TestItem(string a_name)
    {
        m_name = a_name;
    }

    private string m_name;
    public string Name
    {
        get
        {
            return m_name;
        }
        set
        {
            m_name = value;
            RaisePropertyChanged("Name");
        }
    }

    private bool m_isSelected;
    public bool IsSelected
    {
        get
        {
            return m_isSelected;
        }
        set
        {
            m_isSelected = value;
            RaisePropertyChanged("IsSelected");
        }
    }
}

查看:

<Window x:Class="WpfAppTests.MainWindow"
    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:WpfAppTests"
    xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
    xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
    mc:Ignorable="d"
    xmlns:modelNoMvvmLight="clr-namespace:WpfAppTests"
    xmlns:modelMvvmLight="clr-namespace:WpfAppTests.ViewModel"
    Title="MainWindow" Height="350" Width="525" >
<Window.DataContext>
    <modelMvvmLight:MainViewModel/>
</Window.DataContext>

<StackPanel>
    <ListView Name="ListView" ItemsSource="{Binding TestItemCollection}" SelectedItem="{Binding SelectedItemProperty}">
        <i:Interaction.Triggers>
            <i:EventTrigger EventName="SelectionChanged">
                <i:InvokeCommandAction Command="{Binding SelectionChanged}"/>
            </i:EventTrigger>
        </i:Interaction.Triggers>

        <ListView.ItemTemplate>
            <DataTemplate>
                <TextBlock Text="{Binding Name}"/>
            </DataTemplate>
        </ListView.ItemTemplate>

        <ListView.ItemContainerStyle>
            <Style TargetType="ListViewItem" >
                <Setter Property="IsSelected" Value="{Binding IsSelected}"/>
            </Style>
        </ListView.ItemContainerStyle>
    </ListView>
</StackPanel>
</Window>