In WPF, how to databind to the Window DataContext from inside the DataTemplate of a contained ListBox?
Asked Answered
T

4

12

I have a WPF Window with a view model set as its DataContext, and have a ListBox with a DataTemplate and its ItemsSource bound to the view model, like in the following example:

View model:

using System.Collections.Generic;

namespace Example
{
    class Member
    {
        public string Name { get; set; }
        public int Age { get; set; }
    }

    class Team
    {
        private List<Member> members = new List<Member>();

        public string TeamName { get; set; }
        public List<Member> Members { get { return members; } }
    }
}

MainWindow.xaml:

<Window x:Class="Example.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
 xmlns:l="clr-namespace:Example" 
    Title="Example" Height="300" Width="300" Name="Main">

 <Window.DataContext>
  <l:Team TeamName="The best team">
   <l:Team.Members>
    <l:Member Name="John Doe" Age="23"/>
    <l:Member Name="Jane Smith" Age="20"/>
    <l:Member Name="Max Steel" Age="24"/>
   </l:Team.Members>
  </l:Team>
 </Window.DataContext>

 <ListBox ItemsSource="{Binding Path=Members}">
  <ListBox.ItemTemplate>
   <DataTemplate>
    <StackPanel Orientation="Horizontal">
     <TextBlock Text="{Binding Path=TeamName}" Margin="4"/>
     <TextBlock Text="{Binding Path=Name}" Margin="4"/>
    </StackPanel>
   </DataTemplate>
  </ListBox.ItemTemplate>
 </ListBox>
</Window>

Of course, the TeamName property of the Team class is not displayed in the ListBox items because each item of the LisBox is the DataContext of the List.ItemTemplate, and it overrides the DataContext of the Window.

The question is: How do I databind to the TeamName property of the view model (Window.DataContext) from within the ListBox's DataTemplate?

Transcription answered 24/12, 2009 at 20:18 Comment(0)
S
16

I would extract the l:Team declaration to the Window.Resources section, and reference it from within the DataContext and the DataTemplate:

<Window x:Class="Example.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
 xmlns:l="clr-namespace:Example" 
    Title="Example" Height="300" Width="300" Name="Main">

 <Window.Resources>
  <l:Team x:Key="data" TeamName="The best team">
   <l:Team.Members>
    <l:Member Name="John Doe" Age="23"/>
    <l:Member Name="Jane Smith" Age="20"/>
    <l:Member Name="Max Steel" Age="24"/>
   </l:Team.Members>
  </l:Team>
 <Window.Resources>

 <Window.DataContext>
     <StaticResource ResourceKey="data"/>
 </Window.DataContext>

 <ListBox ItemsSource="{Binding Path=Members}">
  <ListBox.ItemTemplate>
   <DataTemplate>
    <StackPanel Orientation="Horizontal">
     <TextBlock Text="{Binding Source={StaticResource data}, Path=TeamName}" Margin="4"/>
     <TextBlock Text="{Binding Path=Name}" Margin="4"/>
    </StackPanel>
   </DataTemplate>
  </ListBox.ItemTemplate>
 </ListBox>
</Window>
Stylish answered 24/12, 2009 at 20:37 Comment(1)
+1. Of course! I hadn't thought about sharing the view model as a resource. I was trying complicating the dindings with RelativeSources and FindAncestor without success.Transcription
P
19

You can also use RelativeSource binding, it's not that complicated:

<TextBlock Text="{Binding Path=DataContext.TeamName, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}" Margin="4"/>
Papiamento answered 25/12, 2009 at 1:9 Comment(0)
S
16

I would extract the l:Team declaration to the Window.Resources section, and reference it from within the DataContext and the DataTemplate:

<Window x:Class="Example.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
 xmlns:l="clr-namespace:Example" 
    Title="Example" Height="300" Width="300" Name="Main">

 <Window.Resources>
  <l:Team x:Key="data" TeamName="The best team">
   <l:Team.Members>
    <l:Member Name="John Doe" Age="23"/>
    <l:Member Name="Jane Smith" Age="20"/>
    <l:Member Name="Max Steel" Age="24"/>
   </l:Team.Members>
  </l:Team>
 <Window.Resources>

 <Window.DataContext>
     <StaticResource ResourceKey="data"/>
 </Window.DataContext>

 <ListBox ItemsSource="{Binding Path=Members}">
  <ListBox.ItemTemplate>
   <DataTemplate>
    <StackPanel Orientation="Horizontal">
     <TextBlock Text="{Binding Source={StaticResource data}, Path=TeamName}" Margin="4"/>
     <TextBlock Text="{Binding Path=Name}" Margin="4"/>
    </StackPanel>
   </DataTemplate>
  </ListBox.ItemTemplate>
 </ListBox>
</Window>
Stylish answered 24/12, 2009 at 20:37 Comment(1)
+1. Of course! I hadn't thought about sharing the view model as a resource. I was trying complicating the dindings with RelativeSources and FindAncestor without success.Transcription
C
1

I'd create a view model for a team member, with a TeamName property, something like:

class MemberViewModel
{
    ...
    private TeamViewModel _team; 
    public string TeamName{ get { return _team.Name; } } 
}

class TeamViewModel
{
    public List< MemberViewModel > Members { get{ ... } }
    // You may consider using ObservableCollection<> instead of List<>
}

Then your XAML will look as clean as in your example. With MVVM, you shouldn't need any exotic binding tricks in the view. All you need should be available via the view model.

Comical answered 24/12, 2009 at 21:6 Comment(1)
And how would I do the databindings?Transcription
B
0

Why dont you bind your DataContext to team, then make your itemsource bound to team.members?

usually I am assigning my datacontext in my codebehind but its still shouldnt be anydifferent for you.

itemsouce="{Binding Path="team.Members"}

I guess really its the same thing as what Aviad suggested. Just I assign datacontext in my codebehind.

Bibcock answered 26/12, 2009 at 2:58 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.