Silverlight 中用鼠标同时选中和移动多个控件
在设计 WinForm 程序时,我们可以很方便的同时选择窗体上的多个控件来调整控件的位置。在 Silverlight 应用程序中有时我们也想实现同样的功能,以提供更好的用户体验。本文将要介绍的就是在 Silverlight 程序中实现同时选中和移动多个控件。
1、实现鼠标拖动选择时显示所选区域
2、移动所选区域时同时移动在该区域内的控件
要实现鼠标拖动选择时显示所选区域功能,可以在鼠标拖动时在 Canvas 容器中动态添加一个 Rectangle 来显示类似在 Windows 资源管理器拖动选择文件时的选择框。实现前面所述功能的操作:在 Canvas 容器中按下鼠标左键并拖动鼠标来修改选择区域,选定目标区域后松开鼠标按键,这时显示一个表示所选区域的矩形选框。由实现的操作可知,我们需要在鼠标左键按下时在 Canvas 容器中添加一个矩形框,然后在鼠标移动时根据鼠标的位置更新矩形框的大小,最后在鼠标左键弹起显示最终的选择区域并查找出在选择区域中的控件。
private Point origPoint; /* 鼠标点击的位置 */ private Rectangle rect; /* 选择的区域 */ private Rect selectedRect; /* 选择的矩形区域 */ private bool isMultipleSelected; private List<FrameworkElement> selectedElements = new List<FrameworkElement>(); public MainPage() { InitializeComponent(); this.rootCanvas.MouseLeftButtonDown += Handle_MouseLeftButtonDown; } private void Handle_MouseLeftButtonDown(object sender, MouseButtonEventArgs e) { if (e.OriginalSource is Canvas) { if (rect != null) { isMultipleSelected = false; rootCanvas.Children.Remove(rect); rect = null; } else { isMultipleSelected = true; rect = new Rectangle(); origPoint = e.GetPosition(rootCanvas); rootCanvas.Children.Add(rect); rect.SetValue(Canvas.LeftProperty, origPoint.X); rect.SetValue(Canvas.TopProperty, origPoint.Y); rect.Fill = new SolidColorBrush(Colors.LightGray); rect.Stroke = new SolidColorBrush(Colors.Black); rect.StrokeThickness = 3; rect.Opacity = .5; rect.MouseLeftButtonDown += Handle_MouseLeftButtonDown; rootCanvas.MouseMove += Handle_MouseMove; rootCanvas.MouseLeftButtonUp += Handle_MouseLeftButtonUp; } } else if (e.OriginalSource is Rectangle) { isMultipleSelected = false; origPoint = e.GetPosition(rootCanvas); rect.MouseMove += Handle_MouseMove; rect.MouseLeftButtonUp += Handle_MouseLeftButtonUp; rect.CaptureMouse(); /* * 查找在选择范围内的控件 * */ double rLeft = (double)rect.GetValue(Canvas.LeftProperty); double rTop = (double)rect.GetValue(Canvas.TopProperty); foreach (FrameworkElement item in rootCanvas.Children) { double cLeft = (double)item.GetValue(Canvas.LeftProperty); double cTop = (double)item.GetValue(Canvas.TopProperty); Rect rc1 = new Rect(selectedRect.X, selectedRect.Y, selectedRect.Width, selectedRect.Height); Rect rc2 = new Rect(cLeft, cTop, item.ActualWidth, item.ActualHeight); rc1.Intersect(rc2); /* 判断控件所在的矩形区域与选择的矩形区域是否相交 */ if (rc1 != Rect.Empty) { if (!selectedElements.Contains(item)) selectedElements.Add(item); } } } } private void Handle_MouseLeftButtonUp(object sender, MouseButtonEventArgs e) { if (isMultipleSelected) { /* 所选区域的矩形 */ selectedRect = new Rect((double)rect.GetValue(Canvas.LeftProperty), (double)rect.GetValue(Canvas.TopProperty), rect.Width, rect.Height); rootCanvas.MouseLeftButtonUp -= Handle_MouseLeftButtonUp; rootCanvas.MouseMove -= Handle_MouseMove; } } private void Handle_MouseMove(object sender, MouseEventArgs e) { if (isMultipleSelected) { Point curPoint = e.GetPosition(rootCanvas); if (curPoint.X > origPoint.X) { rect.Width = curPoint.X - origPoint.X; } if (curPoint.X < origPoint.X) { rect.SetValue(Canvas.LeftProperty, curPoint.X); rect.Width = origPoint.X - curPoint.X; } if (curPoint.Y > origPoint.Y) { rect.Height = curPoint.Y - origPoint.Y; } if (curPoint.Y < origPoint.Y) { rect.SetValue(Canvas.TopProperty, curPoint.Y); rect.Height = origPoint.Y - curPoint.Y; } } }
在上面的代码中首先定义了几个要用到的变量:origPoint 用来保存鼠标点击时的位置,在动态修改矩形选框和移动控件时会用到;rect 显示矩形选框;selectedRect 矩形选框的矩形大小;isMultipleSelected 标识是否是拖动鼠标进行选择;selectedElements 保存在选择区域中的控件。
在鼠标左键单击事件中,先判断是在 Canvas 容器中单击的还是在 Rectangle 中单击的,注意我们是用 e.OriginalSource 来判断引发事件的对象的,因为 Canvas 和 Rectangle 都要处理鼠标左键单击事件,在 Rectangle 上单击时同时会触发 Canvas 上的单击事件,因此用 sender 来判断引发事件的对象是不准确的。如果是在 Canvas 中进行的单击,先判断 rect 变量是否为 null,如果 rect 不为 null 说明之前已进行过选择或单击,则在 Canvas 中移除 rect 并将 rect 设为 null,将 isMultipleSelected 设为 false;如果 rect 为 null,则将 isMultipleSelected 设为 true 表示当前操作是在进行选择,接着新建一个 Rectangle 并记下鼠标点击的位置,然后将新建的 rect 添加到 Canvas 中。如果是在 Rectangle 上进行的单击,则表示已经执行完选择操作了要进行移动选择区域的操作,首先记下鼠标点击的位置并附加相应的处理事件,然后通过遍历 Canvas 的子控件来找出在选择区域的所有控件并保存到selectedElements 中。在判断控件是否在选择区域中时用的是 Rect 的 Intersect (判断两个矩形是否相交) 方法进行判断的,开始想到的是判断控件所在的矩形的四个顶点和选择区域的四个顶点的位置关系,网上打了一下很多人也是这么做的,后来想到 WinForm 中的 Rectangle 中有 Intersect 这个方法,Silverlight 中有没有呢,后来发现 Silverlight 中的 Rectangle 没有 Intersect 这个方法而 Rect 有这个方法,这就是代码中为什么要用 selectedRect 这个变量保存选择区域所在的矩形。
在鼠标左键的弹起事件中,首先判断是不是在进行选择,如果是则记下选择区域所在的矩形。
在鼠标移动事件中,如果是在进行选择,根据鼠标当前所在的位置更新 rect 选择区域的大小。
到这里我们已经完成了建立选择区域的操作,运行结果如下图所示:接下来实现移动选择区域时同时对选择的控件进行移动。
实现移动选择区域时现时对选择的控件进行移动的操作是:鼠标在选择区域上按下,接着移动鼠标,在移动鼠标时更新选择区域的位置同时也更新选择的控件的位置。我们需要处理的事件是 Rectangle 的鼠标单击、移动和弹起事件。
private bool isMouseCaptured;
首先新建一个 isMouseCaptured 变量,用来标识鼠标是否选中了矩形选框 rect,在 Canvas 的单击事件中将 isMouseCaptured 设为 false,在 Rectangle 的单击事件中将 isMouseCaptured 设为 true 。
选择区域的单击事件的处理代码在上面的代码中已经有了,移动事件和弹起事件的处理代码如下:
// 鼠标移动事件处理代码 加入Handle_MouseMove中 if (isMouseCaptured) { double deltaV = e.GetPosition(rootCanvas).Y - origPoint.Y; double deltaH = e.GetPosition(rootCanvas).X - origPoint.X; double newTop = deltaV + (double)rect.GetValue(Canvas.TopProperty); double newLeft = deltaH + (double)rect.GetValue(Canvas.LeftProperty); foreach (var item in selectedElements) { double cLeft = (double)item.GetValue(Canvas.LeftProperty); double cTop = (double)item.GetValue(Canvas.TopProperty); item.SetValue(Canvas.LeftProperty, deltaH + cLeft); item.SetValue(Canvas.TopProperty, deltaV + cTop); } rect.SetValue(Canvas.TopProperty, newTop); rect.SetValue(Canvas.LeftProperty, newLeft); origPoint = e.GetPosition(rootCanvas); } // 鼠标左键弹起事件处理代码 加入Handle_MouseLeftButtonUp中 if (isMouseCaptured) { selectedElements.Clear(); rect.ReleaseMouseCapture(); isMouseCaptured = false; rootCanvas.Children.Remove(rect); rect = null; }
在鼠标移动事件中我们根据鼠标当前的位置更新选择区域的位置和选择的控件的位置,在鼠标左键弹起事件中将记录的选择的控件清除并把选择区域 rect 从 Canvas 容器中移除。
到这里我们已经实现了用鼠标现时选择和移动多个控件的操作。示例代码下载:http://zdd.me/myfiles
下面是演示(先用鼠标将上面的图标拖到下面,然后可以用鼠标拖动选择多个移动):