Click here to Skip to main content
15,881,455 members
Please Sign up or sign in to vote.
0.00/5 (No votes)
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
HTML
<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:
C#
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
<?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
Posted

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)



CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900