TreeView与拖放和HierarchicalDataTemplate和其他控制元素作为TreeViewItem

本文关键字:元素 TreeViewItem 控制 其他 拖放 HierarchicalDataTemplate TreeView | 更新日期: 2023-09-27 18:18:17

我试图得到一个TreeView工作,它使用HierarchialDataTemplate绑定节点到ObservableCollection。TreeViewItems是带有复选框和组合框的网格。所有这些都有点工作,但我没有设法让拖放功能工作。

代码基于TreeView, HierarchicalDataTemplate和递归数据和http://www.codeproject.com/Articles/55168/Drag-and-Drop-Feature-in-WPF-TreeView-Control

<Window x:Class="TreeView_HierarchicalDataTemplate.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:TreeView_HierarchicalDataTemplate="clr-namespace:TreeView_HierarchicalDataTemplate"
    Title="MainWindow" x:Name="mainWindow" Height="350" Width="525">
<Window.Resources>
    <BooleanToVisibilityConverter x:Key="BoolToVis" />
</Window.Resources>

<Grid>
    <TreeView Name="TreeView_After" AllowDrop="True" DataContext="{Binding ElementName=mainWindow, Path=TreeModel}" ItemsSource="{Binding Items}">
        <TreeView.ItemContainerStyle>
            <Style TargetType="{x:Type TreeViewItem}">
                <EventSetter Event="TreeViewItem.DragOver" Handler="TreeView_After_DragOver"/>
                <EventSetter Event="TreeViewItem.Drop" Handler="TreeView_After_Drop"/>
                <EventSetter Event="TreeViewItem.MouseMove" Handler="TreeView_After_MouseMove"/>
                <EventSetter Event="TreeViewItem.MouseDown" Handler="TreeView_After_MouseDown"/>
            </Style>
        </TreeView.ItemContainerStyle>
        <TreeView.Resources>
            <HierarchicalDataTemplate DataType="{x:Type TreeView_HierarchicalDataTemplate:NodeViewModel}" ItemsSource="{Binding Children}">
                <Grid Background="LightBlue">
                    <Grid.RowDefinitions>
                        <RowDefinition/>
                    </Grid.RowDefinitions>
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="Auto" />
                        <ColumnDefinition Width="Auto" />
                        <ColumnDefinition Width="Auto"/>
                        <ColumnDefinition/>
                    </Grid.ColumnDefinitions>
                    <TextBlock Grid.Row="0" Grid.Column="0" Text="{Binding Name}"></TextBlock>
                    <GridSplitter Visibility="{Binding Path=Show, Converter={StaticResource BoolToVis}}" Grid.Column="0" Width="5"/>
                    <CheckBox Visibility="{Binding Path=Show, Converter={StaticResource BoolToVis}}" Grid.Row="0" Grid.Column="1" Margin="5,5,0,0">activated</CheckBox>
                    <GridSplitter Visibility="{Binding Path=Show, Converter={StaticResource BoolToVis}}" Grid.Column="0" Width="5"/>
                    <StackPanel Visibility="{Binding Path=Show, Converter={StaticResource BoolToVis}}" Grid.Row="0" Grid.Column="2" Orientation="Horizontal">
                        <TextBlock Margin="5">Action:</TextBlock>
                        <ComboBox>
                            <TextBlock>Move To</TextBlock>
                        </ComboBox>
                        <TextBlock Margin="5">C:''Videos'Folder'aVideo.mkv</TextBlock>
                    </StackPanel>
                    <GridSplitter Visibility="{Binding Path=Show, Converter={StaticResource BoolToVis}}" Grid.Column="1" Width="5"/>
                    <StackPanel Visibility="{Binding Path=Show, Converter={StaticResource BoolToVis}}" Grid.Row="0" Grid.Column="3" Orientation="Horizontal">
                        <TextBlock Margin="5">Duplicate of:</TextBlock>
                        <ComboBox>
                            <TextBlock>None</TextBlock>
                        </ComboBox>
                    </StackPanel>
                </Grid>
            </HierarchicalDataTemplate>
        </TreeView.Resources>
    </TreeView>
</Grid>

这是Observable Collection

public class TreeViewModel
{
    public ObservableCollection<NodeViewModel> Items { get; set; }
}
public class NodeViewModel : UIElement
{
    public NodeViewModel()
    {
        Show = true;
    }
    public string Id { get; set; }
    public string Name { get; set; }
    public bool Show { get; set; }
    public ObservableCollection<NodeViewModel> Children { get; set; }
}

但是这个Mouse down事件不起作用

private void TreeView_After_MouseDown(object sender, MouseButtonEventArgs e)
    {
        if (e.ChangedButton == MouseButton.Left)
        {
            _lastMouseDown = e.GetPosition(TreeView_After);
        }
    }

另外,如果我从UIElement派生NodeViewModel,我只会从元素(UIElement)到容器(NodeViewModel)进行类型转换。但是,如果我从UIElement中派生出NodeViewModel, TreeView就不再显示了:(

private NodeViewModel GetNearestContainer(UIElement element)
    {
        // Walk up the element tree to the nearest tree view item.
        NodeViewModel container = element as NodeViewModel;
        while ((container == null) && (element != null))
        {
            element = VisualTreeHelper.GetParent(element) as UIElement;
            container = element as NodeViewModel;
        }
        return container;
    }

TreeView与拖放和HierarchicalDataTemplate和其他控制元素作为TreeViewItem

我得到了Rohit Vats的一些帮助,像这样:

MainWindow.xaml:

<Window x:Class="TreeView_HierarchicalDataTemplate.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:TreeView_HierarchicalDataTemplate="clr-namespace:TreeView_HierarchicalDataTemplate"
    Title="MainWindow" x:Name="mainWindow" Height="350" Width="525">
<Window.Resources>
    <BooleanToVisibilityConverter x:Key="BoolToVis" />
</Window.Resources>

<Grid>
    <TreeView Name="TreeView_After" AllowDrop="True" DataContext="{Binding ElementName=mainWindow, Path=TreeModel}" ItemsSource="{Binding Items}">
        <TreeView.ItemContainerStyle>
            <Style TargetType="{x:Type TreeViewItem}">
                <EventSetter Event="TreeViewItem.DragOver" Handler="TreeView_After_DragOver"/>
                <EventSetter Event="TreeViewItem.Drop" Handler="TreeView_After_Drop"/>
                <EventSetter Event="TreeViewItem.MouseMove" Handler="TreeView_After_MouseMove"/>
            </Style>
        </TreeView.ItemContainerStyle>
        <TreeView.Resources>
            <HierarchicalDataTemplate DataType="{x:Type TreeView_HierarchicalDataTemplate:NodeViewModel}" ItemsSource="{Binding Children}">
                <Grid Background="LightBlue" MouseDown="TreeView_After_MouseDown">
                    <Grid.RowDefinitions>
                        <RowDefinition/>
                    </Grid.RowDefinitions>
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="Auto" />
                        <ColumnDefinition Width="Auto" />
                        <ColumnDefinition Width="Auto"/>
                        <ColumnDefinition/>
                    </Grid.ColumnDefinitions>
                    <TextBlock Grid.Row="0" Grid.Column="0" Text="{Binding Name}"></TextBlock>
                    <GridSplitter Visibility="{Binding Path=Show, Converter={StaticResource BoolToVis}}" Grid.Column="0" Width="5"/>
                    <CheckBox Visibility="{Binding Path=Show, Converter={StaticResource BoolToVis}}" Grid.Row="0" Grid.Column="1" Margin="5,5,0,0">activated</CheckBox>
                    <GridSplitter Visibility="{Binding Path=Show, Converter={StaticResource BoolToVis}}" Grid.Column="0" Width="5"/>
                    <StackPanel Visibility="{Binding Path=Show, Converter={StaticResource BoolToVis}}" Grid.Row="0" Grid.Column="2" Orientation="Horizontal">
                        <TextBlock Margin="5">Action:</TextBlock>
                        <ComboBox>
                            <TextBlock>Move To</TextBlock>
                        </ComboBox>
                        <TextBlock Margin="5">C:''Videos'Folder'aVideo.mkv</TextBlock>
                    </StackPanel>
                    <GridSplitter Visibility="{Binding Path=Show, Converter={StaticResource BoolToVis}}" Grid.Column="1" Width="5"/>
                    <StackPanel Visibility="{Binding Path=Show, Converter={StaticResource BoolToVis}}" Grid.Row="0" Grid.Column="3" Orientation="Horizontal">
                        <TextBlock Margin="5">Duplicate of:</TextBlock>
                        <ComboBox>
                            <TextBlock>None</TextBlock>
                        </ComboBox>
                    </StackPanel>
                </Grid>
            </HierarchicalDataTemplate>
        </TreeView.Resources>
    </TreeView>
</Grid>

TreeViewModel:

public class TreeViewModel
{
    public ObservableCollection<NodeViewModel> Items { get; set; }
}
public class NodeViewModel
{
    public NodeViewModel()
    {
        Show = true;
    }
    public string Id { get; set; }
    public string Name { get; set; }
    public bool Show { get; set; }
    public ObservableCollection<NodeViewModel> Children { get; set; }
}

主窗口代码:

public partial class MainWindow : Window
{
    TreeViewModel TreeViewModel_After;

    public MainWindow()
    {
        TreeViewModel_After = new TreeViewModel();
        TreeViewModel_After.Items = new ObservableCollection<NodeViewModel>{
                       new NodeViewModel { Name = "Root", Children =  new ObservableCollection<NodeViewModel> {
                          new NodeViewModel { Name = "Level1" ,  Children = new ObservableCollection<NodeViewModel>{ 
                              new NodeViewModel{ Name = "Level2"}}} } }};
        InitializeComponent();

    }
    public TreeViewModel TreeModel
    {
        get
        {
            return TreeViewModel_After;
        }
    }


    Point _lastMouseDown;
    NodeViewModel draggedItem, _target;

    private void TreeView_After_MouseDown(object sender, MouseButtonEventArgs e)
    {
        if (e.ChangedButton == MouseButton.Left)
        {
            _lastMouseDown = e.GetPosition(TreeView_After);
        }
    }

    private bool CheckGridSplitter(UIElement element)
    {
        if (element is GridSplitter)
        {
            return true;
        }
        GridSplitter GridSplitter = FindParent<GridSplitter>(element);
        if (GridSplitter != null)
        {
            return true;
        }
        return false;
    }

    private void TreeView_After_MouseMove(object sender, MouseEventArgs e)
    {
        try
        {
            if (e.LeftButton == MouseButtonState.Pressed)
            {
                UIElement element = e.OriginalSource as UIElement;
                bool bGridSplitter = CheckGridSplitter(element);

                Point currentPosition = e.GetPosition(TreeView_After);

                if ((Math.Abs(currentPosition.X - _lastMouseDown.X) > 10.0) ||
                    (Math.Abs(currentPosition.Y - _lastMouseDown.Y) > 10.0))
                {
                    draggedItem = (NodeViewModel)TreeView_After.SelectedItem;
                    if ( (draggedItem != null) && !bGridSplitter)
                    {
                        DragDropEffects finalDropEffect = DragDrop.DoDragDrop(TreeView_After, TreeView_After.SelectedValue,
                            DragDropEffects.Move);
                        //Checking target is not null and item is dragging(moving)
                        if ((finalDropEffect == DragDropEffects.Move) && (_target != null))
                        {
                            // A Move drop was accepted
                            if (draggedItem.Name != _target.Name)
                            {
                                CopyItem(draggedItem, _target);
                                _target = null;
                                draggedItem = null;
                            }      

                        }
                    }
                }
            }
        }
        catch (Exception)
        {
        }
    }
    private void TreeView_After_DragOver(object sender, DragEventArgs e)
    {
        try
        {
            Point currentPosition = e.GetPosition(TreeView_After);

            if ((Math.Abs(currentPosition.X - _lastMouseDown.X) > 10.0) ||
                (Math.Abs(currentPosition.Y - _lastMouseDown.Y) > 10.0))
            {
                // Verify that this is a valid drop and then store the drop target
                NodeViewModel item = GetNearestContainer(e.OriginalSource as UIElement);
                if (CheckDropTarget(draggedItem, item))
                {
                    e.Effects = DragDropEffects.Move;
                }
                else
                {
                    e.Effects = DragDropEffects.None;
                }
            }
            e.Handled = true;
        }
        catch (Exception)
        {
        }
    }
    private void TreeView_After_Drop(object sender, DragEventArgs e)
    {
        try
        {
            e.Effects = DragDropEffects.None;
            e.Handled = true;
            // Verify that this is a valid drop and then store the drop target
            NodeViewModel TargetItem = GetNearestContainer(e.OriginalSource as UIElement);
            if (TargetItem != null && draggedItem != null)
            {
                _target = TargetItem;
                e.Effects = DragDropEffects.Move;
            }
        }
        catch (Exception)
        {
        }

    }
    private bool CheckDropTarget(NodeViewModel _sourceItem, NodeViewModel _targetItem)
    {
        //Check whether the target item is meeting your condition
        bool _isEqual = false;
        if(_sourceItem.Name != _targetItem.Name )
        {
            _isEqual = true;
        }
        return _isEqual;
    }

    private void CopyItem(NodeViewModel _sourceItem, NodeViewModel _targetItem)
    {
        //Asking user wether he want to drop the dragged TreeViewItem here or not
        if (MessageBox.Show("Would you like to drop " + _sourceItem.Name + " into " + _targetItem.Name + "", "", MessageBoxButton.YesNo) == MessageBoxResult.Yes)
        {
            try
            {
                _targetItem.Children.Add(_sourceItem);
            }
            catch (Exception)
            {
            }
        }
    }

    private NodeViewModel GetNearestContainer(UIElement element)
    {
        // Walk up the element tree to the nearest tree view item.
        TreeViewItem UIContainer = FindParent<TreeViewItem>(element);
        NodeViewModel NVContainer = null;
        if (UIContainer != null)
        {
            NVContainer = UIContainer.DataContext as NodeViewModel;
        }
        return NVContainer;
    }

    private static Parent FindParent<Parent>(DependencyObject child)
            where Parent : DependencyObject
    {
        DependencyObject parentObject = child;
        parentObject = VisualTreeHelper.GetParent(parentObject);
        //check if the parent matches the type we're looking for
        if (parentObject is Parent || parentObject == null)
        {
            return parentObject as Parent;
        }
        else
        {
            //use recursion to proceed with next level
            return FindParent<Parent>(parentObject);
        }
    }
}