WPF TreeView MouseDown - PullRequest
       5

WPF TreeView MouseDown

2 голосов
/ 19 мая 2010

У меня есть что-то вроде этого в TreeView:

<DataTemplate x:Key="myTemplate">
    <StackPanel MouseDown="OnItemMouseDown">
    ...
    </StackPanel>
</DataTemplate>

Используя это, я получаю события нажатия мыши, если нажимаю на элементы на панели стека. Однако ... кажется, что за панелью стека есть еще один элемент - TreeViewItem - по нему очень сложно попасть, но не невозможно, и именно тогда начинаются проблемы.

Я попытался обработать PreviewMouseDown для TreeViewItem, однако для этого требуется

e.Handled = false

в противном случае стандартное поведение в виде дерева перестает работать.


Хорошо, вот исходный код ...

MainWindow.xaml

<Window x:Class="WPFMultiSelectTree.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:WPFMultiSelectTree"    
    Title="Multiple Selection Tree" Height="300" Width="300">

<Window.Resources>

    <!-- Declare the classes that convert bool to Visibility -->
    <local:VisibilityConverter x:Key="visibilityConverter"/>
    <local:VisibilityInverter x:Key="visibilityInverter"/>

    <!-- Set the style for any tree view item -->
    <Style TargetType="TreeViewItem">

        <Style.Triggers>
            <DataTrigger Binding="{Binding Selected}" Value="True">
                <Setter Property="Background" Value="DarkBlue"/>
                <Setter Property="Foreground" Value="White"/>
            </DataTrigger>
        </Style.Triggers>

        <EventSetter Event="PreviewMouseDown" Handler="OnTreePreviewMouseDown"/>
    </Style>

    <!-- Declare a hierarchical data template for the tree view items -->
    <HierarchicalDataTemplate
        x:Key="RecursiveTemplate"
        ItemsSource="{Binding Children}">

        <StackPanel Margin="2" Orientation="Horizontal" MouseDown="OnTreeMouseDown">
            <Ellipse Width="12" Height="12" Fill="Green"/>
            <TextBlock
                Margin="2"
                Text="{Binding Name}"
                Visibility="{Binding Editing, Converter={StaticResource visibilityInverter}}"/>
            <TextBox
                Margin="2"
                Text="{Binding Name}"
                KeyDown="OnTextBoxKeyDown"
                IsVisibleChanged="OnTextBoxIsVisibleChanged"
                Visibility="{Binding Editing, Converter={StaticResource visibilityConverter}}"/>
            <TextBlock
                Margin="2"
                Text="{Binding Index, StringFormat=({0})}"/>
        </StackPanel>

    </HierarchicalDataTemplate>

    <!-- Declare a simple template for a list box -->
    <DataTemplate x:Key="ListTemplate">
        <TextBlock Text="{Binding Name}"/>
    </DataTemplate>

</Window.Resources>

<Grid>

    <!-- Declare the rows in this grid -->
    <Grid.RowDefinitions>
        <RowDefinition Height="Auto"/>
        <RowDefinition/>
        <RowDefinition Height="Auto"/>
        <RowDefinition/>
    </Grid.RowDefinitions>

    <!-- The first header -->
    <TextBlock Grid.Row="0" Margin="5" Background="PowderBlue">Multiple selection tree view</TextBlock>

    <!-- The tree view -->
    <TreeView
        Name="m_tree"
        Margin="2"
        Grid.Row="1"
        ItemsSource="{Binding Children}"
        ItemTemplate="{StaticResource RecursiveTemplate}"/>

    <!-- The second header -->
    <TextBlock Grid.Row="2" Margin="5" Background="PowderBlue">The currently selected items in the tree</TextBlock>

    <!-- The list box -->
    <ListBox 
        Name="m_list"
        Margin="2"
        Grid.Row="3"
        ItemsSource="{Binding .}"
        ItemTemplate="{StaticResource ListTemplate}"/>

</Grid>

</Window>

MainWindow.xaml.cs

/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
    private Container m_root;
    private Container m_first;
    private ObservableCollection<Container> m_selection;
    private string m_current;

    /// <summary>
    /// Constructor
    /// </summary>
    public MainWindow()
    {
        InitializeComponent();

        m_selection = new ObservableCollection<Container>();

        m_root = new Container("root");

        for (int parents = 0; parents < 50; parents++)
        {
            Container parent = new Container(String.Format("parent{0}", parents + 1));

            for (int children = 0; children < 1000; children++)
            {
                parent.Add(new Container(String.Format("child{0}", children + 1)));
            }

            m_root.Add(parent);
        }

        m_tree.DataContext = m_root;
        m_list.DataContext = m_selection;
        m_first = null;
    }

    /// <summary>
    /// Has the shift key been pressed?
    /// </summary>
    private bool ShiftPressed
    {
        get
        {
            return Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown(Key.RightShift);
        }
    }

    /// <summary>
    /// Has the control key been pressed?
    /// </summary>
    private bool CtrlPressed
    {
        get
        {
            return Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl);
        }
    }

    /// <summary>
    /// Clear down the selection list
    /// </summary>
    private void DeselectAndClear()
    {
        foreach(Container container in m_selection)
        {
            container.Selected = false;
        }

        m_selection.Clear();
    }

    /// <summary>
    /// Add the container to the list (if not already present),
    /// mark as selected
    /// </summary>
    /// <param name="container"></param>
    private void AddToSelection(Container container)
    {
        if (container == null)
        {
            return;
        }

        foreach (Container child in m_selection)
        {
            if (child == container)
            {
                return;
            }
        }

        container.Selected = true;

        m_selection.Add(container);
    }

    /// <summary>
    /// Remove container from list, mark as not selected
    /// </summary>
    /// <param name="container"></param>
    private void RemoveFromSelection(Container container)
    {
        m_selection.Remove(container);

        container.Selected = false;
    }

    /// <summary>
    /// Process single click on a tree item
    /// 
    /// Normally just select an item
    /// 
    /// SHIFT-Click extends selection
    /// CTRL-Click toggles a selection
    /// </summary>
    /// <param name="sender"></param>
    private void OnTreeSingleClick(object sender)
    {
        FrameworkElement element = sender as FrameworkElement;

        if (element != null)
        {
            Container container = element.DataContext as Container;

            if (container != null)
            {
                if (CtrlPressed)
                {
                    if (container.Selected)
                    {
                        RemoveFromSelection(container);
                    }
                    else
                    {
                        AddToSelection(container);
                    }
                }
                else if (ShiftPressed)
                {
                    if (container.Parent == m_first.Parent)
                    {
                        if (container.Index < m_first.Index)
                        {
                            Container item = container;

                            for (int i = container.Index; i < m_first.Index; i++)
                            {
                                AddToSelection(item);

                                item = item.Next;

                                if (item == null)
                                {
                                    break;
                                }
                            }
                        }
                        else if (container.Index > m_first.Index)
                        {
                            Container item = m_first;

                            for (int i = m_first.Index; i <= container.Index; i++)
                            {
                                AddToSelection(item);

                                item = item.Next;

                                if (item == null)
                                {
                                    break;
                                }
                            }
                        }
                    }
                }
                else
                {
                    DeselectAndClear();

                    m_first = container;

                    AddToSelection(container);
                }
            }
        }
    }

    /// <summary>
    /// Process double click on tree item
    /// </summary>
    /// <param name="sender"></param>
    private void OnTreeDoubleClick(object sender)
    {
        FrameworkElement element = sender as FrameworkElement;

        if (element != null)
        {
            Container container = element.DataContext as Container;

            if (container != null)
            {
                container.Editing = true;

                m_current = container.Name;
            }
        }
    }

    /// <summary>
    /// Clicked on the stack panel in the tree view
    /// 
    /// Double left click:
    /// 
    /// Switch to editing mode (flips visibility of textblock and textbox)
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    private void OnTreeMouseDown(object sender, MouseButtonEventArgs e)
    {
        Debug.WriteLine("StackPanel mouse down");

        switch(e.ChangedButton)
        {
            case MouseButton.Left:
                switch (e.ClickCount)
                {
                    case 2:
                        OnTreeDoubleClick(sender);

                        e.Handled = true;
                        break;
                }
                break;
        }
    }

    /// <summary>
    /// Clicked on tree view item in tree
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    private void OnTreePreviewMouseDown(object sender, MouseButtonEventArgs e)
    {
        Debug.WriteLine("TreeViewItem preview mouse down");

        switch (e.ChangedButton)
        {
            case MouseButton.Left:
                switch (e.ClickCount)
                {
                    case 1:
                        {
                            // We've had a single click on a tree view item
                            // Unfortunately this is the WHOLE tree item, including the +/-
                            // symbol to the left. The tree doesn't do a selection, so we
                            // have to filter this out...
                            MouseDevice device = e.Device as MouseDevice;

                            Debug.WriteLine(String.Format("Tree item clicked on: {0}", device.DirectlyOver.GetType().ToString()));

                            // This is bad. The whole point of WPF is for the code
                            // not to know what the UI has - yet here we are testing for
                            // it as a workaround. Sigh...
                            if (device.DirectlyOver.GetType() != typeof(Path))
                            {
                                OnTreeSingleClick(sender);
                            }

                            // Cannot say handled - if we do it stops the tree working!
                            //e.Handled = true;
                        }
                        break;
                }
                break;
        }
    }

    /// <summary>
    /// Key press in text box
    /// 
    /// Return key finishes editing
    /// Escape key finishes editing, restores original value (this doesn't work!)
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    private void OnTextBoxKeyDown(object sender, KeyEventArgs e)
    {
        switch(e.Key)
        {
            case Key.Return:
                {
                    TextBox box = sender as TextBox;

                    if (box != null)
                    {
                        Container container = box.DataContext as Container;

                        if (container != null)
                        {
                            container.Editing = false;

                            e.Handled = true;
                        }
                    }
                }
                break;

            case Key.Escape:
                {
                    TextBox box = sender as TextBox;

                    if (box != null)
                    {
                        Container container = box.DataContext as Container;

                        if (container != null)
                        {
                            container.Editing = false;

                            container.Name = m_current;

                            e.Handled = true;
                        }
                    }
                }
                break;
        }
    }

    /// <summary>
    /// When text box becomes visible, grab focus and select all text in it.
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    private void OnTextBoxIsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e)
    {
        bool visible = (bool)e.NewValue;

        if (visible)
        {
            TextBox box = sender as TextBox;

            if (box != null)
            {
                box.Focus();

                box.SelectAll();
            }
        }
    }
}

Вот класс Контейнер

public class Container : INotifyPropertyChanged
{
    private string m_name;
    private ObservableCollection<Container> m_children;
    private Container m_parent;

    private bool m_selected;
    private bool m_editing;

    /// <summary>
    /// Constructor
    /// </summary>
    /// <param name="name">name of object</param>
    public Container(string name)
    {
        m_name = name;

        m_children = new ObservableCollection<Container>();

        m_parent = null;

        m_selected = false;
        m_editing = false;
    }

    /// <summary>
    /// Name of object
    /// </summary>
    public string Name
    {
        get
        {
            return m_name;
        }

        set
        {
            if (m_name != value)
            {
                m_name = value;

                OnPropertyChanged("Name");
            }
        }
    }

    /// <summary>
    /// Index of object in parent's children
    /// 
    /// If there's no parent, the index is -1
    /// </summary>
    public int Index
    {
        get
        {
            if (m_parent != null)
            {
                return m_parent.Children.IndexOf(this);
            }

            return -1;
        }
    }

    /// <summary>
    /// Get the next item, assuming this is parented
    /// 
    /// Returns null if end of list reached, or no parent
    /// </summary>
    public Container Next
    {
        get
        {
            if (m_parent != null)
            {
                int index = Index + 1;

                if (index < m_parent.Children.Count)
                {
                    return m_parent.Children[index];
                }
            }

            return null;
        }
    }

    /// <summary>
    /// List of children
    /// </summary>
    public ObservableCollection<Container> Children
    {
        get
        {
            return m_children;
        }
    }

    /// <summary>
    /// Selected status
    /// </summary>
    public bool Selected
    {
        get
        {
            return m_selected;
        }

        set
        {
            if (m_selected != value)
            {
                m_selected = value;

                OnPropertyChanged("Selected");
            }
        }
    }

    /// <summary>
    /// Editing status
    /// </summary>
    public bool Editing
    {
        get
        {
            return m_editing;
        }

        set
        {
            if (m_editing != value)
            {
                m_editing = value;

                OnPropertyChanged("Editing");
            }
        }
    }

    /// <summary>
    /// Parent of this object
    /// </summary>
    public Container Parent
    {
        get
        {
            return m_parent;
        }

        set
        {
            m_parent = value;
        }
    }

    /// <summary>
    /// WPF Property Changed event
    /// </summary>
    public event PropertyChangedEventHandler PropertyChanged;

    /// <summary>
    /// Handler to inform WPF that a property has changed
    /// </summary>
    /// <param name="name"></param>
    private void OnPropertyChanged(string name)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(name));
        }
    }

    /// <summary>
    /// Add a child to this container
    /// </summary>
    /// <param name="child"></param>
    public void Add(Container child)
    {
        m_children.Add(child);

        child.m_parent = this;
    }

    /// <summary>
    /// Remove a child from this container
    /// </summary>
    /// <param name="child"></param>
    public void Remove(Container child)
    {
        m_children.Remove(child);

        child.m_parent = null;
    }
}

Два класса VisibilityConverter и VisibilityInverter являются реализациями IValueConverter, который переводит bool в Visibility. Они гарантируют, что TextBlock отображается, когда он не редактируется, и TextBox отображается при редактировании.

...