Event handlers behaving abnormally - c#

I'm using the following WPF
<Button Style="{DynamicResource NoChromeButton}" x:Name="cmdImage" Grid.Row="1" Margin="10" MouseDoubleClick="cmdImage_MouseDoubleClick" MouseDown="imgMain_MouseDown_1" MouseMove="imgMain_MouseMove_1" MouseUp="imgMain_MouseUp_1">
<Grid x:Name="ImageGrid">
<Image x:Name="imgMain" Panel.ZIndex="0" />
<Button x:Name="rectBounds" Template="{StaticResource DesignerItemTemplate}" Visibility="Hidden" IsVisibleChanged="Button_IsVisibleChanged" Panel.ZIndex="1" />
</Grid>
</Button>
The weird part is that the MouseUp, MouseDown, and MouseMove events of the outermost button don't even trigger iff the ImageSource of the Image isn't null (an image is loaded).
I tried moving them to the Image control. They do trigger, but behave unexpectedly.
private void imgMain_MouseDown_1(object sender, MouseButtonEventArgs e)
{
startPoint = e.GetPosition(ImageGrid);
rect = new Rectangle
{
Margin =
new Thickness(e.GetPosition(ImageGrid).X, e.GetPosition(ImageGrid).Y,
ImageGrid.ActualWidth - e.GetPosition(ImageGrid).X,
ImageGrid.ActualHeight - e.GetPosition(ImageGrid).Y),
Stroke = Brushes.Black,
StrokeThickness = 1.0
};
ImageGrid.Children.Add(rect);
Panel.SetZIndex(rect, 2);
}
private void imgMain_MouseMove_1(object sender, MouseEventArgs e)
{
if (e.LeftButton == MouseButtonState.Released || rect == null)
return;
Point pos = e.GetPosition(ImageGrid);
double x = Math.Min(pos.X, startPoint.X);
double y = Math.Min(pos.Y, startPoint.Y);
double w = Math.Max(pos.X, startPoint.X) - x;
double h = Math.Max(pos.Y, startPoint.Y) - y;
rect.Margin = new Thickness(x, y, ImageGrid.ActualWidth - x - w, ImageGrid.ActualHeight - y - h);
}
private void imgMain_MouseUp_1(object sender, MouseButtonEventArgs e)
{
rect = null;
}
By all apparent rules, a draggable rectangle should appear, and disappear once you let go of the mouse button. It doesn't. What's funny is that when I change the visibility of rectBounds, a rectangle does appear.

There are a few things to fix here.
First, your XAML. A button inside another button? I can't imagine how that should feel. Then the ZIndex. It's redundant since Image and Button are in the same Grid cell and the Image is declared before the Button. And it's also not necessary to set in on the newly created Rectangle in imgMain_MouseDown_1.
The weird part is that the MouseUp, MouseDown, and MouseMove events of
the outermost button don't even trigger if the ImageSource of the
Image isn't null.
This is exactly what you would expect, because a mouse event is only generated on those areas of a control that are actually drawn, in other words where hit testing succeeds. When there is no actual content (no image in your example), hit testing fails. You might simply assign a transparent background to the Grid to ensure that hit testing always succeeds:
<Grid x:Name="ImageGrid" Background="Transparent">
<Image x:Name="imgMain" />
<Button x:Name="rectBounds" Template="{StaticResource DesignerItemTemplate}" Visibility="Hidden" IsVisibleChanged="Button_IsVisibleChanged" />
</Grid>
By all apparent rules, a draggable rectangle should appear, and
disappear once you let go of the mouse button.
No it shouldn't, unless you actually remove that Rectangle from the Grid. Setting rect = null doesn't do that.
private void imgMain_MouseUp_1(object sender, MouseButtonEventArgs e)
{
ImageGrid.Children.Remove(rect);
rect = null;
}

Related

Handling trackpad zoom in WinUI 3 Canvas

Many Windows applications support two fingered touchpad gestures. You can scroll by moving two fingers horizontally or vertically, and you can zoom by changing the distance between the fingers.
I'm trying replicate this behaviour in a WinUI 3 canvas.
According to the Microsoft documentation "The touchpad does not raise manipulation events. Instead, pointer events will be raised for touchpad input."
Listening for the PointerWheelChanged event handler, I'm able to detect two finger scrolls with the following code
private void OnPointerWheelChanged(object sender, PointerRoutedEventArgs e)
{
var pointer = e.GetCurrentPoint(myCanvas);
var isHorizontalScroll = pointer.Properties.IsHorizontalMouseWheel;
var scrollDelta = pointer.Properties.MouseWheelDelta;
// Perform the scrolling
e.Handled = true;
}
But, I'm unable to get the fingers position so I can calculate the zoom delta. Is there some lower API I can use that will give me access to the individual fingers? Or some other way to capture the zoom gesture?
I have created a little sample for you, which handles zoom using the trackpad:
MainWindow.xaml:
<Canvas x:Name="myCanvas" PointerWheelChanged="Canvas_PointerWheelChanged" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Background="Green">
<Rectangle Width="40" Height="40" Fill="Red" Canvas.Left="0" Canvas.Top="0" Canvas.ZIndex="0" />
</Canvas>
MainWindow.xaml.cs:
private void Canvas_PointerWheelChanged(object sender, PointerRoutedEventArgs e)
{
var ctrl = Microsoft.UI.Input.InputKeyboardSource.GetKeyStateForCurrentThread(Windows.System.VirtualKey.Control
if (ctrl.HasFlag(Windows.UI.Core.CoreVirtualKeyStates.Down))
{
var delta = e.GetCurrentPoint(myCanvas).Properties.MouseWheelDelta;
//Here you can handle your zooming
//My sample just resizes the rectangle
foreach (UIElement children in myCanvas.Children)
{
if (children is Rectangle rect)
{
double newSize = rect.Height + delta;
if (newSize < 0)
newSize = 0;
rect.Height = rect.Width = newSize;
}
}
}
}
The trick is, to check for the control-key press in the PointerWheelChanged-event, because the touchpad behaves the same like when you zoom using control + Mousewheel

How to create a control for panning and tilt together?

I'd like to create a control in my WPF app that allows the user to drag a dot inside a box/circle. This will be used to drive the pan and tilt values for a camera.
I am not sure how to create a control like that. The picture below is an example of the type of control that I want to develop.
Here's a very quick solution to get you started.
For the XAML, I've used an Ellipse control for the "dot". The Ellipse is placed inside a Canvas control (which allows the dot to be moved around):-
<Grid Background="White"
MouseUp="ParentOnMouseUp">
<Canvas x:Name="canvas"
Background="Green"
Width="200"
Height="200"
HorizontalAlignment="Center"
VerticalAlignment="Center"
MouseMove="CanvasOnMouseMove">
<!-- Implement your blue circle b/g as an Image control here ... />
<Ellipse x:Name="dot"
Width="20"
Height="20"
Fill="Blue"
Loaded="DotOnLoaded"
MouseDown="DotOnMouseDown"/>
</Canvas>
</Grid>
First I handle the Ellipse's MouseDown event:
private void DotOnMouseDown(object sender, MouseButtonEventArgs e)
{
_isDraggingDot = true;
}
All I do here is set a flag to indicate that I'm starting to drag the dot.
Next, I handle the Canvas MouseMove event, which is where I move the dot around. It includes logic to ensure the dot doesn't stray outside the canvas:
private void CanvasOnMouseMove(object sender, MouseEventArgs e)
{
if (_isDraggingDot)
{
var mousePos = e.GetPosition(canvas);
var x = mousePos.X;
if (x < 0)
{
x = 0;
}
if (x > canvas.Width)
{
x = canvas.Width;
}
var y = mousePos.Y;
if (y < 0)
{
y = 0;
}
if (y > canvas.Height)
{
y = canvas.Height;
}
dot.SetValue(Canvas.LeftProperty, x - (dot.Width / 2.0)); // offset ensures dot is centred on mouse pointer
dot.SetValue(Canvas.TopProperty, y - (dot.Height / 2.0));
}
}
This is also where you would calculate the dot's vertical and horizontal offset from centre, and use these values to update the pan and tilt.
Finally, I implement the MouseUp event on the outer control (the Grid in my example):
private void ParentOnMouseUp(object sender, MouseButtonEventArgs e)
{
_isDraggingDot = false;
CentreDot();
}
private void CentreDot()
{
dot.SetValue(Canvas.LeftProperty, (canvas.Width / 2.0) - (dot.Width / 2.0));
dot.SetValue(Canvas.TopProperty, (canvas.Height / 2.0) - (dot.Height / 2.0));
}
The reason for handling the event on the outer control is to ensure that the dot is returned to the centre if the user releases the mouse button outside the Canvas.
(Note that I also set a b/g colour on the Grid, otherwise it defaults to transparent and won't detect mouse events!)
Lastly I wire up the Ellipse's Loaded event to initially centre the dot when the UI loads:
private void DotOnLoaded(object sender, RoutedEventArgs e)
{
CentreDot();
}
As I mentioned, this is just a quick solution where the dot simply follows the mouse. One If you don't like this, you could calculate how far the mouse is (vertically and horizontally) from the canvas centre, then use a small percentage of these values to position the dot away from the centre, effectively requiring more mouse movement to move the dot, which might feel more "natural".
Another idea may be to "snap" the dot's position to the nearest of the four arrow buttons (N,S,E,W), or even include the points in between (NE,SE,SW,NW).

Keeping position and size of canvas after panning canvas in wpf

I'm a newbie, I'm trying to built a visual drawing. I have a canvas and images (images which are created by clicked event) on this canvas.
Code XAML:
<Canvas Name="drawing" Background="Black"
MouseDown="drawing_MouseDown"
MouseMove="drawing_MouseMove"
MouseUp="drawing_MouseUp">
<Canvas.RenderTransform>
<MatrixTransform/>
</Canvas.RenderTransform>
</Canvas>
Code-behind:
namespace Panning_used_MatrixTransform
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private Point firstPoint = new Point();
private void drawing_MouseDown(object sender, MouseButtonEventArgs e)
{
firstPoint = e.GetPosition(this);
drawing.CaptureMouse();
}
private void drawing_MouseMove(object sender, MouseEventArgs e)
{
if (e.MiddleButton == MouseButtonState.Pressed)
{
var lsPoint = e.GetPosition(this);
var res = lsPoint - firstPoint;
var element = sender as UIElement;
var transform = element.RenderTransform as MatrixTransform;
var matrix = transform.Matrix;
matrix.TranslatePrepend(res.X, res.Y);
transform.Matrix = matrix;
//udate first point
firstPoint = lsPoint;
}
}
private void drawing_MouseUp(object sender, MouseButtonEventArgs e)
{
drawing.ReleaseMouseCapture();
}
}
}
I don't know how to keeping position and size of canvas not change when we panning this canvas (like CAD drawing).
More information:
I want to create the software which allow at the beginning we has nothing on the canvas. When we click a button, we can create some images where we want on the canvas. And clearly at the beginning nothing on it (So nothing on the canvas - XAML code).
Please help me!
For your requirement, the manipulation should be performed on the images, instead of the canvas. So when you pan the images, the canvas stays still.
I make a few changes to the XAML code, add another image for testing, also move the event from the canvas to the outer grid, but I do not rename your event handlers so you can compile the new code more easily.
<Grid Background="Black"
MouseDown="drawing_MouseDown"
MouseMove="drawing_MouseMove"
MouseUp="drawing_MouseUp">
<Canvas Name="drawing">
<Image x:Name="imgSource" Source="Resources/my-pic.png"
Height="100" Width="100" Canvas.Top="30" Canvas.Left="30">
<Image.RenderTransform>
<MatrixTransform/>
</Image.RenderTransform>
</Image>
<Image x:Name="imgSource2" Source="Resources/my-pic.png"
Height="100" Width="100" Canvas.Top="230" Canvas.Left="230">
<Image.RenderTransform>
<MatrixTransform/>
</Image.RenderTransform>
</Image>
</Canvas>
</Grid>
In the code behind, I apply the manipulation to each child element of the canvas.
I also remove the CaptureMouse()/ReleaseMouseCapture() since I think those two lines are not necessary.
private void drawing_MouseMove(object sender, MouseEventArgs e)
{
if (e.MiddleButton == MouseButtonState.Pressed))
{
var lsPoint = e.GetPosition(this);
var res = lsPoint - firstPoint;
foreach (UIElement element in drawing.Children)
{
var transform = element.RenderTransform as MatrixTransform;
var matrix = transform.Matrix;
matrix.TranslatePrepend(res.X, res.Y);
transform.Matrix = matrix;
}
//udate first point
firstPoint = lsPoint;
}
}

Move rectangle with mouse without clicking any button

I am writing a silverlight application, where there is a requirement, that I need to draw rectangle over image and move it along with the mouse move. I can move the rectangle by holding left mouse click, but now I need to move without clicking or holding the mouse left click.
I have seen many examples but they all implements the moving shapes and rectangle on left mouse click, which definitely is not my requirement.
I tried many ways but couldn't get it right. Below is the code what I do currently. Any suggestions would be welcomed.
XAML
<Canvas x:Name="draw" Grid.Column="0" Background="Transparent"
Margin="0,0,0,150" Grid.RowSpan="2">
<Rectangle x:Name="SquareBlue" Width="100" Height="100" Canvas.Top="155" Canvas.Left="268" Fill="Transparent" Stroke="Black" StrokeThickness="2" />
</Canvas>
<Image x:Name="myImage" Height="100"/>
<TextBox x:Name="X" Margin="0,0,110,0"></TextBox>
<TextBox x:Name="Y" Margin="0,0,110,0"/>
<Image x:Name="pictureBox1" Height="100"/>
Code Behind
Boolean isMouseCaptured;
Double mouseX;
Double mouseY;
Int32 zIndex = 0;
private void Shape_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
Shape s = sender as Shape;
isMouseCaptured = false;
s.ReleaseMouseCapture();
mouseY = -1;
mouseX = -1;
}
private void Shape_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
Shape s = sender as Shape;
mouseY = e.GetPosition(null).Y;
mouseX = e.GetPosition(null).X;
isMouseCaptured = true;
s.CaptureMouse();
s.SetValue(Canvas.ZIndexProperty, zIndex);
zIndex++;
}
private void Shape_MouseMove(object sender, MouseEventArgs e)
{
if (isMouseCaptured)
{
Shape s = sender as Shape;
double deltaY = e.GetPosition(null).Y - mouseY;
double deltaX = e.GetPosition(null).X - mouseX;
double newTop = deltaY + (double)s.GetValue(Canvas.TopProperty);
double newLeft = deltaX + (double)s.GetValue(Canvas.LeftProperty);
s.SetValue(Canvas.TopProperty, newTop);
s.SetValue(Canvas.LeftProperty, newLeft);
mouseY = e.GetPosition(null).Y;
mouseX = e.GetPosition(null).X;
X.Text = mouseX.ToString();
Y.Text = mouseY.ToString();
}
You probably want to consider the shape captured whenever the Shape_MouseMove is fired.
And then, every mouse move on the canvas/window moves the shape to be centerd around the mouse cursor.
You then have to decide when to release it maybe:
by detecting when the mouse leaves the canvas/window.
using the buttons for releasing explicitly.
checking the time between mouseMove events. if 1 second passed, then be in a logical state where the shape is not and cannot be captured in mouseMove. only when mouse is out of the shape, return to normal state. so user will lose the move and has to cursor out and in again to capture again.
additionaly, when entering the state described in 3, launch a timer and then if after a second the cursor is on the shape, re-capture.

WPF - how do I bind a control's position to the current mouse position?

Is there a way to bind to the mouse position in WPF in the XAML file? Or does that have to be done in code? I have a control inside a Canvas, and I just want the control to follow the mouse while the mouse cursor is inside the Canvas.
Thanks
EDIT:
OK, I figured it out a relatively easy way using the code-behind file. I added a MouseMove event handler on the Canvas, and then added:
private void Canvas_MouseMove(object sender, MouseEventArgs e)
{
// Get the x and y coordinates of the mouse pointer.
System.Windows.Point position = e.GetPosition(this);
double pX = position.X;
double pY = position.Y;
// Sets the position of the image to the mouse coordinates.
myMouseImage.SetValue(Canvas.LeftProperty, pX);
myMouseImage.SetValue(Canvas.TopProperty, pY);
}
using http://msdn.microsoft.com/en-us/library/ms746626.aspx as a guideline.
I tried to make a kind of decorator for this purpose.
You wrap the object, mouse position above which you want to control and bind some control to decorator MousePosition property.
public class MouseTrackerDecorator : Decorator
{
static readonly DependencyProperty MousePositionProperty;
static MouseTrackerDecorator()
{
MousePositionProperty = DependencyProperty.Register("MousePosition", typeof(Point), typeof(MouseTrackerDecorator));
}
public override UIElement Child
{
get
{
return base.Child;
}
set
{
if (base.Child != null)
base.Child.MouseMove -= _controlledObject_MouseMove;
base.Child = value;
base.Child.MouseMove += _controlledObject_MouseMove;
}
}
public Point MousePosition
{
get
{
return (Point)GetValue(MouseTrackerDecorator.MousePositionProperty);
}
set
{
SetValue(MouseTrackerDecorator.MousePositionProperty, value);
}
}
void _controlledObject_MouseMove(object sender, MouseEventArgs e)
{
Point p = e.GetPosition(base.Child);
// Here you can add some validation logic
MousePosition = p;
}
}
and XAML
<local:MouseTrackerDecorator x:Name="mouseTracker">
<Canvas Width="200" Height="200" Background="Red">
<Button Width="20" Height="20" Canvas.Left="{Binding ElementName=mouseTracker, Path=MousePosition.X}" Canvas.Top="{Binding ElementName=mouseTracker, Path=MousePosition.Y}" />
</Canvas>
</local:MouseTrackerDecorator>
Tried a few examples only. The msdn documentation is , i think, incorrectly worded
"How to: Make an Object Follow the Mouse Pointer"
should be
"How to: Make an object's size increase based on mouse position"
anyway.
I was able to achieve this effect by changing the canvas properties. Also not sure why everyone was attaching the event handler to the objects next top level layout property and not the window. Maybe you and most of the examples online are going for a different effect
<Window x:Class="FollowMouse.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525" MouseMove="MouseMoveHandler">
<Canvas>
<Ellipse Name="ellipse" Fill="LightBlue"Width="100" Height="100"/>
</Canvas>
code behind
private void MouseMoveHandler(object sender, MouseEventArgs e)
{
/// Get the x and y coordinates of the mouse pointer.
System.Windows.Point position = e.GetPosition(this);
double pX = position.X;
double pY = position.Y;
/// Sets eclipse to the mouse coordinates.
Canvas.SetLeft(ellipse, pX);
Canvas.SetTop(ellipse, pY);
Canvas.SetRight(ellipse, pX);
}

Categories

Resources