程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> 關於.NET >> 從WPF Diagram Designer Part 1學習控件模板、移動、改變大小和旋轉

從WPF Diagram Designer Part 1學習控件模板、移動、改變大小和旋轉

編輯:關於.NET

由於上周主要做了項目組產品架構、給公司新員工培訓以及其他會議等事情,在OpenExpressApp對建模支持的初步計劃中我列了一些建模任務還沒有開展,其中參考部分在以前的blog中都已經介紹了(MetaModelEngine:元模型引擎開發思路、DSM:使用MetaEdit+編寫Family Tree Modeling Language、讀書筆記:Visual Studio DSL工具特定領域開發指南)。今天手頭上沒有其他重要事情了,可以開始進行學習WPF的圖形設計器了,這也就是我在WPF - 圖形設計器(Diagram Designer)中介紹的一個有源碼的設計器,以前看過,覺得它已經實現了圖形設計器的一些基本功能,只要先學會它就應該可以編寫出自己的一個簡易設計器。這個系列分為四部分,每部分都是在原有基礎上擴展一些設計器功能,我也將分為四篇blog把從中學到的內容整理一下,對WPF和設計器感興趣的可以看看。

WPF Diagram Designer: Part 1

這篇文章介紹了通過WPF的控件模板以及Thumb來實現圖形設計器的移動Drag、改變大小resize和旋轉rotate這三個幾本功能,示例代碼 界面如下:

控件模板

以往我們在使用Window下的控件時,都是通過控件本身提供的很多屬性來更改外觀,而在WPF下,你會發現控件並沒有提供太多的定制 屬性,這是因為WPF把外觀和內容隔離開來,通過控件模板的概念讓我們可以更方便、更有想象力的來定制我們需要的界面。模板可以允許 我們用任何東西來完全替代一個元素的可視樹,但控件本身的其他功能並不受影響。WPF中的每個Control的默認外觀都是在模板中定義的 ,大家可以通過我以前說的這個工具來查看WPF - 模板查看工具:Show Me The Template及如何查看第三方主題

控件模板由ControlTemplate類來表示,它派生自FrameworkTemplate抽象類,它的重要部分是它的VisualTree內容屬性,它包含了定義 想要的外觀的可視樹。通過以下方式可以定義一個控件模板類:

<Canvas>
   <Canvas.Resources>
    <ControlTemplate x:Key="DesignerItemTemplate" TargetType="ContentControl">
      <ContentPresenter Content="{TemplateBinding ContentControl.Content}"/>
    </ControlTemplate>
   </Canvas.Resources>
   <ContentControl Name="DesignerItem"
           Width="100"
           Height="100"
           Canvas.Top="100"
           Canvas.Left="100"
           Template="{StaticResource DesignerItemTemplate}">
    <Ellipse Fill="Blue"/>
   </ContentControl>
</Canvas>

限制目標類型

ControlTemplate和Style一樣,也有一個TargetType屬性來限制模板可以被應用到的類型上,如果沒有一個顯示的TargetType,則目標 類型將被隱式的設置為Control。由於沒有默認的控件模板,所以它與 Style是不同的,當使用TargetType時不允許移除模板的x:Key。

模板綁定TemplateBinding

在控件模板中,從目標元素插入屬性值的關鍵是數據綁定,我們可以通過一個簡單、輕量級的模板綁定TemplateBinding來處理。 TemplateBinding的數據源總是目標元素,而Path則是目標元素的任何一個依賴屬性。使用方式如上例的{TemplateBinding ContentControl.Content},如果我們設置了TargetType,可以更簡單的使用為{TemplateBinding Content}

TemplateBinding僅僅是一個便捷的設置模板綁定的機制,對於有些可凍結的屬性(如Brush的Color屬性)時綁定會失敗,這時候我們 可以使用常規的Binding來達到同樣效果,通過使用一個RelativeSource,其值為{Relative Source TemplatedParent}以及一個Path。

ContentPresenter

在控件模板中應該使用輕量級的內容顯示元素 ContentPresenter,而不是ContentControl。ContentPresenter顯示的內容和 ContentControl是一樣的,但是ContentControl是一個帶有控件模板的成熟控件,其內部包含了ContentPresenter。

如果我們在使用ContentPresenter時忘記了將它的Content設置為{TemplateBinding Content}時,它將隱式的假設{TemplateBinding Content}就是我們需要的內容

與觸發器交互

在模板內部可以使用觸發器,但是在進行綁定時需要注意只能使用Binding,因為觸發器位於控件可視樹模板外部

Thumb

在WPF中有一個Thumb的控件,在MSDN文檔中是這麼寫的: " ...represents a control that lets the user drag and resize controls." 從字面上來看這個是一個用來處理拖放和設置大小的控件,正好應該在圖形設計器中來處理移動和改變大小等動作。在以下介 紹的Move、Resize和 Rotate這三個功能都是使用Thumb來做的。

移動(Move)

MoveThumb 是從Thumb繼承下來,我們實現了DragDelta事件來處理移動操作,

代碼

public class MoveThumb : Thumb
   {
     public MoveThumb()
     {
       DragDelta += new DragDeltaEventHandler(this.MoveThumb_DragDelta);
     }
     private void MoveThumb_DragDelta(object sender, DragDeltaEventArgs e)
     {
       ContentControl designerItem = DataContext as ContentControl;
       if (designerItem != null)
       {
         Point dragDelta = new Point(e.HorizontalChange, e.VerticalChange);
         RotateTransform rotateTransform = designerItem.RenderTransform as RotateTransform;
         if (rotateTransform != null)
         {
           dragDelta = rotateTransform.Transform(dragDelta);
         }
         Canvas.SetLeft(designerItem, Canvas.GetLeft(designerItem) + dragDelta.X);
         Canvas.SetTop(designerItem, Canvas.GetTop(designerItem) + dragDelta.Y);
       }
     }
   }

實現代碼中假定DataContext為我們需要操作的圖形控件,這個可以在控件模板中看到:

<ControlTemplate x:Key="DesignerItemControlTemplate" TargetType="ContentControl">
  <Grid>
   <s:DragThumb DataContext="{Binding RelativeSource={RelativeSource TemplatedParent}}" Cursor="SizeAll"/>
   <ContentPresenter Content="{TemplateBinding ContentControl.Content}"/>
  </Grid>
</ControlTemplate>

RelativeSource

PreviousData 列表的前一個數據項

TemplatedParent 應用模板的元素

Self  元素自身

FindAncestor 通過父元素鏈去找

命中測試 IsHitTestVisible

如果我們現在拖動一個圓形,那麼界面如下:

我們現在拖動時會發現,只能在灰色部分才允許拖動,在圓形區域由於捕獲的不是MoveThumb而不能拖動。這時候我們只需要簡單的設 置IsHitTest為false即可

<Ellipse Fill="Blue" IsHitTestVisible="False"/>

Resize

更改大小仍舊使用的是Thumb,我們建立了一個控件模板:

<ControlTemplate x:Key="ResizeDecoratorTemplate" TargetType="Control">
  <Grid>
   <Thumb Height="3" Cursor="SizeNS" Margin="0 -4 0 0"
       VerticalAlignment="Top" HorizontalAlignment="Stretch"/>
   <Thumb Width="3" Cursor="SizeWE" Margin="-4 0 0 0"
       VerticalAlignment="Stretch" HorizontalAlignment="Left"/>
   <Thumb Width="3" Cursor="SizeWE" Margin="0 0 -4 0"
       VerticalAlignment="Stretch" HorizontalAlignment="Right"/>
   <Thumb Height="3" Cursor="SizeNS" Margin="0 0 0 -4"
       VerticalAlignment="Bottom" HorizontalAlignment="Stretch"/>
   <Thumb Width="7" Height="7" Cursor="SizeNWSE" Margin="-6 -6 0 0"
       VerticalAlignment="Top" HorizontalAlignment="Left"/>
   <Thumb Width="7" Height="7" Cursor="SizeNESW" Margin="0 -6 -6 0"
       VerticalAlignment="Top" HorizontalAlignment="Right"/>
   <Thumb Width="7" Height="7" Cursor="SizeNESW" Margin="-6 0 0 -6"
       VerticalAlignment="Bottom" HorizontalAlignment="Left"/>
   <Thumb Width="7" Height="7" Cursor="SizeNWSE" Margin="0 0 -6 -6"
       VerticalAlignment="Bottom" HorizontalAlignment="Right"/>
   </Grid>
  </ControlTemplate>

設置了這個樣式的Control界面如下圖所示:

對於改變大小,我們只要按照MoveThumb一樣,從Thumb繼承一個ResizeThumb來處理改變大小的動作,對於控件模板,我們只要把上面 的Thumb替換成ResizeThumb即可

代碼

public class ResizeThumb : Thumb
{
   public ResizeThumb()
   {
     DragDelta += new DragDeltaEventHandler(this.ResizeThumb_DragDelta);
   }
   private void ResizeThumb_DragDelta(object sender, DragDeltaEventArgs e)
   {
     Control item = this.DataContext as Control;
     if (item != null)
     {
       double deltaVertical, deltaHorizontal;
       switch (VerticalAlignment)
       {
         case VerticalAlignment.Bottom:
           deltaVertical = Math.Min(-e.VerticalChange,
             item.ActualHeight - item.MinHeight);
           item.Height -= deltaVertical;
           break;
         case VerticalAlignment.Top:
           deltaVertical = Math.Min(e.VerticalChange,
             item.ActualHeight - item.MinHeight);
           Canvas.SetTop(item, Canvas.GetTop(item) + deltaVertical);
           item.Height -= deltaVertical;
           break;
         default:
           break;
       }
       switch (HorizontalAlignment)
       {
         case HorizontalAlignment.Left:
           deltaHorizontal = Math.Min(e.HorizontalChange,
             item.ActualWidth - item.MinWidth);
           Canvas.SetLeft(item, Canvas.GetLeft(item) + deltaHorizontal);
           item.Width -= deltaHorizontal;
           break;
         case HorizontalAlignment.Right:
           deltaHorizontal = Math.Min(-e.HorizontalChange,
             item.ActualWidth - item.MinWidth);
           item.Width -= deltaHorizontal;
           break;
         default:
           break;
       }
     }
     e.Handled = true;
   }
}

加入到DesignerItemTemplate控件模板

<ControlTemplate x:Key="DesignerItemTemplate" TargetType="ContentControl">
  <Grid DataContext="{Binding RelativeSource={RelativeSource TemplatedParent}}">
   <s:MoveThumb Template="{StaticResource MoveThumbTemplate}" Cursor="SizeAll"/>
   <Control Template="{StaticResource ResizeDecoratorTemplate}"/>
   <ContentPresenter Content="{TemplateBinding ContentControl.Content}"/>
  </Grid>
</ControlTemplate>

Rotate

我們實現旋轉功能,仍舊是通過從Thumb繼承下來一個RotateThumb,具體實現代碼如下:

代碼

public class RotateThumb : Thumb
   {
     private double initialAngle;
     private RotateTransform rotateTransform;
     private Vector startVector;
     private Point centerPoint;
     private ContentControl designerItem;
     private Canvas canvas;
     public RotateThumb()
     {
       DragDelta += new DragDeltaEventHandler(this.RotateThumb_DragDelta);
       DragStarted += new DragStartedEventHandler(this.RotateThumb_DragStarted);
     }
     private void RotateThumb_DragStarted(object sender, DragStartedEventArgs e)
     {
       this.designerItem = DataContext as ContentControl;
       if (this.designerItem != null)
       {
         this.canvas = VisualTreeHelper.GetParent(this.designerItem) as Canvas;
         if (this.canvas != null)
         {
           this.centerPoint = this.designerItem.TranslatePoint(
             new Point(this.designerItem.Width * this.designerItem.RenderTransformOrigin.X,
                  this.designerItem.Height * this.designerItem.RenderTransformOrigin.Y),
                  this.canvas);
           Point startPoint = Mouse.GetPosition(this.canvas);
           this.startVector = Point.Subtract(startPoint, this.centerPoint);
           this.rotateTransform = this.designerItem.RenderTransform as RotateTransform;
           if (this.rotateTransform == null)
           {
             this.designerItem.RenderTransform = new RotateTransform(0);
             this.initialAngle = 0;
           }
           else
           {
             this.initialAngle = this.rotateTransform.Angle;
           }
         }
       }
     }
     private void RotateThumb_DragDelta(object sender, DragDeltaEventArgs e)
     {
       if (this.designerItem != null && this.canvas != null)
       {
         Point currentPoint = Mouse.GetPosition(this.canvas);
         Vector deltaVector = Point.Subtract(currentPoint, this.centerPoint);
         double angle = Vector.AngleBetween(this.startVector, deltaVector);
         RotateTransform rotateTransform = this.designerItem.RenderTransform as RotateTransform;
         rotateTransform.Angle = this.initialAngle + Math.Round(angle, 0);
         this.designerItem.InvalidateMeasure();
       }
     }

樣式如下:

代碼

<!-- RotateThumb Style -->
  <Style TargetType="{x:Type s:RotateThumb}">
   <Setter Property="RenderTransformOrigin" Value="0.5,0.5"/>
   <Setter Property="Cursor" Value="Hand"/>
   <Setter Property="Control.Template">
    <Setter.Value>
     <ControlTemplate TargetType="{x:Type s:RotateThumb}">
      <Grid Width="30" Height="30">
       <Path Fill="#AAD0D0DD"
          Stretch="Fill"
          Data="M 50,100 A 50,50 0 1 1 100,50 H 50 V 100"/>
      </Grid>
     </ControlTemplate>
    </Setter.Value>
   </Setter>
  </Style>
  <!-- RotateDecorator Template -->
  <ControlTemplate x:Key="RotateDecoratorTemplate" TargetType="{x:Type Control}">
   <Grid>
    <s:RotateThumb Margin="-18,-18,0,0" VerticalAlignment="Top" HorizontalAlignment="Left"/>
    <s:RotateThumb Margin="0,-18,-18,0" VerticalAlignment="Top" HorizontalAlignment="Right">
     <s:RotateThumb.RenderTransform>
      <RotateTransform Angle="90" />
     </s:RotateThumb.RenderTransform>
    </s:RotateThumb>
    <s:RotateThumb Margin="0,0,-18,-18" VerticalAlignment="Bottom" HorizontalAlignment="Right">
     <s:RotateThumb.RenderTransform>
      <RotateTransform Angle="180" />
     </s:RotateThumb.RenderTransform>
    </s:RotateThumb>
    <s:RotateThumb Margin="-18,0,0,-18" VerticalAlignment="Bottom" HorizontalAlignment="Left">
     <s:RotateThumb.RenderTransform>
      <RotateTransform Angle="270" />
     </s:RotateThumb.RenderTransform>
    </s:RotateThumb>
   </Grid>
  </ControlTemplate>

加入移動、大小和旋轉功能的DesignerItemStyle

<Style x:Key="DesignerItemStyle" TargetType="ContentControl">
  <Setter Property="MinHeight" Value="50"/>
  <Setter Property="MinWidth" Value="50"/>
  <Setter Property="RenderTransformOrigin" Value="0.5,0.5"/>
  <Setter Property="Template">
   <Setter.Value>
    <ControlTemplate TargetType="ContentControl">
     <Grid DataContext="{Binding RelativeSource={RelativeSource TemplatedParent}}">
      <Control x:Name="RotateDecorator"
           Template="{StaticResource RotateDecoratorTemplate}"
           Visibility="Collapsed"/>
      <s:MoveThumb Template="{StaticResource MoveThumbTemplate}"
             Cursor="SizeAll"/>
      <Control x:Name="ResizeDecorator"
           Template="{StaticResource ResizeDecoratorTemplate}"
           Visibility="Collapsed"/>
      <ContentPresenter Content="{TemplateBinding ContentControl.Content}"/>
     </Grid>
     <ControlTemplate.Triggers>
      <Trigger Property="Selector.IsSelected" Value="True">
       <Setter TargetName="ResizeDecorator"
         Property="Visibility" Value="Visible"/>
       <Setter TargetName="RotateDecorator"
         Property="Visibility" Value="Visible"/>
      </Trigger>
     </ControlTemplate.Triggers>
    </ControlTemplate>
   </Setter.Value>
  </Setter>
</Style>

裝飾Adorner

 

WPF支持Adorner來修飾WPF控件,在改變大小等情況下我們可以根據需要來顯示,有些建模工具支持選中控件後顯示快捷工具條,這個 就可以通過使用Adorner來實現。

本篇通過建立一個裝飾類DesignerItemDecorator控件,加入DesignerItemTemplate中,由DesignerItemDecorator控件來控制是否顯示 以及如何顯示裝飾部分。

控件模板

<ControlTemplate x:Key="DesignerItemTemplate" TargetType="ContentControl">
  <Grid DataContext="{Binding RelativeSource={RelativeSource TemplatedParent}}">
   <s:MoveThumb Template="{StaticResource MoveThumbTemplate}" Cursor="SizeAll"/>
   <ContentPresenter Content="{TemplateBinding ContentControl.Content}"/>
   <s:DesignerItemDecorator x:Name="decorator" ShowDecorator="true"/>
  </Grid>
  <ControlTemplate.Triggers>
   <Trigger Property="Selector.IsSelected" Value="True">
    <Setter TargetName="decorator" Property="ShowDecorator" Value="true"/>
   </Trigger>
  </ControlTemplate.Triggers>
</ControlTemplate>

實現  class DesignerItemDecorator : Control

代碼

public class DesignerItemDecorator : Control
{
   private Adorner adorner;
   public bool ShowDecorator
   {
     get { return (bool)GetValue(ShowDecoratorProperty); }
     set { SetValue(ShowDecoratorProperty, value); }
   }
   public static readonly DependencyProperty ShowDecoratorProperty =
     DependencyProperty.Register
       ("ShowDecorator", typeof(bool), typeof(DesignerItemDecorator),
     new FrameworkPropertyMetadata
       (false, new PropertyChangedCallback(ShowDecoratorProperty_Changed)));

   private void HideAdorner()
   {
     ...
   }
   private void ShowAdorner()
   {
     ...
   }
   private static void ShowDecoratorProperty_Changed
     (DependencyObject d, DependencyPropertyChangedEventArgs e)
   {
     DesignerItemDecorator decorator = (DesignerItemDecorator)d;
     bool showDecorator = (bool)e.NewValue;
     if (showDecorator)
     {
       decorator.ShowAdorner();
     }
     else
     {
       decorator.HideAdorner();
     }
   }
}

實現class DesignerItemAdorner : Adorner

代碼

public class DesignerItemAdorner : Adorner
{
   private VisualCollection visuals;
   private DesignerItemAdornerChrome chrome;
   protected override int VisualChildrenCount
   {
     get
     {
       return this.visuals.Count;
     }
   }
   public DesignerItemAdorner(ContentControl designerItem)
      : base(designerItem)
   {
     this.chrome = new DesignerItemAdornerChrome();
     this.chrome.DataContext = designerItem;
     this.visuals = new VisualCollection(this);
   }
   protected override Size ArrangeOverride(Size arrangeBounds)
   {
     this.chrome.Arrange(new Rect(arrangeBounds));
     return arrangeBounds;
   }
   protected override Visual GetVisualChild(int index)
   {
     return this.visuals[index];
   }
}

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved