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

时间:2018-03-24 17:14:35

标签: c# wpf listview mvvm multibinding

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

但是,如果我仅在TextBox更改ComboBox.SelectedItem时更新而不是在ListView内的SelectedItems更改时更新。 所以我也从我的ViewModel SelectedEntry(ListView)(与SelectedItem相同)中包含了MultiBinding的绑定。

现在我得到以下内容:

enter image description here

解释
Selection总是落后一步,并使用ListView中的前一个SelectedItems绑定到TextBox.Text。即使我通过 CTRL Shift + 选择多个条目,也可以单击。但是,如果ComboBox.SelectedItem更改,则会按预期更新TextBox

如果SelectionView在ListView中发生变化,TextBox会立即相应地更新其内容,我该如何获得该行为(最好是我想使用我的ViewModel的SelectedEntries而不是SelectedItems ListView,如果可能的话,以MVVM兼容的方式)?

修改

  • 我注意到当Converter被调用时,SelectedEntry(实现 INotifyPropertyChanged)已更新,但SelectedEntries (ObservableCollection和实现INotifyPropertyChanged)不是。
  • 我还包含了一个调用命令的SelectionChanged事件 并将SelectedItems作为参数传递给命令。如果说 可能有所帮助,但我宁愿有一个适当的约束 相应更新。

代码:

模型TypeData(ComboBox):

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

视图模型:

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>

OutputConverter:

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

1 个答案:

答案 0 :(得分:2)

我建议为Entry类提供一个IsSelected属性(绑定到ListViewItem的IsSelectedProperty)。当选择更改时,您可以遍历您的集合(绑定到ListView)并检查它们是否被选中。像这样(借口MvvmLight,RelayCommand = ICommand,ViewModelBase扩展ObservableObject):

<强>视图模型:

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)。

<强> TestItem:

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>