gpt4 book ai didi

c# - 防止 WPF 控件在 MouseMove 事件上重叠

转载 作者:行者123 更新时间:2023-11-30 12:21:05 27 4
gpt4 key购买 nike

我正在开发一个使用全屏的动态 C# WPF 应用程序(在 Windows 10 上)Grid .控件在运行时动态添加到网格(在 Dictionary<> 中管理),我最近添加了代码,使用 TranslateTransform 使用鼠标(也在运行时)沿网格移动控件。 (我现在怀疑它的可行性)。

有什么方法可以防止控件在移动时在网格上重叠或“共享空间”?换句话说,添加某种碰撞检测。我会使用 if 吗?检查控制裕度范围之类的声明?我的移动事件如下所示:

主窗口.xaml.cs:

public partial class MainWindow : Window
{
// Orientation variables:
public bool _isInDrag = false;
public Dictionary<object, TranslateTransform> PointDict = new Dictionary<object, TranslateTransform();
public Point _anchorPoint;
public Point _currentPoint;

public MainWindow()
{
InitializeComponent();
}

public static void Control_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
if (_isInDrag)
{
var element = sender as FrameworkElement;
element.ReleaseMouseCapture();
_isInDrag = false;
e.Handled = true;
}
}

public static void Control_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
var element = sender as FrameworkElement;
_anchorPoint = e.GetPosition(null);
element.CaptureMouse();
_isInDrag = true;
e.Handled = true;
}

public static void Control_MouseMove(object sender, MouseEventArgs e)
{
if (_isInDrag)
{
_currentPoint = e.GetPosition(null);
TranslateTransform tt = new TranslateTransform();
bool isMoved = false;
if (PointDict.ContainsKey(sender))
{
tt = PointDict[sender];
isMoved = true;
}
tt.X += _currentPoint.X - _anchorPoint.X;
tt.Y += (_currentPoint.Y - _anchorPoint.Y);
(sender as UIElement).RenderTransform = tt;
_anchorPoint = _currentPoint;
if (isMoved)
{
PointDict.Remove(sender);
}
PointDict.Add(sender, tt);
}
}
}

MainWindow.xaml(示例):

<Window x:Name="MW" x:Class="MyProgram.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:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:MyProgram"
mc:Ignorable="d"
Title="MyProgram" d:DesignHeight="1080" d:DesignWidth="1920" ResizeMode="NoResize" WindowState="Maximized" WindowStyle="None">

<Grid x:Name="MyGrid" />
<Image x:Name="Image1" Source="pic.png" Margin="880,862,0,0" Height="164" Width="162" HorizontalAlignment="Left" VerticalAlignment="Top" MouseLeftButtonDown="Control_MouseLeftButtonDown" MouseLeftButtonUp="Control_MouseLeftButtonUp" MouseMove="Control_MouseMove" />
<TextBox x:Name="Textbox1" Margin="440,560,0,0" HorizontalAlignment="Left" VerticalAlignment="Top" MouseLeftButtonDown="Control_MouseLeftButtonDown" MouseLeftButtonUp="Control_MouseLeftButtonUp" MouseMove="Control_MouseMove" />
</Window>

编辑:似乎用 TranslateTransform 移动控件不会更改该控件的边距。不知道为什么。

编辑 2:没有得到太多关注。如果有人需要澄清任何事情,请询问。

编辑 3: 很确定我不能使用 TranslateTransform因为它不会改变给定控件的边距。有替代方案吗?

编辑 4: 为那些想要复制和粘贴的人添加了一些“样板”代码。如果您对此有任何疑问,请告诉我。

最佳答案

长话短说:Demo from the bottom of this answer

如果您想在不向每个控件添加事件处理程序的情况下修改您的 UI,方法是使用 Adorners。装饰器(顾名思义)是装饰另一个控件以添加额外的视觉效果或根据您的情况添加功能的控件。装饰器驻留在 AdornerLayer 中,您可以自己添加或使用每个 WPF Window 已有的装饰器。 AdornerLayer 位于所有其他控件之上。

您从未提到当用户在控件重叠时松开鼠标按钮时会发生什么,所以如果发生这种情况,我只是将控件重置到其原始位置。

在这一点上,我通常会解释移动控件时要记住的内容,但由于您的原始示例甚至包含人们通常忘记的 CaptureMouse,我认为您无需进一步解释即可理解代码:)

您可能想要添加/改进的几件事:

  • 对齐网格功能(像素精确移动对于普通用户来说可能有点难以承受)
  • 在计算重叠时考虑RenderTransformLayoutTransform 和非矩形形状(如果需要)
  • 将编辑功能(启用、禁用等)移动到单独的控件中并添加专用的 AdornerLayer
  • 在编辑模式下禁用交互式控件(ButtonsTextBoxesComboBoxes 等)
  • 当用户按下 Esc 时取消移动
  • 限制移动到父容器的边界完成
  • active 装饰器移动到 AdornerLayer 的顶部
  • 让用户一次移动多个控件(通常通过使用 Ctrl 选择它们)

以前未回答的问题:

Are you saying controls are no longer assigned a margin when using TranslateTransform?

一点也不 - 您可以结合使用 Grid.RowGrid.ColumnMarginRenderTransform LayoutTransform 但是确定控件的实际显示位置将是一场噩梦。如果您坚持使用一个(在本例中例如 MarginLayoutTransform),使用和跟踪就会容易得多。如果您发现自己同时需要多个控件,则必须通过转换 (0, 0 )(ActualWidth, ActualHeight)TransformToAncestor。相信我,您不想去那里 - 保持简单,坚持使用其中之一。

下面的代码不是“如何移动东西的 chalice ”但它应该让您了解如何做以及您可以用它做些什么(调整大小、旋转、删除控件等)。布局完全基于控件的 LeftTop 边距。如果您愿意,将所有 Margins 换成 LayoutTransforms 应该不难,只要您保持一致即可。

移动装饰器

using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Shapes;

public class MoveAdorner : Adorner
{
// The parent of the adorned Control, in your case a Grid
private readonly Panel _parent;
// Same as "AdornedControl" but as a FrameworkElement
private readonly FrameworkElement _child;

// The visual overlay rectangle we can click and drag
private readonly Rectangle _rect;
// Our own collection of child elements, in this example only _rect
private readonly UIElementCollection _visualChildren;

private bool _down;
private Point _downPos;
private Thickness _downMargin;

private List<Rect> _otherRects;

protected override int VisualChildrenCount => _visualChildren.Count;

protected override Visual GetVisualChild(int index)
{
return _visualChildren[index];
}

public MoveAdorner(FrameworkElement adornedElement) : base(adornedElement)
{
_child = adornedElement;
_parent = adornedElement.Parent as Panel;
_visualChildren = new UIElementCollection(this,this);
_rect = new Rectangle
{
HorizontalAlignment = HorizontalAlignment.Stretch,
VerticalAlignment = VerticalAlignment.Stretch,
StrokeThickness = 1,
};

SetColor(Colors.LightGray);

_rect.MouseLeftButtonDown += RectOnMouseLeftButtonDown;
_rect.MouseLeftButtonUp += RectOnMouseLeftButtonUp;
_rect.MouseMove += RectOnMouseMove;

_visualChildren.Add(_rect);
}

private void SetColor(Color color)
{
_rect.Fill = new SolidColorBrush(color) {Opacity = 0.3};
_rect.Stroke = new SolidColorBrush(color) {Opacity = 0.5};
}

private void RectOnMouseMove(object sender, MouseEventArgs args)
{
if (!_down) return;

Point pos = args.GetPosition(_parent);
UpdateMargin(pos);
}

private void UpdateMargin(Point pos)
{
double deltaX = pos.X - _downPos.X;
double deltaY = pos.Y - _downPos.Y;

Thickness newThickness = new Thickness(_downMargin.Left + deltaX, _downMargin.Top + deltaY, 0, 0);

//Restrict to parent's bounds
double leftMax = _parent.ActualWidth - _child.ActualWidth;
double topMax = _parent.ActualHeight - _child.ActualHeight;

newThickness.Left = Math.Max(0, Math.Min(newThickness.Left, leftMax));
newThickness.Top = Math.Max(0, Math.Min(newThickness.Top, topMax));

_child.Margin = newThickness;

bool overlaps = CheckForOverlap();

SetColor(overlaps ? Colors.Red : Colors.Green);
}

// Check the current position for overlaps with all other controls
private bool CheckForOverlap()
{
if (_otherRects == null || _otherRects.Count == 0)
return false;

Rect thisRect = GetRect(_child);
foreach(Rect otherRect in _otherRects)
if (thisRect.IntersectsWith(otherRect))
return true;

return false;
}

private Rect GetRect(FrameworkElement element)
{
return new Rect(new Point(element.Margin.Left, element.Margin.Top), new Size(element.ActualWidth, element.ActualHeight));
}

private void RectOnMouseLeftButtonUp(object sender, MouseButtonEventArgs args)
{
if (!_down) return;

Point pos = args.GetPosition(_parent);

UpdateMargin(pos);

if (CheckForOverlap())
ResetMargin();

_down = false;
_rect.ReleaseMouseCapture();
SetColor(Colors.LightGray);
}

private void ResetMargin()
{
_child.Margin = _downMargin;
}

private void RectOnMouseLeftButtonDown(object sender, MouseButtonEventArgs args)
{
_down = true;
_rect.CaptureMouse();
_downPos = args.GetPosition(_parent);
_downMargin = _child.Margin;

// The current position of all other elements doesn't have to be updated
// while we move this one so we only determine it once
_otherRects = new List<Rect>();
foreach (FrameworkElement child in _parent.Children)
{
if (ReferenceEquals(child, _child))
continue;
_otherRects.Add(GetRect(child));
}
}

// Whenever the adorned control is resized or moved
// Update the size of the overlay rectangle
// (Not 100% necessary as long as you only move it)
protected override Size MeasureOverride(Size constraint)
{
_rect.Measure(constraint);
return base.MeasureOverride(constraint);
}

protected override Size ArrangeOverride(Size finalSize)
{
_rect.Arrange(new Rect(new Point(0,0), finalSize));
return base.ArrangeOverride(finalSize);
}
}

用法

private void DisableEditing(Grid theGrid)
{
// Remove all Adorners of all Controls
foreach (FrameworkElement child in theGrid.Children)
{
var layer = AdornerLayer.GetAdornerLayer(child);
var adorners = layer.GetAdorners(child);
if (adorners == null)
continue;
foreach(var adorner in adorners)
layer.Remove(adorner);
}
}

private void EnableEditing(Grid theGrid)
{
foreach (FrameworkElement child in theGrid.Children)
{
// Add a MoveAdorner for every single child
Adorner adorner = new MoveAdorner(child);

// Add the Adorner to the closest (hierarchically speaking) AdornerLayer
AdornerLayer.GetAdornerLayer(child).Add(adorner);
}
}

演示 XAML

<Grid>
<Button Content="Enable Editing" HorizontalAlignment="Left" Margin="10,10,0,0" VerticalAlignment="Top" Width="100" Click="BtnEnable_Click"/>
<Button Content="Disable Editing" HorizontalAlignment="Left" Margin="115,10,0,0" VerticalAlignment="Top" Width="100" Click="BtnDisable_Click"/>

<Grid Name="grid" Background="AliceBlue" Margin="10,37,10,10">
<Button Content="Button" HorizontalAlignment="Left" Margin="83,44,0,0" VerticalAlignment="Top" Width="75"/>
<Ellipse Fill="#FFF4F4F5" HorizontalAlignment="Left" Height="100" Margin="207,100,0,0" Stroke="Black" VerticalAlignment="Top" Width="100"/>
<Rectangle Fill="#FFF4F4F5" HorizontalAlignment="Left" Height="100" Margin="33,134,0,0" Stroke="Black" VerticalAlignment="Top" Width="100"/>
</Grid>
</Grid>

预期结果

当禁用编辑时,无法移动控件,可以无障碍地单击/交互交互式控件。启用编辑模式后,每个控件都会覆盖一个可以移动的装饰器。如果目标位置与另一个控件重叠,装饰器将变为红色,并且如果用户松开鼠标按钮,边距将重置为初始位置。

Quick Demo

关于c# - 防止 WPF 控件在 MouseMove 事件上重叠,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/47610982/

27 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com