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注入漏洞检测。

  • 相关阅读:
    POJ Problem 2631 Roads in the North【树的直径】
    POJ Problem 1985 Cow Marathon 【树的直径】
    Light OJ 1049 Farthest Nodes in a Tree【树的直径】
    HDU Problem 4707 Pet【并查集】
    HDU Problem 1325 Is It A Tree?【并查集】
    HDU Problem 5631 Rikka with Graph【并查集】
    HDU Problem 5326 Work 【并查集】
    文件比较软件修改比较文件时间戳方法
    什么是Oracle 数据泵
    文件对比工具文件上传 FTP如何匹配本地文件
  • 原文地址:https://www.cnblogs.com/zhuqil/p/1628278.html
Copyright © 2011-2022 走看看