WPF下Draggable Border的实现

本文借鉴了WPF Draggable Label(http://www.codeproject.com/Articles/71792/WPF-Draggable-Label)

在近期需要实现的一个工程中,希望在主界面上有很多Cards,而每个Card是可以拖动的,于是我就希望把所有内容都塞到一个Border里面,于是问题就变成了实现一个DraggableBorder。

然后就搜到了上面提到的链接,发现不仅能拖动,还能调整大小,真是意外的收获。

实现的基本思想就是添加鼠标事件,则首先就是按下的时候要捕获鼠标,松开的时候释放鼠标:

        protected override void OnMouseEnter(MouseEventArgs e)
        {
            if (e.LeftButton == MouseButtonState.Pressed)
            {
                this.CaptureMouse();
            }
            base.OnMouseEnter(e);
        }

        protected override void OnMouseLeave(MouseEventArgs e)
        {
            this.Cursor = Cursors.Arrow;
            this.ReleaseMouseCapture();
            base.OnMouseLeave(e);
        }

        protected override void OnMouseLeftButtonUp(MouseButtonEventArgs e)
        {
            this.Cursor = Cursors.Arrow;
            this.ReleaseMouseCapture();
            base.OnMouseLeftButtonUp(e);
        }

        protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
        {
            this.CaptureMouse();
            base.OnMouseLeftButtonDown(e);
        }
    

然后在鼠标移动的时候,如果鼠标被捕获,就做出相应的响应。

首先就是要判断到底是移动位置还是改变大小,原文提供的一个小技巧就是在边上加上几个矩阵,然后当鼠标在矩阵内的时候就是改成修改大小的鼠标样式。

                    const int dragHandleWidth = 5;

                    //var topHandle = new Rect(0, )
                    var bottomHandle = new Rect(0, height - dragHandleWidth, width, 2 * dragHandleWidth);
                    var rightHandle = new Rect(width - dragHandleWidth, 0, 2 * dragHandleWidth, height);
                    var _titleLabelHandle = new Rect(0, 0, _titleLabel.ActualWidth, _titleLabel.ActualHeight);

                    Point relativeLocation = window.TranslatePoint(currentLocation, this);

                    if (_titleLabelHandle.Contains(relativeLocation))
                    {
                        this.Cursor = Cursors.Hand;
                    }
                    else if (rightHandle.Contains(relativeLocation))
                    {
                        this.Cursor = Cursors.SizeWE;
                    }
                    else if (bottomHandle.Contains(relativeLocation))
                    {
                        this.Cursor = Cursors.SizeNS;
                    }
        

如果是Hand,就根据鼠标的移动来改变Border的位置,类似这样

                if (this.Cursor == Cursors.Hand)
                {
                    var group = new TransformGroup();
                    if (_previousTransform != null)
                    {
                        group.Children.Add(_previousTransform);
                    }
                    group.Children.Add(move);

                    this.RenderTransform = group;

                    OnDrag(new DraggableBorderDragEventArgs(currentLocation));
                }

如果是改变大小,那就是在鼠标在Border边上的时候改变大小。

                else if (this.Cursor == Cursors.SizeWE)
                {
                    double newwidth = width + (currentLocation.X - _previousLocation.X);
                    if (newwidth > this.MinWidth)
                    {
                        this.Width = newwidth;
                    }

                    OnResize(new DraggableBorderResizeEventArgs(new Size(this.Width, this.Height)));
                }
                else if (this.Cursor == Cursors.SizeNS)
                {
                    double newheight = height + (currentLocation.Y - _previousLocation.Y);
                    if (newheight > this.MinHeight)
                    {
                        this.Height = newheight;
                        scroll.Height = newheight - _titleLabel.ActualHeight - this.BorderThickness.Top - this.BorderThickness.Bottom;
                    }
                }

这样移动边框有几个问题就是:

1、  边框内的其它容器可能无法响应鼠标拖动事件。譬如我加一个Scroll在里面,托的时候边框也会跟着移动,很蛋疼。

于是我的解决办法是,在Border的最上面加了一个Label,然后只有托Label的时候才能拖动边框,这个设计基本就是借鉴了Windows的窗口设计思路。

在原容器内,Border的坐标是乱的,它的位置的改变并不能和坐标改变对应起来。但我如果想通过代码来改变Border位置怎么办呢?于是我基本是沿用了上面拖动窗口的思路,提供了如下函数,如果想改变Border的位置,那就直接调这个函数就了。

        public void DraggableBorderMove(double x_move, double y_move)
        {
            var move = new TranslateTransform(x_move, y_move);
            var group = new TransformGroup();
            if (_previousTransform != null)
                group.Children.Add(_previousTransform);
            group.Children.Add(move);
            this.RenderTransform = group;
            _previousLocation = new Point(_previousLocation.X + x_move, _previousLocation.Y + y_move);
            _previousTransform = this.RenderTransform;
        }

另外我还根据需求隐藏了原有的Content属性等而用一个StackPanel替代。另外还隐藏了Height等属性。

posted @ 2012-10-17 08:22  cuero  阅读(826)  评论(0编辑  收藏  举报