Silverlight 中用鼠標同時選中和移動多個控件
在設計 WinForm 程序時,我們可以很方便的同時選擇窗體上的多個控件來調整控件的位置。在 Silverlight 應用程序中有時我們也想實現同樣的功能,以提供更好的用戶體驗。本文將要介紹的就是在 Silverlight 程序中實現同時選中和移動多個控件。
1、實現鼠標拖動選擇時顯示所選區域
2、移動所選區域時同時移動在該區域內的控件
要實現鼠標拖動選擇時顯示所選區域功能,可以在鼠標拖動時在 Canvas 容器中動態添加一個 Rectangle 來顯示類似在 Windows 資源管理器拖動選擇文件時的選擇框。實現前面所述功能的操作:在 Canvas 容器中按下鼠標左鍵并拖動鼠標來修改選擇區域,選定目標區域后松開鼠標按鍵,這時顯示一個表示所選區域的矩形選框。由實現的操作可知,我們需要在鼠標左鍵按下時在 Canvas 容器中添加一個矩形框,然后在鼠標移動時根據鼠標的位置更新矩形框的大小,最后在鼠標左鍵彈起顯示最終的選擇區域并查找出在選擇區域中的控件。
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 的鼠標單擊、移動和彈起事件。
首先新建一個 isMouseCaptured 變量,用來標識鼠標是否選中了矩形選框 rect,在 Canvas 的單擊事件中將 isMouseCaptured 設為 false,在 Rectangle 的單擊事件中將 isMouseCaptured 設為 true 。
選擇區域的單擊事件的處理代碼在上面的代碼中已經有了,移動事件和彈起事件的處理代碼如下:
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