Click here to Skip to main content
12,692,778 members (33,659 online)

Multibind a treeviewitem with checkbox to XPath and an attached property

Mustanseer Sadarwala asked:

Open original thread
Hello,

I am working on a customised control which is, simply put, a TreeView with ChecBoxes. The TreeView resides inside a ComboBox.
The TreeView being inside a combobox is just a part of the requirement and I have already achieved the relation I wanted between them. My problem starts with binding TreeViewItems with two different bindings. I have reached the current state after a lot of reading and googling. And am really stuck, hence the question here.

The data to back the TreeView is a XML. This XML is loaded in the ComboBox as a string DependencyProperty named as 'Value' and the TreeView binds to that DependencyProperty of the ComboBox. All this is being done in the UI layer because I believe that this is part of the behavior of the custom control that I am creating and hence no view model backing the view. I am willing to stand corrected here.

Here is my XAML
<ComboBox x:Class="HierarchicalCheckComboBox.HCCB"
             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" 
             mc:Ignorable="d" 
             d:DesignHeight="30" d:DesignWidth="300" 
          DropDownClosed="ComboBox_DropDownClosed">
    <ComboBox.Resources>
        <HierarchicalDataTemplate x:Key="NodeTemplate" 
                                  ItemsSource="{Binding XPath=child::node()}">
            <StackPanel Orientation="Horizontal">
                <CheckBox x:Name="checkBox" IsChecked="{Binding XPath=@checked}" />
                <TextBlock x:Name="text" Text="{Binding XPath=@text}" />
            </StackPanel>
        </HierarchicalDataTemplate>
        
        <XmlDataProvider x:Key="xmlDataProvider" />
    </ComboBox.Resources>
    
    <ComboBoxItem MaxHeight="100" MinHeight="25" >
        <TreeView Name="treeView1" 
                  ItemsSource="{Binding Source={StaticResource xmlDataProvider}, XPath=/root/child}" 
                  ItemTemplate="{StaticResource NodeTemplate}">
            <TreeView.ItemContainerStyle>
                <Style TargetType="{x:Type TreeViewItem}">
                    <Setter Property="IsExpanded" Value="True" />
                </Style>
            </TreeView.ItemContainerStyle>
        </TreeView>
    </ComboBoxItem>
</ComboBox>


This is the partial code behind class:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Xml;
using System.IO;

namespace HierarchicalCheckComboBox
{
    /// <summary>
    /// Interaction logic for HCCB.xaml
    /// </summary>
    public partial class HCCB : ComboBox
    {
        bool _settingState = false;
        /// <summary>
        /// The dependency property which will be used to bind with the CLR property 'Value'. This value is set either ways.
        /// </summary>
        public static readonly DependencyProperty ValueProperty;

        static HCCB ()
        {
            ValueProperty = DependencyProperty.Register("Value", typeof(string), typeof(HCCB), 
                new FrameworkPropertyMetadata(string.Empty, FrameworkPropertyMetadataOptions.AffectsArrange, new PropertyChangedCallback(OnValueChanged)));
        }

        public string Value
        {
            get { return (string)GetValue(ValueProperty); }
            set
            {
                if (Value.Equals(value))
                    return;
                SetValue(ValueProperty, value);
            }
        }

        public HCCB()
        {
            InitializeComponent();
        }

        private static void OnValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            HCCB hccb = d as HCCB;
            if (hccb == null || hccb._settingState)
                return;

            string newValue = e.NewValue as string, oldValue = e.OldValue as string;

            if (newValue == null || newValue.Equals(oldValue))
                return;

            XmlDocument document = new XmlDocument();
            document.PreserveWhitespace = false;
            document.LoadXml(newValue);

            XmlDataProvider dataProvider = hccb.FindResource("xmlDataProvider") as XmlDataProvider;
            dataProvider.Document = document;
        }

        private void ComboBox_DropDownClosed(object sender, EventArgs e)
        {
            HCCB hccb = sender as HCCB;
            if (hccb == null)
                return;

            XmlDataProvider dataProvider = hccb.FindResource("xmlDataProvider") as XmlDataProvider;
            XmlDocument document = dataProvider.Document;
            string newXml = document.OuterXml;
            if (!Value.Equals(newXml))
            {
                try
                {
                    _settingState = true;
                    Value = newXml;
                }
                finally
                {
                    _settingState = false;
                }
            }
        }
    }
}


This is how a sample XML, which is set on the 'Value' property of the ComboBox looks:
<?xml version="1.0" encoding="utf-16"?>
<root>
  <child text="text1" checked="false">
    <child text="text11" checked="true" />
  </child>
  <child text="text2" checked="true">
    <child text="text21" checked="false" />
    <child text="text22" checked="true" />
  </child>
  <child text="text3" checked="false" >
    <child text="text33" checked="false" >
      <child text="text333" checked="true" >
        <child text="text3333" checked="false" >
        </child>
      </child>
    </child>
  </child>
</root>


Now, I want to create a parent child relation between the CheckBoxex. They are not tri-state. So, when I check a CheckBox of a TreeViewItem all the CheckBoxes of the child TreeViewItems should be checked. Same for uncheck. I referred to many sources on the net to get an idea of how that can be done. The answer by Dr. WPF in this comes the closest to what I need.
But, I am still at loss as to how will I bind the AttachedProperty of the TreeViewItem to XPath as well as an implementation to set the corresponding AttachedProperty of the child element.
This article uses the view model to achieve the same.
One option I have is to follow a combination of this and the previous article to first create a view model from the XML using LINQ. And then use that view model to do the parent child relation. Something similar is done here which uses a custom library. But, is there no other way to achieve this?


These are the articles I referred to get where I got.
. WPF-XmlDataProvider-Two-Way-Data-Binding
. Two-way binding of Xml data to the WPF TreeView
. WPF: XmlDataProvider Two-Way Data Binding
. WPF: Visualizing arbitrary XML documents in a TreeView control
Tags: WPF, checkbox, Treeview, XPath

Preview



When answering a question please:
  1. Read the question carefully.
  2. Understand that English isn't everyone's first language so be lenient of bad spelling and grammar.
  3. If a question is poorly phrased then either ask for clarification, ignore it, or edit the question and fix the problem. Insults are not welcome.
Let's work to help developers, not make them feel stupid.
Please note that all posts will be submitted under the The Code Project Open License (CPOL).



Advertise | Privacy | Mobile
Web01 | 2.8.170118.1 | Last Updated 26 Mar 2009
Copyright © CodeProject, 1999-2017
All Rights Reserved. Terms of Service
Layout: fixed | fluid

CodeProject, 503-250 Ferrand Drive Toronto Ontario, M3C 3G8 Canada +1 416-849-8900 x 100