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

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.

Related

Drawing image into a Window and update it

I have an unsafe class that generate a Bitmap which is converted to ToImageSource in order to draw into a Window. The Bitmap itself contains a sinusoidal text which is frequently updated and I want to it "move" from the left to the right (marquee style?). Anyway it works just fine in a WinForm but I'm stuck with the WPF Window.
Here are some code samples:
public AboutWindow()
{
InheritanceBehavior = InheritanceBehavior.SkipAllNow;
InitializeComponent();
Initialize();
}
protected override void OnRender(DrawingContext drawingContext)
{
base.OnRender(drawingContext);
drawingContext.DrawImage(bitmapRender.WindowBitmap, drawingArea);
if (!worker.IsBusy)
worker.RunWorkerAsync(); // BackgroundWorker in charge of updating the bitmap
}
void DispatcherTimerRender_Tick(object sender, EventArgs e) => InvalidateVisual();
My issues are: there is nothing displayed on the Window and the DispatchedTimer that calls InvalidateVisual() leads to this exception:
System.InvalidOperationException: 'Cannot use a DependencyObject that belongs to a different thread than its parent Freezable.'
I have looked at other threads and I'm aware that WPF is a retained drawing system but I would love to achieve it anyway.
Any suggestion about the "best" way to achieve this?
Any useful explanation/link would be very much appreciated.
[Edit]
<Window x:Class="CustomerManagement.View.AboutWindow"
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"
mc:Ignorable="d" Height="450" WindowStartupLocation="CenterScreen" Width="800" ResizeMode="NoResize" AllowsTransparency="True" WindowStyle="None">
<Grid KeyDown="Grid_KeyDown">
<Image Width="800" Height="450" Source="{Binding 'Image'}" />
</Grid>
</Window>
You should use an Image element that has its Source property bound to an ImageSource property in a view model. This is the "standard" way, based on the MVVM architectural pattern, and therefore the "best" way - in my opinion.
<Image Source="{Binding Image}"/>
The view model could look like this:
public class ViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private ImageSource image;
public ImageSource Image
{
get { return image; }
set
{
image = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Image)));
}
}
}
and an instance of it would be assigned to the DataContext of the window:
public AboutWindow()
{
InitializeComponent();
var vm = new ViewModel();
DataContext = vm;
}
For testing it, the code below performs a slide show of image files in a directory. You may as well assign any other ImageSource - e.g. a DrawingImage - to the Image property.
var imageFiles = Directory.GetFiles(..., "*.jpg");
var index = -1;
var timer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(1) };
timer.Tick += (s, e) =>
{
if (++index < imageFiles.Length)
{
vm.Image = new BitmapImage(new Uri(imageFiles[index]));
}
else
{
timer.Stop();
}
};
timer.Start();

Hit-Testing does not work in WPF Border with lower Z-index

I have a piece of code with two Border elements, but the hit-testing only works for the topmost Border (Border2) in the code below. This means that when I right-click, I see the message box, but when I left-click, nothing happens. Is there a way to fix this so that I can capture different mouse events with sibling controls that have different Z-index values? Here is my code:
<Window x:Class="HiTest.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:HiTest"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Grid>
<local:Border1 Background="Transparent"/>
<local:Border2 Background="Transparent"/>
</Grid>
</Window>
public class Border1 : Border
{
public Border1()
{
MouseLeftButtonDown += Border1_MouseLeftButtonDown;
}
private void Border1_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
MessageBox.Show("Left");
}
}
public class Border2 : Border
{
public Border2()
{
MouseRightButtonDown += Border2_MouseRightButtonDown;
}
private void Border2_MouseRightButtonDown(object sender, MouseButtonEventArgs e)
{
MessageBox.Show("Right");
}
}
In summary, I want to be able to capture different mouse events with sibling controls that have different Z-index values. How can I achieve this?
it is not possible to capture events for sibling elements. Bubbling only works for elements with parent and child relation.
Something like this with parent child relation.
<Grid Name="Parent">
<Border Name="Border01" Background="Transparent" MouseDown="Border01_MouseDown">
<Border Name="Border02" Background="Transparent" MouseDown="Border01_MouseDown" />
</Border>
</Grid>
private void Border01_MouseDown(object sender, MouseButtonEventArgs e)
{
if (sender.GetType() == typeof(Grid)) { }
if (sender.GetType() == typeof(Border))
{
if (((Border)sender).Name == "Border01" & e.LeftButton == MouseButtonState.Pressed)
{
MessageBox.Show("Left & Button01");
}
if (((Border)sender).Name == "Border02" & e.RightButton == MouseButtonState.Pressed)
{
MessageBox.Show("Right & Button02");
}
}
}
If you have sibling elements you need to delegate the MouseDown from the top element to the lower element by yourself.

How to detach from the Visual Tree in WPF

I'm trying to correctly remove a UIElement from an InlineUIContainer in order to use it in another Panel but the program keeps crashing with this message "Specified Visual is already a child of another Visual or the root of a CompositionTarget.".
I've created a small application to illustrate my pain. In this program, once Randy the button is killed\deleted by his girlfriend, he doesn't still detach from his parent, whom I got find out was UIElementIsland. And then any attempt to add Randy as the child of anything else crashes the application (The Apocalypse Button proves my point :) ). You can click to check Randy's parents before\after deleting Randy to notice that he is constantly under UIElementIsland as a child, If he is detached the whole problem\apocalypse should be averted.
It's a Funny application so copy and compile even if it's just for the fun! Any help\ideas would be appreciated!
THE C# Part:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Windows.Threading;
namespace DetachingfromUIElementIsland
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
int t = 0;
static string[] info = new string[] { "Okay, Lets have a look...", "Checking."
, "Checking..", "Checking...", "Seen it!" };
/// <summary>
/// Makes the App fancy :)
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
void timer_Tick(object sender, EventArgs e)
{
display.Text = info[t];
if (t == 0)
timer.Interval = new TimeSpan(0, 0, 0, 0, 300);
t++;
if (t >= 4)
{
t = 0;
timer.Stop();
display.Text = GetRandysParent();
}
}
private void deleteRandy_Click(object sender, RoutedEventArgs e)
{
// This might be the bug.
// Maybe there's a better way to do this.
// If there was a VisualTreeHelper.Remove().
randy_container.Child = null;
display.Text = "Haha! I just killed Randy!!! He'll never get the chance"
+ "\n to hurt another woman again!";
display.Background = Brushes.Violet;
end.Visibility = System.Windows.Visibility.Visible;
}
DispatcherTimer timer = null;
/// <summary>
/// Check if Randy is Still attached to UIElementIsland
/// </summary>
/// <returns></returns>
private string GetRandysParent()
{
// Check the visual tree to see if randy is removed properly
DependencyObject dp = VisualTreeHelper.GetParent(randy);
string text = string.Empty;
if (dp != null)
{
display.Background = Brushes.LightGreen;
text = "Randy's Dad is Mr " + dp.ToString();
}
else
{
// This should be what you'll get when the code works properly
display.Background = Brushes.Red;
text = "Weird...Randy doesn't seem to have a dad...";
}
return text;
}
private void findParents_Click(object sender, RoutedEventArgs e)
{
display.Background = Brushes.Yellow;
// Creates a timer to display some fancy stuff
// and then Randy's.
// Just to prove to you that this button actually works.
timer = new DispatcherTimer();
timer.Start();
timer.Tick += timer_Tick;
timer.Interval = new TimeSpan(0, 0, 0, 0, 700);
}
private void randy_Click(object sender, RoutedEventArgs e)
{
// Get Randy to introduce himself
display.Text = "Hi, I'm Randy!!!";
display.Background = Brushes.Orange;
}
private void end_Click(object sender, RoutedEventArgs e)
{
// If randy is removed properly, this would not crash the application.
StackPanel s = new StackPanel();
s.Children.Add(randy);
// CRASH!!!
}
}
}
The XAML:
<Window x:Class="DetachingfromUIElementIsland.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">
<FlowDocument IsEnabled="True" x:Name="document">
<Paragraph>
<InlineUIContainer x:Name="randy_container">
<!--Meet Randy-->
<Button Name="randy" Content="I am a Randy, the button" Click="randy_Click" ToolTip="Meet Randy"/>
</InlineUIContainer>
<LineBreak/>
<LineBreak/>
<InlineUIContainer x:Name="container2">
<!--Meet Randy's Ex Girlfriend-->
<Button Name="deleteRandy" Content="Randy dumped me for another girl :(, click me to delete him" Click="deleteRandy_Click" ToolTip="Meet Randy's Ex Girlfriend"/>
</InlineUIContainer>
<LineBreak/>
<LineBreak/>
<InlineUIContainer x:Name="container3">
<!--He can help you find Randy's Parents-->
<Button Name="findParents" Content="Click me to find randy's parents" Click="findParents_Click" ToolTip="He can help you find Randy's Parents"/>
</InlineUIContainer>
<LineBreak/>
<LineBreak/>
<InlineUIContainer x:Name="Apocalypse">
<!--End the world, Crash the application-->
<Button x:Name="end" Content="Avenge Randy's Death" Click="end_Click" ToolTip="End the world, Crash the application" Visibility="Hidden"/>
</InlineUIContainer>
</Paragraph>
<Paragraph>
<InlineUIContainer>
<TextBlock x:Name="display" Foreground="Black"/>
</InlineUIContainer>
</Paragraph>
</FlowDocument>
</Window>
The whole code was supposed to be shorter than this, but I spiced it up to make it a bit fun. Hope I brightened someone's day a little. But still, help me :).
Answer:
Derive from Randy's InlineUIContainer as follows:
public class DerivedInlineUIContainer : InlineUIContainer
{
public DerivedInlineUIContainer()
{
}
public void RemoveFromLogicalTree(FrameworkElement f)
{
this.RemoveLogicalChild(f);
}
}
Now you could kill Randy properly this time, and add him to UIElement heaven (The StackPanel):
randy_container.RemoveFromLogicalTree(randy);
IDisposable disp = VisualTreeHelper.GetParent(randy) as IDisposable;
if (disp != null)
disp.Dispose();
// Poor Randy is going to heaven...
StackPanel heaven = new StackPanel();
heaven.add(randy);
Thanks everyone.
Removing the visual parent doesn't seem to help:
private void end_Click(object sender, RoutedEventArgs e)
{
IDisposable disp = VisualTreeHelper.GetParent(randy) as IDisposable;
if (disp != null)
disp.Dispose();
DependencyObject parent = VisualTreeHelper.GetParent(randy);
if (parent == null)
MessageBox.Show("No parent");
// If randy is removed properly, this would not crash the application.
StackPanel s = new StackPanel();
s.Children.Add(randy);
}
So you could either create a new Button:
public MainWindow()
{
InitializeComponent();
randy_container.Child = CreateRandyButton();
}
private void end_Click(object sender, RoutedEventArgs e)
{
StackPanel s = new StackPanel();
s.Children.Add(CreateRandyButton());
}
private Button CreateRandyButton()
{
Button button = new Button { Name = "randy", Content = "I am a Randy, the button", ToolTip = "Meet Randy" };
button.Click += randy_Click;
return button;
}
...or simply hide it as suggested by #Sinatr.
It's funny, but also very noisy. You would get answer much faster if your demo is short.
Instead of removing/adding visual you can simply hide/show it:
void deleteRandy_Click(object sender, RoutedEventArgs e) =>
randy.Visibility = Visibility.Hidden;
void end_Click(object sender, RoutedEventArgs e) =>
randy.Visibility = Visibility.Visible;
This way you are not playing with visual tree in unrecoverable way. You can use MVVM + data templates or x:Shared=False resources if you really want to remove UI element and then add new one.
I found a workaround in case the parent is still a UIElementIsland. Since it implements IDisposable, you can clear its children that way:
var parent = VisualTreeHelper.GetParent(element);
if (parent is IDisposable uiElementIsland)
{
uiElementIsland.Dispose();
}
It's not nice, but it works.

WPF - Popup Menu on MouseDown, then select with MouseUp

I am trying to create a UserControl that behaves like so:
A button to start with, and when a user clicks it and holds, a Radial menu appears
User keeps holding left mouse, and releases on the menu item they want
I am using this Radial menu https://github.com/Julien-Marcou/RadialMenu
Here is my attempt:
<UserControl x:Class="NetBoard.RadialButton"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:NetBoard"
xmlns:radial="clr-namespace:RadialMenu.Controls;assembly=RadialMenu"
mc:Ignorable="d" >
<Grid>
<Button Name="Button"
PreviewMouseDown="Button_MouseDown"
PreviewMouseUp="Button_MouseUp" />
<radial:RadialMenu Name="RadialMenu" />
</Grid>
</UserControl>
public partial class RadialButton : UserControl
{
public RadialButton()
{
InitializeComponent();
// Test menu items
for (int i = 0; i < 3; i++)
{
RadialMenu.Content.Add(new RadialMenuItem { Content = i });
}
foreach (RadialMenuItem item in RadialMenu.Content)
{
item.PreviewMouseLeftButtonUp += Slice_MouseUp;
}
}
private void Button_MouseDown(object sender, MouseButtonEventArgs e)
{
RadialMenu.IsOpen = true;
}
private void Button_MouseUp(object sender, MouseButtonEventArgs e)
{
RadialMenu.IsOpen = false;
}
private void Slice_MouseUp(object sender, MouseButtonEventArgs e)
{
MessageBox.Show("It worked!");
}
The radial menu pops up fine, but the PreviewMouseLeftButtonUp event does not fire on the RadialMenuItems. The Menu Items don't even behave like they are being moused-over (they would change color).
Any ideas on how to accomplish this?
I actually got this working using a Label instead of a button.

PreviewMouseMove firing twice

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");
}
}

Categories

Resources