Search

'selectedItem'에 해당되는 글 1건

  1. 2010.07.14 TwoWay Binding of SelectedItem in TreeView

TwoWay Binding of SelectedItem in TreeView

프로그래밍 2010.07.14 19:22 Posted by 아일레프

Hello guys, I’m a elementary WPF programmar and elementary English writer. J  And this is my first English article. I think you are fortune, because you can see that someone start something.  

OK, now I start.

SelectedItem of TreeView does not support two-way binding(It is readonly) and I don’t know why(someone knows it?) . I searched this topic, but I couldn’t find any good solution.  

I had used to SelectedItemChanged event and maually set the IsSelected of TreeViewItem has SelectedItem as It’s DataContext.

I’m a really lazy person, but I can’t take it anymore. Finally, I found an easy & good(really?) solution.

Now, I can bind SelectedItem of TreeView like this way.

 

<TreeView x:Name="mainTreeView"  ItemsSource="{Binding TemplateService.SelectedTemplate.Regions}"    

                                             illef:TreeViewExtension.SelectedItem="{Binding TemplateService.CurrentSelectedRegion}">

I want to introduce how can I make it.

First of all, I need a helper function return a TreeViewItem has SelectedItem as it’s DataContext. You can use a below method. (This function was suggested by Mr. Sys.pe.kr)  

       public static TreeViewItem ContainerFromItem(this TreeView treeView, object item)

        {

            TreeViewItem containerThatMightContainItem = (TreeViewItem)treeView.ItemContainerGenerator.ContainerFromItem(item);

            if (containerThatMightContainItem != null)

            {

                return containerThatMightContainItem;

            }

            else

            {

                return ContainerFromItem(treeView.ItemContainerGenerator, treeView.Items, item);

            }

        }

 

        private static TreeViewItem ContainerFromItem(ItemContainerGenerator parentItemContainerGenerator, ItemCollection itemCollection, object item)

        {

            foreach (object curChildItem in itemCollection)

            {

                TreeViewItem parentContainer = (TreeViewItem)parentItemContainerGenerator.ContainerFromItem(curChildItem);

                if (parentContainer == null)

                {

                    continue;

                }

                TreeViewItem containerThatMightContainItem = (TreeViewItem)parentContainer.ItemContainerGenerator.ContainerFromItem(item);

                if (containerThatMightContainItem != null)

                {

                    return containerThatMightContainItem;

                }

                TreeViewItem recursionResult = ContainerFromItem(parentContainer.ItemContainerGenerator, parentContainer.Items, item);

                if (recursionResult != null)

                {

                    return recursionResult;

                }

            }

            return null;

        }

And I need a class define attached property.

    public static class TreeViewExtension

    {

          public static readonly DependencyProperty SelectedItemProperty =

                    DependencyProperty.RegisterAttached("SelectedItem", typeof(object), typeof(TreeViewExtension),

                             new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, SelectedItemIsChanged));

 

        public static void SetSelectedItem(TreeView treeView, object value)

        {

            treeView.SetValue(SelectedItemProperty, value);

        }

        public static object GetSelectedItem(TreeView treeView)

        {

            return treeView.GetValue(SelectedItemProperty);

        }

        private static void SelectedItemIsChanged(object sender, DependencyPropertyChangedEventArgs e)

        {

                   //TO DO:?

        }

       }

You can guess what will be placed in “TO DO”. Who is the sender? Yes, sender is TreeView and e.NewValue is new selectedItem value.

So, I placed below job in there.

        private static void SelectedItemIsChanged(object sender, DependencyPropertyChangedEventArgs e)

        {

                   SetSelectedItem(e.NewValue);

                   TreeView treeView = sender as TreeView;

                   treeView.SelectedItemChanged += (o,e)=> SetSelectedItem(e.newValue);

                   if(treeView.SelectedItem != GetSelectedItem(treeView))

                   {

                             treeView.ContainerFromItem(GetSelectedItem(treeView)).IsSelected=true;

                   }

        }

I think you can easly know what I want to do. However, upper method is so ugly. Why? First, it doesn’t work J Second, It will make memory leak. The static class must do not handle any UIElement’s events.

In these case, there are two solutions I found.

First, use WeakEvent. This is greate solution, but there is problem it is hard to implement WeakEvent. See this CodeProject site. – Weak Event in C#

Second, my favorite Watcher-Register Pattern, I named it. J

1)    Made Watcher class. That will handle UIElement’s events.

2)    Store it in UIElement’s another attached property.

Made Watcher class.

    public class SelectedItemWatcher

    {

        private TreeView treeView;

 

        public SelectedItemWatcher(TreeView treeView)

        {

            this.treeView = treeView;

            if (treeView.SelectedItem != null)

            {

                TreeViewExtension.SetSelectedItem(treeView, treeView.SelectedItem);

            }

            treeView.SelectedItemChanged += new RoutedPropertyChangedEventHandler<object>(TreeView_SelectedItemChanged);

        }

        void TreeView_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)

        {

            TreeViewExtension.SetSelectedItem(treeView, treeView.SelectedItem);

        }

        TreeViewItem clearedTreeViewItem;

        public void SetValue(object value)

        {

            if (value == null)

            {

                TreeViewItem selectedTreeViewItem = treeView.SelectFirstObject<TreeViewItem>(s => s.IsSelected);

                if (selectedTreeViewItem != null)

                {

                    treeView.PreviewMouseLeftButtonDown += SelectedTreeViewItem_PreviewMouseLeftButtonDown;

                    selectedTreeViewItem.IsSelected = false;

                    clearedTreeViewItem = selectedTreeViewItem;

                }

            }

            if (treeView.SelectedItem != value)

            {

                treeView.PreviewMouseLeftButtonDown -= SelectedTreeViewItem_PreviewMouseLeftButtonDown;

                TreeViewItem treeViewItem = TreeViewExtension.ContainerFromItem(treeView, value);

                if (treeViewItem == null)

                {

                    return;

                }

                treeViewItem.IsSelected = true;

            }

        }

        private void SelectedTreeViewItem_PreviewMouseLeftButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e)

        {

            DependencyObject dependencyObject = e.MouseDevice.DirectlyOver as DependencyObject;

            TreeViewItem treeViewItem = dependencyObject.FindParentInVisualTreeNode<TreeViewItem>(20);

            if (treeViewItem == clearedTreeViewItem)

            {

                treeViewItem.IsSelected = true;

                treeView.PreviewMouseLeftButtonDown -= SelectedTreeViewItem_PreviewMouseLeftButtonDown;

            }

        }

    }

Store it as another Attached Property in TreeView.

          public static readonly DependencyProperty SelectedItemProperty =

                    DependencyProperty.RegisterAttached("SelectedItem", typeof(object), typeof(TreeViewExtension),

                             new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, SelectedItemIsChanged));

        //For storing Watcher Class

        private static readonly DependencyProperty SelectedItemWatcherProperty = DependencyProperty.RegisterAttached("SelectedItemWatcher", typeof(SelectedItemWatcher), typeof(TreeViewExtension));

        #region GetValue & SetValue

        public static void SetSelectedItemWatcher(TreeView treeView, object value)

        {

            treeView.SetValue(SelectedItemWatcherProperty, value);

        }

        public static SelectedItemWatcher GetSelectedItemWatcher(TreeView treeView)

        {

            return (SelectedItemWatcher)treeView.GetValue(SelectedItemWatcherProperty);

        }

        private static void SelectedItemIsChanged(object sender, DependencyPropertyChangedEventArgs e)

        {

            TreeView treeView = sender as TreeView;

            Debug.Assert(treeView != null);

 

            if (TreeViewExtension.GetSelectedItemWatcher(treeView) == null)

            {

                TreeViewExtension.SetSelectedItemWatcher(treeView, new SelectedItemWatcher(treeView));

            }

            else

            {

                TreeViewExtension.GetSelectedItemWatcher(treeView).SetValue(e.NewValue);

            }

        }

In upper case, You can handle UIElement’s event without any memory leak.

OK, Thank you for your reading J I attached my TreeViewExtension.cs contains upper classes.

Have a good day!

저작자 표시
신고