我正在使用全屏Grid
的动态C#WPF应用程序(在Windows 10上)。控件在运行时动态添加到网格中(在Dictionary<>
中管理)我最近添加了代码,使用TranslateTransform
(使用鼠标)(也在运行时)沿着网格移动控件(我现在怀疑的可行性)。
有没有办法可以防止控件在移动时重叠或“共享空间”?换句话说,添加某种碰撞检测。我会使用if
语句来检查控制边距范围吗?我的移动事件如下所示:
MainWindow.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:为想要复制的人添加了一些“样板”代码。糊。如果您对此有任何疑问,请与我们联系。
答案 0 :(得分:1)
TL; DR:Demo from the bottom of this answer
如果要在不向每个控件添加事件处理程序的情况下修改UI,则可以使用Adorners
。 Adorners(顾名思义)控件装饰另一个控件以添加其他视觉效果或在您的案例功能中。 Adorners位于AdornerLayer
,您可以自行添加或使用每个WPF Window
已有的。 AdornerLayer
位于所有其他控件之上。
你从未提到当用户在控件重叠时松开鼠标按钮会发生什么,所以如果发生这种情况,我只需将控件重置为原始位置。
此时我通常会解释在移动控件时应该记住什么,但由于您的原始示例甚至包含CaptureMouse
人通常会忘记,我认为您将理解代码而无需进一步解释:)< / p>
您可能想要添加/改进的一些事项:
RenderTransform
,LayoutTransform
和非矩形形状(如果需要)AdornerLayer
Buttons
,TextBoxes
,ComboBoxes
等)AdornerLayer
以前未回答的问题:
您是说在使用TranslateTransform时不再为控件分配边距?
完全没有 - 您可以使用Grid.Row
,Grid.Column
,Margin
,RenderTransform
和 LayoutTransform
的组合,但那么确定控件实际显示的位置将是一场噩梦。如果您坚持使用(例如Margin
或LayoutTransform
),则更容易使用和跟踪。如果您发现自己处于需要多个同时的情况,则必须通过转换(0, 0)
和{来确定控件的角来找到实际位置{1}} (ActualWidth, ActualHeight)
。相信我,你不想去那里 - 保持简单,坚持其中一个。
下面的代码不是“如何移动东西的圣杯”但它应该让你知道如何做到这一点以及你可以用它做什么(调整大小,旋转,删除控件等)。布局完全基于控件的TransformToAncestor
和Left
边距。如果您愿意,只要保持一致,就不应该为Top
换取所有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);
}
}
演示XAML
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);
}
}
预期结果
禁用编辑时无法移动控件,可以无阻碍地单击/交互交互式控件。启用编辑模式后,每个控件都会覆盖一个可以移动的装饰器。如果目标位置与另一个控件重叠,则如果用户放开鼠标按钮,则装饰器将变为红色并且边距将重置为初始位置。
答案 1 :(得分:0)
没有其他方法可以检查您移动的地方是否存在控制权。
由于你要移动很多UI元素,最好使用canvas而不是grid,你可以使用Top和Left参数布局元素。
以下是修改后的代码
@Query("DELETE FROM User.TABLE_NAME")
public void nukeTable();
@Query("DELETE FROM " + User.TABLE_NAME + " WHERE " + User.COLUMN_ID + " = :id")
int deleteById(long id);
的Xaml
public partial class MainWindow : Window
{
public bool _isInDrag = false;
public Dictionary<object, TranslateTransform> PointDict = new Dictionary<object, TranslateTransform>();
public Point _anchorPoint;
public Point _currentPoint;
public MainWindow()
{
InitializeComponent();
}
public void Control_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
if (_isInDrag)
{
var element = sender as FrameworkElement;
element.ReleaseMouseCapture();
Panel.SetZIndex(element, 0);
_isInDrag = false;
e.Handled = true;
}
}
public void Control_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
var element = sender as FrameworkElement;
_anchorPoint = e.GetPosition(null);
element.CaptureMouse();
Panel.SetZIndex(element, 10);
_isInDrag = true;
e.Handled = true;
}
public void Control_MouseMove(object sender, MouseEventArgs e)
{
if (_isInDrag)
{
_currentPoint = e.GetPosition(null);
FrameworkElement fw = sender as FrameworkElement;
if (fw != null)
{
FrameworkElement fwParent = fw.Parent as FrameworkElement;
if (fwParent != null)
{
Point p = new Point(_currentPoint.X - _anchorPoint.X + Canvas.GetLeft((sender as UIElement)), _currentPoint.Y - _anchorPoint.Y + Canvas.GetTop((sender as UIElement)));
List<HitTestResult> lst = new List<HitTestResult>()
{
VisualTreeHelper.HitTest(fwParent , p),
VisualTreeHelper.HitTest(fwParent, new Point(p.X + fw.Width, p.Y)),
VisualTreeHelper.HitTest(fwParent, new Point(p.X, p.Y + fw.Height)),
VisualTreeHelper.HitTest(fwParent, new Point(p.X + fw.Width, p.Y +fw.Height)),
};
bool success = true;
foreach (var item in lst)
{
if (item != null)
{
if (item.VisualHit != sender && item.VisualHit != fwParent && fw.IsAncestorOf(item.VisualHit) == false)
{
success = false;
break;
}
}
}
if (success)
{
Canvas.SetTop((sender as UIElement), p.Y);
Canvas.SetLeft((sender as UIElement), p.X);
_anchorPoint = _currentPoint;
}
}
}
}
}
}