I try to automate tests of a drag and drop behavior in a WPF application. One custom control is dragged on another:
Drag and drop behavior implemented in the usual WPF way:
<UserControl ...
MouseMove="ToolboxModule_OnMouseMove">
private void ToolboxModule_OnMouseMove(object sender, MouseEventArgs e)
{
base.OnMouseMove(e);
var data = new DataObject();
data.SetData("ModuleDescription", DataContext);
if (e.LeftButton == MouseButtonState.Pressed)
DragDrop.DoDragDrop(this, data, DragDropEffects.Copy);
}
<UserControl ...
Drop="WorkspaceView_OnDrop" AllowDrop="True">
private void WorkspaceView_OnDrop(object sender, DragEventArgs e)
{
var dropped = e.Data.GetData("ModuleDescription");
var viewModel = (WorkspaceViewModel)DataContext;
if (viewModel.ChainVm.AddModuleCommand.CanExecute(dropped))
viewModel.ChainVm.AddModuleCommand.Execute(dropped);
}
But when I try to automate this with WinAppDriver, the drag and drop does not work. Cursor shape is not changed and nothing happens.
I've tried several approaches:
Actions Drag and Drop
var moduleControl = mainWindow.GetToolboxModuleControl(moduleName);
var actions = new Actions(_session);
actions.DragAndDrop(moduleControl, mainWindow.WorkspaceControl).Perform();
Actions click and hold
var moduleControl = mainWindow.GetToolboxModuleControl(moduleName);
var actions = new Actions(_session);
actions.ClickAndHold(moduleControl).MoveByOffset(200, 0).Release().Perform();
Driver mouse operations (from example)
_session.Mouse.MouseMove(moduleControl.Coordinates, 50, 50);
_session.Mouse.MouseDown(null);
_session.Mouse.MouseMove(mainWindow.WorkspaceControl.Coordinates, 100, 100);
_session.Mouse.MouseUp(null);
Driver mouse operations with delays
_session.Mouse.MouseMove(moduleControl.Coordinates, 50, 50);
Thread.Sleep(1000);
_session.Mouse.MouseDown(null);
Thread.Sleep(1000);
_session.Mouse.MouseMove(mainWindow.WorkspaceControl.Coordinates, 100, 100);
Thread.Sleep(1000);
_session.Mouse.MouseUp(null);
Nothing works. Any ideas what could be wrong and how to fix it?
When I try to move the app window by dragging it's title bar via WinAppDriver, it successfully moves the window. So the dragging operations technically work, but not in the case of dragging a control within the window.
Found the answer.
WinAppDriver doesn't move the mouse, but "teleport" it. It means mouse cursor is not dragged all the way to the target with finite speed, it is being jumped from start to end position without any intermediate positions.
In this particular case it causes the problem. What actually happens:
Cursor teleported to the center of first element. MouseMove and
other mouse movement events are firing on first element.
DragDrop.DoDragDrop method is not executed, because left mouse
button is not pressed.
Left mouse button is pressed. MouseDown, Click and other related event are firing on first element. DragDrop.DoDragDrop method is
not executed, because there is no mouse movement.
Cursor jumped to second element without touching the first. MouseMove and other mouse movement events are firing on second
element only. Since no MouseMove event is firing on first element
with left mouse button pressed, the drag and drop process never
starts.
Solutions are simple: move the cursor within first element boundaries before jump to the second or change the event, where drag and drop process starts (MouseDown instead of MouseMove, for example). I chose the latter:
<UserControl ...
MouseDown="ToolboxModule_OnMouseDown">
private void ToolboxModule_OnMouseDown(object sender, MouseButtonEventArgs e)
{
base.OnMouseDown(e);
var data = new DataObject();
data.SetData("ModuleDescription", DataContext);
DragDrop.DoDragDrop(this, data, DragDropEffects.Copy);
}
Related
I made Event for Grid in WPF C#.
The MouseMove Event.
I want to Trigger MouseMove event When mouse Left button is Pressed and Keep the Event even when mouse is Out of Grid Or even out of the main Window.
When Button is Pressed Keep the Mousemove event for the Grid All over the screen Until Button is Releasd.
consider this is Mouse move event Method for the Grid
private void Grid_MouseMove(object sender, MouseEventArgs e)
{
if (e.LeftButton == MouseButtonState.Pressed) // Only When Left button is Pressed.
{
// Perform operations And Keep it Until mouse Button is Released.
}
}
The Goal is to Rotate 3D model When User Hold Left button and to rotate Model while he is moving the mouse until button releases.
This is for To make the Program and Rotation Eeasier for the user. Especialy performing Long rotations cause mouse get out of the grid.
I tried to use while but it fails and you know its because of single threaded.
so the way im thinking is to somehow expand a new Grid all over the screen when button is pressed inside the original Grid and to keep it until release.
And of course Dummy Grid witch is Hidden.
What are you trying to do is to work with the stream of events. As far as I understand your flow supposed to be the following:
Left mouse button pressed
Mouse moved1 (rotate model)
Mouse moved2 (rotate model)
...
N. Left mouse up (stop rotation)
There is an interesting concept which is called Reactive Programming.
http://rxwiki.wikidot.com/101samples
There is a library for C# (Reactive-Extensions)
Your code could look like this one:
// create event streams for mouse down/up/move using reflection
var mouseDown = from evt in Observable.FromEvent<MouseButtonEventArgs>(image, "MouseDown")
select evt.EventArgs.GetPosition(this);
var mouseUp = from evt in Observable.FromEvent<MouseButtonEventArgs>(image, "MouseUp")
select evt.EventArgs.GetPosition(this);
var mouseMove = from evt in Observable.FromEvent<MouseEventArgs>(image, "MouseMove")
select evt.EventArgs.GetPosition(this);
// between mouse down and mouse up events
// keep taking pairs of mouse move events and return the change in X, Y positions
// from one mouse move event to the next as a new stream
var q = from start in mouseDown
from pos in mouseMove.StartWith(start).TakeUntil(mouseUp)
.Let(mm => mm.Zip(mm.Skip(1), (prev, cur) =>
new { X = cur.X - prev.X, Y = cur.Y - prev.Y }))
select pos;
// subscribe to the stream of position changes and modify the Canvas.Left and Canvas.Top
// property of the image to achieve drag and drop effect!
q.ObserveOnDispatcher().Subscribe(value =>
{
//rotate your model here. The new mouse coordinates
//are stored in value object
RotateModel(value.X, value.Y);
});
Actually building a stream of mouse events is a very classical example of using RX.
http://theburningmonk.com/2010/02/linq-over-events-playing-with-the-rx-framework/
You can subscribe on this stream of events in the Windows constructor, so you don't depend on the Grid, and you don't have to draw fake Grid!
Some nice links to start with:
The Rx Framework by example
Rx. Introduction
I have a program which uses the mouse's .X position (relative to the form boundaries) to change the .Left value of a button Object.
The problem is that I have this button over the top of other objects like Picture Boxes, Buttons, TrackBar's etc. and when I hove over these other elements, the button stops tracking the mouse's .X position.
How can I make the button track the mouse movement regardless of the mouse doing other stuff on the form too? (I also need to interact with the other elements at the same time too).
My Code:
/* i create the mousemove tracking event */
this.MouseMove += new MouseEventHandler(btnBat1_MouseMove);
/* and use it by making a new method */
public void btnBat1_MouseMove(Object sender, MouseEventArgs e)
{
// I use the variable mouseXCo to change the button1.Left value.
mouseXCo = e.X;
}
Thanks in advance guys :-)
I am using a PNG image as a cursor by translating the image with the mouse coordinates and it moves very nice. The problem is when I tried to click a button, for some reason the button flashes as I move the mouse over it. The button goes inactive or not focused and mouse clicks are not working until for some fraction of a second where if I keep moving the mouse and clicking over the button, then the mouse click works. I tried also to play with the z-index having no results.
When I tried the hidden cursor without the image, the mouse events works without problems. This is the code I am using:
TranslateTransform T = new TranslateTransform();
public GameTitle()
{
InitializeComponent();
Mouse.OverrideCursor = Cursors.None;
this.MouseMove += GameTitle_MouseMove;
CompositionTarget.Rendering +=CompositionTarget_Rendering;
}
void CompositionTarget_Rendering(object sender, EventArgs e)
{
CursorImage.RenderTransform = T;
}
void GameTitle_MouseMove(object sender, MouseEventArgs e)
{
T.X = e.GetPosition(this).X;
T.Y = e.GetPosition(this).Y;
}
This is with the image cursor:
And this is without the image cursor and the mouse cursor hidden:
How can I make the buttons works with the image cursor?
Thanks in advance for your help!
While I was writing the question, I keep searching for answers and I found this nice software (http://www.aha-soft.com/artcursors/cur-editor.htm) that helped me reading a PNG image and saving it as a Cursor (*.cur) file. Then I added the file to the project resources in Visual Studio and added the following code as described in one of the answer in https://stackoverflow.com/a/8715560/1278771:
Mouse.OverrideCursor = new Cursor(new System.IO.MemoryStream(MyNameSpace.Properties.Resources.TheResourceName));
Now, it works very nice!:
I have a program with two WPF treeviews that allow dragging and dropping between the two. The problem is, it can be annoying to open / close items on the treeviews because moving the mouse just one pixel while holding the left mouse button triggers the drag / drop functionality. Is there some way to specify how far the mouse should move before it's considered a drag / drop?
There's a system parameter for this. If you have
Point down = {where mouse down event happened}
Point current = {position in the MouseMove eventargs}
then the mouse has moved the minimum drag distance if
Math.Abs(current.X - down.X) >= SystemParameters.MinimumHorizontalDragDistance ||
Math.Abs(current.Y - down.Y) >= SystemParameters.MinimumVerticalDragDistance
Just build a little buffer into your code that determines when the drag starts.
flag mouse down
on mouse move - check for mouse down.. if yes, check to see if its moved farther than whatever buffer you specify (3 pixels is probably good)
if it has, start the drag.
Following this article for Drag and Drop implementation, you would have to handle 2 mouse events in order to delay the dragging until the mouse has moved a certain distance. First, add a handler for PreviewMouseDown which stores the initial mouse position relative to your control. Don't use the MouseDown event because it is a bubbling event and may have been handled by a child control before reaching your control.
public class DraggableControl : UserControl
{
private Point? _initialMousePosition;
public DraggableControl()
{
PreviewMouseDown += OnPreviewMouseDown;
}
private void OnPreviewMouseDown(object sender, MouseButtonEventArgs e) {
_initialMousePosition = e.GetPosition(this);
}
Additionally, handle MouseMove to check the moved distance and eventually initiate the drag operation:
...
public DraggableControl()
{
...
MouseMove += OnMouseMove;
}
...
private void OnMouseMove(object sender, MouseEventArgs e)
{
// Calculate distance between inital and updated mouse position
var movedDistance = (_initialMousePosition - e.GetPosition(this)).Length;
if (movedDistance > yourThreshold)
{
DragDrop.DoDragDrop(...);
}
}
}
I've been coding a Windows app chess game in C# as an exercise in honing my skills, and also because it's fun. I have included functionality that allows a player to select the option to highlight the squares a piece can legally move to when it gets clicked. A CustomControl handles the rendering of the chessboard and it also highlights the squares.
It all works as planned until the player begins to drag the piece to a new square. The moment the mouse moves, the highlights go away. I suspect that a Paint event is raised and the board redraws itself. And since the highlights are not part of the initial board layout, they don't get drawn.
What I would like to happen is for the squares to remain highlighted until the piece is dropped on its destination square. Is it possible to accomplish this? Any suggestions will be appreciated.
Psuedo code:
void piece_MouseDown(object sender, MouseEventArgs e)
{
Piece piece = (Piece)sender;
legalSquares = CalculateLegalSquares(piece.CurrentSquare);
if (legalSquares.Count > 0 && this.showLegalMoves)
{
chessBoard1.HighlightSquares(legalSquares);
}
// I believe a Paint event gets raised either here...
piece.DoDragDrop(piece, DragDropEffects.Move);
}
void piece_DragEnter(object sender, DragEventArgs e)
{
// ...or here, that removes the highlights.
if (e.Data.GetDataPresent("Chess.Piece"))
{
e.Effect = DragDropEffects.Move;
}
else
{
e.Effect = DragDropEffects.None;
}
}
void piece_DragDrop(object sender, DragEventArgs e)
{
Piece piece = (Piece)e.Data.GetData("Chess.Piece");
if (piece.CurrentSquare != dropSquare)
{
if (legalSquares.Contains(dropSquare))
{
// This is where I’d like the highlights to stop
// DoStuff()
}
}
}
It sounds like you are highlighting the valid squares by drawing directly, but this will get erased on any repaint. You will probably lose the highlights if your window is repainted for other reasons also, such as minimizing and restoring it, or dragging another window on top of it.
If this is the case, you probably need to override the OnPaint method and do your highlighting there. When you want to change what is highlighted, set some state in your class to control what is drawn as highlighted in the OnPaint method, and then Invalidate your window.