简体   繁体   中英

WPF ListView SelectedItem Not Updating With Expander in DataTemplate

I'm creating a WPF application using C# and MVVM, and I'm having some trouble with the SelectedItem of a ListView.

In the contrived example, the scenario: I have a PersonViewModel containing a List of Person objects. Each person object has its own List of Address objects. My goal is to load all of the Person objects into a ListView . Additionally, the ListView DataTemplate should include Expanders that show the available addresses for each person.

The problem: This all actually works decently enough. The only problem is that if a user selects the expander for a person object, it doesn't actually change the SelectedItem property of the ListView (which is bound to a SelectedPerson object in the view model).

I'm relatively confident that the code works otherwise because if I click on the ListView row (outside the boundaries of the contained expander), the SelectedPerson property is updated. Is there anyway to somehow bind when the expander is clicked to the SelectedPerson property? (I'm also open to other UI ideas that will get my information across in a cleaner, more easily implemented manner).

Person.cs

using System.Collections.Generic;

namespace ListViewExpanderTest
{
    public class Person
    {
        public Person()
        {
            Addresses = new List<Address>();
        }
        public string Name { get; set; }
        public List<Address> Addresses { get; set; }
    }
}

Address.cs

namespace ListViewExpanderTest
{
    public class Address
    {
        public int HouseNumber { get; set; }
        public string StreetName { get; set; }
        public string City { get; set; }
    }
}

PersonViewModel.cs

using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.CompilerServices;

namespace ListViewExpanderTest
{
    public class PersonViewModel : INotifyPropertyChanged
    {
        private Person _selectedPerson;
        private Address _selectedAddress;

        public List<Person> People { get; set; }
        public Person SelectedPerson
        {
            get
            {
                return _selectedPerson;
            }
            set
            {
                _selectedPerson = value;
                OnPropertyChanged();
            }
        }

        public Address SelectedAddress
        {
            get
            {
                return _selectedAddress;
            }
            set
            {
                _selectedAddress = value;
                OnPropertyChanged();
            }
        }

        public PersonViewModel()
        {
            People = new List<Person>
            {
                new Person
                {
                    Name = "Person 1",
                    Addresses = new List<Address>
                    {
                        new Address {HouseNumber = 1, StreetName = "Fake St", City = "Fake City" },
                        new Address {HouseNumber = 2, StreetName = "Super Fake St", City = "Super Fake City" }
                    }
                },
                new Person
                {
                    Name = "Person 2",
                    Addresses = new List<Address>
                    {
                        new Address {HouseNumber = 10, StreetName = "Fake St", City = "Fake City" },
                        new Address {HouseNumber = 20, StreetName = "Super Fake St", City = "Super Fake City" }
                    }
                }
            };
        }

        public event PropertyChangedEventHandler PropertyChanged;

        protected void OnPropertyChanged([CallerMemberName] string propertyName = "")
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

MainWindow.xaml

<Window x:Class="ListViewExpanderTest.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:ListViewExpanderTest"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525">
    <Window.DataContext>
        <local:PersonViewModel />
    </Window.DataContext>
    <Grid>
        <ListView ItemsSource="{Binding People, Mode=TwoWay}"
                  SelectedItem="{Binding SelectedPerson}">
            <ListView.ItemTemplate>
                <DataTemplate>
                    <Expander >
                        <Expander.Header>
                            <StackPanel Orientation="Horizontal">
                                <TextBlock Text="{Binding Name}" />
                            </StackPanel>
                        </Expander.Header>
                        <Expander.Content>
                            <ListView ItemsSource="{Binding Addresses, Mode=TwoWay}"
                                      SelectedItem="{Binding SelectedAddress}">
                                <ListView.View>
                                    <GridView>
                                        <GridViewColumn Width="50" Header="Number" DisplayMemberBinding="{Binding HouseNumber}" />
                                        <GridViewColumn Width="100" Header="Street" DisplayMemberBinding="{Binding StreetName}" />
                                        <GridViewColumn Width="100" Header="City" DisplayMemberBinding="{Binding City}" />
                                    </GridView>
                                </ListView.View>
                            </ListView>
                        </Expander.Content>
                    </Expander>
                </DataTemplate>
            </ListView.ItemTemplate>
        </ListView>
    </Grid>
</Window>

As your view model contains a single SelectedPerson property it doesn't make sense to me to include an expander in the datatemplate. If you can change your UI, the below example seems to make more sense to me. You can obviously place the address 'panel' anywhere you like.

Also, have you tried selecting an address from the sub listview? Does the binding work?

<Grid>
    <Grid.ColumnDefinitions>
        <ColumnDefinition></ColumnDefinition>
        <ColumnDefinition></ColumnDefinition>
    </Grid.ColumnDefinitions>

    <ListView ItemsSource="{Binding People, Mode=TwoWay}"    
              SelectedItem="{Binding SelectedPerson}"
              DisplayMemberPath="Name">           
    </ListView>

    <ListView Grid.Column="1" 
              ItemsSource="{Binding SelectedPerson.Addresses, Mode=TwoWay}" 
              SelectedItem="{Binding SelectedAddress}">
        <ListView.View>
            <GridView>
                <GridViewColumn Width="50" Header="Number" DisplayMemberBinding="{Binding HouseNumber}" />
                <GridViewColumn Width="100" Header="Street" DisplayMemberBinding="{Binding StreetName}" />
                <GridViewColumn Width="100" Header="City" DisplayMemberBinding="{Binding City}" />
            </GridView>
        </ListView.View>
    </ListView>
</Grid>

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM