代码改变世界

DiagramDesigner的学习心得一

2013-01-06 15:10  爱研究源码的javaer  阅读(898)  评论(1编辑  收藏  举报

DiagramDesignerCodeProject上关于WPF的控件模板,移动拖放,改变控件大小,旋转的很好的文章。

在博客园博主周金根的博客上也有相应的介绍。它总共分四部分,每部分都循序渐进。

首先我们来讲第一部分,关于控件的移动,和改变大小,MoveAndResize.

MoveResize项目运行起来的效果如下:

image

在MoveResize项目的window1.xaml中:

image

  <ContentControl Width="130"
                    MinWidth="50"
                    Height="130"
                    MinHeight="50"
                    Canvas.Top="150"
                    Canvas.Left="470"
                    Template="{StaticResource DesignerItemTemplate}">
      <Ellipse Fill="Red"
               IsHitTestVisible="False"/>
    </ContentControl>
    <ContentControl Width="130"
                    MinWidth="50"
                    Height="130"
                    MinHeight="50"
                    Canvas.Top="150"
                    Canvas.Left="150"
                    Template="{StaticResource DesignerItemTemplate}">
      <Path Fill="Blue"
            Data="M 0,5 5,0 10,5 5,10 Z"
            Stretch="Fill"
            IsHitTestVisible="False"/>
    </ContentControl>

这两个ContentControl控件一个是左边的菱形,另一个则是一个圆形。样式都采用了DesignerItemTemplate.

 <!-- Designer Item Template-->
    <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>

对上面的两段xaml里的TemplateBinding和ContentPresenter我们可以引用周金根博主里面的解释:

  • 限制目标类型
    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,因为触发器位于控件可视树模板外部

我们看到在DesignerItemTemplate里还有两个MoveThumbTemplateResizeDecoratorTemplate .

MoveThumbTemplate其实就是一个Rectangle搞定。我们把它的Fill改成Blue看下:

image

ResizeDecoratorTemplate就是四个角上的image

我们来看看ResizeThumb这个类是怎么实现的:

  public class ResizeThumb : Thumb
    {
        public ResizeThumb()
        {
            //当有逻辑焦点或鼠标捕获时,随着鼠标位置改变一次或多次
            DragDelta += new DragDeltaEventHandler(this.ResizeThumb_DragDelta);
        }

        private void ResizeThumb_DragDelta(object sender, DragDeltaEventArgs e)
        {
            Control designerItem = this.DataContext as Control;//获取父控件,通过DataContext

            if (designerItem != null)
            {
                double deltaVertical, deltaHorizontal;

                switch (VerticalAlignment)
                {
                    case VerticalAlignment.Bottom:
                        deltaVertical = Math.Min(-e.VerticalChange, designerItem.ActualHeight - designerItem.MinHeight);//计算减小的高度
                        designerItem.Height -= deltaVertical;//减小高度
                        break;
                    case VerticalAlignment.Top:
                        deltaVertical = Math.Min(e.VerticalChange, designerItem.ActualHeight - designerItem.MinHeight);
                        Canvas.SetTop(designerItem, Canvas.GetTop(designerItem) + deltaVertical);//重新设置相对于相对于Canvas的上边距
                        designerItem.Height -= deltaVertical;
                        break;
                    default:
                        break;
                }

                switch (HorizontalAlignment)
                {
                    case HorizontalAlignment.Left:
                        deltaHorizontal = Math.Min(e.HorizontalChange, designerItem.ActualWidth - designerItem.MinWidth);
                        Canvas.SetLeft(designerItem, Canvas.GetLeft(designerItem) + deltaHorizontal);//重新设置相对于Canvas的左边距
                        designerItem.Width -= deltaHorizontal;//减小宽度
                        break;
                    case HorizontalAlignment.Right:
                        deltaHorizontal = Math.Min(-e.HorizontalChange, designerItem.ActualWidth - designerItem.MinWidth);
                        designerItem.Width -= deltaHorizontal;
                        break;
                    default:
                        break;
                }
            }

            e.Handled = true;
        }
    }

MoveThumbTemplate是基于MoveThumb类的:

 public class MoveThumb : Thumb
    {
        public MoveThumb()
        {
            DragDelta += new DragDeltaEventHandler(this.MoveThumb_DragDelta);
        }

        private void MoveThumb_DragDelta(object sender, DragDeltaEventArgs e)
        {
            Control designerItem = this.DataContext as Control;//获取父控件,通过DataContext

            if (designerItem != null)
            {
                double left = Canvas.GetLeft(designerItem);//获取现在相对于Canvas画布的左边距
                double top = Canvas.GetTop(designerItem);//获取现在相对于Canvas画布的上边距

                Canvas.SetLeft(designerItem, left + e.HorizontalChange);//e.HorizontalChange 水平改变大小,有可能为负
                Canvas.SetTop(designerItem, top + e.VerticalChange);//e.VerticalChange 垂直改变大小,有可能为负
            }
        }
    }

下篇我们来分析下旋转 还有 根据Adorner来怎么实现。