本文借鉴了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等属性。