Search code examples
wpfdata-bindingdatacontextxmldataprovider

Setting Datacontext


Okay, I'm going to try and explain my excercise now.. This is my PageOverzicht.xaml, and this code works. It gives me a dropdown with the colors of the flowers in (blue, orange, white, ...). Now the datacontext is hardcoded to find the flowers with white color. Goal: set datacontext via code behind, so that the next step can be to use selectionchanged property to select flowers with the selected color.

So now I need to set datacontext first so that it is not hardcoded to white.. Listbox consists out of xml nodes, names of plants that have the color white, using the Page_Loaded method from PageOverzicht.

<Page x:Class="Planten_BIS.PageOverzicht"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
      xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
      xmlns:local="clr-namespace:Planten_BIS"
      mc:Ignorable="d"
      d:DesignHeight="450" d:DesignWidth="800"
      Title="PageOverzicht">

    <Page.Resources>
        <XmlDataProvider x:Key="CatalogDataSource" XPath="catalog" Source="data/catalogus.xml"></XmlDataProvider>
        <DataTemplate x:Key="listItemTemplate">
            <StackPanel Orientation="Horizontal">
                <TextBlock Name="ImageName" Visibility="Collapsed" Text="{Binding XPath=botanical, StringFormat=images/{0}.jpg}" />

                <Border BorderBrush="white" BorderThickness="2" CornerRadius="10" Background="{StaticResource AchtergrondKleur}">
                    <Rectangle Width="100" Height="100" RadiusX="10" RadiusY="10">
                        <Rectangle.Fill>
                            <ImageBrush ImageSource="{Binding Text, ElementName=ImageName}" />
                        </Rectangle.Fill>
                    </Rectangle>
                </Border>
                <StackPanel Orientation="Vertical" Margin="10" VerticalAlignment="Center">
                    <ListBoxItem Content="{Binding XPath=common}"/>
                    <ListBoxItem Content="{Binding XPath=price}"/>
                </StackPanel>
            </StackPanel>
        </DataTemplate>
    </Page.Resources>
    <Grid>
        <ListBox Name="ListboxFlowers" Background="Transparent" Foreground="white" DataContext="{Binding Source={StaticResource CatalogDataSource}, XPath=color[@name\=\'White\']/plant}" ItemsSource="{Binding}" ItemTemplate="{StaticResource listItemTemplate}"></ListBox>
    </Grid>
</Page>

little part from xml where data comes from:

<?xml version="1.0" encoding="ISO8859-1" ?>
<catalog>
  <color name="White">
    <plant>
      <common>Jacob's Ladder</common>
      <botanical>Polemonium caeruleum i</botanical>
      <zone>Annual</zone>
      <light>Shade</light>
      <price>$9.26</price>
      <availability>022199</availability>
      <color>white</color>
      <description>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</description>
    </plant>

Mainwindow with frame to PageOverzicht:

<Window x:Class="Planten_BIS.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:scm="clr-namespace:System.ComponentModel;assembly=WindowsBase"
        xmlns:local="clr-namespace:Planten_BIS"
        mc:Ignorable="d"
        Title="Plant Catalog" Height="600" Width="800">
    <Window.Resources>
        <Style x:Key="buttonStyle" TargetType="Button">
            <Setter Property="Background" Value="{StaticResource ToolBarKleur}" />
            <Setter Property="BorderBrush" Value="{StaticResource RandKleur}" />
            <Setter Property="Foreground" Value="{StaticResource LetterKleur}" />
            <Setter Property="Height" Value="30" />
            <Setter Property="Margin" Value="6" />
        </Style>
        <Style x:Key="comboStyle" TargetType="ComboBox">
            <Setter Property="Background" Value="{StaticResource ToolBarKleur}" />
            <Setter Property="BorderBrush" Value="{StaticResource RandKleur}" />
            <Setter Property="Foreground" Value="{StaticResource LetterKleur}" />
            <Setter Property="Width" Value="100" />
            <Setter Property="Height" Value="30" />
            <Setter Property="Margin" Value="6" />
        </Style>
        <XmlDataProvider x:Key="CatalogDataSource" XPath="catalog" Source="data/catalogus.xml"></XmlDataProvider>
        <CollectionViewSource x:Key="cvsColors" Source="{StaticResource CatalogDataSource}">
            <CollectionViewSource.SortDescriptions>
                <scm:SortDescription PropertyName="color" />
            </CollectionViewSource.SortDescriptions>
        </CollectionViewSource>
        <DataTemplate x:Key="comboItemTemplate">
            <Label Content="{Binding XPath=@name}"/>
        </DataTemplate>

    </Window.Resources>
    <DockPanel LastChildFill="True">
        <ToolBar Background="{StaticResource ToolBarKleur}" DockPanel.Dock="Top">
            <Button Style="{StaticResource buttonStyle}" Content="Backward"></Button>
            <Button Style="{StaticResource buttonStyle}" Content="Forward"></Button>
            <ComboBox Style="{StaticResource comboStyle}" SelectedIndex="0"  ItemsSource="{Binding Source={StaticResource CatalogDataSource}, XPath=color}" ItemTemplate="{StaticResource comboItemTemplate}"></ComboBox>
        </ToolBar>
        <Frame Source="PageOverzicht.xaml" Name="frame" NavigationUIVisibility="Hidden">
            <Frame.Background>
                <ImageBrush ImageSource="assets/background.jpg" Stretch="UniformToFill"/>
            </Frame.Background>
        </Frame>
    </DockPanel>
</Window>

Solution

  • The data source handling should be moved to a control which is the common parent of the ComboBox and the Frame, in this case I chose the MainWindow.

    You should add the required DependecyProperty definitions to the MainWindow which can be used for data binding for the ComboBox.ItemsSource, ComboBox.SelectedItem and the Frame.DataContext.

    For the XML handling I replaced the XMLDataProvider with an XElement data source which allows LINQ To XML in order to comfortably filter or traverse the XML object tree in C#.

    Now the ComboBox binds to a collection of XElement items representing XML color nodes. The selected ComboBox item is a single XML color element. The descendant plant nodes of color are used to set the Frame.DataContext, which is inherited to the Page.DataContext. The ListBox in the Page now binds its ItemsSource directly to the DataContext which is the MainWindow.PlantsOfSelectedColor property. Alternatively, e.g. if you need to set the Page.DataContext to something different, you can let the Binding traverse the visual tree to find the MainWindow.PlantsOfSelectedColor using RelativeSource FindAncestor for the Binding.RelativeSource of ListBox.ItemsSource.

    MainWindow.xaml.cs

    public partial class MainWindow : Window
    {
      public static readonly DependencyProperty PlantColorNodesProperty = DependencyProperty.Register(
        "PlantColorNodes",
        typeof(IEnumerable<XElement>),
        typeof(MainWindow),
        new PropertyMetadata(default(IEnumerable<XElement>)));
    
      public IEnumerable<XElement> PlantColorNodes
      {
        get => (IEnumerable<XElement>) GetValue(MainWindow.PlantColorNodesProperty);
        set => SetValue(MainWindow.PlantColorNodesProperty, value);
      }
    
      public static readonly DependencyProperty SelectedPlantColorNodeProperty = DependencyProperty.Register(
        "SelectedPlantColorNode",
        typeof(XElement),
        typeof(MainWindow),
        new PropertyMetadata(default(XElement), OnSelectedPlantColorNodeChanged));
    
      public XElement SelectedPlantColorNode
      {
        get => (XElement) GetValue(MainWindow.SelectedPlantColorNodeProperty);
        set => SetValue(MainWindow.SelectedPlantColorNodeProperty, value);
      }
    
      public static readonly DependencyProperty PlantsOfSelectedColorProperty = DependencyProperty.Register(
        "PlantsOfSelectedColor",
        typeof(IEnumerable<XElement>),
        typeof(MainWindow),
        new PropertyMetadata(default(IEnumerable<XElement>)));
    
      public IEnumerable<XElement> PlantsOfSelectedColor
      {
        get => (IEnumerable<XElement>) GetValue(MainWindow.PlantsOfSelectedColorProperty);
        set => SetValue(MainWindow.PlantsOfSelectedColorProperty, value);
      }
    
      public MainWindow()
      {
        InitializeComponent();
    
        // Open XML an collect all 'color' nodes
        this.PlantColorNodes = XElement.Load("data/catalogus.xml").Elements("color");
      }
    
      private static void OnSelectedPlantColorNodeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
      {
        var colorNode = e.NewValue as XElement;
    
        // Get the 'plant' child nodes of the selected 'color' node
        (d as MainWindow).PlantsOfSelectedColor = colorNode.Elements();
      }
    }
    

    MainWindow.xaml

    <Window>
      <DockPanel LastChildFill="True">
        <ComboBox ItemsSource="{Binding RelativeSource={RelativeSource AncestorType=MainWindow}, Path=PlantColorNodes}" 
                  SelectedItem="{Binding RelativeSource={RelativeSource AncestorType=MainWindow}, Path=SelectedPlantColorNode}">
          <ComboBox.ItemTemplate>
            <DataTemplate>
              <TextBlock Text="{Binding Attribute[name].Value}" />
            </DataTemplate>
          </ComboBox.ItemTemplate>
        <ComboBox>
        <Frame DatContext="{Binding RelativeSource={RelativeSource AncestorType=MainWindow}, Path=PlantsOfSelectedColor}"
               Source="PageOverzicht.xaml" />
        </DockPanel>
    </Window>
    

    PageOverzicht.xaml

    <Page>
      <ListBox ItemsSource="{Binding}">
        <ListBox.ItemTemplate>
          <DataTemplate  >
            <StackPanel Orientation="Horizontal">
              <TextBlock Text="{Binding Element[botanical].Value}" />
              <TextBlock Text="{Binding Element[common].Value}"/>
              <TextBlock Text="{Binding Element[price].Value}"/>          
            </StackPanel>
          </DataTemplate>
        </ListBox.ItemTemplate>
      </ListBox>
    </Page>