天天看點

使用擴充方法對代碼的行為進行封裝的例子:封裝UIElement的“拖動”

很多情況下,我們需要對界面上的元素進行拖動,用滑鼠在VS中biaji,biaji,biaji,點幾個事件,然後再寫出一堆代碼,浪費時間不說,由IDE自動生成的那些代碼實在是太難看,影響心情。本文使用擴充方法,對于這類行為需要進行封裝,以使代碼更簡單簡潔。

封裝原則如下:

(1)要簡單,最好是一行代碼就搞定;

(2)要強大,能用于盡量多的類;

(3)要靈活,可适用于盡量多的場景。

在本文的末尾添加了修改版,修改版代碼更簡潔,操作更簡單,且可以設定多個拖動邏輯。

====

設計下面的擴充方法原型:

public static void SetDraggable(this UIElement element, IInputElement relativeTo, Action<DraggableContext> moveCallback = null, Action<DraggableContext> beforeDragCallback = null, Action<DraggableContext> afterDragCallback = null)

element 是拖動的界面元素,relativeTo 是參照物,moveCallback 是移動過程中的回調方法。DraggableContext 是一個類,包裝了調用方所需要的資訊。beforeDragCallback 是拖動前的處理,假設要拖動一個TextBlock,拖動前可以将它改變顔色,以示醒目。afterDragCallback 是拖動結束後的處理。

DraggableContext 的代碼為:

public class DraggableContext  {      public DependencyObject Owner { get; private set; }      public IInputElement RelativeTo { get; private set; }      public Point StartPoint { get; internal set; }      public Point EndPoint { get; internal set; }     public Point Offset      {          get { return new Point { X = EndPoint.X - StartPoint.X, Y = EndPoint.Y - StartPoint.Y }; }      }     private Boolean _dragging;     public Boolean Dragging          get { return _dragging; }          internal set          {              if (value != _dragging)              {                  _dragging = value;                  if (value == true)                  {                      if (BeforeDragCallback != null)                          BeforeDragCallback(this);                  }                  else                      if (AfterDragCallback != null)                          AfterDragCallback(this);              }          }      public Action<DraggableContext> MoveCallback { get; private set; }     public Action<DraggableContext> BeforeDragCallback { get; private set; }     public Action<DraggableContext> AfterDragCallback { get; private set; }     public DraggableContext(DependencyObject owner, IInputElement relativeTo, Action<DraggableContext> moveCallback = null, Action<DraggableContext> beforeDragCallback = null, Action<DraggableContext> afterDragCallback = null)          Owner = owner;          RelativeTo = relativeTo;          MoveCallback = moveCallback;          BeforeDragCallback = beforeDragCallback;          AfterDragCallback = afterDragCallback;      public override int GetHashCode()          return Owner.GetHashCode();      }  }

然後,還需要一個Dictionary,來儲存所有的調用者:

  private static Dictionary<Object, DraggableContext> _dicDragContext = new Dictionary<Object, DraggableContext>();

然後,是對拖動邏輯的實作:

public static void SetDraggable(this UIElement element, IInputElement relativeTo, Action<DraggableContext> moveCallback = null, Action<DraggableContext> beforeDragCallback = null, Action<DraggableContext> afterDragCallback = null)      if (element == null) throw new ArgumentNullException("element");     _dicDragContext[element] = new DraggableContext(element, relativeTo, moveCallback, beforeDragCallback, afterDragCallback);      element.MouseLeftButtonDown += new System.Windows.Input.MouseButtonEventHandler(element_MouseLeftButtonDown);      element.MouseLeftButtonUp += new System.Windows.Input.MouseButtonEventHandler(element_MouseLeftButtonUp);      element.MouseLeave += new System.Windows.Input.MouseEventHandler(element_MouseLeave);      element.MouseMove += new System.Windows.Input.MouseEventHandler(element_MouseMove);  private static void element_MouseLeftButtonUp(object sender, System.Windows.Input.MouseButtonEventArgs e)      _dicDragContext[sender].Dragging = false;  private static void element_MouseLeftButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e)      DraggableContext ctx = _dicDragContext[sender];      ctx.Dragging = true;      ctx.StartPoint = e.GetPosition(ctx.RelativeTo);      ctx.EndPoint = ctx.StartPoint;  private static void element_MouseLeave(object sender, System.Windows.Input.MouseEventArgs e)  private static void element_MouseMove(object sender, System.Windows.Input.MouseEventArgs e)      if (ctx.Dragging == true)          ctx.Dragging = true;          ctx.EndPoint = e.GetPosition(ctx.RelativeTo);          if (ctx.MoveCallback != null)              ctx.MoveCallback(ctx);          ctx.StartPoint = ctx.EndPoint; 

最後,還需要提供一個擴充方法清除對對象和事件的引用,以避免出現記憶體洩漏。這個方法需要顯示調用。

public static void UnsetDraggable(this UIElement element)      element.MouseLeftButtonDown -= new System.Windows.Input.MouseButtonEventHandler(element_MouseLeftButtonDown);      element.MouseLeftButtonUp -= new System.Windows.Input.MouseButtonEventHandler(element_MouseLeftButtonUp);      element.MouseLeave -= new System.Windows.Input.MouseEventHandler(element_MouseLeave);      element.MouseMove -= new System.Windows.Input.MouseEventHandler(element_MouseMove);     if (_dicDragContext.ContainsKey(element)) _dicDragContext.Remove(element); 

完整代碼如下:

代碼

下面,通過兩個案例,看一下這樣的封裝能帶來什麼好處。

案例1:在一個ScrollViewer[scroll]中有1副Image[imgMain],當這個圖像過大時,需要支援拖動,拖動時,滑鼠由箭頭變成手的樣子,拖動完畢再變回來。界面見下圖:

<a href="http://images.cnblogs.com/cnblogs_com/xiaotie/WindowsLiveWriter/UIElement_EF06/image_4.png"></a>

代碼如下:

private void UserControl_Loaded(object sender, RoutedEventArgs e)      this.imgMain.SetDraggable(this, this.Scroll, UseHandCursor, UseArrowCursor);  private void UseHandCursor(DraggableContext ctx)      (ctx.Owner as FrameworkElement).Cursor = Cursors.Hand;  private void UseArrowCursor(DraggableContext ctx)      (ctx.Owner as FrameworkElement).Cursor = Cursors.Arrow;  private void Scroll(DraggableContext ctx)      this.scroll.ScrollToHorizontalOffset(this.scroll.HorizontalOffset - ctx.Offset.X);      this.scroll.ScrollToVerticalOffset(this.scroll.VerticalOffset - ctx.Offset.Y); 

如果使用 Lambda 表達式,則更簡單。

案例2:還是和上面類似場景,在一個Canvas[cvsFont]上有很多文字(TextBlock[tb]),有的文字壓着線了,需要手動拖好。當選中文字時,文字顯示紅色,拖動完畢,變成黑色。

<a href="http://images.cnblogs.com/cnblogs_com/xiaotie/WindowsLiveWriter/UIElement_EF06/image_6.png"></a>

相關代碼為:

tb.SetDraggable(this.cvsFont,                      (DraggableContext ctx) =&gt;                          {                              FontContext pos = tb.DataContext as FontContext;                              pos.X += ctx.Offset.X / ImageScale;                              pos.Y += ctx.Offset.Y / ImageScale;                              tb.SetValue(Canvas.TopProperty, pos.Y * ImageScale);                              tb.SetValue(Canvas.LeftProperty, pos.X * ImageScale);                          },                              tb.Foreground = new SolidColorBrush(Colors.Red);                              tb.Foreground = new SolidColorBrush(Colors.Black);                          }                      );

FontContext 存儲了當縮放比為1時,tb 相對 cvsFont 的位置。這些TextBlock的生命周期比較短,它們排隊領便當時,需要Unset一下:

tb.UnsetDraggable();

可以将常用的場景封裝成方法,比如說,在DraggableContext類中添加下面的回調方法,将元素在Canvas上移動的邏輯抽象出來:

public static void MoveOnCanvas(DraggableContext ctx)      Canvas cvs = ctx.RelativeTo as Canvas;      if (cvs == null) throw new NotSupportedException("RelativeTo 必須是 Canvas");      ctx.Owner.SetValue(Canvas.TopProperty, (double)(ctx.Owner.GetValue(Canvas.TopProperty)) + ctx.Offset.X);      ctx.Owner.SetValue(Canvas.LeftProperty, (double)(ctx.Owner.GetValue(Canvas.LeftProperty)) + ctx.Offset.Y); 

如果一個元素放置在Canvas上,且隻需要支援拖動,不需要其它的邏輯,則一句話就搞定了:

xxx.SetDraggable(cvsXXX,DraggableContext.MoveOnCanvas);

2010年11月13日對代碼進行修改,修改後的代碼更簡潔,使用更簡單,不用手動Unset,且可以挂接多個邏輯。

 本文轉自xiaotie部落格園部落格,原文連結http://www.cnblogs.com/xiaotie/archive/2010/11/10/1874054.html如需轉載請自行聯系原作者

xiaotie 集異璧實驗室(GEBLAB)

繼續閱讀