朋友們,注意了!從本文開始,將使用 Visual Studio 2010 , Blend 4 作為主要開發工具。
本文包括:
- Converter 綁定轉換及修改模板完善 TreeViewItem 在 MouseOver 時呈現外觀
- Behavior 附加拖拽行為
- Adorner 裝飾器裝飾拖拽效果
完善 TreeView 外觀
- 建立一 WPF 應用程式。
(注意本節開始用 VS2010 和 Blend 4 開發,使用 VS 08 或 Blend 3 將無法正常開啟工程項目。實在抱歉!但不影響查看代碼學習!)
- 建立一使用者控制項,參見上節用 Blend 建立基本 TreeView 外觀。
(整個 Win7 學習版不小,接下來的章節也將會建立繼承 UserControl 或 Control 的使用者控制項,以方便管理與複用)
- TreeViewItem 的 ControlTemplate 實質布局如,一個兩行三列的 Grid:
- 預設布局致使子節點(即 ItemHost)總會比父節點向右位移第一列的寬度(值:19)。
為達到位移效果並且使得 ItemHost 能橫跨所有列(這樣 Border 就可以填充整行了),現在修改如,兩行一列的 Grid,StackPanel 左邊被 Margin 填充(值:19):
- XAML 中 StackPanel 的 Margin 綁定如下(綁定對象是 TreeViewItem):
<StackPanel x:Name="stackPanel" Orientation="Horizontal" Margin="{Binding RelativeSource=
{RelativeSource FindAncestor, AncestorType=TreeViewItem, AncestorLevel=1}, Converter={StaticResource ConverterLoginMarginLeft}}">
- ConverterLoginMarginLeft類定義如下(利用 VisualTreeHelper):
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture){ double columnWidth = 19.0; double left = 0.0; UIElement element = value as TreeViewItem; while (element.GetType() != typeof(TreeView)) { element = (UIElement)VisualTreeHelper.GetParent(element); if (element.GetType() == typeof(TreeViewItem)) left += columnWidth; } return new Thickness(left,0,0,0);}
- 最初做法是綁定到到父節點的 StackPanel,然後擷取其 Margin 並在原基礎上加一值(如 19),但受 WPF 布局系統的布局順序影響(如果用 Mode=OneWaytoSource)以及 Converter 在 OneWay 模式下目標資料的更新並沒有導致資料來源更改,以致尋找上級 StackPanel 時 Margin 值沒增加(如果 Model=Default),所以Border 都沒能成功填充整行。
附加行為,裝飾器
- 為實現附加行為,首先添加引用:System.Windows.Interactivity.dll (還需要引用相應命名空間,建議先敲上代碼,再按 Shitf+Alt+F10 顯示提示添加命名空間)
- 建立一類並繼承 Behavior<T> 介面。
注意這裡是直接把行為應用在 ItemsControl 控制項(TreeView)而不是 TreeViewItem 上。 因為像後面將要介紹的主顯示控制項將會有多種顯示視圖,如果每個視圖都增加拖拽行為不靈活;而且如果在拖拽的對象上附加行為,當按 Ctrl 多選時將較難類比 Win7 裝飾拖拽的多個對象。
class ItemsControlDragDropBehavior:Behavior<ItemsControl>
- 首先方法 OnAttached,OnDetaching,並增加相應委託事件:
protected override void OnAttached(){ base.OnAttached(); this.AssociatedObject.PreviewMouseLeftButtonDown += new MouseButtonEventHandler(AssociatedObject_PreviewMouseLeftButtonDown); ......}protected override void OnDetaching(){ //同理......;}
- 滑鼠按下時:_isDown = true;
滑鼠移動時需要判斷:(關鍵邏輯) 注意:一調用_adornerlayer.CaptureMouse(); 將會再次執行AssociatedObject_PreviewMouseMove(object sender, MouseEventArgs e)事件。
void AssociatedObject_PreviewMouseMove(object sender, MouseEventArgs e){ if (_isDown == false) return; if (_isDragging == true) { DragMoved(e); } else if (OverMiniDistance(e)) { DragStarted(e); }}
private void DragStarted(MouseEventArgs e){ _isDragging = true; _adornerlayer.CaptureMouse(); _startPoint = e.GetPosition(_adornerlayer); _adornerlayer = this.AssociatedObject as ItemsControl; Object data = Utilities.GetDataContext(_adornerlayer, _startPoint); _adorner = new TreeViewItemAdorner(_adornerlayer, data); AdornerLayer.GetAdornerLayer(_adornerlayer).Add(_adorner);}
- 上面的 GetDataContext 方法(這樣就避免了要尋找特定的控制項類型,靜態方法定義在 Utilities.cs 工具類裡):
public static object GetDataContext(ItemsControl itemsControl, Point p){ FrameworkElement element = itemsControl.InputHitTest(p) as FrameworkElement; var data = element.DataContext; return data;}
- 最後處理放開滑鼠。
- 這裡的滑鼠移動只有(實際是通過改變 TreeViewItemAdorner 裝飾器的屬性來實現移動):
private void DragMoved(MouseEventArgs e){ Point CurrentPosition = e.GetPosition(_adornerlayer); _adorner.LeftOffset = CurrentPosition.X - _startPoint.X; _adorner.TopOffset = CurrentPosition.Y - _startPoint.Y;}
- 建立一個繼承 Adorner 的類,並且要調用基類的建構函式,這樣才能知道在哪個對象上應用裝飾:
class TreeViewItemAdorner : Adornerpublic TreeViewItemAdorner(UIElement adornedElement, object data) : base(adornedElement)
- 如果想擷取被裝飾元素的副本,可以在建構函式中:
VisualBrush _brush = new VisualBrush(adornedElement); 例如可以在 Rectangle 中填充 _brush。
當然了,這個樣本裡我傳入的 UIElement adornerElement 參數類型為 ItemsControl(即整個 TreeView 控制項),如果想擷取選中項的 TreeViewItem 可以用 ItemContainerGenerator 或者 VisualTreeHelper 遞迴尋找。
如果只是在被裝飾元素中加些裝飾物,則一般重載 OnRender 方法並在方法:
protected override void OnRender(DrawingContext drawingContext){......};
如:
drawingContext.DrawImage(bi, adornedElementRect);
可以加動畫,透明度等,具體 API 可以查詢 MSDN 的 Adorner 或下載本範例程式碼。
- 最後編譯器(Ctrl+Shift+B),可以在 Blend 中的 Assets 面板中拖拽 ItemsControlDragDropBehavior 行為(在 Behaviors 中)。
注意,這個行為稍加修改還可以複用到後面將要介紹的主顯示控制項中。 所以 class ItemsControlDragDropBehavior : Behavior<ItemsControl> 中的 T 類型是 ItemsControl。
代碼中也盡量免去具體的類型(如 TreeView)。
- 為了避免不必要的判斷邏輯,注意 ItemsControlDragDropBehavior 行為只處理 Item 的拖拽,所以可以把紅矩形的去掉,或直接 ''Create Empty'' 建立一個只有 ItemsPresenter 的 TreeView 的 ControlTemplate,或設定 ScrollViewer 控制項 HorizontalScrollBarVisibility="Disabled",主要目的是使 TreeView 控制項出現水平捲軸,防止拖拽捲軸時出錯,而且如果分類樹有多個 TreeView 時出現多個捲軸也不合適。還有,有時因為ScollViewer 可能會佔無限空間,致使拖拽不能移出 ScollViewer範圍。
當然也可以添加邏輯,判斷拖拽的目標類型。 簡單的實現方法是讓水平與豎直方向的捲軸放在整個分類樹的外面。
- 最後把建立的使用者控制項拖拽到 MainWindow 上,並加個 GridSpitter。
- 附圖:
小結
- 通過修改基本控制項的模板樣式,可以定製外觀,注意利用 Blend 的可視化介面學習預設控制項的布局與模板樣式。
- VisualTreeHelper 與 ItemContainerGenerator 有幾個常用的靜態方法方法尋找控制項。
例如可以尋找樣式模板中的元素控制項。
- 附加行為松耦合并靈活複用。
與附加屬性不同,可以用附加行為處理行為;當涉及綁定時可以考慮用附加屬性。
- 整個應用程式都常用到的方法可以建個工具類,方法為靜態方法。如擷取滑鼠座標等。
TreeView 的 Drop 等更多內容將在下節介紹。如有建議或意見歡迎留言。
代碼下載
Technorati 標籤: WPF,TreeView,Adorner,Behavior