惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

宝玉的分享
宝玉的分享
NISL@THU
NISL@THU
E
Exploit-DB.com RSS Feed
L
LINUX DO - 热门话题
L
Lohrmann on Cybersecurity
K
Kaspersky official blog
Project Zero
Project Zero
Cisco Talos Blog
Cisco Talos Blog
T
The Exploit Database - CXSecurity.com
P
Palo Alto Networks Blog
C
CXSECURITY Database RSS Feed - CXSecurity.com
T
Threatpost
S
Schneier on Security
G
GRAHAM CLULEY
The Hacker News
The Hacker News
T
Threat Research - Cisco Blogs
Scott Helme
Scott Helme
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
P
Privacy & Cybersecurity Law Blog
C
Cyber Attacks, Cyber Crime and Cyber Security
Cyberwarzone
Cyberwarzone
C
CERT Recently Published Vulnerability Notes
T
Tor Project blog
AWS News Blog
AWS News Blog
Simon Willison's Weblog
Simon Willison's Weblog
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
爱范儿
爱范儿
P
Privacy International News Feed
云风的 BLOG
云风的 BLOG
P
Proofpoint News Feed
S
Securelist
G
Google Developers Blog
The Last Watchdog
The Last Watchdog
Google Online Security Blog
Google Online Security Blog
美团技术团队
F
Fortinet All Blogs
小众软件
小众软件
Recorded Future
Recorded Future
V
Visual Studio Blog
B
Blog RSS Feed
H
Help Net Security
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
Google DeepMind News
Google DeepMind News
Blog — PlanetScale
Blog — PlanetScale
博客园 - 聂微东
Stack Overflow Blog
Stack Overflow Blog
Martin Fowler
Martin Fowler
Latest news
Latest news
Spread Privacy
Spread Privacy
H
Heimdal Security Blog

博客园 - seabluescn

WPF开发快速入门【8】WPF进行简单的3D开发 WPF开发快速入门【6】下拉框与枚举类型 WPF开发快速入门【5】DataGrid的使用 WPF开发快速入门【4】自定义控件与用户控件 WPF开发快速入门【3】WPF的基本特性(附加属性) WPF开发快速入门【2】WPF的基本特性(Style、Trigger、Template) WPF开发快速入门【1】WPF的布局 WPF开发快速入门【0】前言与目录 WPF优秀组件推荐之FreeSpire WPF优秀组件推荐之LiveCharts WPF优秀组件推荐之MahApps WPF优秀组件推荐之Stylet(二) WPF优秀组件推荐之Stylet(一) TensorFlow.NET机器学习入门【9】后记 TensorFlow.NET机器学习入门【8】采用GPU进行学习 TensorFlow.NET机器学习入门【7】采用卷积神经网络(CNN)处理Fashion-MNIST TensorFlow.NET机器学习入门【6】采用神经网络处理Fashion-MNIST TensorFlow.NET机器学习入门【5】采用神经网络实现手写数字识别(MNIST) TensorFlow.NET机器学习入门【4】采用神经网络处理分类问题 - seabluescn
WPF开发快速入门【7】WPF的拖放功能(Drag and Drop)
seabluescn · 2022-08-26 · via 博客园 - seabluescn

概述

本文描述WPF的拖放功能(Drag and Drop)。

拖放功能涉及到两个功能,一个就是拖,一个是放。拖放可以发生在两个控件之间,也可以在一个控件自己内部拖放。假设界面上有两个控件,一个TreeView,一个ListView,那么可能发生的拖动有以下几种情况:

1、TreeView -> ListView

2、ListView -> TreeView 

3、TreeView -> TreeView 

4、ListView -> ListView

对于拖的控件需要在鼠标移动事件中检测左键按下并启动拖动操作;对于放的控件需要处理Drop等事件来接收数据。如果是在控件内部拖动,则以上两个动作都要处理。

为简便起见,本文就以ListView拖动到TreeView为例进行讲解。 

在拖与放的控件之间一定会有数据传递,我们可以设计一个类型来进行数据传输,由于ListView本身就是绑定到一个对象列表的,我就把选中的对象字节拿来传递了,没有额外定义类型。

    public class ListViewAdvNodeItem
    {
        public string Title {get;set;}      
     
    }

 listView.ItemsSource的数据类型为:BindableCollection<ListViewAdvNodeItem> ListViewAdvNodeItems,通过this.listView.SelectedItem可以得到的数据类型即为:ListViewAdvNodeItem

 设计代码如下:

    <ListView x:Name="listView"                          
              Mouse.MouseMove="listView_MouseMove" >        
    </ListView>

 在listView_MouseMove事件中,我们将启动拖动功能。

              private void listView_MouseMove(object sender, MouseEventArgs e)
       { 
if (sender is ListView listview

&& e.LeftButton == MouseButtonState.Pressed
&& listview.SelectedItem != null) { DragDrop.DoDragDrop(listview, listview.SelectedItem, DragDropEffects.Move); } }

 通过DragDrop.DoDragDrop方法启动拖动,该方法有三个参数:

1、发起拖动的控件

2、传输的数据(这里是一个ListViewAdvNodeItem类型的对象)

3、拖动的类型,一般为Move或Copy

下面就要在TreeView控件中处理放的事件了

设计代码:

    <TreeView x:Name="treeView"               
              AllowDrop="True" 
              DragDrop.Drop="treeView_Drop"
              DragDrop.DragOver="treeView_DragOver"
              DragDrop.DragEnter="treeView_DragEnter"
              DragDrop.DragLeave="treeView_DragLeave" >   
    </TreeView>

 首先要设置AllowDrop="True",然后重点处理DragDrop.Drop事件:

        private void treeView_Drop(object sender, DragEventArgs e)
        {
            if (e.Data.GetData(typeof(ListViewAdvNodeItem)) is ListViewAdvNodeItem fromListNode)
            {
                if (e.OriginalSource is TextBlock txtTitle)
                { if (txtTitle.Tag is Excerpt toExcerpt)
                    {
                        //处理业务
                    }
                }
            }     
        }

在处理Drop事件时,我们需要知道两件事情,1:拖来的是什么数据?2、放哪里了?

首先,通过e.Data.GetData(typeof(ListViewAdvNodeItem))就可以获得数据来源,这里GetData得到的对象就是上面的 listview.SelectedItem;

其次,通过e.OriginalSource 我们将获得数据放在哪里的问题。这段代码很难理解,要回头看一下TreeView的ItemTemplate定义

        <TreeView.ItemTemplate>
            <HierarchicalDataTemplate DataType="{x:Type local:TreeViewAdvNodeItem}" ItemsSource="{Binding Children}">
                <Border x:Name="itemBorder" Margin="2" BorderBrush="White" BorderThickness="1" >
                    <StackPanel Orientation="Horizontal">
                        <Image x:Name="nodeImage" Source="../Images/FolderClose.png" Width="20" Height="20"/>
                        <TextBlock Text="{Binding Title}" Tag="{Binding Excerpt}" VerticalAlignment="Center" FontSize="13" Margin="2"/>
                    </StackPanel>
                </Border>
            </HierarchicalDataTemplate>
        </TreeView.ItemTemplate>

从这个模板定义可以看出,TreeView中用来显示Title的控件是一个TextBlock,然后这个TextBlock的Tag属性上还绑定了一个业务对象。

再回头看上面一段代码,就可以看出具体的逻辑:当鼠标放开时,其所指的对象是一个TextBlock,然后取到这个TextBlock的Tag对象,里面包含了我想要的业务数据。

到此拖放功能就完成了。

为了更好的展现效果,我们可以对拖放的目标进行判断,对于一些不能放的位置显示禁止拖放的图标,这时就需要处理DragOver事件了

        private void treeView_DragOver(object sender, DragEventArgs e)
        {   //判断是否允许拖动          
                       e.Effects = DragDropEffects.None;
            if (e.Data.GetData(typeof(ListViewAdvNodeItem)) is ListViewAdvNodeItem fromListNode)
            {
                if (e.OriginalSource is TextBlock txtTitle)
                {
                    if (txtTitle.Tag is Excerpt toExcerpt)
                    {
                        if (CanDrop(fromListNode.Excerpt, toExcerpt))  //业务判断
                        {
                            e.Effects = DragDropEffects.Move;
                        }
                    }
                }               
            } 
            e.Handled = true;
        }

装饰器

如果拖动时,有下面这样的一个标签跟随鼠标移动,其显示内容是拖动对象的Title,效果就更好了。

这个就需要通过装饰器来实现。

关于装饰器的介绍:装饰器概述 - WPF .NET Framework | Microsoft Docs

首先我们建一个装饰器对象DragTitleAdorner

    public class DragTitleAdorner : Adorner
    {
        private readonly ContentPresenter _contentPresenter;
        private Control Control
        {
            get
            {
                return (Control)this.AdornedElement;
            }
        }

        public DragTitleAdorner(UIElement adornedElement, Point pos, string? Title = "") : base(adornedElement)
        {
            IsHitTestVisible = false;

            int width = 22;
            if (Title != null)
            {
                width += (int)MeasureTextWidth(Title, 14, "宋体");
            }

            this._contentPresenter = new ContentPresenter
            {
                Content = new Border
                {
                    Background = Brushes.SteelBlue,
                    Width = width,
                    Height = 28,
                    BorderBrush = Brushes.Gray,
                    BorderThickness = new Thickness(1),
                    HorizontalAlignment = HorizontalAlignment.Left,
                    VerticalAlignment = VerticalAlignment.Top,
                    CornerRadius= new CornerRadius(5),
                    Child = new TextBlock
                    {
                        Text = Title,
                        FontSize = 14,
                        FontFamily= new FontFamily("宋体"),
                        Foreground = Brushes.White,
                        HorizontalAlignment = HorizontalAlignment.Left,
                        VerticalAlignment = VerticalAlignment.Center,
                        Margin = new Thickness(10, 0, 0, 0),
                    },
                },
            };

            double left = pos.X;
            double top = pos.Y;
            this.Margin = new Thickness(left + 5, top + 10, 0, 0);
        }

        #region Override

        protected override int VisualChildrenCount
        {
            get
            {
                return 1;
            }
        }

        protected override Visual GetVisualChild(int index) // replace the Visual of the TextBox with the visual of the _contentPresenter;
        {
            return this._contentPresenter;
        }

        protected override Size MeasureOverride(Size constraint)
        {
            this._contentPresenter.Measure(this.Control.RenderSize); // delegate the measure override to the ContentPresenter's Measure
            return this.Control.RenderSize;
        }

        protected override Size ArrangeOverride(Size finalSize)
        {
            this._contentPresenter.Arrange(new Rect(finalSize));
            return finalSize;
        }

        #endregion Override

        private  double MeasureTextWidth(string text, double fontSize, string fontFamily)
        {
            FormattedText formattedText = new FormattedText(
            text,
            System.Globalization.CultureInfo.InvariantCulture,
            FlowDirection.LeftToRight,
            new Typeface(fontFamily.ToString()),
            fontSize,
            Brushes.Black
            );
            return formattedText.WidthIncludingTrailingWhitespace;
        }

    }

View Code

在构造这个对象时,我们将传入两个重要的参数:Point pos 和 string Title ,这两个参数决定了它在何处显示什么内容。

程序用代码构建了一个Border,其内有一个TextBlock,并通过pos参数来控制了它的位置。

下面,在treeView_DragOver事件中显示这个装饰器即可。

        private void treeView_DragOver(object sender, DragEventArgs e)
        {
            if (e.Data.GetData(typeof(ListViewAdvNodeItem)) is ListViewAdvNodeItem fromListNode)
            { 
                //显示装饰器
                AdornerLayer adornerLayer = AdornerLayer.GetAdornerLayer(this.treeView);
                if (adornerLayer != null)
                {
                    Adorner[] adorners = adornerLayer.GetAdorners(this.treeView);
                    if (adorners != null)
                    {
                        foreach (var adorner in adorners)
                        {
                            adornerLayer.Remove(adorner);
                        }
                    }

                    DragTitleAdorner _adorner = new DragTitleAdorner(this.treeView, pos, fromListNode.Excerpt?.Title);
                    adornerLayer.Add(_adorner);
                }
            } 
            e.Handled = true;
        }

 更多信息请参考文末源码。

资源

系列目录:WPF开发快速入门【0】前言与目录 

代码下载:Learn WPF: WPF学习笔记 (gitee.com)