c# - MVVM ListView 多重绑定(bind) SelectedItems + SelectedItem (ListView) + SelectedItem (ComboBox) 到 TextBox.Text。无法正确更新

标签 c# wpf listview mvvm multibinding

我正在尝试将 ViewModel (ComboBox) 中的 SelectedItems (ListView) 和 SelectedItem 或 SelectedCategory 多重绑定(bind)到只读 TextBox。 OutputConverter 仅在为 TextBox 创建文本之前检查是否至少选择了一项 (ListView) 和 TypeData (ComboBox)。

但是,如果我仅尝试这样做,则 TextBox 仅在 ComboBox.SelectedItem 更改时更新,而不是在 ListView 内的 SelectedItems 更改时更新。
因此,我还从我的 ViewModel SelectedEntry (ListView)(与 SelectedItem 相同)中包含了作为 MultiBinding 的绑定(bind)。

现在我得到以下信息:

enter image description here

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

如果 ListView 中的选择发生变化,TextBox 会立即相应地更新其内容(最好我想使用 ViewModel 的 SelectedEntries 和不是 ListView 的 SelectedItems(如果可能的话,以符合 MVVM 的方式)?

编辑:

  • 我注意到转换器被称为 SelectedEntry (实现 INotifyPropertyChanged) 已更新,但 SelectedEntries (ObservableCollection 并实现 INotifyPropertyChanged)不是。
  • 我还添加了一个调用命令的 SelectionChanged 事件 并将 SelectedItems 作为参数传递给命令。如果说 可能有帮助,但我宁愿有一个适当的绑定(bind) 相应更新。

代码:

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

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;
    }
}

模型条目(ListView):

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));
        }
    }
}

View 模型:

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();
    }
}

最佳答案

我建议为您的 Entry Class 提供一个 IsSelected 属性(绑定(bind)到 ListViewItem 的 IsSelectedProperty)。当选择更改时,您只需迭代集合(绑定(bind)到 ListView)并检查它们是否被选中。像这样(请原谅 MvvmLight、RelayCommand = ICommand、ViewModelBase 扩展 ObservableObject):

View 模型:

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);
        }
    }
}

我在这里打印了名称,但您也可以将它们添加到字符串属性并将其绑定(bind)到文本框或将项目添加到集合(可能绑定(bind)到显示所选条目的 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>

关于c# - MVVM ListView 多重绑定(bind) SelectedItems + SelectedItem (ListView) + SelectedItem (ComboBox) 到 TextBox.Text。无法正确更新,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/49467551/

相关文章:

java - ListView 没有动画

c# - 我可以使用类型化工厂设施返回基于(枚举)参数的实现吗?

c# - 两个包含接口(interface)的列表与一个包含具有两个接口(interface)的结构的列表

c# - 在虚拟模式下更改 'DataGridView' 的单元格背景颜色

c# - 如何在 unity 5 中限制鼠标输入的旋转?

android - 在不使用 smoothScrollToPosition 的情况下转到 Listview 中的项目

java - 在 Android 中选择项目后如何关闭弹出 ListView

wpf - 如何禁用某些按钮的加速键

WPF - 使用用户控件并设置设计时高度和宽度

c# - 绑定(bind)到 IEnumerable<object> 依赖属性