0
votes

I would like to implement a master/detail view (very much like the Windows Explorer) using a WPF TreeView control and the MVVM design pattern. I am safe regarding handling the TreeView control, but I do not know how I can pass the currently selected item in the master view (the TreeView) as DataContext to the Detail view (which is actually a UserControl Holding a ListView). Most preferably I would like to do that XAML. Does anybody know how to do that?

To clarify I'll post a little address book demo here.

This is the main window view which basically splits the client area in three columns using a grid. The left one holds the tree, the middle one a splitter and the right one the details view.

<Window.Resources>
    <HierarchicalDataTemplate DataType="{x:Type vm:Address}" ItemsSource="{Binding Residents}">
        <TextBlock>
            <TextBlock.Text>
                <MultiBinding StringFormat="{}{0}, {1}, {2}">
                    <Binding Path="Street"/>
                    <Binding Path="City"/>
                    <Binding Path="ZipCode"/>
                </MultiBinding>
            </TextBlock.Text>
        </TextBlock>
    </HierarchicalDataTemplate>

    <DataTemplate DataType="{x:Type vm:Resident}">
        <TextBlock>
            <TextBlock.Text>
                <MultiBinding StringFormat="{}{0} {1}">
                    <Binding Path="FirstName"/>
                    <Binding Path="LastName"/>
                </MultiBinding>
            </TextBlock.Text>
        </TextBlock>
    </DataTemplate>
</Window.Resources>

<Grid>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="150"/>
        <ColumnDefinition Width="5"/>
        <ColumnDefinition Width="*"/>
    </Grid.ColumnDefinitions>
    <TreeView BorderThickness="0" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" ItemsSource="{Binding Addresses}"/>
    <GridSplitter Grid.Column="1" Width="5" HorizontalAlignment="Stretch" />
    <v:ResidentDetailView Grid.Column="2" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" DataContext="???"/>
</Grid>

As you can see in the last line I would like to pass the selected item in the tree as DataContext to the Details view.

This is the details view which is a UserControl:

<ListView ItemsSource="{Binding Residents}">
    <ListView.View>
        <GridView>
            <GridViewColumn DisplayMemberBinding="{Binding FirstName}" Header="FirstName" Width="100"/>
            <GridViewColumn DisplayMemberBinding="{Binding LastName}" Header="LastName" Width="100"/>
        </GridView>
    </ListView.View>
</ListView>

And finally the main window view model which I would like to share between main and Detail view:

public class Resident
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
}

public class Address
{
    public string Street { get; set; }
    public string City { get; set; }
    public int ZipCode { get; set; }
    public List<Resident> Residents { get; } = new List<Resident>();
}

public class MainWindowViewModel
{
    public ObservableCollection<Address> Addresses { get; } = new ObservableCollection<Address>();

    public MainWindowViewModel()
    {
        var a = new Address {Street = "Broadway 1", City = "New York", ZipCode = 12345};
        a.Residents.Add(new Resident { FirstName = "John", LastName = "Miller" });
        a.Residents.Add(new Resident { FirstName = "Lisa", LastName = "Miller" });
        Addresses.Add(a);
        a = new Address { Street = "Wall Street 1", City = "New York", ZipCode = 12345 };
        a.Residents.Add(new Resident { FirstName = "Paul", LastName = "Walker" });
        a.Residents.Add(new Resident { FirstName = "Frank", LastName = "Brown" });
        a.Residents.Add(new Resident { FirstName = "Mark", LastName = "Smith" });
        Addresses.Add(a);
        a = new Address { Street = "Market Street 1", City = "San Francisco", ZipCode = 23456 };
        a.Residents.Add(new Resident { FirstName = "Jack", LastName = "Ness" });
        a.Residents.Add(new Resident { FirstName = "Joe", LastName = "Jackson" });
        a.Residents.Add(new Resident { FirstName = "Jill", LastName = "Baude" });
        Addresses.Add(a);
        a = new Address { Street = "Rodeo Drive 1", City = "Los Angeles", ZipCode = 34567 };
        a.Residents.Add(new Resident { FirstName = "Roger", LastName = "Water" });
        a.Residents.Add(new Resident { FirstName = "Andy", LastName = "Murray" });
        a.Residents.Add(new Resident { FirstName = "Peter", LastName = "Hammer" });
        a.Residents.Add(new Resident { FirstName = "Lola", LastName = "White" });
        Addresses.Add(a);
    }
}

Thanks for your help. Johannes

1
Long question, while you simply asking "I do not know how I can pass the currently selected item in the master view". See this.Sinatr
Well I am hoping to get an answer like <View:ItemDetails DataContext="{Binding SelectedItem, ElementName=ItemTreeView}" /> which I found here stackoverflow.com/questions/15206671/…. Unfortunately it is not workingJohannes
I know I could handle the selection changed Event in the VM using the blend interactions e.g. and then update a DetailsVM and make the Details view update itself. But I still hope this should be done simplerJohannes

1 Answers

0
votes

This would certainly be possible with the kind of binding you've found in that other thread.

However, I'd just add a property for the current selected item to the ViewModel. It'll probably be good to keep track of it anyway (for example, if you ever want to change the details view if they click something else):

public class MainWindowViewModel
{
    public ObservableCollection<Address> Addresses { get; } = new ObservableCollection<Address>();

    public Address SelectedAddress { //get and set with INotifyPropertyChanged }

    public MainWindowViewModel()
    {
        ...
    }
}

And in the view:

<TreeView BorderThickness="0" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" ItemsSource="{Binding Addresses}" SelectedItem="{Binding SelectedAddress}"/>
<GridSplitter Grid.Column="1" Width="5" HorizontalAlignment="Stretch" />
<v:ResidentDetailView Grid.Column="2" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" DataContext="{Binding SelectedAddress}"/>