WPF IDataObject,拖放操作,剪切板操作
了解IDataObject接口
WPF中的拖放和剪切板操作都是建立在IDataObject的操作的,那么我们先来仔细研修一下IDataObject接口。
此接口在Windows Forms中就已经有,这里就只讨论WPF中的(System.Windows.IDataObject接口)
IDataObject保存这一个数据的多种表现形式,比如用这个对象保存我的信息,如果输出文字的话,可能是我的名字,如果输出图像的话,就是我的照片,输出数字的话,就是我的身份证号……当然数据本身和数据类型都是可以自定义的也不是不需要有联系的。
IDataObject用字符串来表示一种类型的名称,当然也可以使用Type类,最终Type.FullName属性会被用作类型字符串。
其次类型之间可以存在转换功能,比如UnicodeText和System.String类型都是一样的,那么如果查询UnicodeText但IDataObject中包含有System.String类型,那么应该可以返回System.String类型的值。
来这样看IDataObject的成员函数分析:
interface IDataObject object GetData(string/Type); //返回指定类型名称的数据 bool GetDataPresent(string/Type); //检查是否存在指定类型 void SetData(string/Type, object) (object); //添加新的数据:类型名称:string或Type.FullName或object.GetType().FullName string[] GetFormats(); //返回所有存在的数据类型名称,字符串数组
//所有函数还包含一个bool参数,来指定是否考虑数据类型之间可能存在的转换关系 |
下面是IDataObject类的全部成员
使用DataObject类
了解完了IDataObject接口,怎么使用啊?同样在System.Windows命名空间下,有一个类继承了IDataObject接口:DataObject类,我们先利用DataObject,演示下IDataObject的使用:
IDataObject ido = new DataObject(); ido.SetData(23f); //添加一个float,类型名称是System.Single ido.SetData(typeof(Guid), Guid.NewGuid()); //添加一个System.Guid ido.SetData("我的类型", new Button() { Content = "hehe" }); //添加一个自定义类型(自定义名称),存储一个WPF按钮 //查询 bool hasGuid = ido.GetDataPresent("System.Guid"); bool hasSingle = ido.GetDataPresent(typeof(Single)); bool hasDouble = ido.GetDataPresent(typeof(Double)); Guid guid = (Guid)ido.GetData(typeof(Guid)); Button btn = (Button)ido.GetData("我的类型"); string[] types = ido.GetFormats(); //输出 Trace.WriteLine(hasGuid, "存在Guid"); Trace.WriteLine(hasSingle, "存在float"); Trace.WriteLine(hasDouble, "存在double"); Trace.WriteLine(guid, "Guid"); Trace.WriteLine(btn, "Button"); Trace.WriteLine(String.Join(" <=> ", types), "IDataObject所有类型"); |
输出:
存在Guid: True 存在float: True 存在double: False Guid: 0687001c-dc0d-4c87-8d27-ff3b6655bab0 Button: System.Windows.Controls.Button: hehe IDataObject所有类型: 我的类型 <=> System.Guid <=> System.Single |
通过DataObject,我们可以使用IDataObject提供的接口函数,同时注意如果自动转换参数没有指定的话,DataObject的执行是默认为true。
从例子里可以看出来,IDataObject的类型名称和数据都是完全可以自定义的,没有任何限制。
DataFormats类
虽然IDataObject的类型名称可以自定义,但是在一般情况下我们可以直接利用DataFormats类已经提供的预定义字段来进行IDataObject的数据操作,这些字段的值也是跟操作系统所紧密联系的。
DataFormats包含的数据类型名称有:图像格式,HTML格式,文件格式,声音格式……这里就不一一列举了。
在后面具体讲“剪切板操作”和“拖放操作”中,我们都会用到DataFormats类。
再议DataObject类
上面提到过DataObject类,但仅仅为了演示IDataObject接口的使用,这里了解了DataFormats类后,我们可以彻底研究一下这个DataObject类,这个类成员函数看起来比较多,实际上很好理解,大致是这样一个结构:
class DataObject : IDataObject /* 省略继承了所有IDataObject的成员函数,同时如果自动转换参数没有指定的话,默认为true */ //这里提供一些常用数据类型的操作辅助函数,本质上是DataFormats类中的字段来做数据类型名,然后调用IDataObject的操作函数 Contains/Set/Get/Audio/FileDrop... //附加事件,当指定对象进行拖放或剪切板操作 Copy, Pasting, SettingData; |
辅助函数为了调用方便,但不是必须的,比如是用GetImage等效于是用GetData(DataFormats.Bitmap)。
剪贴板操作
掌握了IDataObject后,拖放和剪切板操作就简单多了,我们先看剪切板操作,不得不提Clipboard类
static class Clipboard void Clear(); //清空剪切板
IDataObject GetDataObject(); //返回当前剪切板的IDataObject void SetDataObject(object) (object); //设置剪切板的IDataObject bool IsCurrent(IDataObject); //判断IDataObject是否等于当前剪切板的内容 //下面的这些函数就像DataObject类的辅助函数一样,利用DataFormats的字段做一些常用数据操作 bool ContainsXXX(); xxx GetXXX(); void SetXXX(xxx); |
其中核心操作清空,进剪切板和出剪切板分别是Clear, SetDataObject, GetDataObject,其他所有其它函数围绕着这3大核心功能,整个Clipboard类和DataObject类很像(毕竟都是在操作IDataObject)。
下面我们来利用Clipboard类做一个非常有意思的WPF程序:
功能是:在TextBox中输入3行文字,第一行是普通字符串文本,第二行是数字,第三行是一个图像文件的路径。
程序输入正确后,会把字符串,数字和图像复制进剪切板,然后把它们粘贴下面。
另外,这个程序最有意思的地方是:由于对剪切板设置的不同组数据,因此程序运行后,在记事本里粘贴会显示文字”Mgen!”,而在画图里粘贴的话会显示上面那张图片。利用Windows剪贴板管理器可以直观看到这些数据:
其实,所谓的复制粘贴代码就是Clipboard类中的IDataObject的操作,下面是程序的主逻辑代码:
XAML
<StackPanel> <TextBox Name="tbx" AcceptsReturn="True" VerticalScrollBarVisibility="Visible" Height="100"/> <Button Click="btn_Click">复制并粘贴</Button> <TextBlock Text="下面是剪切板内信息"/> <StackPanel Name="view"/> </StackPanel> |
C#
public partial class Window1 : Window { public Window1() { InitializeComponent(); } private void btn_Click(object sender, RoutedEventArgs e) { try { var strs = tbx.Text.Split(new string[] { Environment.NewLine }, StringSplitOptions.None); var s = strs[0]; var i = Convert.ToInt32(strs[1]); var img = new BitmapImage(new Uri(strs[2])); var data = new DataObject(); data.SetData(DataFormats.UnicodeText, s); data.SetData(typeof(int), i); data.SetData(DataFormats.Bitmap, img); Clipboard.SetDataObject(data, true); Paste(); } catch { } } void Paste() { view.Children.Clear(); view.Children.Add(new TextBlock() { Text = "文字:" + Clipboard.GetText() }); view.Children.Add(new TextBlock() { Text = "数字:" + Clipboard.GetData(typeof(int).FullName).ToString() }); view.Children.Add(new Image() { Source = Clipboard.GetImage() }); } } |
拖放操作
拖放操作,顾名思义分“拖”和“放”两个操作,是数据从源头到终点的传递操作:
需要用到两个类的成员:DragDrop类和UIElement类
class DragDrop //针对数据终点 DragEnter, Leave, Over //进入,离开,移动 //针对数据源 GiveFeedBack QueryContinueDrag //开始拖放操作 DragDropEffects DoDragDrop(DependencyObject datasource, object data, DragDropEffects allowedEffects); class UIElement //允许数据被拖放至此:指定数据终点 AllowDrop: bool; |
“拖放”中拖的操作主要就是在鼠标按下事件中调用DoDragDrop方法来开始拖放操作。
放的操作就是(首先AllowDrop=true)在DragEnter,DragLeave,DragOver以及Drop事件中对数据进行处理。
比如下面这个程序,当往ListBox拖放控件后,会显示控件类型,同时也可以从系统资源管理器中拖入文件或文件夹,路径会被显示在ListBox中。
XAML:
<DockPanel> <Button Name="btn">Button</Button> <Label Name="label">Label</Label> <ListBox Name="list" AllowDrop="True" DragEnter="ListBox_DragEnter" Drop="ListBox_Drop"></ListBox> </DockPanel> |
代码:
public partial class Window1 : Window { public Window1() { InitializeComponent(); ApplyDragEvents(btn); ApplyDragEvents(label); } private void ListBox_DragEnter(object sender, DragEventArgs e) { if (e.Data.GetDataPresent("MyControl") || e.Data.GetDataPresent(DataFormats.FileDrop)) e.Effects = DragDropEffects.Copy; } private void ListBox_Drop(object sender, DragEventArgs e) { var list = e.Source as ListBox; if (e.Data.GetDataPresent("MyControl")) { var con = e.Data.GetData("MyControl") as Control; AddItem(con.ToString()); } else { string[] files = e.Data.GetData(DataFormats.FileDrop) as string[]; foreach (var s in files) AddItem(s); } } void AddItem(string s) { list.Items.Add(new ListBoxItem() { Content = s }); } void ApplyDragEvents(UIElement ele) { Point staPoint = new Point(); ele.PreviewMouseDown += (sender, e) => { staPoint = e.GetPosition((IInputElement)e.Source); }; ele.PreviewMouseMove += (sender, e) => { if (e.LeftButton == MouseButtonState.Pressed) { Point position = e.GetPosition(null); if (Math.Abs(position.X - staPoint.X) > SystemParameters.MinimumHorizontalDragDistance || Math.Abs(position.Y - staPoint.Y) > SystemParameters.MinimumVerticalDragDistance) { var dataobj = new DataObject("MyControl", ele); DragDrop.DoDragDrop(ele, dataobj, DragDropEffects.Move); } } }; } } |