我的WPF应用程序有一个UserControl
,它应该看起来像一个弹出窗口,但它不是一个窗口。控件不会从Window
类下降的原因是因为它包含第三方虚拟屏幕键盘,并且该控件必须与TextBox
控件位于同一窗口中单击其按钮时发送输入字符。如果键盘控件不在同一窗口中,则它甚至看不到TextBox
控件。
我遇到的问题是拖动对话框时表现糟糕。它足够慢,鼠标离开拖动区域,它停止跟随鼠标。我需要一个更好的方法。
以下是xaml对控件的摘录:
<Grid Name="LayoutRoot">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Border Background="{DynamicResource PopupBackground}"
BorderBrush="{DynamicResource PopupBorder}"
BorderThickness="5,5,5,0"
MouseLeftButtonDown="Grid_MouseLeftButtonDown"
MouseLeftButtonUp="Grid_MouseLeftButtonUp"
MouseMove="Grid_MouseMove">
. . .
</Border>
</Grid>
这是鼠标事件处理程序:
private void Grid_MouseLeftButtonDown( object sender, MouseButtonEventArgs e ) {
Canvas canvas = Parent as Canvas;
if ( canvas == null ) {
throw new InvalidCastException( "The parent of a KeyboardPopup control must be a Canvas." );
}
DraggingControl = true;
CurrentMousePosition = e.GetPosition( canvas );
e.Handled = true;
}
private void Grid_MouseLeftButtonUp( object sender, MouseButtonEventArgs e ) {
Canvas canvas = Parent as Canvas;
if ( canvas == null ) {
throw new InvalidCastException( "The parent of a KeyboardPopup control must be a Canvas." );
}
if ( DraggingControl ) {
Point mousePosition = e.GetPosition( canvas );
// Correct the mouse coordinates in case they go off the edges of the control
if ( mousePosition.X < 0.0 ) mousePosition.X = 0.0; else if ( mousePosition.X > canvas.ActualWidth ) mousePosition.X = canvas.ActualWidth;
if ( mousePosition.Y < 0.0 ) mousePosition.Y = 0.0; else if ( mousePosition.Y > canvas.ActualHeight ) mousePosition.Y = canvas.ActualHeight;
// Compute the new Left & Top coordinates of the control
Canvas.SetLeft( this, Left += mousePosition.X - CurrentMousePosition.X );
Canvas.SetTop( this, Top += mousePosition.Y - CurrentMousePosition.Y );
}
e.Handled = true;
}
private void Grid_MouseMove( object sender, MouseEventArgs e ) {
Canvas canvas = Parent as Canvas;
if ( canvas == null ) {
// It is not. Throw an exception
throw new InvalidCastException( "The parent of a KeyboardPopup control must be a Canvas." );
}
if ( DraggingControl && e.LeftButton == MouseButtonState.Pressed ) {
Point mousePosition = e.GetPosition( canvas );
// Correct the mouse coordinates in case they go off the edges of the control
if ( mousePosition.X < 0.0 ) mousePosition.X = 0.0; else if ( mousePosition.X > canvas.ActualWidth ) mousePosition.X = canvas.ActualWidth;
if ( mousePosition.Y < 0.0 ) mousePosition.Y = 0.0; else if ( mousePosition.Y > canvas.ActualHeight ) mousePosition.Y = canvas.ActualHeight;
// Compute the new Left & Top coordinates of the control
Canvas.SetLeft( this, Left += mousePosition.X - CurrentMousePosition.X );
Canvas.SetTop ( this, Top += mousePosition.Y - CurrentMousePosition.Y );
CurrentMousePosition = mousePosition;
}
e.Handled = true;
}
请注意,控件必须放在使用它的窗口中的Canvas
内。
我无法使用DragMove
,因为它是Window
类的一种方法,此类来自UserControl
。如何提高此控件拖动的性能?我是否必须使用Win32 API?
答案 0 :(得分:6)
您只需使用MouseDragElementBehavior。
<强> UPD 强>
关于MouseDragElementBehavior
行为的重要事项:
MouseDragElementBehavior行为不适用于处理MouseClick事件的任何控件(例如,Button,TextBox和ListBox控件)。如果您需要能够拖动其中一种类型的控件,请将该控件设置为可以拖动的控件的子项(例如,边框)。然后,您可以将MouseDragElementBehavior行为应用于父元素。
您还可以实现自己的拖动行为:
public class DragBehavior : Behavior<UIElement>
{
private Point elementStartPosition;
private Point mouseStartPosition;
private TranslateTransform transform = new TranslateTransform();
protected override void OnAttached()
{
Window parent = Application.Current.MainWindow;
AssociatedObject.RenderTransform = transform;
AssociatedObject.MouseLeftButtonDown += (sender, e) =>
{
elementStartPosition = AssociatedObject.TranslatePoint( new Point(), parent );
mouseStartPosition = e.GetPosition(parent);
AssociatedObject.CaptureMouse();
};
AssociatedObject.MouseLeftButtonUp += (sender, e) =>
{
AssociatedObject.ReleaseMouseCapture();
};
AssociatedObject.MouseMove += (sender, e) =>
{
Vector diff = e.GetPosition( parent ) - mouseStartPosition;
if (AssociatedObject.IsMouseCaptured)
{
transform.X = diff.X;
transform.Y = diff.Y;
}
};
}
}
答案 1 :(得分:4)
根据@ DmitryMartovoi的回答中的信息,我想出了一种方法来完成这项工作。我仍然给德米特里一个+1,因为如果没有他的贡献,我将无法弄清楚这一点。
我所做的是在TranslateTransform
构造函数中创建了UserControl's
并将其分配给RenderTransform
属性:
RenderTransform = new TranslateTransform();
在XAML中,我将用户点击的Border
控件命名为拖动整个控件:
<Border Background="{DynamicResource PopupBackground}"
BorderBrush="{DynamicResource PopupBorder}"
BorderThickness="5,5,5,0"
MouseLeftButtonDown="Grid_MouseLeftButtonDown"
MouseLeftButtonUp="Grid_MouseLeftButtonUp"
MouseMove="Grid_MouseMove"
Name="TitleBorder">
. . .
</Border>
最后,我修改了各种鼠标事件处理程序,如下所示:
private void Grid_MouseLeftButtonDown( object sender, MouseButtonEventArgs e ) {
CurrentMousePosition = e.GetPosition( Parent as Window );
TitleBorder.CaptureMouse();
}
private void Grid_MouseLeftButtonUp( object sender, MouseButtonEventArgs e ) {
if ( TitleBorder.IsMouseCaptured ) {
TitleBorder.ReleaseMouseCapture();
}
}
private void Grid_MouseMove( object sender, MouseEventArgs e ) {
Vector diff = e.GetPosition( Parent as Window ) - CurrentMousePosition;
if ( TitleBorder.IsMouseCaptured ) {
( RenderTransform as TranslateTransform ).X = diff.X;
( RenderTransform as TranslateTransform ).Y = diff.Y;
}
}
这很好用。拖动UserControl
时,整个Border
及其所有内容都会顺利移动,与鼠标保持同步。如果您点击其表面上的任何其他位置,整个UserControl
都不会移动。
再次感谢@DmitryMartovoi提供他提供的代码。
编辑:我正在编辑这个答案,因为上面的代码虽然有效但并不完美。它的缺点是,当您单击标题栏区域并开始拖动之前,控件将弹回屏幕上的原始位置。这很烦人,完全错了。我提出的方法实际上完美无缺,首先将控件放在Canvas
中。控件的父级是Canvas
或以下代码不起作用很重要。我也停止使用RenderTransform
。我添加了一个名为canvas
的私有属性Canvas
。我在弹出控件中添加了一个Loaded
事件处理程序来进行一些重要的初始化:
private void KeyboardPopup_Loaded( object sender, RoutedEventArgs e ) {
canvas = Parent as Canvas;
if ( canvas == null ) {
throw new InvalidCastException( "The parent of a KeyboardPopup control must be a Canvas." );
}
}
完成所有这些后,这里是修改过的Mouse事件处理程序:
private void TitleBorder_MouseLeftButtonDown( object sender, MouseButtonEventArgs e ) {
StartMousePosition = e.GetPosition( canvas );
TitleBorder.CaptureMouse();
}
private void TitleBorder_MouseLeftButtonUp( object sender, MouseButtonEventArgs e ) {
if ( TitleBorder.IsMouseCaptured ) {
Point mousePosition = e.GetPosition( canvas );
Canvas.SetLeft( this, Canvas.GetLeft( this ) + mousePosition.X - StartMousePosition.X );
Canvas.SetTop ( this, Canvas.GetTop ( this ) + mousePosition.Y - StartMousePosition.Y );
canvas.ReleaseMouseCapture();
}
}
private void TitleBorder_MouseMove( object sender, MouseEventArgs e ) {
if ( TitleBorder.IsMouseCaptured && e.LeftButton == MouseButtonState.Pressed ) {
Point mousePosition = e.GetPosition( canvas );
// Compute the new Left & Top coordinates of the control
Canvas.SetLeft( this, Canvas.GetLeft( this ) + mousePosition.X - StartMousePosition.X );
Canvas.SetTop ( this, Canvas.GetTop ( this ) + mousePosition.Y - StartMousePosition.Y );
StartMousePosition = mousePosition;
}
}
当您单击标题栏以再次移动它时,控件将保留在您放置它的位置,并且只有在您单击标题栏时它才会移动。单击控件中的任何其他位置都不会执行任何操作,并且拖动平稳且响应迅速。
答案 2 :(得分:1)
http://www.codeproject.com/Tips/442276/Drag-and-Drop-WPF-Controls 这是我花了很多时间后得到的很棒的解决方案。虽然此处显示的示例是常规控件,但经过一些更改后,您也可以使其适用于用户控件。
答案 3 :(得分:0)
为此,我的解决方案是@DmitryMartovoi和以下线程之间的混合:https://www.codeproject.com/Questions/1014138/Csharp-WPF-RenderTransform-resets-on-mousedown
我唯一的变化是来自@DmitryMartovoi的答案是在鼠标左键按下的地方。首次单击时,这将阻止其传送。为此,您还需要Systems.Windows.Interactivity.WPF
Nuget程序包。
AssociatedObject.MouseLeftButtonDown += (sender, e) =>
{
var mousePos = e.GetPosition(parent);
mouseStartPosition = new Point(mousePos.X-transform.X, mousePos.Y-transform.Y);
AssociatedObject.CaptureMouse();
};