I'm using the ManipulationDelta event handler to drag and drop a simple ellipse in a canvas across the screen. I'm using the standard approach posted online in several places. Following is the code in my event handler:
Ellipse dragableItem = sender as Ellipse;
TranslateTransform translateTransform = dragableItem.RenderTransform as TranslateTransform;
double newPosX = Canvas.GetLeft(dragableItem) + translateTransform.X + e.Delta.Translation.X;
double newPosY = Canvas.GetTop(dragableItem) + translateTransform.Y + e.Delta.Translation.Y;
if (!isCanvasBoundary(newPosX, TestCanvas.ActualWidth - dragableItem.ActualWidth, 0))
translateTransform.X += e.Delta.Translation.X;
if (!isCanvasBoundary(newPosY, TestCanvas.ActualHeight - dragableItem.ActualHeight, 0))
translateTransform.Y += e.Delta.Translation.Y;
The drag and drop operation works fine, but there's a nasty delay of around 1 second between when the user begins dragging to when the ellipse actually changes its position. I can see by printing to the Debugger that the event handler itself finishes executing almost instantly, so I'm guessing it has something to do a pre-programmed refresh rate for all UIElements on the screen that's causing this delay?
Is there anyway around this issue?
I had the same problem some time ago. I guess that the delay is to decide whether the gesture is drag or tap. It's hard to touch a screen without any accidental drag.
To eliminate this lag, you can use PointerMove and PointerPressed events. Here's my example. You have canvas with two ellipses which can be dragged without any delay.
XAML
<Grid>
<Canvas x:Name="Board" PointerMoved="Canvas_PointerMoved" Background="Black">
<Ellipse Width="64" Height="64" Fill="Red"
Canvas.Left="32" Canvas.Top="128" PointerPressed="Ellipse_PointerPressed"/>
<Ellipse Width="96" Height=" 96" Fill="Blue"
Canvas.Left="128" Canvas.Top="16" PointerPressed="Ellipse_PointerPressed"/>
</Canvas>
</Grid>
As you can see, I'm handling PointerMoved event in canvas and PointerPressed event in ellipses. It's important that the background of the canvas is not transparent to handle touch events.
C#
public sealed partial class MainPage : Page
{
UIElement draggedItem = null;
Point offset;
public MainPage()
{
this.InitializeComponent();
this.NavigationCacheMode = NavigationCacheMode.Required;
}
private void Ellipse_PointerPressed(object sender, PointerRoutedEventArgs e)
{
draggedItem = sender as UIElement;
offset = e.GetCurrentPoint(draggedItem).Position;
}
private void Canvas_PointerMoved(object sender, PointerRoutedEventArgs e)
{
if (draggedItem == null)
return;
Point dragPoint = e.GetCurrentPoint(Board).Position;
Canvas.SetLeft(draggedItem, dragPoint.X - offset.X);
Canvas.SetTop(draggedItem, dragPoint.Y - offset.Y);
}
}
I think the code is quite simple and understandable. I use PointerPressed to decide which object is dragged. I'm also calculating some offset, because we want to move the object relative to the point where user touches.
Related
I'm making a game inventory system where I have a grid and borders that take up a grid space have a background image of the icon. For example in grid position 0,0 I have a border with a background of a health potion. I want to be able to drag this border to any other grid location. Right now I'm working on just clicking and dragging the border to follow the mouse.
What I have so far sort of works but not really. If sort of freaks out when I left click on the icon and move slowly. However I can move out of the icon fast and it doesn't keep up so it then stops moving until I'm back over it. Any ideas on how to get this kind of functionality?
public partial class MainWindow : Window
{
TranslateTransform trans = new TranslateTransform();
Border border;
public MainWindow()
{
InitializeComponent();
// create a broder and set it's background image
border = new Border();
border.Visibility = System.Windows.Visibility.Visible;
var img = (Image)MasterGrid.FindResource("notepad");
var imgBrush = new ImageBrush(img.Source);
border.Background = imgBrush;
border.Margin = new Thickness(2.0);
// add the border to the grid
Grid.SetRow(border, 0);
Grid.SetColumn(border, 1);
Grid.SetRowSpan(border, 2);
Grid.SetColumnSpan(border, 1);
InvGrid.Children.Add(border);
// hook up events
_00.MouseUp += new System.Windows.Input.MouseButtonEventHandler(_00_MouseUp);
border.MouseDown += border_MouseDown;
border.MouseMove += border_MouseMove;
}
void border_MouseMove(object sender, MouseEventArgs e)
{
// make the border follow the mouse position
trans.X = e.GetPosition(border).X;
trans.Y = e.GetPosition(border).Y;
}
void border_MouseDown(object sender, MouseButtonEventArgs e)
{
border.RenderTransform = trans;
}
private void _00_MouseUp(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
MessageBox.Show("test");
}
}
It's not that easy, so I will give you some steps:
You should know sizes of cells, then you are able to calculate position of each one.
Prepare a container (a Grid for example), which will be only for moving purpose.
When you start dragging put the item in the container (2.), set its position under your cursor and hide the cell's content.
When you drop the item, calculate over which cell you are (maybe there is even a function to get a control under the cursor) and do the rest of logic.
I'm not sure if dragging is possible in case of regular controls, maybe you will have to replace your Grid with Canvas.
Try something like this:
<Grid>
<Border x:Name="DragContainer" Width="40" Height="40" />
<!-- OTHER STUFF - YOUR GRID WITH ITEMS -->
</Grid>
and modify only Border's Margin.
Another way to do it is to do dragging inside Canvas, then you will be able to set Canvas.Left, Canvas.Top - it should work properly.
<Canvas>
<Border x:Name="DragContainer" Width="40" Height="40" />
<!-- OTHER STUFF - YOUR GRID WITH ITEMS -->
</Canvas>
It works within a specific control, but it doesn't work out the specific control.
How to get mouse position and use mouse events independently of any control just directly from screen (without Platform Invoke)?
2 needed points:
Mouse events when mouse is not within a control, but on a screen.
Mouse position when mouse is not within a control, but on a screen.
It should be solved without using Platform Invoke.
Next two don't work:
System.Windows.Input.Mouse.GetPosition(this)
Doesn't get mouse position out a specific control.
System.Windows.Forms.Cursor.Position.X
System.Windows.Forms.Cursor.Position doesn't work because it has no types in a WPF app, but it works in a Windows Forms app.
IntelliSense gets System.Windows.Forms.Cursor.Position, but it doesn't get any type of Position, hence I can't get:
Position.X
Position.Y
and
Point pointToWindow = Mouse.GetPosition(this);
Point pointToScreen = PointToScreen(pointToWindow);
Doesn't get mouse position out a specific control.
Using MouseDown event of a control you can try this:
var point = e.GetPosition(this.YourControl);
EDIT:
You can capture mouse event to a specific control using Mouse.Capture(YourControl); so it will capture the mouse events even if it is not on that control. Here is the link
You can use PointToScreen
Converts a Point that represents the current coordinate system of the
Visual into a Point in screen coordinates.
Something like this:
private void MouseCordinateMethod(object sender, MouseEventArgs e)
{
var relativePosition = e.GetPosition(this);
var point= PointToScreen(relativePosition);
_x.HorizontalOffset = point.X;
_x.VerticalOffset = point.Y;
}
Do note that Mouse.GetPosition returns a Point, and PointToScreen converts the point to the screen coordinate
EDIT:
You can use the Mouse.Capture(SepcificControl);. From MSDN
Captures mouse input to the specified element.
I have little new found,
Code is below, fisrt build and run the Window ,
then just wheel your mouse one time on the window to invoke the endless screen detect of the Mouse Position.
(Thus I didn't find the way to detect mouse event out of the control in the second point of the question, but similar use an endless thread.)
But I just use a little skill to enable Windows.Forms in WPF Project, by simply use the Forms code in pure method, then refer that method in the Event Code Block.
.
Here's the Code:
Add two references to project:
System.Drawing
System.Windows.Forms
Xaml part:
<Window x:Class="Test.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:g="clr-namespace:Gma.UserActivityMonitor;assembly=Gma.UserActivityMonitor"
Title="MainWindow" Height="350" Width="525"
MouseWheel="MainWindow_OnMouseWheel">
<Grid>
<TextBlock Name="TBK" />
</Grid>
</Window>
Class Code:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
public void KeepReportMousePos()
{
//Endless Report Mouse position
Task.Factory.StartNew(() =>
{
while(true){
this.Dispatcher.Invoke(
DispatcherPriority.SystemIdle,
new Action(() =>
{
GetCursorPos();
}));
}
});
}
public void GetCursorPos()
{
//get the mouse position and show on the TextBlock
System.Drawing.Point p = System.Windows.Forms.Cursor.Position;
TBK.Text = p.X + " " + p.Y;
}
private void MainWindow_OnMouseWheel(object sender, MouseWheelEventArgs e)
{
//invoke mouse position detect when wheel the mouse
KeepReportMousePos();
}
}
Why complicate things? Just pass null to get screen coordinates:
private void MouseCordinateMethod(object sender, MouseEventArgs e)
{
var screenPos = e.GetPosition(null);
// ...
}
I have a textblock control on canvas which can be dragged horizontally to the right correctly as shown in the first and second image.
Then after I a 90 degree rotation angle is applied to its CompositeTransform, dragging the textblock to the right actually move it vertically towards the top as shown by the third and fourth image. What am I missing?
public CompositeTransform CurrentTransform = new CompositeTransform();
.....
TextBlock.RenderTransform = CurrentTransform;
....
private double angle;
public double Angle
{
get
{
return angle;
}
set
{
if (angle != value)
{
angle = value;
CurrentTransform.CenterX = 0;
CurrentTransform.CenterY = 0;
CurrentTransform.Rotation = angle;
}
}
}
The moving of the textbox is handled inside
private void CanvasText_ManipulationDelta(object sender, System.Windows.Input.ManipulationDeltaEventArgs e)
{
CurrentTransform.TranslateX += e.DeltaManipulation.Translation.X;
CurrentTransform.TranslateY += e.DeltaManipulation.Translation.Y;
}
For those who are in the same boat, I managed to fix this by attaching external gesture listener from Windows Phone toolkit instead of using the built-in CanvasText_ManipulationDelta event. The textbox dragging works correctly even after rotation.
<Grid HorizontalAlignment="Center" VerticalAlignment="Center">
<Image x:Name="ImageOriginal"
Source="{Binding WbPreview, Mode=TwoWay}"
Stretch="Uniform"/>
<Grid x:Name="GridDraw"
Tap="GridDraw_Tap"
Background="Transparent"/>
<Canvas x:Name="CanvasText">
<toolkit:GestureService.GestureListener>
<toolkit:GestureListener Tap="GestureListener_Tap"
DragDelta="GestureListener_DragDelta"/>
</toolkit:GestureService.GestureListener>
</Canvas>
</Grid>
I have an image that is inside a popup. When I am inside the canvas, and if a key is down I want to rotate the popup. My code doesnt update the rotation unless I leave the canvas (move mouse outside of canvas), then enter again (move mouse inside canvas). I confirmed the angle is being updated when the key is down inside the canvas, so I have no clue why it doesnt update unless I exit and reenter.
<Popup Name="floatingTip"
AllowsTransparency="True"
Placement="Relative"
PlacementTarget="{Binding ElementName=MainCanvas}">
<Popup.RenderTransform>
<RotateTransform CenterX="1" CenterY="1" Angle="{Binding Angle}"/>
</Popup.RenderTransform>
<Image Source="{Binding Name}"
Width="{Binding Width}"
Height="{Binding Height}"/>
</Popup>
When the mouse enters the canvas space, I set CanvasIsActive = true.
Here is the KeyDown event:
private void Window_KeyDown(object sender, KeyEventArgs e)
{
if (CanvasIsActive && currentTool == "staticBrush")
{
cursorImage.Angle += 20;
this.Title = cursorImage.Angle.ToString();
}
}
Here is my Angle property that is updated when a key is down:
double imgAngle;
public double Angle
{
get { return imgAngle; }
set
{
imgAngle = value;
OnPropertyChanged("Angle");
}
}
I figured it out. To refresh the rotation inside the popup, the popup IsOpen needs to be set to false, then back to true. Another solution is to move the RotateTransform from the Popup to the Image.
I have a rectangle and on my MouseMove event I want to transform the rectangle whenever the rectangle's width has changed.
I have code sorta like this:
private Rectangle _rectangle;
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
_rectangle = GetTemplatedChild("PART_RangeRectangle") as Rectangle;
if(_rectangle != null)
{
_rectangle.MouseMove += new MouseEventHandler(_rectangle_MouseMove);
}
}
private void _rectangle_MouseMove(object sender, MouseEventArgs e)
{
if(e.LeftButton == MouseButtonState.Pressed && _rectangle != null)
{
_rectangle.Width += 50;
_rectangle.RenderTransform = new TranslateTransform(-10, 0);
}
}
My Xaml looks sorta like this:
<Grid>
<Canvas>
<Rectangle Name="PART_RangeRectangle" StrokeThickness="5"
RenderTransformOrigin="0.5, 0.5" />
<Canvas>
</Grid>
When I first trigger the MouseMove event the translation occurs as expected. But this only occurs once. I am getting into that block of code and the width of the rectangle is updating fine, but I've yet to figure out why the transform is not updating.
You're replacing the old transform with an identical transform. You need to modify the existing transform and use += like you do with Width.
Example:
if (_rectangle.RenderTransform is TranslateTransform)
{
(_rectangle.RenderTransform as TranslateTransform).X -= 10;
}
else _rectangle.RenderTransform = new TranslateTransform(-10, 0);
You're not changing your transform. Assigning a RenderTransform does not move the rectangle, it sets an offset. You're not changing that offset after your first assignment.