How can I cancel a user's WPF TreeView click?

后端 未结 9 1936
长情又很酷
长情又很酷 2020-12-14 03:56

I\'ve got a WPF application with a Treeview control.

When the user clicks a node on the tree, other TextBox, ComboBox, etc. controls on the page are populated with a

9条回答
  •  暖寄归人
    2020-12-14 04:12

    You can't actually put your logic into the OnSelectedItemChanged Method, if the logic is there the Selected Item has actually already changed.

    As suggested by another poster, the PreviewMouseDown handler is a better spot to implement the logic, however, a fair amount of leg work still needs to be done.

    Below is my 2 cents:

    First the TreeView that I have implemented:

    public class MyTreeView : TreeView
    {
        static MyTreeView( )
        {
            DefaultStyleKeyProperty.OverrideMetadata(
                typeof(MyTreeView),
                new FrameworkPropertyMetadata(typeof(TreeView)));
        }
    
        // Register a routed event, note this event uses RoutingStrategy.Tunnel. per msdn docs
        // all "Preview" events should use tunneling.
        // http://msdn.microsoft.com/en-us/library/system.windows.routedevent.routingstrategy.aspx
        public static RoutedEvent PreviewSelectedItemChangedEvent = EventManager.RegisterRoutedEvent(
            "PreviewSelectedItemChanged",
            RoutingStrategy.Tunnel,
            typeof(CancelEventHandler),
            typeof(MyTreeView));
    
        // give CLR access to routed event
        public event CancelEventHandler PreviewSelectedItemChanged
        {
            add
            {
                AddHandler(PreviewSelectedItemChangedEvent, value);
            }
            remove
            {
                RemoveHandler(PreviewSelectedItemChangedEvent, value);
            }
        }
    
        // override PreviewMouseDown
        protected override void OnPreviewMouseDown(MouseButtonEventArgs e)
        {
            // determine which item is going to be selected based on the current mouse position
            object itemToBeSelected = this.GetObjectAtPoint(e.GetPosition(this));
    
            // selection doesn't change if the target point is null (beyond the end of the list)
            // or if the item to be selected is already selected.
            if (itemToBeSelected != null && itemToBeSelected != SelectedItem)
            {
                bool shouldCancel;
    
                // call our new event
                OnPreviewSelectedItemChanged(out shouldCancel);
                if (shouldCancel)
                {
                    // if we are canceling the selection, mark this event has handled and don't
                    // propogate the event.
                    e.Handled = true;
                    return;
                }
            }
    
            // otherwise we want to continue normally
            base.OnPreviewMouseDown(e);
        }
    
        protected virtual void OnPreviewSelectedItemChanged(out bool shouldCancel)
        {
            CancelEventArgs e = new CancelEventArgs( );
            if (PreviewSelectedItemChangedEvent != null)
            {
                // Raise our event with our custom CancelRoutedEventArgs
                RaiseEvent(new CancelRoutedEventArgs(PreviewSelectedItemChangedEvent, e));
            }
            shouldCancel = e.Cancel;
        }
    }
    

    some extension methods to support the TreeView finding the object under the mouse.

    public static class ItemContainerExtensions
    {
        // get the object that exists in the container at the specified point.
        public static object GetObjectAtPoint(this ItemsControl control, Point p)
            where ItemContainer : DependencyObject
        {
            // ItemContainer - can be ListViewItem, or TreeViewItem and so on(depends on control)
            ItemContainer obj = GetContainerAtPoint(control, p);
            if (obj == null)
                return null;
    
            // it is worth noting that the passed _control_ may not be the direct parent of the
            // container that exists at this point. This can be the case in a TreeView, where the
            // parent of a TreeViewItem may be either the TreeView or a intermediate TreeViewItem
            ItemsControl parentGenerator = obj.GetParentItemsControl( );
    
            // hopefully this isn't possible?
            if (parentGenerator == null)
                return null;
    
            return parentGenerator.ItemContainerGenerator.ItemFromContainer(obj);
        }
    
        // use the VisualTreeHelper to find the container at the specified point.
        public static ItemContainer GetContainerAtPoint(this ItemsControl control, Point p)
            where ItemContainer : DependencyObject
        {
            HitTestResult result = VisualTreeHelper.HitTest(control, p);
            DependencyObject obj = result.VisualHit;
    
            while (VisualTreeHelper.GetParent(obj) != null && !(obj is ItemContainer))
            {
                obj = VisualTreeHelper.GetParent(obj);
            }
    
            // Will return null if not found
            return obj as ItemContainer;
        }
    
        // walk up the visual tree looking for the nearest ItemsControl parent of the specified
        // depObject, returns null if one isn't found.
        public static ItemsControl GetParentItemsControl(this DependencyObject depObject)
        {
            DependencyObject obj = VisualTreeHelper.GetParent(depObject);
            while (VisualTreeHelper.GetParent(obj) != null && !(obj is ItemsControl))
            {
                obj = VisualTreeHelper.GetParent(obj);
            }
    
            // will return null if not found
            return obj as ItemsControl;
        }
    }
    

    and last, but not least the custom EventArgs that leverage the RoutedEvent subsystem.

    public class CancelRoutedEventArgs : RoutedEventArgs
    {
        private readonly CancelEventArgs _CancelArgs;
    
        public CancelRoutedEventArgs(RoutedEvent @event, CancelEventArgs cancelArgs)
            : base(@event)
        {
            _CancelArgs = cancelArgs;
        }
    
        // override the InvokeEventHandler because we are going to pass it CancelEventArgs
        // not the normal RoutedEventArgs
        protected override void InvokeEventHandler(Delegate genericHandler, object genericTarget)
        {
            CancelEventHandler handler = (CancelEventHandler)genericHandler;
            handler(genericTarget, _CancelArgs);
        }
    
        // the result
        public bool Cancel
        {
            get
            {
                return _CancelArgs.Cancel;
            }
        }
    }
    

提交回复
热议问题