背景
看到趋时软件的公众号文章(WPF自定义Panel:让拖拽变得更简单),发现可以不通过Drag的方法来实现ListBox控件的拖动,而是通过对控件的坐标相加减去实现控件的位移等判断,因此根据文章里面的代码,边理解边学习的写了这一篇博客,里面结合一定自己的理解,而且存在很多问题没能解决,仅实现了简单的流程,如有大佬可以指点,不慎感激!!
现代码实现效果
代码文件
MainWindow.xaml
<Window
x:Class="DragListDemo2.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:DragListDemo2"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:p="clr-namespace:DragListDemo2.Panels"
Title="MainWindow"
Width="800"
Height="450"
mc:Ignorable="d">
<Grid>
<p:DragStackPanel Background="White">
<Button
Height="50"
Margin="0"
Content="ceshi1" />
<Button
Height="50"
Margin="0"
Content="ceshi2" />
<Button
Height="50"
Margin="0"
Content="ceshi3" />
<TextBox
Height="50"
HorizontalContentAlignment="Center"
VerticalContentAlignment="Center"
Text="ceshikankan" />
</p:DragStackPanel>
<!--<ItemsControl HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<p:DragStackPanel Background="Red" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>-->
</Grid>
</Window>
DragStackPanel.cs
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
namespace DragListDemo2.Panels
{
public class DragStackPanel : Panel
{
#region 自定义控件排序的方法
/// <summary>
/// 获取或设置方向
/// </summary>
public Orientation Orientation
{
get { return (Orientation)GetValue(OrientationProperty); }
set { SetValue(OrientationProperty, value); }
}
public static readonly DependencyProperty OrientationProperty =
DependencyProperty.Register("Orientation", typeof(Orientation), typeof(DragStackPanel), new PropertyMetadata(Orientation.Vertical));
protected override Size MeasureOverride(Size availableSize)
{
var panelDesiredSize = new Size();
foreach (UIElement child in InternalChildren)
{
//让内部控件去调用Measure,去计算轮廓
child.Measure(availableSize);
if (this.Orientation == Orientation.Horizontal)
{
panelDesiredSize.Width += child.DesiredSize.Width;
panelDesiredSize.Height = double.IsInfinity(availableSize.Height) ? child.DesiredSize.Height : availableSize.Height;
}
else
{
panelDesiredSize.Width = double.IsInfinity(availableSize.Width) ? child.DesiredSize.Width : availableSize.Width;
panelDesiredSize.Height += child.DesiredSize.Height;
}
}
return panelDesiredSize;
}
protected override Size ArrangeOverride(Size finalSize)
{
double x = 0, y = 0;
foreach (FrameworkElement child in InternalChildren)
{
// 坐标
var position = new Point(x, y);
// 宽度
var width = child.DesiredSize.Width;
// 高度
var height = child.DesiredSize.Height;
// 通过排列方向计算宽度和高度
if (this.Orientation == Orientation.Vertical)
{
width = finalSize.Width;
}
else
{
height = finalSize.Height;
}
// 尺寸
var size = new Size(width, height);
// 排列位置及尺寸
child.Arrange(new Rect(position, size));
// 计算位置
if (this.Orientation == Orientation.Horizontal)
{
x += child.DesiredSize.Width;
}
else
{
y += child.DesiredSize.Height;
}
if (child.Equals(draggingElement))
{
// 获取当前正在拖拽元素的位置坐标
var dragElementPosition = GetDraggingElementMovingPosition(child);
if (dragElementPosition != default)
{
// 处理拖拽元素坐标
var offset = new Point(dragElementPosition.X - position.X, dragElementPosition.Y - position.Y);
child.RenderTransform = new TranslateTransform(offset.X, offset.Y);
}
}
}
return finalSize;
}
#endregion
private FrameworkElement hitElement;
private FrameworkElement draggingElement;
private Point mouseRelativePosition;
private int draggingElementzIndex;
protected override void OnPreviewMouseLeftButtonDown(MouseButtonEventArgs e)
{
// 获取鼠标相对于Panel的坐标
var mousePosition = e.GetPosition(this);
// 通过命中测试获取当前鼠标位置下的元素
var hitTestResult = this.InputHitTest(mousePosition) as FrameworkElement;
// 通过命中测试结果找到当前拖拽的控件子项
draggingElement = FindChild(hitTestResult);
if (draggingElement != null && this.InternalChildren.Contains(draggingElement))
{
// 记录鼠标相对位置,以供后续使用
mouseRelativePosition = e.GetPosition(draggingElement);
// 暂存ZIndex
draggingElementzIndex = Panel.GetZIndex(draggingElement);
// 将ZIndex置顶
Panel.SetZIndex(draggingElement, this.InternalChildren.Count);
// 添加遮罩,防止拖拽时覆盖
//AddOverlay(draggingElement);
e.Handled = true;
}
base.OnPreviewMouseLeftButtonDown(e);
}
protected override void OnPreviewMouseMove(MouseEventArgs e)
{
var mousePosition = e.GetPosition(this);
if (e.LeftButton == MouseButtonState.Pressed && draggingElement != null)
{
// 当前拖拽控件置为不可鼠标命中,以供命中下一层的换位控件
draggingElement.IsHitTestVisible = false;
// 判断当前拖拽的控件是否为顶层控件
if (Panel.GetZIndex(draggingElement) == this.InternalChildren.Count)
{
// 计算出当前拖拽控件相对于this的位置(控件左上角)
var targetPosition = new Point(mousePosition.X - mouseRelativePosition.X - draggingElement.Margin.Left, mousePosition.Y - mouseRelativePosition.Y - draggingElement.Margin.Top);
// 获取当前拖拽控件在this中的原始位置
var draggingElementOriginalPosition = GetDraggingElementOriginPosition(draggingElement);
// 计算拖拽控件移动时的偏移量
var offset = new Point(targetPosition.X - draggingElementOriginalPosition.X, targetPosition.Y - draggingElementOriginalPosition.Y);
// 应用位移
draggingElement.RenderTransform = new TranslateTransform(offset.X, offset.Y);
}
// 命中当前拖拽控件的下一层控件
var hitTestResult = this.InputHitTest(mousePosition) as FrameworkElement;
// 查找被命中的下一层换位控件
hitElement = FindChild(hitTestResult);
// 判断是否有效
if (hitElement != null && this.InternalChildren.Contains(hitElement))
{
// 应用换位
MoveChild(draggingElement, hitElement);
}
e.Handled = true;
}
base.OnPreviewMouseMove(e);
}
protected override void OnPreviewMouseLeftButtonUp(MouseButtonEventArgs e)
{
if (draggingElement == null) return;
mouseRelativePosition = default;
//RemoveOverlay(draggingElement);
Panel.SetZIndex(draggingElement, draggingElementzIndex);
draggingElement.IsHitTestVisible = true;
draggingElement.RenderTransform = null;
draggingElement = null;
e.Handled = true;
base.OnPreviewMouseLeftButtonUp(e);
}
#region Method
/// <summary>
/// 找到拖拽的控件
/// </summary>
/// <param name="frameworkElement"></param>
/// <returns></returns>
private FrameworkElement FindChild(FrameworkElement frameworkElement)
{
if (frameworkElement == null) return null;
DependencyObject parent = VisualTreeHelper.GetParent(frameworkElement);
if (parent != null && parent is FrameworkElement frameworkParent)
{
if (frameworkParent != this)
{
var result = FindChild(frameworkParent);
return result;
}
else
{
return frameworkElement;
}
}
return null;
}
/// <summary>
/// 获得拖动控件在panel中的坐标点
/// </summary>
/// <param name="frameworkElement"></param>
/// <returns></returns>
private Point GetDraggingElementMovingPosition(FrameworkElement frameworkElement)
{
Point position = frameworkElement.TranslatePoint(new Point(), this);
return position;
}
/// <summary>
/// 获得拖动控件的原始坐标
/// </summary>
/// <param name="frameworkElement"></param>
/// <returns></returns>
private Point GetDraggingElementOriginPosition(FrameworkElement frameworkElement)
{
Point position = frameworkElement.TranslatePoint(new Point(), this);
if(frameworkElement.RenderTransform==null) return position;
return new Point(position.X - frameworkElement.RenderTransform.Value.OffsetX, position.Y - frameworkElement.RenderTransform.Value.OffsetY);
}
private void SetDraggingElementMovingPosition(FrameworkElement child, Point dragPosition)
{
mouseRelativePosition = child.TranslatePoint(dragPosition, this);
}
private void MoveChild(FrameworkElement element1, FrameworkElement element2)
{
var index1 = this.InternalChildren.IndexOf(element1);
var index2 = this.InternalChildren.IndexOf(element2);
if (index1 >= 0 && index2 >= 0)
{
this.InternalChildren.RemoveAt(index1);
this.InternalChildren.Insert(index2, element1);
}
}
#endregion
}
}
现存待解决问题
- 控件拖拽时跨越边界问题。
- 跨越控件拖拽问题。
- panel必须设置Background不透明,才能实现OnPreviewMouseMove事件的监控。
完结
研究暂时告一段落,等后面有时间时再深入去理解公众号中的代码,希望这一点经验可以给你带来帮助