Tag Archives: TreeView

Filter WPF TreeView using ICollectionView in MVVM Pattern

Background:

A customer achieved a WPF TreeView with HierarchicalDataTemplate, there have a group of CheckBoxs (Green, Yellow & Red) and being checked:
treeview1

We can see that each item has a Circle with different color(Green, Yellow & Red). Now, he needs to accomplish filtering the TreeViewItem by color when check/uncheck CheckBox controls.

Reference:

In the above article, author used the ICollectionView interface to filter multiple level. This interface gives us the flexibility to Filter a collection even without touching the contents of the data source.

Implementation:

This project has two Models->

Class Model:

public class Class
{
         private string name;

        public string Name
        {
            get { return name; }
            set { name = value; }
        }

        private string imagePath;

        public string ImagePath
        {
            get { return imagePath; }
            set { imagePath = value; }
        }

        private ObservableCollection<Student> students;

        public ObservableCollection<Student> Students
        {
            get
            {
                if (students == null)
                {
                    students = new ObservableCollection<Student>();
                }

                return students;
            }
            set { students = value; }
        }

        public Class()
        { 

        }

        public Class(string name)
        {
            this.name = name;
        }

        public Class(string name, string imagePath)
        {
            this.name = name;
            this.imagePath = imagePath;
        }
}

Student Model:

public class Student
{
         private string name;

        public string Name
        {
            get { return name; }
            set { name = value; }
        }

        private string imagePath;

        public string ImagePath
        {
            get { return imagePath; }
            set { imagePath = value; }
        }

        private string toolTip;

        public string ToolTip
        {
            get { return toolTip; }
            set { toolTip = value; }
        }

        public Student()
        {

        }

        public Student(string name, string imagePath)
        {
            this.name = name;
            this.toolTip = name;
            this.imagePath = imagePath;
        }
}

To trigger the filtering function, we need to have three properties in ViewModel: IsDisplayGreen, IsDisplayRed and IsDisplayYellow. These three Properties are bound to the IsChecked property of the CheckBox.

<CheckBox Content="Green" Margin="2" IsChecked="{Binding Path=IsDisplayGreen, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
            <CheckBox Content="Yellow" Margin="2" IsChecked="{Binding Path=IsDisplayYellow, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
            <CheckBox Content="Red" Margin="2" IsChecked="{Binding Path=IsDisplayRed, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />

The filtering implementation:

private void FilterFunction()
{
            ICollectionView classesDataSourceView =
                    CollectionViewSource.GetDefaultView(ClassList);
                classesDataSourceView.Filter = (classModel =>
                {
                    ICollectionView studentsDataSourceView =
                        CollectionViewSource.GetDefaultView(
                            ((Class)classModel).Students);

                    if (_isDisplayGreen == false && _isDisplayYellow == false && _isDisplayRed==false)
                        studentsDataSourceView.Filter =
                        (studentModel =>false);
                    if (_isDisplayGreen == true && _isDisplayYellow == true && _isDisplayRed == true)
                        studentsDataSourceView.Filter =
                        (studentModel => true);

                    if (_isDisplayGreen == false && _isDisplayYellow == true && _isDisplayRed == false)
                        studentsDataSourceView.Filter =
                        (studentModel =>
                            ((Student)studentModel).ImagePath.
                                               Equals("../Images/Yellow-icon.png"));
                    if (_isDisplayGreen == true && _isDisplayYellow == false && _isDisplayRed == false)
                        studentsDataSourceView.Filter =
                        (studentModel =>
                            ((Student)studentModel).ImagePath.
                                               Equals("../Images/Green-icon.png"));
                    if (_isDisplayGreen == false && _isDisplayYellow == false && _isDisplayRed == true)
                        studentsDataSourceView.Filter =
                        (studentModel =>
                            ((Student)studentModel).ImagePath.
                                               Equals("../Images/Red-icon.png"));
                    if (_isDisplayGreen == true && _isDisplayYellow == false && _isDisplayRed == true)
                        studentsDataSourceView.Filter =
                        (studentModel =>
                            ((Student)studentModel).ImagePath.
                                               Equals("../Images/Red-icon.png") 
                                               || ((Student)studentModel).ImagePath.
                                               Equals("../Images/Green-icon.png"));
                    if (_isDisplayGreen == false && _isDisplayYellow == true && _isDisplayRed == true)
                        studentsDataSourceView.Filter =
                        (studentModel =>
                            ((Student)studentModel).ImagePath.
                                               Equals("../Images/Red-icon.png")
                                               || ((Student)studentModel).ImagePath.
                                               Equals("../Images/Yellow-icon.png"));
                    if (_isDisplayGreen == true && _isDisplayYellow == true && _isDisplayRed == false)
                        studentsDataSourceView.Filter =
                        (studentModel =>
                            ((Student)studentModel).ImagePath.
                                               Equals("../Images/Yellow-icon.png")
                                               || ((Student)studentModel).ImagePath.
                                               Equals("../Images/Green-icon.png"));

                    return !studentsDataSourceView.IsEmpty;
                });
}

Properties:

private bool _isDisplayGreen = true;
private bool _isDisplayRed = true;
private bool _isDisplayYellow = true;

public bool IsDisplayGreen
{
            get { return _isDisplayGreen; }
            set
            {
                _isDisplayGreen = value;
                FilterFunction();
                OnPropertyChanged("IsDisplayRed");
                OnPropertyChanged("IsDisplayGreen");
                OnPropertyChanged("IsDisplayYellow");
            }
}

public bool IsDisplayRed
{
            get { return _isDisplayRed; }
            set
            {
                _isDisplayRed = value;
                FilterFunction();
                OnPropertyChanged("IsDisplayRed");
                OnPropertyChanged("IsDisplayGreen");
                OnPropertyChanged("IsDisplayYellow");
            }
}

public bool IsDisplayYellow
{
            get { return _isDisplayYellow; }
            set
            {
                _isDisplayYellow = value;
                FilterFunction();
                OnPropertyChanged("IsDisplayRed");
                OnPropertyChanged("IsDisplayGreen");
                OnPropertyChanged("IsDisplayYellow");
            }
}

Of course, the ViewModel need to be inherited from PropertyChangedBase class:

public class PropertyChangedBase : INotifyPropertyChanged
{
        public event PropertyChangedEventHandler PropertyChanged;
        protected void OnPropertyChanged(string propertyName)
        {
            var handler = PropertyChanged;
            if (handler != null)
            {
                var e = new PropertyChangedEventArgs(propertyName);
                handler(this, e);
            }
        }
}

Screenshot:
WpfTVHierarchicalDataTemplateFilter

Download Sample: WpfTVHierarchicalDataTemplateFilter

How to accomplish Drag and Drop WPF TreeViewItem

Someone wants to accomplish drag and drop TreeViewItem in the WPF application, the data is loaded from a XML file and save back to it.

Firstly, we need to accomplish the first requirement-loading data from xaml file and display them in the WPF TreeView control.

XML file:

<?xml version="1.0" encoding="utf-8"?>
<Collection>
  <item Name="Jimmy">
    <Name>Jimmy Chen</Name>
    <Age>25</Age>
    <Gender>Male</Gender>
    <Icon>jimmy.png</Icon>
  </item>
  <item Name="James">
    <Name>James Hoffman</Name>
    <Age>23</Age>
    <Gender>Male</Gender>
    <Icon>james.png</Icon>
  </item>
  <item Name="Franklin">
    <Name>Franklin Chen</Name>
    <Age>22</Age>
    <Gender>Male</Gender>
    <Icon>franklin.png</Icon>
  </item>
</Collection>

Now, we need to load XML data to a ObservableCollection<T> instance:
Family & FamilyMember class:

public class Family : INotifyPropertyChanged
{
        public Family()
        {
            this.Children = new ObservableCollection<FamilyMember>();
        }
        private int _myage;

        public int myAge
        {
            get { return _myage; }
            set
            {
                if (_myage != value)
                {
                    _myage = value;
                    OnPropertyChanged("Age");
                }
            }
        }
        public event PropertyChangedEventHandler PropertyChanged;


        protected void OnPropertyChanged(string p_strPropertyName)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(p_strPropertyName));
        }
        public string myName { get; set; }
        public string myGender { get; set; }
        public string myImg { get; set; }

        public ObservableCollection<FamilyMember> Children { get; set; }
}

public class FamilyMember
{
        public string Name { get; set; }
        public string Age { get; set; }
        public string Img { get; set; }
}

Loading Data:

public ObservableCollection<Family> MyData { get; set; }

public MainWindow()
{
            InitializeComponent();

            XmlDocument xmlDoc = new XmlDocument();
            xmlDoc.Load(System.IO.Directory.GetCurrentDirectory() + "//FamilyBook.xml");
            XmlNodeList nodeName = xmlDoc.GetElementsByTagName("Name");
            XmlNodeList nodeGender = xmlDoc.GetElementsByTagName("Gender");
            XmlNodeList nodeAge = xmlDoc.GetElementsByTagName("Age");
            XmlNodeList nodeImg = xmlDoc.GetElementsByTagName("Icon");
            XmlNodeList elemList = xmlDoc.GetElementsByTagName("item");
            MyData = new ObservableCollection<Family>();
            for (int i = 0; i < elemList.Count; i++)
            {
                MyData.Add(new Family()
                {
                    myName = nodeName[i].InnerText,
                    myGender = nodeGender[i].InnerText,
                    myAge = Convert.ToInt32(nodeAge[i].InnerText),

                    myImg = System.IO.Directory.GetCurrentDirectory() + "/favicon/" + nodeImg[i].InnerText

                });
            }

            this.DataContext = this;
}

To display data in WPF TreeView, we need to write HierarchicalDataTemplate and DataTemplate:

<TreeView HorizontalAlignment="Left" ItemsSource="{Binding MyData}"  x:Name="trvFamilies"  Background="{x:Null}" >
            <TreeView.Resources>

                <HierarchicalDataTemplate DataType="{x:Type local:Family}"  ItemsSource="{Binding Children}">
                    <StackPanel Orientation="Horizontal">
                        <Image Source="{Binding myImg }" Margin="5,0,5,0" Height="18" Width="18" ></Image>
                        <TextBlock Text="{Binding Path=myName}" Width="167" Padding="0,3,0,0"   TextTrimming="CharacterEllipsis" ></TextBlock>
                        <Label Content="{Binding myAge}" Foreground="#FF515151" Width="39"  HorizontalContentAlignment="Right" Margin="0,0,12,0"></Label>

                    </StackPanel>
                </HierarchicalDataTemplate>
                <DataTemplate DataType="{x:Type local:FamilyMember}"  >
                    <StackPanel Orientation="Horizontal">
                        <Image Source="{Binding Img}" Margin="21,0,5,0" Height="18" Width="18" />
                        <TextBlock  x:Name="surl" Text="{Binding Name}" Width="158" TextTrimming="CharacterEllipsis" Padding="0,3,0,0"     />

                        <Label Content="{Binding Age}" Foreground="#FF515151"  Width="30"   HorizontalContentAlignment="Right"  Margin="0,0,12,0"/>

                    </StackPanel>
                </DataTemplate>
            </TreeView.Resources>
        </TreeView>

So far, the UI should be like this:
2014-09-02_210033

Now, we need to accomplish the most difficult part: Drag and drop TreeViewItem.

Anyway, here is an article on MSDN to talk about Drag and Drop Overview: http://msdn.microsoft.com/en-us/library/ms742859(v=vs.110).aspx

We need to set TreeView’s AllowDrop property to True first and handle the following events:

int flag = 0;
Point startPoint;
private void TV_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
            if (e.OriginalSource is TextBlock)
            {
                // Store the mouse position
                startPoint = e.GetPosition(null);
                flag = 1;
            }
}

private void trvFamilies_PreviewMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
            flag = 0;
}

private void TV_MouseMove(object sender, MouseEventArgs e)
{
            if (flag == 1) //Begin Drag
            {
                // Get the current mouse position
                Point mousePos = e.GetPosition(null);
                Vector diff = startPoint - mousePos;

                if (e.LeftButton == MouseButtonState.Pressed &&
                    Math.Abs(diff.X) > SystemParameters.MinimumHorizontalDragDistance ||
                    Math.Abs(diff.Y) > SystemParameters.MinimumVerticalDragDistance)
                {
                    // Get the dragged ListViewItem
                    TreeView treeView = sender as TreeView;
                    TreeViewItem treeViewItem =
                        FindAnchestor<TreeViewItem>((DependencyObject)e.OriginalSource);
                    if (treeViewItem != null)
                    {
                        // Find the data behind the ListViewItem
                        Family contact = (Family)treeView.ItemContainerGenerator.ItemFromContainer(treeViewItem);

                        // Initialize the drag & drop operation
                        DataObject dragData = new DataObject("myFormat", contact);
                        DragDrop.DoDragDrop(treeViewItem, dragData, DragDropEffects.Move);
                    }
                }
            }
}

// Helper to search up the VisualTree
private static T FindAnchestor<T>(DependencyObject current)
            where T : DependencyObject
{
            do
            {
                if (current is T)
                {
                    return (T)current;
                }
                current = VisualTreeHelper.GetParent(current);
            }
            while (current != null);
            return null;
}

private void FindDropTarget(TreeView tv, out TreeViewItem pItemNode, DragEventArgs pDragEventArgs)
{
            pItemNode = null;

            DependencyObject k = VisualTreeHelper.HitTest(tv, pDragEventArgs.GetPosition(tv)).VisualHit;

            while (k != null)
            {
                if (k is TreeViewItem)
                {
                    TreeViewItem treeNode = k as TreeViewItem;
                    if (treeNode.DataContext is Family)
                    {
                        pItemNode = treeNode;
                    }
                }
                else if (k == tv)
                {
                    Console.WriteLine("Found treeview instance");
                    return;
                }

                k = VisualTreeHelper.GetParent(k);
            }
}

private void trvFamilies_PreviewDragEnter(object sender, DragEventArgs e)
{
            if (!e.Data.GetDataPresent("myFormat") || sender == e.Source)
            {
                e.Effects = DragDropEffects.None;
            }
}

private void trvFamilies_PreviewDrop(object sender, DragEventArgs e)
{
            if (e.Data.GetDataPresent("myFormat"))
            {
                TreeViewItem itemNode;
                FindDropTarget((TreeView)sender, out itemNode, e);
                Family dropItem = (itemNode != null && itemNode.IsVisible ? itemNode.DataContext as Family : null);
                Family dragItem = e.Data.GetData("myFormat") as Family;
                if (dropItem != null)
                {
                    TreeView treeView = sender as TreeView;
                    Console.WriteLine("Index: " + (MyData.IndexOf(dropItem) + 1).ToString());
                    MyData.Remove(dragItem);
                    //MyData.Insert(MyData.IndexOf(dropItem) + 1, dragItem);
                    MyData.Insert(MyData.IndexOf(dropItem) >= 1 ? MyData.IndexOf(dropItem) : 0, dragItem);
                }
                flag = 0;//Release Drag Operation
            }
}

Screenshot(gif):
WpfTVDragDrop

Download Sample: WpfTVDragDrop