WPF自定义控件

WPF自定义控件 —— 自绘篇
本文示例源代码或素材下载
      首先我们来说最简单的画面的呈现。
  一.在自定义控件上画矩形
  增加一个自定义控件类:  



  打开创建的类重载OnRender函数并注释掉静态构造函数,代码如下
 public class CustomerRender : Control
    {
        //static CustomerRender()
        //{
        // DefaultStyleKeyProperty.OverrideMetadata(typeof(CustomerRender), new FrameworkPropertyMetadata(typeof(CustomerRender)));
        //}

        protected override void OnRender(DrawingContext drawingContext)
        {
            drawingContext.DrawRectangle(Brushes.Bisque, null, new Rect(0, 0, 50, 50));
        }

    }
  在Window1这个类中修改如下
    public partial class Window1 : Window
    {
        public Window1()
        {
            InitializeComponent();
            CustomerRender customerRender = new CustomerRender();
            customerRender.Width = 200;
            customerRender.Height = 300;
            this.Content = customerRender;
        }
    }
运行程序我们可以看到:
  当然你还可以通过drawingContext来画圆圈、不规则矩形、圆角矩形和写文字等等。具体可以看DrawingContext 中的API介绍和MSDN文档。

  二.拖动矩形
一般来说UI是数据的反应,UI的变化其实是后段数据的变化。我们一般在移动到方块上方后,按住鼠标开始拖动,那么在鼠标移动上去之后,我们首先判断此位置是否有矩形,点击鼠标的时候,我们记录点下的这个坐标为A,鼠标移动的时候我们根据移动时候鼠标所在坐标和原来的A坐标比较,然后把这个差值和矩形的开始坐标相加,通过InvalidateVisual()方法不断重画就达到了移动的效果。

  如果你需要鼠标移动出窗口还可以移动该矩形请加CaptureMouse()来锁定当前鼠标,但不要忘记在鼠标放开的时候把它给释放掉ReleaseMouseCapture()。
  我们在window类中为我们的控件赋上底色
InitializeComponent();
CustomerRender customerRender = new CustomerRender();
customerRender.Width = 200;
customerRender.Height = 300;
customerRender.Background = Brushes.Green;
this.Content = customerRender;
  并在控件的OnRender函数中加入
drawingContext.DrawRectangle(Background, null,new Rect(new Point(0,0),this.RenderSize));
效果如图:
  这时你拖动方块可能会发现方块可以被移出背景,这在WPF下是允许的,但有时候这又不符合情理,我们需要把多余的部分裁减掉。

        protected override void OnRender(DrawingContext drawingContext)
        {
            Rect rect = new Rect(new Point(0, 0), this.RenderSize);

            drawingContext.DrawRectangle(Background, null, rect);

            //创建一个填充色为Bisque边框色为Orange,横坐标为2,纵坐标为3,宽度为80,长度为50的矩形

            Pen pen = new Pen(Brushes.Orange, 1);

            pen.Freeze();//冻结可以加快呈现速度

            RectangleGeometry rectangleGeometry = new RectangleGeometry(rect);
            rectangleGeometry.Freeze();

            drawingContext.PushClip(rectangleGeometry);

            drawingContext.DrawRectangle(Brushes.Bisque, pen, _rectangle);

            drawingContext.Pop();
        }
主要注意drawingContext.PushClip(rectangleGeometry);到drawingContext.Pop();之间是剪切的有效范围。 Pop()结束符不仅仅用于剪切,还适用于遮幕,Effect效果等有Push开头的那些玩艺。(关于GuidelineSet推荐一个网址: http://www.wpftutorial.net/DrawOnPhysicalDevicePixels.html)
  剪切后的效果如下:

  三.动画效果
   当然我们也可以做些拖动预览效果如下:

  实现的原理无非是拖动的时候画一个假矩形,并把该矩形呈现的代码放在剪切的效果之外,拖动结束后把假的位置给真的调用重画一遍就实现了。
我们能不能用动画效果使得矩形慢慢移动到指定位置,答案当然是肯定的。这里可以用System.Windows.Media.Animation命名空间下的一些方法来实现
RectAnimation rectAnimation = new RectAnimation
(_rectangle,_preivewRectangle,new Duration(TimeSpan.FromMilliseconds(600)));
drawingContext.DrawRectangle(Brushes.Bisque, pen, _rectangle,rectAnimation.CreateClock());
你也可以用来做颜色的变换,大小的变化等等。

  四.多个矩形
   有多个矩形可以把矩形放到一个**中依次判断,层叠的话就是后段数据存放的先后顺序问题,UI显示的快慢和后段数据查找有直接的联系,这个也是算法的价值体现。
  小结
  这里讲的做法也是winform里的大多数实现方式,只不过WPF使得我们对像素处理关心的更少,自绘在WPF的应用中相对winform要少了很多,除非用在大数据量的地方,WPF复合控件能完成大部分的需求——只要你能熟练定义样式,并了解可视树,逻辑树,以及各个控件的用法。
WPF自定义控件 —— 布局
本篇是上一篇自绘的补充,但需要一定的WPF相关知识,感谢Clingingboy 通宵达旦的帮助。                  本文示例源代码或素材下载

      一.ScrollViewer
  在WPF自定义控件 —— 自绘篇我们做了一个可拖动的矩形,但你是否发现当矩形拖出背景后就不见了,一般来说对于不可见区域需要有ScrollBar来呈现,如图:

  对于这一应用在WPF中最常用的应该在控件外面包个ScrollViewer,那么如何使得我们的控件支持ScrollViewer呢?
  首先我们来了解一下ScrollViewer基本原理

  通过上图我们可以看到ScrollViewer是以Grid为容器组成的控件,其中主要包括ScrollContentPresenter,和两个ScrollBar,其中ScrollBar就是我们第一张图中看到那两条,它也是一个由多个控件组成的复合控件,在这里先略过ScrollBar;来看红色边框内的ScrollContentPresenter,可以看到我们的控件CustomerRender在ScrollContentPresenter内,那么我们控件呈现的位置必定是由它来控制的。
  这个神秘的ScrollContentPresenter到底做了什么能让我们看到一部分内容呢?看下这张图就清楚了

我们可以知道ScrollContentPresenter实际对我们玩了一个遮罩效果,把我们的控件当作一个背景图,用ScrollBar来移动背景位置,在ScrollViewer外的控件可视部分统统被裁减掉了。只要继承UIElement的控件就可以重载GetLayoutClip方法来剪切区域。
protected override Geometry GetLayoutClip(Size layoutSlotSize)
{
  return new RectangleGeometry(new Rect(base.RenderSize));
} 

 
  二.ArrangeOverride
  ScrollContentPresenter又是如何控制子元素的坐标呢?重载ArrangeOverride函数便可,具体看代码注释
protected override Size ArrangeOverride(Size arrangeBounds)
{
  //得到**中的第一个元素 
  UIElement visualChild = this.GetVisualChild(0) as UIElement;
  //把子元素的左上角坐标定义到容器之外 
  Point point = new Point(-40, -50);

  if (visualChild != null)
  {
    Rect finalRect = new Rect(point, visualChild.DesiredSize);

//设置元素坐标和大小 
    visualChild.Arrange(finalRect);
  }

  return arrangeBounds;
} 

 
  这段代码中参数arrangeBounds是父容器传进的值,一般表示你可以有多大的利用空间,这个函数的返回值一般指的是你控件RenderSize的大小.
  RenderSize有什么用?(欢迎大家补充)
  在onRender里可以用,比如画背景。
  在MeasureOverride函数中当参数值为无限大时用来得知可用空间的大小。

  三.MeasureOverride
  visualChild.DesiredSize的值实际就是我们常用的ActualHeight和ActualWidth的源头,也就是控件的实际大小,我们可以重载MeasureOverride产生。下面是我们的自定义控件用的。
protected override Size MeasureOverride(Size constraint)
{
  Size size = new Size
    (
    //判断形参constraint中传的值大,还是我们的Rectangle的值大,以最大的那个作为控件的长宽
    Math.Max(double.IsInfinity(constraint.Width) ? this.RenderSize.Width : constraint.Width, _preivewRectangle.Right),
    Math.Max(double.IsInfinity(constraint.Height) ? this.RenderSize.Height : constraint.Height, _preivewRectangle.Bottom));
  return size;
}

 
要说明下的是外容器的大小并不会触发MeasureOverride(如把窗体拖大),只会触发ArrangeOverride,如果你要重新为DesiredSize赋值并通知父容器请使用Measure函数,它会调用父控件的OnChildDesiredSizeChanged方法来通知,同理父控件要监听子控件的大小变化只要重载该方法即可,这个方法可以一直沿着可视树向上引发InvalidateMeasure函数,InvalidateMeasure通过DispatcherPriority为Render来异步调用Measure。
   ArrangeOverride中尽量不要调用本身的Measure,Measure函数会再次调用InvalidateArrange方法从而引起循环。控件容器放生变化时可以重载OnRenderSizeChanged实现。
  Measure和Arrange的具体关系如下图:



  四.补充
  如果想要自定义的ScrollBar你可以能要根绝ScrollBar的值实时进行重绘图,这个好处是数据量大,你只需呈现当前画面中的图形,缺点是动一动就要重绘。通过例如ScrollViewer裁减的方式,遮罩得时候不会重绘,不过刚开始呈现的时候数据量大会慢。
   另外在复合控件中配合Transform中的各种类来进行布局,使用CompositionTarget和动画类可以产生很多效果。         
                                    
                 来源:博客园    作者:Curry
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值