zoukankan      html  css  js  c++  java
  • WPF中的图表设计器 – 1

    [原文地址] http://www.codeproject.com/KB/WPF/WPFDiagramDesigner_Part1.aspx
    [原文作者] sukram

    [译者]WizRay

    介绍

    在这篇文章中,我将介绍在Canvas中移动、缩放或者旋转任何类型的对象。为此,我将提供两个不同的解决方案——一个使用了WPF中Adoner的方案和一个不使用的。

    关于代码

    附件中的Visual Studio 2008 工程由三个项目组成:

    ScreenShot02

    MoveResize: 这个版本解释了如何在不使用WPF中Adorner的情况下移动、缩放对象;

    MoveResizeRotate: 另外,这个项目解释了如何在不使用WPF中Adorner的情况下旋转对象。当我们移动、缩放对象时,旋转可能会出现一些小的副作用。我们能够通过比较这个项目和前一个项目来简单的跟踪这些副作用;

    MoveResizeRotateWithAdorners: 第三个项目最终解释了如何使用WPF中的Adorner来对WPF中的对象进行移动、缩放、旋转。而且提供了一个示例,解释了如何在缩放操作时使用Adorner来提供对象真实尺寸的可视化回馈。

    ScreenShot03

    准备工作

    我们从一个简单的图表开始:

    <Canvas>
        <Ellipse Fill="Blue" Width="100" Height="100" Canvas.Top="100" Canvas.Left="100" />
    </Canvas>

    你可能觉得这个图表很不起眼,但不论怎样,这就是我们的开始。它很好理解,而且具有一个图表所需的所有要素:一个用于绘制图形的Canvas。不过你也许是对的,这个图表没什么大用——他只是静态的。

    所以让我们做一些准备工作,把这个圆形保存到ContentControl中:

    <Canvas>
        <ContentControl Width="100" Height="100" Canvas.Top="100" Canvas.Left="100">
            <Ellipse Fill="Blue" />
        </ContentControl>
    </Canvas>

    你或许说这也没比刚才好多少,我们仍然不能移动这个圆形,那它比好在哪儿呢?OK,这个ContentControl提供了一个我们放置在Canvas中的对象的容器,事实上,这个ContentControl就是我们要移动、缩放和旋转的对象。由于ContentControl的Content可以使任何类型的对象,所以我们将能够移动、缩放和旋转在Canvas中的任何对象!

    注意:由于ContentControl的关键作用,我们把他称作DesignerItem。

    我们现在为DesignerItem声明一个Control Template。这实际上提供了进一步的抽象,这样,从这儿开始,我们在完全不顾及ContentControl的内容情况下对此进行扩展。

    <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>

    我们完成了准备工作,我们已经准备好向Canvas中引入一些动态的元素了。

    移动

    在MSDN中,Thumb元素是这样解释的:“…represents a control that lets the user drag and resize controls.”。这看上去正好是我们需要的元素来做移动,下面我们将使用Thumb来完成我们的功能:

    public class MoveThumb : Thumb
    {
        public MoveThumb()
        {
            DragDelta += MoveThumb_DragDelta;
        }
    
        private void MoveThumb_DragDelta(object sender, DragDeltaEventArgs e)
        {
            Control item = DataContext as Control;
    
            if (item != null)
            {
                double left = Canvas.GetLeft(item);
                double top = Canvas.GetTop(item);
    
                Canvas.SetLeft(item, left + e.HorizontalChange);
                Canvas.SetTop(item, top + e.VerticalChange);
            }
        }
    }

    MoveThumb类型继承自Thumb,提供了DragDelta的事件响应。通过相应DragDelta事件,将DataContext转换为ContentControl类型,而后根据Thumb在水平和垂直方向上拖动的距离更新DataContext的位置。你可能已经猜到了,DataContext中的元素就是刚才的DesignerItem,但是他是从哪儿来的呢?我们通过更新DesignerItme的模板来处理:

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

    从这儿我们看到,MoveThumb的DataContext属性绑定到了父级模板对象,也就是DesignerItem。注意我们在这儿加入了一个Grid作为面板来布局,它能够将ContentPresenter和MoveThumb显示在同样的位置,具有同样的尺寸。现在,我们编译、运行这个程序:

    ScreenShot04

    运行结果中,我们得到了一个灰色的MoveThumb,表面盖着一个蓝色的圆形。你甚至可以发现,可以通过拖动来改变他们的位置,但是只有在灰色的部分也就是MoveThumb可见的部分才能拖动。这是因为圆形阻碍了鼠标事件,使事件穿过了MoveThumb。我们通过将圆形的IsHitTest属性设为false来修正这个问题。

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

    MoveThumb继承了Thumb的样式,这在我们的功能中没有用。我们为它创建一个只包括一个透明矩形的新的模板。当然更加通用的方法是为MoveThumb建立默认的样式,但是在这儿,自定义样式就够了。

    现在DesignerItem的模板如下:

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

    到此,我们已经能够在Canvas中移动对象了,下面,我们来让它支持缩放。

    缩放

    我们记得,MSDN中对于Thumb的介绍里说明了其可以拖动和缩放。所以我们继续使用Thumb来创建ResizeDecoratorTeamplate模板。

    <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>

    在这儿我们看到,这个模板由一组8个Thumb填满的Grid元素组成,这就是我们用来支持缩放的控件。通过设定Thumb的各种属性,我们实现了一个类似于真正的缩放修饰框的布局。

    ScreenShot05

    这很令人兴奋,但是现在它只不过是个表象,因为还没有在上面添加任何关于DragDelta事件的处理方法。于是,我们使用ResizeThumb来代替Thumb元素。

    public class ResizeThumb : Thumb
    {
        public ResizeThumb()
        {
            DragDelta += ResizeThumb_DragDelta;
        }
    
        private void ResizeThumb_DragDelta(object sender, DragDeltaEventArgs e)
        {
            Control item = 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;
        }
    }
    

    根据ResizeThumb的水平和垂直对齐的设定,这些ResizeThumb能够更新DesignerItem的宽度、高度和(或)位置。现在,通过添加一个使用了ResizeDecoratorTemplate模板的控件,我们把刚刚完成的拖动和缩放两部分集成到一起。

    <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>

    很好,现在,我们能够对对象进行拖动和缩放了。下面我们让他转起来。

    旋转

    为了能够让Canvas中的元素能够旋转,我们可以遵循前两张阐述的方法完成,但是这次,我们创建一个名为RotateThumb的继承自Thumb的类,并且在名为RotateDecoratorTemplate的模板中加入了4个RotateThumb来实现。这些和缩放的修饰框集成到一起时,就象这样:

    ScreenShot06

    RotateThumb和RotateDecoratorTemplate的代码与之前我们使用的代码非常相像,所以在此,我不列出详细代码。

    注意:我最早的解决方案中,使用了WPF中的TranslateTransform,ScaleTransform和RotateTransform。而后发现这是错误的,因为在WPF中,Transform没有改变对象的真实属性(宽度、高度、位置等),Transform只是一种渲染时的修改。所以我在实现拖动、放缩时,没有使用TranslateTransform和ScaleTransform,但是使用RotateTransform实现了旋转功能,因为在WPF中没有其他手段来实现对所有元素的旋转。

    DesignerItem的样式

    为了方便起见,我们将DesignerItem的模板包装到一个Style中,同时我们也能设定很多其他属性诸如MinWidth、MaxHeight和RenderTransformOrigin等。一个Trigger使得再选中状态时显示缩放和旋转的修饰框,这其中使用了Attached Property,也就是Selector.IsSelected。

    注意:在WPF中提供了名为Selector的类,它允许用户在他的子级元素中选择出一个。本文中,我没有使用任何Selector元素,但是我是用了Selector.IsSelected的Attached Property来模拟了选中操作。

    <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>

    OK,我们完成了!现在,我们可以拖动、缩放、旋转一个对象。仅仅三个类和数行的XAML代码就使我们完成了这些功能!最妙的是,我们不需要知道需要拖动、缩放、旋转的元素本身的任何信息,所有的行为都被包装到一个模板中!

    使用Adorner的方案

    在本章,我将演示如何将缩放和旋转的修饰框提取到AdornerLayer中,以便能够在其他元素表面加以渲染。

    ScreenShot07

    通过DesignerItem的模板,我们能够解释基于Adorner的方案:

    <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>
    

    这个模板与前面几张我们使用的模板很相像,除了将缩放和旋转的修饰框替换为DesignerItemDecorator的新的类,这个类从Control继承,没有默认Style,替换为在ShowAdorner属性为true时显示的类。

    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();
            }
        }
    }

    当DesignerItem被选中时,下面这个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];
        }
    }

    我们看到,这个Adorner只有一个子级元素:DesignerItemAdornerChrome,这是实际上在缩放和旋转项目上提供了拖动的事件处理。这个元素具有默认的Style,使得其具有我们前面几章提到的ResizeThumb和RotateThumb的样式,所以我再次不再重复这些代码。

    自定义Adorner

    显然我们可以将自定义的Adorner加入到DesignerItem中。下面的示例中,我将一个能够显示被缩放元素实际宽高的Adorner加入到其中。详细的代码请参见附件,如果你有任何问题,请与我联系。

    ScreenShot08



    (全文完)


    以下为广告部分

    您部署的HTTPS网站安全吗?

    如果您想看下您的网站HTTPS部署的是否安全,花1分钟时间来 myssl.com 检测以下吧。让您的HTTPS网站变得更安全!

    SSL检测评估

    快速了解HTTPS网站安全情况。

    安全评级(A+、A、A-...)、行业合规检测、证书信息查看、证书链信息以及补完、服务器套件信息、证书兼容性检测等。

    SSL证书工具

    安装部署SSL证书变得更方便。

    SSL证书内容查看、SSL证书格式转换、CSR在线生成、SSL私钥加解密、CAA检测等。

    SSL漏洞检测

    让服务器远离SSL证书漏洞侵扰

    TLS ROBOT漏洞检测、心血漏洞检测、FREAK Attack漏洞检测、SSL Poodle漏洞检测、CCS注入漏洞检测。

  • 相关阅读:
    16. 3Sum Closest
    17. Letter Combinations of a Phone Number
    20. Valid Parentheses
    77. Combinations
    80. Remove Duplicates from Sorted Array II
    82. Remove Duplicates from Sorted List II
    88. Merge Sorted Array
    257. Binary Tree Paths
    225. Implement Stack using Queues
    113. Path Sum II
  • 原文地址:https://www.cnblogs.com/zhuqil/p/1628278.html
Copyright © 2011-2022 走看看