PreviewMouseMove firing twice - c#

I have a problem with a simple code. I was looking for a few hours a solution, but no effects.
I have a Canvas and Rectangle. I move Rectangle, if the cursor is outside, delegate pMouseMove fires only once for each pixel. Conversely, if the cursor is at the Rectangle, delagate fires twice for each pixel. I want to run it only once, as if it were outside the Rectangle, how to do it?
XAML:
<Window x:Class="WpfApplication1.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">
<Canvas x:Name="Can" Height="257" Width="503" Background="Gray">
<TextBox Name="tb" Width="77" Height="20" Canvas.Left="0" Canvas.Top="-21"/>
</Canvas>
</Window>
Code-behind:
public partial class MainWindow : Window
{
Rectangle rect = new Rectangle();
private static int i;
private static string s;
public MainWindow()
{
InitializeComponent();
rect.Height = 50;
rect.Width = 50;
rect.Fill = Brushes.Black;
Can.Children.Add(rect);
Can.PreviewMouseMove += pMouseMove;
}
private void pMouseMove(object sender, MouseEventArgs e)
{
//cursor over Rectangle
Canvas.SetTop(rect, e.GetPosition(Can).Y + 10);
Canvas.SetLeft(rect, e.GetPosition(Can).X + 10);
//cursor outside Rectangle
//Canvas.SetTop(rect, e.GetPosition(Can).Y - 10);
//Canvas.SetLeft(rect, e.GetPosition(Can).X - 10);
//Counter
i++;
tb.Text = i.ToString();
//e.Handled = true;
}
}
Sorry for my bad english

Events in WPF are Routed Events, which effectively means that your Canvas will receive events from the canvas itself and everything inside the canvas. As you noticed, the Canvas's PreviewMouseMove event is receiving events from both the Canvas and the Rectangle.
[Update]
I ran your code and added a line to check the value of the e.OriginalSource to see what originally raised the event. Like this:
private void pMouseMove(object sender, MouseEventArgs e)
{
// print out e.OriginalSource just for learning purposes
Console.WriteLine("OriginalSource:" + e.OriginalSource.ToString());
}
My original answer was to check e.OriginalSource's type because I thought you were receiving the same event twice. But I now see what you are saying: if e.OriginalSource is the Rectangle, the PreviewMouseMove event gets raised twice as often compared to when e.OriginalSource is the Canvas. There's something internal to the Rectangle's implementation that is doing this (only way to find out is to use a tool like Reflector to see the internal logic. However, there is a workaround where you can make the frequency of the event consistent.
You can set rect.IsHitTestVisible = false; and that will eliminate the Rectangle from sending events and being e.OriginalSource -- so that means all PreviewMouseMove events will come from the Canvas. Then you can use VisualTreeHelper.HitTest to check to see if the mouse position is inside the Rectangle.
I just ran this code below and I think this is a way to guarantee consistent raising of events, but still have your hit test capability.
In the constructor:
rect.Fill = Brushes.Black;
rect.IsHitTestVisible = false;
Can.Children.Add(rect);
In the PreviewMouseMove handler:
private void pMouseMove(object sender, MouseEventArgs e)
{
// Debug.WriteLine(e.OriginalSource.ToString());
HitTestResult result = VisualTreeHelper.HitTest(rect, e.GetPosition(sender as UIElement));
if (result != null) {
Debug.WriteLine("Mouse inside rect")
}
else {
Debug.WriteLine("Mouse outside rect");
}
}

Related

WPF MouseEnter/MouseLeave/MouseMove Event Tunneling

I am trying to make a button where the contents change when the mouse enters the button.
Currently, this is the code that I'm working with:
Xaml
<StackPanel Orientation="Vertical">
<StackPanel Orientation="Horizontal">
<Button x:Name="One"
Content="ONE"
Height="50"
Width="Auto"
MouseEnter="One_OnMouseEnter"
MouseLeave="One_OnMouseLeave" />
<Button x:Name="Two"
PreviewMouseMove="Two_OnMouseEnter">
<Grid>
<Ellipse Fill="Black"
Height="40"
Width="40" />
<Label Content="TWO"
Foreground="White"
VerticalAlignment="Center"
HorizontalAlignment="Center" />
</Grid>
</Button>
</StackPanel>
</StackPanel>
C# Code-Behind File
private void One_OnMouseEnter(object sender, MouseEventArgs e)
{
Button b = sender as Button;
if (b != null)
{
b.Foreground = Brushes.Purple;
b.FontSize = 24;
}
}
private void One_OnMouseLeave(object sender, MouseEventArgs e)
{
Button b = sender as Button;
if (b != null)
{
b.Foreground = Brushes.Black;
b.FontSize = 12;
}
}
private void Two_OnMouseEnter(object sender, MouseEventArgs e)
{
Ellipse el = sender as Ellipse;
if (el != null)
{
el.Height = 60;
el.Width = 60;
el.Fill = Brushes.White;
}
Label l = sender as Label;
if (l != null)
{
l.Foreground = Brushes.Black;
}
Grid g = sender as Grid;
if (g != null)
{
g.Height = 200;
g.Width = 200;
}
}
The first button that's there works as expected.
When the mouse moves onto the "One" button, the text contents changes as expected. Text color changes to purple, and font size increases.
I am trying to do something similar with the second button. Increase the size and change the color of the elliptical, change the color of the label, and change the size of the grid.
The problem is that the second button does not seem to respond as expected. I've tried to use PreviewMouseMove, which I understand to use a Tunneling routing strategy, which should trigger on the Button's child elements. I have used breakpoints to check, and the event seems to only trigger with the sender being the Button.
My question is: Why isn't the event being raised on the children as I've read that the Tunneling routing strategy is supposed to work and what can I do to fix it?
Also, the MouseEnter and MouseLeave events seem to follow the Bubbling routing strategy, but the behavior more closely resembles what I want to do. Can I force this to use a Tunneling routing strategy?
edit:
In order to further explain the goal of this project:
What I am intending to do, is to have a more complete understanding of Event Tunneling in WPF.
This morning, I examined the book more and found a way to make this work when the mouse enters the area of each specific child, which is an improvement.
Here is the new code within the C# Code-Behind File:
private void Two_OnMouseEnter(object sender, MouseEventArgs e)
{
Ellipse el = e.OriginalSource as Ellipse;
if (el != null)
{
el.Height = 60;
el.Width = 60;
el.Fill = Brushes.Orange;
}
TextBlock t = e.OriginalSource as TextBlock;
if (t != null)
{
t.Foreground = Brushes.Blue;
}
Grid g = e.OriginalSource as Grid;
if (g != null)
{
g.Height = 200;
g.Width = 200;
}
}
The difference with this code is that it uses the MouseEventArgs e object within the method signature, casting e.OriginalSource as the object type instead of casting the sender object.
In addition, this code is called using the PreviewMouseMove Event call in the XAML file:
<Button x:Name="Two" PreviewMouseMove="Two_OnMouseEnter">
which appears to only allow the contents to change when the mouse enters the area, but not when the mouse leaves the area. Which leads me back to part of my original question: can I force MouseEnter and MouseLeave to follow the Tunneling Routing Strategy?
It's not doing anything because the event is coming from the Button (which is therefore the sender).
Something like this would be what you are trying to do:
private void Two_OnMouseEnter(object sender, MouseEventArgs e)
{
Button b = sender as Button;
Grid g = b.Content as Grid;
Ellipse el = g.Children[0] as Ellipse;
Label l = g.Children[1] as Label;
g.Height = 200;
g.Width = 200;
el.Height = 60;
el.Width = 60;
el.Fill = Brushes.White;
l.Foreground = Brushes.Black;
}
UPDATE
To explain the tunneling strategy a bit more, consider the following example (I've removed the event handlers for "One" for convenience):
<StackPanel Orientation="Vertical">
<StackPanel Orientation="Horizontal">
<Button x:Name="One" Content="ONE" Height="50" Width="Auto" />
<Button x:Name="Two">
<Grid PreviewMouseMove="Grid_PreviewMouseMove">
<Ellipse Fill="Black" Height="40" Width="40" PreviewMouseMove="Ellipse_PreviewMouseMove" />
<Label Content="TWO" Foreground="White" VerticalAlignment="Center" HorizontalAlignment="Center"
PreviewMouseMove="Label_PreviewMouseMove"/>
</Grid>
</Button>
</StackPanel>
</StackPanel>
And in the code-behind:
private void Grid_PreviewMouseMove(object sender, MouseEventArgs e)
{
Grid g = sender as Grid;
if (g != null)
{
g.Height = 200;
g.Width = 200;
}
System.Diagnostics.Debug.WriteLine("Sender: " + sender.GetType() + "; Source: " + e.Source.GetType());
}
private void Ellipse_PreviewMouseMove(object sender, MouseEventArgs e)
{
Ellipse el = sender as Ellipse;
if (el != null)
{
el.Height = 60;
el.Width = 60;
el.Fill = Brushes.White;
}
System.Diagnostics.Debug.WriteLine("Sender: " + sender.GetType() + "; Source: " + e.Source.GetType());
}
private void Label_PreviewMouseMove(object sender, MouseEventArgs e)
{
Label l = sender as Label;
if (l != null)
{
l.Foreground = Brushes.Black;
}
System.Diagnostics.Debug.WriteLine("Sender: " + sender.GetType() + "; Source: " + e.Source.GetType());
}
These methods could theoretically be refactored into one method, with some logic applied to detect the Type of the sender, but it would still be in the XAML three times.
One thing to notice is that the Grid won't fire its own events unless you set it's Background property (e.g., to Transparent), though it will still fire when the Ellipse and Label fire.
Another thing to notice is that when the Label fires the event, i.e. when you move the mouse over the Label, the event handler for the Ellipse is not called. This is because the tunneling strategy proceeds logically down the tree, rather than visually. If you look at the "Source: " part in the message in the Output window you'll see what I mean. The same would also be true for bubbling events.
As a general observation, the thing with this kind of strategy is that with events firing from different elements, your control isn't really functioning as one single Button, which looks to be the intention. If you move the mouse in slowly, you'll notice the Ellipse fires first, then the Label once you reach that.
There aren't any PreviewMouseEnter or PreviewMouseLeave events, so tunneling couldn't be used for that anyway.

How can i highlight line when user hovers over it in wpf?

Currently users can draw on the canvas by clicking and dragging their mouse. How can I change the color of the line to indicate the user's cursor is hovering over the line? It would be ideal to make it highlight when the cursor is within 5 pixels of any given line to indicate they are close.
Inital drawing...
When user's cursor is either hovering directly over or within 5 pixels of any given line.
MainWindow.xaml
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApplication1"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Canvas Name="paintSurface" MouseDown="Canvas_MouseDown_1" MouseMove="Canvas_MouseMove_1" >
<Canvas.Background>
<SolidColorBrush Color="White" Opacity="0"/>
</Canvas.Background>
</Canvas>
</Grid>
</Window>
MainWindow.cs
using System.Windows;
using System.Windows.Input;
using System.Windows.Shapes;
namespace WpfApplication1
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
Point currentPoint = new Point();
public MainWindow()
{
InitializeComponent();
}
private void Canvas_MouseDown_1(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
if (e.ButtonState == MouseButtonState.Pressed)
currentPoint = e.GetPosition(this);
}
private void Canvas_MouseMove_1(object sender, System.Windows.Input.MouseEventArgs e)
{
if (e.LeftButton == MouseButtonState.Pressed)
{
Line line = new Line();
line.Stroke = SystemColors.WindowFrameBrush;
line.X1 = currentPoint.X;
line.Y1 = currentPoint.Y;
line.X2 = e.GetPosition(this).X;
line.Y2 = e.GetPosition(this).Y;
currentPoint = e.GetPosition(this);
paintSurface.Children.Add(line);
}
}
}
}
Note: There are two versions of the sample program here, new and old. Take a look at both to get an idea of what you can do.
Here's a sample application that fixes the issue you were unaware of, which is the fact that you drew numerous lines with one stroke and not a single line. You should use a Polyline. If you use Visual Studio 2015, then there's a Live Visual Tree, which will show you exactly what I mean; otherwise, you can use a tool such as Snoop to see the same thing. It also addresses you original question, which is the highlighting.
The new version is the first code portion that is shown here. It uses a dictionary to link base line and highlight lines, so that you may get to the underlying base line if need to (such as when you want to delete it). It also highlights the base, versus the highlighting the highlight line, which is what the old version did. The highlight line is simply used for the selection zone buffer. Increase or decrease its stroke to get the desired selection buffer (you mentioned 5 pixels in your post).
Preview:
XAML:
<Window x:Class="WpfApplication.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApplication"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Canvas Name="paintSurface" Background="White" MouseDown="Canvas_MouseDown"
MouseUp="Canvas_MouseUp" MouseMove="Canvas_MouseMove"/>
</Window>
C#:
using System.Collections.Generic;
using System.Windows;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Shapes;
namespace WpfApplication
{
public partial class MainWindow : Window
{
Polyline _baseLine;
Polyline _highlightLine;
Point _currentPoint;
bool _newLine;
Dictionary<Polyline, Polyline> _lines = new Dictionary<Polyline, Polyline>();
public MainWindow()
{
InitializeComponent();
}
private void Canvas_MouseDown(object sender, MouseButtonEventArgs e)
{
_newLine = true;
}
private void Canvas_MouseUp(object sender, MouseButtonEventArgs e)
{
if (_highlightLine != null && !_newline)
{
_highlightLine.MouseEnter += ShowHighlight;
_highlightLine.MouseLeave += HideHighlight;
}
}
private void Canvas_MouseMove(object sender, MouseEventArgs e)
{
if (e.LeftButton == MouseButtonState.Pressed)
{
if (_newLine)
{
_baseLine = new Polyline
{
Stroke = SystemColors.WindowFrameBrush,
StrokeThickness = 1.0
};
_highlightLine = new Polyline
{
Opacity = 0.0,
Stroke = SystemColors.WindowFrameBrush,
StrokeThickness = 10.0
};
paintSurface.Children.Add(_baseLine);
paintSurface.Children.Add(_highlightLine);
_lines.Add(_highlightLine, _baseLine);
_newLine = false;
}
_currentPoint = e.GetPosition(this);
_baseLine.Points.Add(_currentPoint);
_highlightLine.Points.Add(_currentPoint);
}
}
private void ShowHighlight(object sender, MouseEventArgs e)
{
var line = sender as Polyline;
if (line != null)
{
_lines[line].Stroke = new SolidColorBrush(Colors.LimeGreen);
}
}
private void HideHighlight(object sender, MouseEventArgs e)
{
var line = sender as Polyline;
if (line != null)
{
_lines[line].Stroke = SystemColors.WindowFrameBrush;
}
}
}
}
You'll notice the _newLine flag boolean. I use it to indicate whether a new Polyline should be drawn. When the mouse is down, that's an indicator that a new line needs to be created. I don't hook up the MouseEnter and MouseLeave handles for the line until the mouse is up because I don't want highlighting to be distracting during the drawing process of the line. You have to give some sort of stroke to the _highlightLine and then set its opacity to 0 to make it invisible, but still respond to hit tests; otherwise, MouseEnter and MouseLeave handlers will never get invoked.
OLD (The old version of the program. Still a good one to check out.):
What I do here is add a highlighting polyline on top of the base one and set its stroke to be 10 instead of the base's 1. You can adjust that stroke thickness to get yourself the desired selection "buffer" zone. I literally spent about 10-15 minutes on this, so there could be ways to improve it, but this should give you a solid base to build upon. If you wish to perform some actions down the road on these lines you're highlighting, such being able to delete them, then I suggest adding both the _baseLine and the _highlightLine to a dictionary, where _highlightLine is the key and _baseLine is the value. That way, when you select the _highlightLine, you may access the underlying _baseLine.
Preview:
XAML:
<Window x:Class="WpfApplication.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApplication"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Canvas Name="paintSurface" Background="White" MouseDown="Canvas_MouseDown"
MouseUp="Canvas_MouseUp" MouseMove="Canvas_MouseMove"/>
</Window>
C#:
using System.Windows;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Shapes;
namespace WpfApplication
{
public partial class MainWindow : Window
{
Polyline _baseLine;
Polyline _highlightLine;
Point _currentPoint;
bool _newLine;
public MainWindow()
{
InitializeComponent();
}
private void Canvas_MouseDown(object sender, MouseButtonEventArgs e)
{
_newLine = true;
}
private void Canvas_MouseUp(object sender, MouseButtonEventArgs e)
{
if (_highlightLine != null && !_newline)
{
_highlightLine.MouseEnter += ShowHighlight;
_highlightLine.MouseLeave += HideHighlight;
}
}
private void Canvas_MouseMove(object sender, MouseEventArgs e)
{
if (e.LeftButton == MouseButtonState.Pressed)
{
if (_newLine)
{
_baseLine = new Polyline
{
Stroke = SystemColors.WindowFrameBrush,
StrokeThickness = 1.0
};
_highlightLine = new Polyline
{
Stroke = new SolidColorBrush(Colors.Green),
Opacity = 0.0,
StrokeThickness = 10.0
};
paintSurface.Children.Add(_baseLine);
paintSurface.Children.Add(_highlightLine);
_newLine = false;
}
_currentPoint = e.GetPosition(this);
_baseLine.Points.Add(_currentPoint);
_highlightLine.Points.Add(_currentPoint);
}
}
private void ShowHighlight(object sender, MouseEventArgs e)
{
var line = sender as Polyline;
if (line != null)
{
line.Opacity = 1.0;
}
}
private void HideHighlight(object sender, MouseEventArgs e)
{
var line = sender as Polyline;
if (line != null)
{
line.Opacity = 0.0;
}
}
}
}
Additional Thoughts:
If you want to go full XAML as far as styling, you've got a few options. First option is to create a style that highlights TargetType Polyline on IsMouseOver property being true; however, you won't get the 5 pixel buffer with this one. To accomplish that 5 pixel buffer, you'd need to create a custom template, which requires more work than what I've demonstrated here. Of course... if you're feeling very adventurous, there's always the option of deriving from Shape and create yourself a highlightable/selectable Polyline -- it's just a lot of work, compared to the above code. The bright side is that it'll be reusable. It just depends on your situation, needs and wants.

Viewport3D Mouse Event doesn't fire when hitting background

I set up a Viewport3D with a MouseEventHandler
[...]
Main3DWindow.MouseUp += new MouseButtonEventHandler(mainViewport_MouseUp);
[...]
void mainViewport_MouseUp (object sender, MouseButtonEventArgs e) {
Point location = e.GetPosition(Main3DWindow);
ModelVisual3D result = GetHitTestResult(location);
if (result == null) {
_CurrentData.Unselect();
return;
}
_CurrentData.SelectItemFromObjectList(result);
}
And it works pretty fine when an object is clicked.
My expectation was: If no object is clicked (because the user clicked at the background) the result is null. But in fact the mainViewport_MouseUp-method is not even called.
My question: how can i detect clicks on the background of the Viewport3D?
It is as you wrote, it wont be fired.
I solved that by defining events on border and put viewport into border. Sample is from XAML:
<Border
MouseWheel="mainViewport_MouseWheel"
MouseMove="mainViewport_MouseMove"
MouseLeftButtonDown="mainViewport_MouseLeftButtonDown"
Background="Black">
<Viewport3D
Name="mainViewport"
ClipToBounds="True"
Grid.Row="0"
Grid.Column="0"
Grid.ColumnSpan="3"
Margin="0,0,0,0">
.....
</Viewport3D>
</Border>
And in the code:
private void mainViewport_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
Point location = e.GetPosition(mainViewport);
try
{
ModelVisual3D result = (ModelVisual3D)GetHitTestResult(location);
//some code.......
}
catch
{
//some code .......
}
}

Why does the MouseMove event not work if mouse loses focus on object?

I basically have a simple problem in my program that I just want to make sure goes right. It should on the click of the mouse button add the MouseEventHandler and then move the circle along with the mouse until the event handler gets removed. I simplified the code to the very basics:
XAML:
<Window x:Class="WpfApplication1.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">
<Grid Name="grid1" Background="White" MouseLeftButtonUp="grid_MouseUp">
<Ellipse Height="50" HorizontalAlignment="Left" Margin="12,12,0,0" Name="ellipse1" Stroke="{x:Null}" VerticalAlignment="Top" Width="50" Fill="Black" MouseLeftButtonDown="ellipse1_MouseDown" />
</Grid>
</Window>
C#:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private static Point _oldPoint = new Point(), _newPoint = new Point();
private void ellipse1_MouseDown(object sender, MouseButtonEventArgs e)
{
_oldPoint = e.GetPosition(grid1);
grid1.MouseMove += new MouseEventHandler(grid_MouseMove);
}
private void grid_MouseUp(object sender, MouseButtonEventArgs e)
{
grid1.MouseMove -= new MouseEventHandler(grid_MouseMove);
}
private void grid_MouseMove(object sender, MouseEventArgs e)
{
_newPoint = e.GetPosition(grid1);
ellipse1.Margin = new Thickness(ellipse1.Margin.Left - _oldPoint.X + _newPoint.X, ellipse1.Margin.Top - _oldPoint.Y + _newPoint.Y, 0, 0);
_oldPoint = _newPoint;
}
}
Now in general this code works fine and I think is quite neat as it doesn't check the movement of the mouse until one actually presses the button. However, my question is as follows:
I had to add the MouseMove event to the grid rather than to the circle, because once the mouse pointer loses focus of the circle (by moving the mouse too fast) it doesn't trigger the MouseMove event anymore. But why exactly does that happen? At the beginning of the event the mouse was definitely above the circle and then it moved. Yes, it moved away from the circle but shouldn't that still trigger the event?
You can capture the mouse and handle all events in your ellipse.
<Grid Name="grid1" Background="White">
<Ellipse Height="50" HorizontalAlignment="Left" Margin="12,12,0,0" Name="ellipse1" Stroke="{x:Null}" VerticalAlignment="Top" Width="50" Fill="Black"
MouseLeftButtonDown="ellipse1_MouseDown" MouseLeftButtonUp="ellipse1_MouseUp" />
</Grid>
with this code behind
private void ellipse1_MouseDown(object sender, MouseButtonEventArgs e)
{
Mouse.Capture(ellipse1);
_oldPoint = e.GetPosition(grid1);
ellipse1.MouseMove += new MouseEventHandler(ellipse1_MouseMove);
}
private void ellipse1_MouseUp(object sender, MouseButtonEventArgs e)
{
Mouse.Capture(null);
ellipse1.MouseMove -= new MouseEventHandler(ellipse1_MouseMove);
}
I've moved and renamed grid_MouseMove to ellipse1_MouseMove.
Adding to what Peter said, if you use the Grid.MouseDown event and checked if the oldPoint is within Ellipse and have then handled the MouseMove event, this odd behavior wont be seen.
I also suggest exploring drag events.
A control only gets the mouse-events as long as the mouse is hovering over that particularly control.
If moving to a new control, the mouse is getting unhooked from the old control and hooked to the new control.
There are ways where you can create a global hook attached to the entire process, but I guess this is not what we are talking about.

How to detect if the scroll viewer reaches bottom in winrt

I'm wondering what's the best approach to detect if a ScrollViewer reaches the bottom, right etc.
I think I can achieve that by using both PointerWheelChanged for mouse and ManipulationDelta for touch. In these event handlers, I can record the HorizontalOffset to find out when will the scroller reach the end. But I think there could be a better way to do it.
I've found this article. But the compression visual states seem not working in winrt. The CurrentStateChanging event method is not getting called.
I also checked another article. But it just works for scroll bar, not a generic approach.
Anyone knows what's the best way to solve this problem?
XAML:
<ScrollViewer
x:Name="sv"
ViewChanged="OnScrollViewerViewChanged">
<Rectangle
x:Name="rect"
Width="2000"
Height="2000"
Fill="Yellow"
Margin="10" />
</ScrollViewer>
Code behind:
private void OnScrollViewerViewChanged(object sender, ScrollViewerViewChangedEventArgs e)
{
var verticalOffset = sv.VerticalOffset;
var maxVerticalOffset = sv.ScrollableHeight; //sv.ExtentHeight - sv.ViewportHeight;
if (maxVerticalOffset < 0 ||
verticalOffset == maxVerticalOffset)
{
// Scrolled to bottom
rect.Fill = new SolidColorBrush(Colors.Red);
}
else
{
// Not scrolled to bottom
rect.Fill = new SolidColorBrush(Colors.Yellow);
}
}
For UWP I got it like this
<ScrollViewer Name="scroll" ViewChanged="scroll_ViewChanged">
<ListView />
</ScrollViewer>
private void scroll_ViewChanged(object sender, ScrollViewerViewChangedEventArgs e)
{
var scrollViewer = (ScrollViewer)sender;
if (scrollViewer.VerticalOffset == scrollViewer.ScrollableHeight)
btnNewUpdates.Visibility = Visibility.Visible;
}
private void btnNewUpdates_Click(object sender, RoutedEventArgs e)
{
itemGridView.ScrollIntoView(itemGridView.Items[0]);
btnNewUpdates.Visibility = Visibility.Collapsed;
}

Categories

Resources