ListView'记得' SelectedItem没有像我预期的那样工作,WPF ListView中的一个错误?

时间:2014-09-18 17:42:29

标签: wpf listview data-binding selecteditem

previous问题中,我描述了一个问题,我无法弄清楚如何记住并在SelectedItem上设置ListView。问题是,当我从ViewModel设置SelectedItem时,ListView不会将SelectedItem显示为突出显示。

这个问题仍然没有答案,所以我试图用一个小例子来重现这个问题。令我惊讶的是,我刚刚了解到,当我创建原始所选项目(我存储在我的ViewModel中)的副本时,问题就解决了,并将其设置为所选项目。如果我设置了已设置的完全相同的对象,则listview将不会显示所选项目。

简短介绍:

我有一个ListView,显示绑定到ItemSource的数据项。 SelectedValue也是绑定到ViewModel上的属性的数据。我有两个按钮,上一个和下一个,用于浏览不同的对象集,每个对象集包含一个不同的ObersevableCollection,它绑定到ListView s ItemSource

想法是存储在ListView中进行的选择,以便当再次显示相同的对象集时,再次选择先前选择的项目。

下面的(丑陋的测试)代码让我到了现在的位置:

请注意;方法ShowList1CommandOnExecute()涵盖了令人惊讶的部分。我添加了一些注释,解释了我看起来很奇怪的东西,而它似乎是让它正常工作的唯一方法。

MainViewModel:

public class MainViewModel : INotifyPropertyChanged
{
    private int _index;

    private Person _selectedPerson1;
    private Person _selectedPerson2;
    private Person _selectedPerson3;

    private ObservableCollection<Person> _list1;
    private ObservableCollection<Person> _list2;
    private ObservableCollection<Person> _list3;

    public RelayCommand ShowList1Command { get; set; }
    public RelayCommand ShowList2Command { get; set; }

    private Person _selectedPerson;
    public Person SelectedPerson
    {
        get
        {
            switch (_index)
            {
                case 0:
                    return _selectedPerson1;
                case 1:
                    return _selectedPerson2;
                case 2:
                    return _selectedPerson3;
            }
            return null;
        }
        set
        {
            if (value != null)
            {
                switch (_index)
                {
                    case 0:
                        _selectedPerson1 = value;
                        break;
                    case 1:
                        _selectedPerson2 = value;
                        break;
                    case 2:
                        _selectedPerson3 = value;
                        break;
                }
            }
            _selectedPerson = value;
            OnPropertyChanged();
        }
    }

    private ObservableCollection<Person> _persons;
    public ObservableCollection<Person> Persons
    {
        get { return _persons; }
        set
        {
            _persons = value;
            OnPropertyChanged();
        }
    }

    public MainViewModel()
    {
        ShowList1Command = new RelayCommand(ShowList1CommandOnExecute, ShowList1CommandOnCanExecute);
        ShowList2Command = new RelayCommand(ShowList2CommandOnExecute, ShowList2CommandOnCanExecute);
        _list1 = new ObservableCollection<Person>();
        _list1.Add(new Person { Name = "Bas" });
        _list1.Add(new Person { Name = "Anke" });
        _list1.Add(new Person { Name = "Suus" });
        _list2 = new ObservableCollection<Person>();
        _list2.Add(new Person { Name = "Freek" });
        _list2.Add(new Person { Name = "Ina" });
        _list2.Add(new Person { Name = "Liam" });
        _list3 = new ObservableCollection<Person>();
        _list3.Add(new Person { Name = "aap" });
        _list3.Add(new Person { Name = "noot" });
        _list3.Add(new Person { Name = "mies" });

        Persons = new ObservableCollection<Person>();

    }

    private void ShowList1CommandOnExecute()
    {
        if (_index < 3)
        {
            _index++;
        }
        else
        {
            _index = 0;
        }

        switch (_index)
        {
            case 0:
                Persons = _list1;
                if (_selectedPerson1 != null)
                {
                    // This is what surprised me, this DOES work. Why do I need a copy of an object??
                    SelectedPerson = new Person(_selectedPerson1.Name);
                }
                break;
            case 1:
                Persons = _list2;
                // This did work, which is why I tried the copied object (see case 0)
                SelectedPerson = new Person {Name = "freek"};
                break;
            case 2:
                Persons = _list3;
                if (_selectedPerson3 != null)
                {
                    // This will NEVER result in the selected item to be visualized as selected
                    // However, when you will check while debugging, the ListView DOES contain the correct selected item
                    SelectedPerson = _selectedPerson3;
                }
                break;
        }
    }

    private bool ShowList1CommandOnCanExecute()
    {
        return true;
    }

    private void ShowList2CommandOnExecute()
    {
        switch (_index)
        {
            case 0:
                SelectedPerson = new Person {Name = "bas"};
                break;
            case 1:
                SelectedPerson = new Person {Name = "Freek"};
                break;
            case 2:
                SelectedPerson = new Person{Name = "mieS"};
                break;
        }
    }

    private bool ShowList2CommandOnCanExecute()
    {
        return true;
    }

    public event PropertyChangedEventHandler PropertyChanged;

    [NotifyPropertyChangedInvocator]
    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        var handler = PropertyChanged;
        if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
    }
}

非常简单的实体类Person

public class Person
{
    public string Name { get; set; }

    public Person()
    {
    }

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

    public override bool Equals(object obj)
    {
        var other = obj as Person;

        if (other != null)
        {
            return Name.ToLowerInvariant().Equals(other.Name.ToLowerInvariant());
        }
        return false;
    }

    public override int GetHashCode()
    {
        return Name.GetHashCode();
    }
}

一个非常简单的用户界面,两个测试按钮和一个ListView

    <Grid.RowDefinitions>
        <RowDefinition Height="100"></RowDefinition>
        <RowDefinition Height="*"></RowDefinition>
    </Grid.RowDefinitions>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="*"></ColumnDefinition>
    </Grid.ColumnDefinitions>

    <ToolBar Grid.Row="0">
        <Button Height="90" Width="90" Command="{Binding ShowList1Command}">Show List1</Button>
        <Button Height="90" Width="90" Command="{Binding ShowList2Command}">Show List2</Button>
    </ToolBar>
    <ListView 
        x:Name="_matchingTvShowsFromOnlineDatabaseListView" 
        Grid.Row="1" 
        Grid.Column="0"
        ItemsSource="{Binding Persons}"
        SelectedItem="{Binding SelectedPerson, Mode=TwoWay}">
        <ListView.ItemTemplate>
            <DataTemplate>
                <TextBlock Text="{Binding Name}"/>
            </DataTemplate>
        </ListView.ItemTemplate>
    </ListView>

</Grid>

同样,问题:

为什么我需要创建以前SelectedItem的副本并将该副本再次设置为选定项目,以便ListView可视化(并突出显示)ListView中的SelectedItem?

2 个答案:

答案 0 :(得分:0)

我已经调查了你的问题,但尚未明确指出问题所在。我能说什么 你就是这样,虽然看起来什么都没有被选中,但ListView上的SelectedItem的值实际上是正确的。我没有其他结论,这是WPF中ListView / ListBox的意外行为。所以看来你确实是对的 - 这是一个错误。

现在,关于解决你的问题,我建议这就是:

视图(为简单起见,我删除了RelayCommand):

<Window x:Class="WpfApplication1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:WpfApplication1"
        Title="MainWindow" Height="350" Width="525">
    <Window.DataContext>
        <local:MainViewModel/>
    </Window.DataContext>
    <Grid>

        <Grid.RowDefinitions>
            <RowDefinition Height="100"></RowDefinition>
            <RowDefinition Height="*"></RowDefinition>
            <RowDefinition Height="auto"></RowDefinition>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*"></ColumnDefinition>
        </Grid.ColumnDefinitions>

        <Button Height="90" Width="90" Click="Button_Click">Toggle List</Button>

        <ListView
            DataContext="{Binding Persons}"
        x:Name="_matchingTvShowsFromOnlineDatabaseListView" 
        Grid.Row="1" 
        Grid.Column="0"
        ItemsSource="{Binding}"
        SelectedItem="{Binding SelectedItem, Mode=TwoWay}">
            <ListView.ItemTemplate>
                <DataTemplate>
                    <TextBlock Text="{Binding Name}"/>
                </DataTemplate>
            </ListView.ItemTemplate>
        </ListView>
        <TextBlock Text="{Binding Index}" Grid.Row="2"/>
    </Grid>
</Window>

ViewModel:

public class MainViewModel : INotifyPropertyChanged
{
    private int _index;
    public int Index
    {
        get { return _index; }
        set
        {
            _index = value;
            OnPropertyChanged();
        }
    }

    private SelectionCollection<Person> _list1;
    private SelectionCollection<Person> _list2;
    private SelectionCollection<Person> _list3;

    private SelectionCollection<Person> _persons;
    public SelectionCollection<Person> Persons
    {
        get { return _persons; }
        set
        {
            _persons = value;
            OnPropertyChanged();
        }
    }

    public MainViewModel()
    {
        _list1 = new SelectionCollection<Person>();
        _list1.Add(new Person { Name = "Bas" });
        _list1.Add(new Person { Name = "Anke" });
        _list1.Add(new Person { Name = "Suus" });
        _list2 = new SelectionCollection<Person>();
        _list2.Add(new Person { Name = "Freek" });
        _list2.Add(new Person { Name = "Ina" });
        _list2.Add(new Person { Name = "Liam" });
        _list3 = new SelectionCollection<Person>();
        _list3.Add(new Person { Name = "aap" });
        _list3.Add(new Person { Name = "noot" });
        _list3.Add(new Person { Name = "mies" });

        Persons = new SelectionCollection<Person>();

    }

    public void ShowList1CommandOnExecute()
    {
        if (Index < 2)
        {
            Index++;
        }
        else
        {
            Index = 0;
        }

        switch (Index)
        {
            case 0:
                Persons = _list1;
                break;
            case 1:
                Persons = _list2;
                break;
            case 2:
                Persons = _list3;
                break;
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;

    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        var handler = PropertyChanged;
        if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
    }
}

最后,新推出的系列也可以跟踪选择:

public class SelectionCollection<T> : ObservableCollection<T>, INotifyPropertyChanged
{
    private T _selectedItem;
    public T SelectedItem
    {
        get { return _selectedItem; }
        set
        {
            _selectedItem = value;
            OnPropertyChanged();
        }
    }


    public event PropertyChangedEventHandler PropertyChanged;

    private void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

请注意,这也可以通过使用CollectionViews来完成,但就个人而言,我更喜欢在ViewModel层中没有这些。

另请注意,_index字段已转换为属性,只允许我绑定它,因为我对它感到困惑,范围在0-3之间,而不是0-2(也固定在上面)。

答案 1 :(得分:0)

游戏开始有点晚,但是我一直在跳铁圈以类似的方式解决这个问题。 使用Viewmodel中的绑定属性在ListView中设置SelectedItem或使用绑定的SelectedIndex设置类似项将不起作用。 直到我尝试使其异步:

Task.Factory.StartNew(() =>
                {
                    BoundSelectedIndex = index;
                });

似乎可以正常工作-更高级的贡献者可能会回答原因...