Silverlight中實(shí)現(xiàn)強(qiáng)壯的、可復(fù)用的拖放行為
原創(chuàng)【51CTO獨(dú)家特稿】簡(jiǎn)單的Silverlight拖放行為可以通過(guò)某些Silverlight控制實(shí)現(xiàn),但Silverlight的總 體表現(xiàn)卻沒(méi)有WPF中對(duì)行為控制得那么好,不過(guò)可以使用附加的行為操作元素的RenderTransform,這樣可以 大大增加Silverlight中拖放實(shí)現(xiàn)的強(qiáng)壯性和可復(fù)用性。
新型實(shí)現(xiàn)
默認(rèn)情況下Silverlight通過(guò)System.Windows.Controls.Primitives中的Thumb控制讓拖放支持變得更容易 ,實(shí)現(xiàn)拖放最簡(jiǎn)單的方法是使用Thumb控制和為應(yīng)用程序外觀應(yīng)用合適的ControlTemplate(控制模板)。但 在某些情況下,使用Thumb不太實(shí)際,在XAML中要“模板”化一個(gè)Thumb控制是很笨重的,你必須在你使用的 每個(gè)單獨(dú)的區(qū)域中為應(yīng)用程序邏輯重復(fù)一次這個(gè)行為,隨著應(yīng)用程序的增長(zhǎng),這種需求將會(huì)使應(yīng)用程序的維 護(hù)變得異常困難。
不過(guò)有兩個(gè)新方法讓你可以在任何UI元素上開(kāi)啟拖放操作,下面是***個(gè)方法:
  | 
這個(gè)實(shí)現(xiàn)最重要的就是具體開(kāi)啟拖放操作的事件:MouseMove、MouseLeftButtonDown和 MouseLeftButtonUp,通過(guò)在這些事件上增加處理程序,你就可以執(zhí)行一些簡(jiǎn)單的拖放操作了。
第二個(gè)方法是:
private bool isDragging = false;private void DragStart(object sender, MouseButtonEventArgs args)
{
Shape draggable = sender as Shape;
if (draggable != null)
{
isDragging = true;
draggable.CaptureMouse();
}
}
private void DragDelta(object sender, MouseEventArgs args)
{
Shape draggable = sender as Shape;
if (draggable != null && isDragging)
{
Point currentPosition = args.GetPosition(null);
TranslateTransform transform = draggable.RenderTransform
as TranslateTransform;
if (transform == null)
{
transform = new TranslateTransform();
draggable.RenderTransform = transform;
}
transform.X = (currentPosition.X - draggable.Width / 2);
transform.Y = (currentPosition.Y - draggable.Height / 2);
}
}
private void DragComplete(object sender, MouseButtonEventArgs args)
{
Shape draggable = sender as Shape;
if (draggable != null)
{
isDragging = false;
draggable.ReleaseMouseCapture();
}
}
正如你所看到的,通過(guò)影響控制RenderTransform而不是特定面板布局參數(shù),如Canvas.Top和Canvas.Left 等附加屬性,或是Grid邊緣空白,你可以將一個(gè)元素移動(dòng)到任何一個(gè)容器中,而且這種方法對(duì)任何一個(gè)UI元 素都是可復(fù)用的,它不允許你橫跨不同的控制重新使用應(yīng)用程序邏輯,為此,你需要一個(gè)具有附加行為的實(shí) 現(xiàn)。
使用附加行為
WPF和.NET 3.0引入了依賴(lài)屬性概念,也就是說(shuō)當(dāng)屬性發(fā)生變化后,它會(huì)通知所有者(它必須是一個(gè) DependencyObject[依賴(lài)對(duì)象]),允許所有者執(zhí)行一塊應(yīng)用程序邏輯。
有兩種類(lèi)型的依賴(lài)屬性,最常用的是依賴(lài)屬性自身,建立和使用DependencyProperty.Register進(jìn)行設(shè)置 都是相同的依賴(lài)對(duì)象。第二種類(lèi)型是附加屬性,使用DependencyProperty.RegisterAttached進(jìn)行設(shè)置,附加 屬性被設(shè)置為它所有者不同的類(lèi)型。
看一下下么的附加屬性例子:
public static readonly DependencyProperty HoverProperty =  | 
通過(guò)事件處理程序OnHoverChanged,當(dāng)你在XAML中設(shè)置那個(gè)屬性時(shí),你可以提供一些應(yīng)用程序邏輯:
private static void OnHoverChanged(DependencyObject obj,DependencyPropertyChangedEventArgs args)
{
Border border = obj as Border;
if (border != null)
{
if(args.OldValue == null && args.NewValue != null)
{
border.MouseEnter += SetHoverBackground;
border.MouseLeave += SetNotHoverBackground;
}
else if(args.OldValue != null && args.NewValue == null)
{
border.MouseEnter -= SetHoverBackground;
border.MouseLeave -= SetNotHoverBackground;
}
}
}
OnHoverChanged代碼僅僅簡(jiǎn)單地增加了事件處理程序,在觸發(fā)MouseEnter事件時(shí)修改對(duì)象的背景顏色,觸 發(fā)MouseLeave事件時(shí)還原到對(duì)象的原始顏色,這段簡(jiǎn)短的代碼功能叫做附加行為,附加行為是后面章節(jié)講述 的拖放實(shí)現(xiàn)的核心概念,下么來(lái)看一個(gè)附加行為IsEnabled:
public static readonly DependencyProperty IsEnabledProperty =  | 
通過(guò)注冊(cè)O(shè)nIsEnabledChanged作為改變事件處理程序的屬性,你可以訂閱你前面使用的事件處理程序:
private static void OnIsEnabledChanged(DependencyObject obj,DependencyPropertyChangedEventArgs args)
{
UIElement dragSource = obj as UIElement;
bool wasEnabled = (args.OldValue != null) ? (bool)args.OldValue : false;
bool isEnabled = (args.NewValue != null) ? (bool)args.NewValue : false;
//如果行為被禁用,移除這個(gè)事件處理程序
if (wasEnabled && !isEnabled)
{
dragSource.MouseLeftButtonDown -= DragStart;
}
//如果行為被附加,添加這個(gè)事件處理程序
if (!wasEnabled && isEnabled)
{
dragSource.MouseLeftButtonDown += DragStart;
}
}
改變事件處理程序的屬性IsEnabled使用前面談到的新型實(shí)現(xiàn)向事件添加處理程序,因此,只需要在你的 應(yīng)用程序中任何或是有UI元素上重復(fù)使用這個(gè)行為,但這種新型實(shí)現(xiàn)有其自身的設(shè)置限制,還不能立即顯現(xiàn) 出來(lái)。
調(diào)用args.GetPosition(null)跟蹤絕對(duì)位置不能適應(yīng)一個(gè)允許嵌套拖放的行為,更好的解決辦法是反復(fù)計(jì) 算拖動(dòng)的變量,為了解決這個(gè)限制,你可以轉(zhuǎn)向私有附加屬性,在思考私有附加行為的范圍時(shí),私有附加屬 性和私有成員變量或?qū)傩灶?lèi)似,公共附加行為和公共附加屬性類(lèi)似。因?yàn)槟阒恍枰櫮闼x元素的x和y坐 標(biāo)值,因此需要兩個(gè)私有附加屬性:
public static readonly DependencyProperty XProperty =DependencyProperty.RegisterAttached(
"X",
typeof(double),
typeof(DragDropBehavior),
new PropertyMetadata(double.NaN));
public static readonly DependencyProperty YProperty =
DependencyProperty.RegisterAttached(
"Y",
typeof(double),
typeof(DragDropBehavior),
new PropertyMetadata(double.NaN));
這些屬性的默認(rèn)值都是double.NAN,表明對(duì)象當(dāng)前沒(méi)有拖放位置,這對(duì)于后面拖放元素時(shí)重置元素是很重 要的,此外這些私有附加屬性,你還需要知道哪些容器是用于參照的,也就是說(shuō)你需要指定原始的坐標(biāo)值, (X,Y) = (0,0),你只需要簡(jiǎn)單地位你的宿主指定另一個(gè)附加屬性:
public static readonly DependencyProperty IsHostProperty =  | 
現(xiàn)在你已經(jīng)為你反復(fù)跟蹤拖放行為建立起了必要的數(shù)據(jù),你需要做的全部?jī)?nèi)容是實(shí)現(xiàn)前面定義的相同的三 個(gè)處理程序:DragStart,DragDelta,DragComplete。在DragStart中,你可以增加更多的功能,在你的行為 類(lèi)中改成使用靜態(tài)函數(shù):
private static void DragStart(object sender, MouseButtonEventArgs args){
UIElement dragSource = sender as UIElement;
//創(chuàng)建執(zhí)行拖動(dòng)操作的TranslateTransform
TranslateTransform dragTransform = new TranslateTransform();
dragTransform.X = 0;
dragTransform.Y = 0;
//如果是首次使用拖放,先要設(shè)置TranslateTransform
dragSource.RenderTransform = (
dragSource.RenderTransform is TranslateTransform) ?
dragSource.RenderTransform : dragTransform;
//分別為MouseMove和MouseLeftButtonUp添加事件處理程序
dragSource.MouseMove += OnDragDelta;
dragSource.MouseLeftButtonUp += OnDragComplete;
//捕獲鼠標(biāo)
dragSource.CaptureMouse();
}
這和你最初的實(shí)現(xiàn)相差不多,你獲得了一個(gè)拖動(dòng)項(xiàng)目,設(shè)置它的RenderTransform,附加適當(dāng)?shù)氖录幚?程序,并捕獲鼠標(biāo)。但DragDelta實(shí)現(xiàn)完全不同,因?yàn)槟悻F(xiàn)在跟蹤的是鼠標(biāo)動(dòng)作了,你需要:
1、找到你的宿主容器;
2、相對(duì)這個(gè)容器計(jì)算當(dāng)前位置;
3、比較前一次記錄的位置和現(xiàn)在的位置,計(jì)算出拖動(dòng)變量;
4、更新RenderTransform和你的私有附加屬性,x和y。
private static void DragDelta(object sender, MouseEventArgs args){
FrameworkElement dragSource = sender as FrameworkElement;
//計(jì)算dragSource的偏移量,更新TranslateTransform
FrameworkElement dragDropHost = FindDragDropHost(dragSource);
Point relativeLocationInHost = args.GetPosition(dragDropHost);
Point relativeLocationInSource = args.GetPosition(dragSource);
//獲得當(dāng)前位置
double xPosition = GetX(dragSource);
double yPosition = GetY(dragSource);
//從前一次位置計(jì)算變數(shù)
double xChange = relativeLocationInHost.X - xPosition;
double yChange = relativeLocationInHost.Y - yPosition;
//如果這不是首次鼠標(biāo)移動(dòng),更新位置
if (!double.IsNaN(xPosition))
{
((TranslateTransform)dragSource.RenderTransform).X += xChange;
}
if (!double.IsNaN(yPosition))
{
((TranslateTransform)dragSource.RenderTransform).Y += yChange;
}
//更新你的私有附加屬性跟蹤拖動(dòng)位置
SetX(dragSource, relativeLocationInHost.X);
SetY(dragSource, relativeLocationInHost.Y);
}
需要特別指出的是找出參照宿主非常重要,不過(guò)說(shuō)來(lái)也很簡(jiǎn)單,只需要在可視化樹(shù)結(jié)構(gòu)中搜索IsHost = true即可。
DragComplete新的實(shí)現(xiàn)
現(xiàn)在,DragStart和DragDelta中的部件算是全部弄好了,現(xiàn)在只剩下DragComplete了,和DragStart一樣 ,DragComplete實(shí)現(xiàn)和你的新型實(shí)現(xiàn)不是完全一樣,值得注意的是它移除了事件處理程序,并釋放了鼠標(biāo)捕 獲,你也必須重置你的私有附加屬性x和y,指出拖動(dòng)項(xiàng)目不再進(jìn)行拖動(dòng)了。
private static void DragComplete(object sender,MouseButtonEventArgs args)
{
UIElement dragSource = sender as UIElement;
dragSource.MouseMove -= DragDelta;
dragSource.MouseLeftButtonUp -= DragComplete;
//設(shè)置x和y的值,以便下一次MouseDown時(shí)好重置
SetX(dragSource, double.NaN);
SetY(dragSource, double.NaN);
//釋放鼠標(biāo)捕獲
dragSource.ReleaseMouseCapture();
}
這個(gè)新的迭代實(shí)現(xiàn)克服了前面描述的限制,它也允許流暢跟蹤鼠標(biāo),在新的實(shí)現(xiàn)中,你只需要把鼠標(biāo)放在 拖動(dòng)項(xiàng)目的中心,在前面的實(shí)現(xiàn)中,鼠標(biāo)需要保持放在容器里面。
public static FrameworkElement FindDragDropHost(
UIElement element)
{
DependencyObject parent = VisualTreeHelper.GetParent(element);
while (parent != null && !GetIsHost(parent))
{
parent = VisualTreeHelper.GetParent(parent);
}
return parent as FrameworkElement;
}
限制和擴(kuò)展
值得注意的是本文講述的拖放行為也有一些限制,這將成為未來(lái)的改進(jìn)方向。首先,即使這種方法能夠在 一個(gè)容器中實(shí)現(xiàn)簡(jiǎn)單的拖放,但在拖放的不同階段不能接受更強(qiáng)壯的應(yīng)用程序邏輯,但在WPF中卻可以實(shí)現(xiàn), 如DragEnter、DragLeave和Drop在Silverlight中就還不能實(shí)現(xiàn),但你可以通過(guò)自定義附加事件實(shí)現(xiàn)來(lái)增加這 些功能。
在設(shè)置IsEnabled后,行為也不能執(zhí)行自定義拖放邏輯,如果在拖放實(shí)現(xiàn)中使用了Canvas.Top和 Canvas.Left,意味著你需要使用獨(dú)立的附加行為,而不是使用自定義事件。這是因?yàn)镾ilverlight缺乏附加 事件引起的,在Silverlight 3.0這將可能會(huì)有這些特性,但你可以通過(guò)復(fù)雜的行為實(shí)現(xiàn)來(lái)克服這些限制,這 個(gè)內(nèi)容已經(jīng)超出了本文的范疇,這里概述的方法傾向?yàn)橐粋€(gè)起點(diǎn),更好的實(shí)現(xiàn)希望通過(guò)開(kāi)源Silverlight庫(kù) Quasar來(lái)實(shí)現(xiàn)。
您正在閱讀的是51CTO獨(dú)家特稿《Silverlight中實(shí)現(xiàn)強(qiáng)壯的、可復(fù)用的拖放行為》
【編輯推薦】















 
 
 




 
 
 
 