Wpf Mouse Events outside the bounds of a user control issue - c#

I have a weird issue I have no ideea how to fix.
I'm creating a color picker from scratch. I'm doing so by creating a set of user controls and putting them together in a "master control".
When a user drags over the hue picker, for example, I handle the mouse down, move and up (inside the hue picker user control).
A simple bool dictates what happens, as far as the actual moving goes.
//Mouse down
_isDrag = true;
//Mouse Move
if(!_isDrag) return;
//Moving the position indicator shape thingy
//Calculating the hue
//Mouse Up
_isDrag = false;
But, if the mouse up occurs outside of the bounds of the hue picker, the mouse up event doesn't fire.
Thus, when the user returns to the area of the hue picker, the shape indicator thingy runs rampart.
I am certain an answer lies somewhere but I'm afraid my searching skills are not up to the task.
I've no idea what to look for.
Thank you for your time.
Solution:
private bool _isDrag;
//Request Mouse capture for the Container
private void MsDown(object sender, MouseButtonEventArgs e)
{
_isDrag = true;
Mouse.Capture(MainContainer);
}
//Release Mouse capture
private void MsUp(object sender, MouseButtonEventArgs e)
{
_isDrag = false;
Mouse.Capture(null);
}
//Move the handle vertically along the main container, with respect to it's width - so it's centered.
private void MsMove(object sender, MouseEventArgs e)
{
if (!_isDrag) return;
Canvas.SetTop(Handle, e.GetPosition(ContentRow).Y - Handle.ActualHeight / 2);
}
Thank you for your answer!
Edit 2:
Following up on my issue. While Capture basically did the trick, I noticed that, if fast dragging outside the bounds of the user control , sometimes the handle would get stuck close to the edge. If I would move the mouse slowly, this wouldn't happen. Weird. Also, I could never reach 0 and .ActualHeight
So I'll post my fix here, just in case another dude encounters this issue.
I split my grid like this:
<Grid.ColumnDefinitions>
<ColumnDefinition Width="7"></ColumnDefinition>
<ColumnDefinition Width="*"></ColumnDefinition>
<ColumnDefinition Width="7"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="7"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
<RowDefinition Height="7"></RowDefinition>
</Grid.RowDefinitions>
With 7 being half the size of my handle (a circle).
The content area (the actual area you can interact with visually) is in the middle cell (in a separate grid with false hit test visibility).
Spanning the entire main grid is an invisible rectangle used for hit testing.
And to move the handle
private void MoveHandle()
{
_pos.X = _pos.X - Handle.ActualWidth/2;
_pos.Y = _pos.Y - Handle.ActualHeight / 2;
//this is just to be sure. I'm paranoid. Being a color picker, these actually matter a lot.
_pos.X = Math.Max(Math.Min(_pos.X, RectColor.ActualWidth - Handle.ActualWidth/2), -Handle.ActualWidth / 2);
_pos.Y = Math.Max(Math.Min(_pos.Y, RectColor.ActualHeight -Handle.ActualWidth/2), -Handle.ActualHeight/2);
Canvas.SetLeft(Handle, _pos.X);
Canvas.SetTop(Handle, _pos.Y);
}
I have no idea why the previous code I had almost worked. It's basically the same thing as before. But, somehow, this performs a million times better. Good luck!

The search term you are looking for is Mouse Capture. Capture in your MouseDown, and you can get mouse events even after the mouse leaves your control.

Related

WPF Touch - Choosing Manipulation Container based on number of manipulators

I have a WPF Touch app (MS Surface, VS 2017, .NET 4.7) that shows a number of simple shapes (lines, rects, circles) above an image, all on on a canvas. I'm having a trouble with selecting my ManipulationContainer for touch.
Roughly what I have is this:
<ScrollViewer x:Name="MyViewer"
ManipulationStarting="Viewer_ManipulationStarting">
<Canvas x:Name="MyCanvas">
<Image x:Name="MyImage" Source={Binding MyImageSource}"/>
<ItemsControl x:Name="MyShapes" ItemsSource="{Binding MyShapes}"/>
</Canvas>
</ScrollViewer>
I want let my user do two different things with touch.
With one finger they can draw new shapes (relative to the canvas)
With two fingers they can zoom the entire canvas (relative to the scrollviewer)
So if the user is drawing a new shape, then ManipulationContainer must be "MyCanvas". But if the user is zooming the whole scene, then the ManipulationContainer must be "MyViewer" (because in that case I'm changing the LayoutTransform of the whole canvas).
So I need (it would seem) to select from one of two different manipulation containers depending on which of these operations is happening. But I can't seem to figure out how. Below is what I have (which doesn't work) where I choose the container. in the ManipulationStarting handler.
private void Scene_ManipulationStarting(object sender, ManipulationStartingEventArgs e)
{
// If zooming, container is the parent viewer. Otherwise it's
// drawing a shape relative to the canvas.
if (e.Manipulators.Count() >= 2)
e.ManipulationContainer = MyViewer; // Zooming whole canvas
else
e.ManipulationContainer = MyCanvas; // Drawing on canvas
e.Handled = true;
}
As you likely guessed, when I first get this, I always get just one manipulator; The user is extremely unlikely to have his very first touch be with two fingers exactly simultaneously. So my code always thinks I'm drawing a shape.
It's only later on, in the ManipulationDelta that I start getting more than one manipulator. But it's too late then to choose a container. It's already been chosen. And if I check the number of manipulators then, my coordinates, delta, and origin are all relative to the very thing I'm trying to move. It makes the zooming jump all over the place.
I don't know what to do about this. I have to let my user zoom at any time so I do not have the option of forcing them to choose a "zoom tool".
I'm sure I'm missing something simple but I don't know what it is and I'd like to avoid blind alleys. Any suggestions?

Buttons to appear around click position of mouse

I'm creating a simple WPF app in which one of the core features would be that wherever the user clicks on the grid inside the main window, a number of buttons should appear around the position of the click.
Now, I try to achieve this with only 1 button. I know that I have to capture the current position of the mouse and then modify the 4 arguments of the Margin of the button (left, top, right, bottom) by creating new instances of Thickness-es.
I managed to create new Thickness-es to the Margins, with the left and top argument set to the mouse X and Y cordinates respectively, but I don't know how to calculate or what to use as the right, and bottom arguments of the newly created Margins.
Here is the relevant function from the xaml.cs (the values in question are indicated as 0-s and grid is intented to refer to the grid):
private void Grid_MouseRightButtonDown(object sender, MouseButtonEventArgs e)
{
var mouseLocation = PointToScreen(Mouse.GetPosition(grid));
RandomButton.Margin = new Thickness(mouseLocation.X, mouseLocation.Y, 0, 0);
}
Here is the relevant part of the xaml:
<StackPanel>
<Button
Name="RandomButton"
Height="30"
Width="30"
Background="#FF130889"
Click="RandomButton_Click"
Content="RandomContent" />
</StackPanel>
It is also worth mentioning that when the button's HorizontalAlignment is set to Left and the VerticalAlignment is set to top, the button seem to do what I want with this setup, but only, when the windowsize is full.
I think I have to use the actual height of the window or the grid, but I don't know how. I know it is something simple, but I just started working with WPF, so I apreciate any kind of help!
As far as I understood the problem is in determining the click relative position.
In this case you can use Mouse.GetPosition method.
Here is the example with Canvas:
private void SetPos()
{
var relativePosition = Mouse.GetPosition(this.MainCanvas);
Canvas.SetLeft(this.btn1, relativePosition.X);
Canvas.SetTop(this.btn1, relativePosition.Y);
btn1.Visibility = Visibility.Visible;
}

Issue with scroll viewer in windows 10 universal app?

I am working on a windows 10 universal App.The basic structure of my page is like
<Grid>
<ScrollViewer ViewChanged="MyScrollViewer_ViewChanged" VerticalScrollBarVisibility="Auto" VerticalScrollMode="Enabled" HorizontalScrollBarVisibility="Disabled" HorizontalScrollMode="Disabled" IsVerticalRailEnabled="True" IsVerticalScrollChainingEnabled="True">
<Pivot>
<PivotItem/>
<PivotItem/>
<PivotItem/>
<PivotItem/>
</Pivot>
</ScrollViewer>
</Grid>
And in my code behind i wrote the event to handle Scroll changed like this
private void MyScrollViewer_ViewChanged(object sender, ScrollViewerViewChangedEventArgs e)
{
//My business logic goes here
}
For each pivot item, there is contents to scroll.
When i run the App what happens is
when i tried to scroll from the scroll bar on the right side,my MyScrollViewer_ViewChanged event getting fired but when i tried to scroll with the mouse wheel,the event not getting fired and i was not able to continue.
Please help to find out the issue.
Thanks
The pivot inside your scrollviewer is intercepting your touch and mouse wheel interactions, that is why you are not receiving anything with your mouse wheel.
Placing a pivot within a scrollviewer is really really weird. What behavior are you trying to achieve ? If you want to scroll vertically in each of the pivot items you should place a scrollviewer in each of your PivotItems, not around your pivot.
I had the same problem i fixed it like this but i hope we find a better way cause it buggy.
private void PivotItem_PointerWheelChanged(object sender, PointerRoutedEventArgs e)
{
if (e.GetCurrentPoint(scrollViewer).Properties.MouseWheelDelta == (-120))
{
// On Mouse Wheel scroll Backward
scrollViewer.ChangeView(null, scrollViewer.VerticalOffset + Window.Current.CoreWindow.Bounds.Height / 10, null, false);
}
if (e.GetCurrentPoint(scrollViewer).Properties.MouseWheelDelta == (120))
{
// On Mouse Wheel scroll Forward
scrollViewer.ChangeView(null,scrollViewer.VerticalOffset - Window.Current.CoreWindow.Bounds.Height / 10, null, false);
}
}

Unexpected behaviour in a game example from a book

I just got the book Head First C# 3rd Edition. In the first part of the book it gets you started creating a simple game where you make bouncing circles that you must avoid while clicking and dragging a "human" to safety. The coding involves C# as well as XAML for the graphics side. Everything works except for one thing. Instead of clicking and dragging the "human" if I click on it it will "follow" the cursor around and the game behaves as if the cursor itself is the human since that is where game over happens when I hit one of the bouncing circles. I cannot figure out what I am doing wrong. I have looked over the code line by line next to the book.....The only thing I can figure out is that this block of code.
private void playArea_PointerMoved(object sender, PointerRoutedEventArgs e)
{
if (humanCaptured)
{
Point pointerPosition = e.GetCurrentPoint(null).Position;
Point relativePosition = grid.TransformToVisual(playArea).TransformPoint(pointerPosition);
if ((Math.Abs(relativePosition.X - Canvas.GetLeft(human)) > human.ActualWidth * 20)
|| (Math.Abs(relativePosition.Y - Canvas.GetTop(human)) > human.ActualHeight * 20))
{
humanCaptured = false;
human.IsHitTestVisible = true;
}
else
{
Canvas.SetLeft(human, relativePosition.X - human.ActualWidth / 2);
Canvas.SetTop(human, relativePosition.Y - human.ActualHeight / 2);
}
}
}
The book says to put the human actual width and height to 3. But if I do that and try to click on the person in the actual game it won't even register the cursor at all. but if I put that at 20 or above and I click on the human as I said earlier it starts following my cursor around and the rest of the game behaves normally.
Here is the code that is supposed to make the cursor be able to "click" and "drag" the human.
private void human_PointerPressed(object sender, PointerRoutedEventArgs e)
{
if (enemyTimer.IsEnabled)
{
humanCaptured = true;
human.IsHitTestVisible = false;
}
}
And here is the related XAML code this is supposed to be working with to make it click and drag but instead is making it follow my cursor around the area:
<StackPanel x:Name="human" Orientation="Vertical" Canvas.Left="17" Canvas.Top="15"
PointerPressed="human_PointerPressed" >
<Ellipse Fill="White" Height="10" Stroke="White" Width="10"/>
<Rectangle Fill="White" Height="25" Stroke="White" Width="10" />
</StackPanel>
Everything else seems to be working right. I have checked the code in the book over and over and gone to their site. Nothing I did is different as far as I can tell. If anyone can help it will be greatly appreciated.
After reading the Book "Head First C#, 3rd Edition"
I would say the problem isn't your code...
It's more about how he detects if a Human get captured, because he does not check if "human" collide with a "enemy" he just did some Pointerevent's which will get triggert if a Pointer (your Mouse) will enter an "enemy" and not if your "human" collide with him...

MouseLeftButtonDown on canvas requires too much precision

I am responding to MouseLeftButtonDown events on elements added to a WPF canvas. It all works fine when clicked (i.e. the eventhandler fires off correctly), but it requires too much precision from the mouse pointer. You have to be perfectly on top of the circle to make it work. I need it to be a little more forgiving; maybe at least 1 or 2 pixles forgiving. The elements on the canvas are nice big circles (about the size of a quarter on the screen), so the circles themselves are not too small, but the StrokeWidth of each one is 1, so it is a thin line.
You can see a screenshot here: http://twitpic.com/1f2ci/full
Most graphics app aren't this picky about the mouse picking, so I want to give the user a familiar experience.
How can I make it a little more forgiving.
You can hook up to the MouseLeftButtonDown event of your root layout object instead, and check which elements is in range of a click by doing this:
List<UIElement> hits = System.Windows.Media.VisualTreeHelper.FindElementsInHostCoordinates(Point, yourLayoutRootElement) as List<UIElement>;
http://msdn.microsoft.com/en-us/library/cc838402(VS.95).aspx
For the Point parameter, you can use the MouseEventArgs parameter e, and call its GetPosition method like this:
Point p = e.GetPosition(null)
I can't remember whether to use HitTest instead of the FindElementsInHostCoordinates. Try both.
http://msdn.microsoft.com/en-us/library/ms608752.aspx
You could create 4 Point objects from the mouse position to create a fake tolerence effect, and call either FindElementsInHostCoordinates or HitTest for all 4 points.
You might want to try to fill the circle with the Transparent colour to make the whole circle clickable...
If that fails, you can also draw helper circles on the same location as the other circles. Make the circle foreground colour Transparent, and make the thickness of the brush a few pixels wider for a more acceptable clickable region around the circle..
Hope this helps!
I think I've done it (with you help to get me started)...
First, I've moved the move event handling to the Canvas instead of each Ellipse. That's good and bad, from an OOP standpoint. At least when the mouse event handling is a responsibility of the HolePattern to set it on up each Hole (the ellipse that is the visual of the Hole), it is abstracted away so that any consumer of my HolePattern will get this functioanality automactically. However, by moving it to the main UI code, I now am dealing with my canvas mouse event at a higher level. But that's not all bad either. We could discuss this part for days.
The point is, I have designed a way to create a "margin of error" when picking something on a canvas with a mouse, and then reading the Hole that the selected Ellipse belongs to, and then I can read the HolePattern that the Hole belongs to, and my entire UI (ListView, textboxes, gridview fo coordinates) are ALL updated by the existing XAML binding, and the Canvas is updated with one call to an existing method to regenerate the canvas.
To be honest, I can't believe I've figured all this out (with your help and others too, of course). It is such a cool feeling to have the vision of this this and see it come to be.
Check out the main code here:
void canvas1_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
int ClickMargin = 2;
Point ClickedPoint = e.GetPosition(canvas1);
Point p1 = new Point(ClickedPoint.X - ClickMargin, ClickedPoint.Y - ClickMargin);
Point p2 = new Point(ClickedPoint.X - ClickMargin, ClickedPoint.Y + ClickMargin);
Point p3 = new Point(ClickedPoint.X + ClickMargin, ClickedPoint.Y + ClickMargin);
Point p4 = new Point(ClickedPoint.X + ClickMargin, ClickedPoint.Y - ClickMargin);
var PointPickList = new Collection<Point>();
PointPickList.Add(ClickedPoint);
PointPickList.Add(p1);
PointPickList.Add(p2);
PointPickList.Add(p3);
PointPickList.Add(p4);
foreach (Point p in PointPickList)
{
HitTestResult SelectedCanvasItem = System.Windows.Media.VisualTreeHelper.HitTest(canvas1, p);
if (SelectedCanvasItem.VisualHit.GetType() == typeof(Ellipse))
{
var SelectedEllipseTag = SelectedCanvasItem.VisualHit.GetValue(Ellipse.TagProperty);
if (SelectedEllipseTag!=null && SelectedEllipseTag.GetType().BaseType == typeof(Hole))
{
Hole SelectedHole = (Hole)SelectedEllipseTag;
SetActivePattern(SelectedHole.ParentPattern);
SelectedHole.ParentPattern.CurrentHole = SelectedHole;
}
}
}
}
Just Increase Stroke ThickNess of the Ellipse so that it is adjustable
thus the MouseLeftButtonDown event works
Example:
In Ellipse tag:
Ellipse
Canvas.Left="10" Canvas.Top="133" Height="24" Name="ellipse1" Width="23" Stroke="Red" MouseLeftButtonDown="ellipse1_MouseLeftButtonDown" ToolTip="Temp Close" StrokeEndLineCap="Flat" StrokeThickness="12"
private void ellipse1_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
Application curApp = Application.Current;
curApp.Shutdown();
}

Categories

Resources