在中介绍了图形设计器的移动、大小和旋转等功能的实现,本篇继续第二部分,学习设计面板、缩略图、框线旋转和工具箱等功能的实现。
设计面板(Designer Canvas :variable size, scrollable)
在中的示例出来的设计器,当把设计对象拖动到DesignerCanvas
边界外时,因为DesignerCanvas没有滚动条,
我们会发现再也找不到这个对象了。想到解决最简单的办法就是给DesignerCanvas添加一个
ScrollViewer,但是
这个办法解决不了这个问题,因为当拖动到Canvas之外时,并不会出发Canvas的大小发生变化,所以仍旧没有滚动条,为了解决这个问题,我们则必须在设计对象移动和改变大小时去调整Canvas的大小。
WPF控件提供一个MeassureOverride
允许控件计算希望的大小,再返回WPF框架来进行布局。我们可以在DesignerCanvas中重载这个方法来解决上面所说的问题,重载方法如下:
代码 protected override Size MeasureOverride(Size constraint){ Size size = new Size(); foreach (UIElement element in base .Children) { double left = Canvas.GetLeft(element); double top = Canvas.GetTop(element); left = double .IsNaN(left) ? 0 : left; top = double .IsNaN(top) ? 0 : top; // measure desired size for each child element.Measure(constraint); Size desiredSize = element.DesiredSize; if ( ! double .IsNaN(desiredSize.Width) && ! double .IsNaN(desiredSize.Height)) { size.Width = Math.Max(size.Width, left + desiredSize.Width); size.Height = Math.Max(size.Height, top + desiredSize.Height); } } // for aesthetic reasons add extra points size.Width += 10 ; size.Height += 10 ; return size;} 注:当设计对象很多时,我猜测可能会有性能问题。在介绍了一个可以显示百万级对象的示例,不知道能否解决这个性能问题,先把这个在这里留个足迹,以便以后可以找到
缩略图(Zoombox)
缩略图如上图所示,使用ZoomBox时需要传入一个 ScrollViewer="{Binding ElementName=DesignerScrollViewer}",以便可以通过移动缩略图上的选择框来移动DesignerCanvas
代码文件【ZoomBox.cs】如下:
代码 public class ZoomBox : Control { private Thumb zoomThumb; private Canvas zoomCanvas; private Slider zoomSlider; private ScaleTransform scaleTransform; private DesignerCanvas designerCanvas; public ScrollViewer ScrollViewer { get { return (ScrollViewer)GetValue(ScrollViewerProperty); } set { SetValue(ScrollViewerProperty, value); } } public static readonly DependencyProperty ScrollViewerProperty = DependencyProperty.Register( " ScrollViewer " , typeof (ScrollViewer), typeof (ZoomBox)); public override void OnApplyTemplate() { base .OnApplyTemplate(); if ( this .ScrollViewer == null ) return ; this .designerCanvas = this .ScrollViewer.Content as DesignerCanvas; if ( this .designerCanvas == null ) throw new Exception( " DesignerCanvas must not be null! " ); this .zoomThumb = Template.FindName( " PART_ZoomThumb " , this ) as Thumb; if ( this .zoomThumb == null ) throw new Exception( " PART_ZoomThumb template is missing! " ); this .zoomCanvas = Template.FindName( " PART_ZoomCanvas " , this ) as Canvas; if ( this .zoomCanvas == null ) throw new Exception( " PART_ZoomCanvas template is missing! " ); this .zoomSlider = Template.FindName( " PART_ZoomSlider " , this ) as Slider; if ( this .zoomSlider == null ) throw new Exception( " PART_ZoomSlider template is missing! " ); this .designerCanvas.LayoutUpdated += new EventHandler( this .DesignerCanvas_LayoutUpdated); this .zoomThumb.DragDelta += new DragDeltaEventHandler( this .Thumb_DragDelta); this .zoomSlider.ValueChanged += new RoutedPropertyChangedEventHandler < double > ( this .ZoomSlider_ValueChanged); this .scaleTransform = new ScaleTransform(); this .designerCanvas.LayoutTransform = this .scaleTransform; } private void ZoomSlider_ValueChanged( object sender, RoutedPropertyChangedEventArgs < double > e) { double scale = e.NewValue / e.OldValue; double halfViewportHeight = this .ScrollViewer.ViewportHeight / 2 ; double newVerticalOffset = (( this .ScrollViewer.VerticalOffset + halfViewportHeight) * scale - halfViewportHeight); double halfViewportWidth = this .ScrollViewer.ViewportWidth / 2 ; double newHorizontalOffset = (( this .ScrollViewer.HorizontalOffset + halfViewportWidth) * scale - halfViewportWidth); this .scaleTransform.ScaleX *= scale; this .scaleTransform.ScaleY *= scale; this .ScrollViewer.ScrollToHorizontalOffset(newHorizontalOffset); this .ScrollViewer.ScrollToVerticalOffset(newVerticalOffset); } private void Thumb_DragDelta( object sender, DragDeltaEventArgs e) { double scale, xOffset, yOffset; this .InvalidateScale( out scale, out xOffset, out yOffset); this .ScrollViewer.ScrollToHorizontalOffset( this .ScrollViewer.HorizontalOffset + e.HorizontalChange / scale); this .ScrollViewer.ScrollToVerticalOffset( this .ScrollViewer.VerticalOffset + e.VerticalChange / scale); } private void DesignerCanvas_LayoutUpdated( object sender, EventArgs e) { double scale, xOffset, yOffset; this .InvalidateScale( out scale, out xOffset, out yOffset); this .zoomThumb.Width = this .ScrollViewer.ViewportWidth * scale; this .zoomThumb.Height = this .ScrollViewer.ViewportHeight * scale; Canvas.SetLeft( this .zoomThumb, xOffset + this .ScrollViewer.HorizontalOffset * scale); Canvas.SetTop( this .zoomThumb, yOffset + this .ScrollViewer.VerticalOffset * scale); } private void InvalidateScale( out double scale, out double xOffset, out double yOffset) { // designer canvas size double w = this .designerCanvas.ActualWidth * this .scaleTransform.ScaleX; double h = this .designerCanvas.ActualHeight * this .scaleTransform.ScaleY; // zoom canvas size double x = this .zoomCanvas.ActualWidth; double y = this .zoomCanvas.ActualHeight; double scaleX = x / w; double scaleY = y / h; scale = (scaleX < scaleY) ? scaleX : scaleY; xOffset = (x - scale * w) / 2 ; yOffset = (y - scale * h) / 2 ; } 样式文件【ZoomBox.xaml】 如下:
代码 < Setter Property = " Template " > < Setter.Value > < ControlTemplate TargetType = " {x:Type s:ZoomBox} " > < Border CornerRadius = " 1 " BorderThickness = " 1 " Background = " #EEE " BorderBrush = " DimGray " > < Expander IsExpanded = " True " Background = " Transparent " > < Border BorderBrush = " DimGray " BorderThickness = " 0,1,0,0 " Padding = " 0 " Height = " 180 " > < Grid > < Canvas Margin = " 5 " Name = " PART_ZoomCanvas " > < Canvas.Background > < VisualBrush Stretch = " Uniform " Visual = " {Binding RelativeSource={RelativeSource TemplatedParent}, Path=ScrollViewer.Content} " /> </ Canvas.Background > < Thumb Name = " PART_ZoomThumb " Cursor = " SizeAll " > < Thumb.Style > < Style TargetType = " Thumb " > < Setter Property = " Template " > < Setter.Value > < ControlTemplate TargetType = " Thumb " > < Rectangle StrokeThickness = " 1 " Stroke = " Black " Fill = " Transparent " /> </ ControlTemplate > </ Setter.Value > </ Setter > </ Style > </ Thumb.Style > </ Thumb > </ Canvas > </ Grid > </ Border > < Expander.Header > < Grid > < Grid.ColumnDefinitions > < ColumnDefinition Width = " Auto " /> < ColumnDefinition Width = " * " /> </ Grid.ColumnDefinitions > < Slider Name = " PART_ZoomSlider " VerticalAlignment = " Center " HorizontalAlignment = " Center " Margin = " 0 " Ticks = " 25,50,75,100,125,150,200,300,400,500 " Minimum = " 25 " Maximum = " 500 " Value = " 100 " IsSnapToTickEnabled = " True " IsMoveToPointEnabled = " False " /> < TextBlock Text = " {Binding ElementName=PART_ZoomSlider, Path=Value} " Grid.Column = " 1 " VerticalAlignment = " Center " HorizontalAlignment = " Right " Margin = " 0,0,14,0 " /> < TextBlock Text = " % " Grid.Column = " 1 " VerticalAlignment = " Center " HorizontalAlignment = " Right " Margin = " 1,0,2,0 " /> </ Grid > </ Expander.Header > </ Expander > </ Border > </ ControlTemplate > </ Setter.Value > </ Setter > </ Style > 框线选择(Rubberband selection)
框线是通过第一篇说过的Adorner来做的,其实在WPF中很多地方都用到了这个功能,如
光标、高亮等。这些Adorner都是放在一个Adorner Layer上,MSDN解释说Adorner Layer是置于一个窗口内所有其它控件之上的。AdornerLayer类只能通过 AdornerLayer.GetAdornerLayer(this) 获取。还可以参考:
DesignerCanvas生成
RubberbandAdorner当按住鼠标左键点击DesignerCanvas时将生成RubberbandAdorner,代码如下:
代码 public class DesignerCanvas : Canvas{ ... protected override void OnMouseMove(MouseEventArgs e) { base .OnMouseMove(e); if (e.LeftButton != MouseButtonState.Pressed) this .dragStartPoint = null ; if ( this .dragStartPoint.HasValue) { AdornerLayer adornerLayer = AdornerLayer.GetAdornerLayer( this ); if (adornerLayer != null ) { RubberbandAdorner adorner = new RubberbandAdorner( this , dragStartPoint); if (adorner != null ) { adornerLayer.Add(adorner); } } e.Handled = true ; } } ...} - 生成RubberbandAdorner : Adorner
代码 public class RubberbandAdorner : Adorner{ .... private Point ? startPoint, endPoint; protected override void OnMouseMove(MouseEventArgs e) { if (e.LeftButton == MouseButtonState.Pressed) { if ( ! this .IsMouseCaptured) { this .CaptureMouse(); } this .endPoint = e.GetPosition( this ); this .UpdateRubberband(); this .UpdateSelection(); e.Handled = true ; } } private void UpdateRubberband(){ double left = Math.Min( this .startPoint.Value.X, this .endPoint.Value.X); double top = Math.Min( this .startPoint.Value.Y, this .endPoint.Value.Y); double width = Math.Abs( this .startPoint.Value.X - this .endPoint.Value.X); double height = Math.Abs( this .startPoint.Value.Y - this .endPoint.Value.Y); this .rubberband.Width = width; this .rubberband.Height = height; Canvas.SetLeft( this .rubberband, left); Canvas.SetTop( this .rubberband, top);} private void UpdateSelection(){ Rect rubberBand = new Rect( this .startPoint.Value, this .endPoint.Value); foreach (DesignerItem item in this .designerCanvas.Children) { Rect itemRect = VisualTreeHelper.GetDescendantBounds(item); Rect itemBounds = item.TransformToAncestor (designerCanvas).TransformBounds(itemRect); if (rubberBand.Contains(itemBounds)) { item.IsSelected = true ; } else { item.IsSelected = false ; } }} ...}
工具箱Toolbox (drag & drop)
工具箱Toolbox
是一个ItemsControl
控件,它的子是ToolboxItem
类型。
代码Toolbox.cs如下:
代码 public class Toolbox : ItemsControl { private Size defaultItemSize = new Size( 65 , 65 ); public Size DefaultItemSize { get { return this .defaultItemSize; } set { this .defaultItemSize = value; } } protected override DependencyObject GetContainerForItemOverride() { return new ToolboxItem(); } protected override bool IsItemItsOwnContainerOverride( object item) { return (item is ToolboxItem); } } Toolbox使用WrapPanel显示ToolboxItem,
样式文件Toolbox.xaml如下:
代码 < Style TargetType = " {x:Type s:ToolboxItem} " > < Setter Property = " Control.Padding " Value = " 5 " /> < Setter Property = " ContentControl.HorizontalContentAlignment " Value = " Stretch " /> < Setter Property = " ContentControl.VerticalContentAlignment " Value = " Stretch " /> < Setter Property = " ToolTip " Value = " {Binding ToolTip} " /> < Setter Property = " Template " > < Setter.Value > < ControlTemplate TargetType = " {x:Type s:ToolboxItem} " > < Grid > < Rectangle Name = " Border " StrokeThickness = " 1 " StrokeDashArray = " 2 " Fill = " Transparent " SnapsToDevicePixels = " true " /> < ContentPresenter Content = " {TemplateBinding ContentControl.Content} " Margin = " {TemplateBinding Padding} " SnapsToDevicePixels = " {TemplateBinding UIElement.SnapsToDevicePixels} " /> </ Grid > < ControlTemplate.Triggers > < Trigger Property = " IsMouseOver " Value = " true " > < Setter TargetName = " Border " Property = " Stroke " Value = " Gray " /> </ Trigger > </ ControlTemplate.Triggers > </ ControlTemplate > </ Setter.Value > </ Setter > </ Style > < Style TargetType = " {x:Type s:Toolbox} " > < Setter Property = " SnapsToDevicePixels " Value = " true " /> < Setter Property = " Focusable " Value = " False " /> < Setter Property = " Template " > < Setter.Value > < ControlTemplate > < Border BorderThickness = " {TemplateBinding Border.BorderThickness} " Padding = " {TemplateBinding Control.Padding} " BorderBrush = " {TemplateBinding Border.BorderBrush} " Background = " {TemplateBinding Panel.Background} " SnapsToDevicePixels = " True " > < ScrollViewer VerticalScrollBarVisibility = " Auto " > < ItemsPresenter SnapsToDevicePixels = " {TemplateBinding UIElement.SnapsToDevicePixels} " /> </ ScrollViewer > </ Border > </ ControlTemplate > </ Setter.Value > </ Setter > < Setter Property = " ItemsPanel " > < Setter.Value > < ItemsPanelTemplate > < WrapPanel Margin = " 0,5,0,5 " ItemHeight = " {Binding Path=DefaultItemSize.Height, RelativeSource={RelativeSource AncestorType=s:Toolbox}} " ItemWidth = " {Binding Path=DefaultItemSize.Width, RelativeSource={RelativeSource AncestorType=s:Toolbox}} " /> </ ItemsPanelTemplate > </ Setter.Value > </ Setter > </ Style >
ToolboxItem是显示在工具箱中的对象,我们可以通过鼠标点击它进行选择,然后拖拽到DesignerCanvas来生成一个设计对象,示例中是通过
XamlWriter.Save保存到DataObject,然后在DesignerCanvas接收这个对象,这部分在进行自己的设计器开发时会进行更改
ToolboxItem的代码如下:
代码 public class ToolboxItem : ContentControl { private Point ? dragStartPoint = null ; static ToolboxItem() { FrameworkElement.DefaultStyleKeyProperty.OverrideMetadata( typeof (ToolboxItem), new FrameworkPropertyMetadata( typeof (ToolboxItem))); } protected override void OnPreviewMouseDown(MouseButtonEventArgs e) { base .OnPreviewMouseDown(e); this .dragStartPoint = new Point ? (e.GetPosition( this )); } protected override void OnMouseMove(MouseEventArgs e) { base .OnMouseMove(e); if (e.LeftButton != MouseButtonState.Pressed) { this .dragStartPoint = null ; } if ( this .dragStartPoint.HasValue) { Point position = e.GetPosition( this ); if ((SystemParameters.MinimumHorizontalDragDistance <= Math.Abs(( double )(position.X - this .dragStartPoint.Value.X))) || (SystemParameters.MinimumVerticalDragDistance <= Math.Abs(( double )(position.Y - this .dragStartPoint.Value.Y)))) { string xamlString = XamlWriter.Save( this .Content); DataObject dataObject = new DataObject( " DESIGNER_ITEM " , xamlString); if (dataObject != null ) { DragDrop.DoDragDrop( this , dataObject, DragDropEffects.Copy); } } e.Handled = true ; } } } DesignerItem增加IsSelected属性
DesignerItem增加是否选择属性,代码如下:
代码 public class DesignerItem : ContentControl{ public bool IsSelected { get { return ( bool )GetValue(IsSelectedProperty); } set { SetValue(IsSelectedProperty, value); } } public static readonly DependencyProperty IsSelectedProperty = DependencyProperty.Register( " IsSelected " , typeof ( bool ), typeof (DesignerItem), new FrameworkPropertyMetadata( false )); ...} 在MouseDown事件时会去设置IsSelected属性:
代码 protected override void OnPreviewMouseDown(MouseButtonEventArgs e){ base .OnPreviewMouseDown(e); DesignerCanvas designer = VisualTreeHelper.GetParent( this ) as DesignerCanvas; if (designer != null ) { if ((Keyboard.Modifiers & (ModifierKeys.Shift | ModifierKeys.Control)) != ModifierKeys.None) { this .IsSelected = ! this .IsSelected; } else { if ( ! this .IsSelected) { designer.DeselectAll(); this .IsSelected = true ; } } } e.Handled = false ;} IsSelected属性触发ResizeDecorator是否显示:
代码 < Style TargetType = " {x:Type s:DesignerItem} " > < Setter Property = " MinHeight " Value = " 50 " /> < Setter Property = " MinWidth " Value = " 50 " /> < Setter Property = " SnapsToDevicePixels " Value = " true " /> < Setter Property = " Template " > < Setter.Value > < ControlTemplate TargetType = " {x:Type s:DesignerItem} " > < Grid DataContext = " {Binding RelativeSource={RelativeSource TemplatedParent}, Path = .} " > < s:MoveThumb x:Name = " PART_MoveThumb " Cursor = " SizeAll " Template = " {StaticResource MoveThumbTemplate} " /> < ContentPresenter x:Name = " PART_ContentPresenter " Content = " {TemplateBinding ContentControl.Content} " Margin = " {TemplateBinding Padding} " /> < s:ResizeDecorator x:Name = " PART_DesignerItemDecorator " /> </ Grid > < ControlTemplate.Triggers > < Trigger Property = " IsSelected " Value = " True " > < Setter TargetName = " PART_DesignerItemDecorator " Property = " ShowDecorator " Value = " True " /> </ Trigger > </ ControlTemplate.Triggers > </ ControlTemplate > </ Setter.Value > </ Setter > </ Style > DesignerItem支持移动选择区域
DesignerItem默认允许移动的是一个透明的矩形区域,如上图左边这个。我们一般希望点击这个形状内部才允许移动和选择,这时候我们可以通过DesignerItem.MoveThumbTemplate来更改这个支持Move的区域,代码如下:
< Path Stroke = " Red " StrokeThickness = " 5 " Stretch = " Fill " IsHitTestVisible = " false " Data = " M 9,2 11,7 17,7 12,10 14,15 9,12 4,15 6,10 1,7 7,7 Z " > < s:DesignerItem. MoveThumbTemplate > < ControlTemplate > < Path Data = " M 9,2 11,7 17,7 12,10 14,15 9,12 4,15 6,10 1,7 7,7 Z " Fill = " Transparent " Stretch = " Fill " /> </ ControlTemplate > </ s:DesignerItem. MoveThumbTemplate > </ Path >
欢迎转载,转载请注明:转载自 [ ]
转载地址:https://blog.csdn.net/weixin_30687587/article/details/94987143 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!