I found a workaround for this bug I want to understand why this is happening. Is it bug? Is there a better workaround? Am I doing something wrong?
Here is the plainest code that I could come up with to show the problem. Inside MainWindow.xaml:
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<StackPanel Orientation="Horizontal">
<local:AnnoyingBlock Margin="5"/>
<TextBlock Text="Tweedle Dee" MouseDown="MoveWindow"/>
</StackPanel>
</Grid>
Inside MainWindow.xaml.cs
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void MoveWindow(object sender, MouseButtonEventArgs e)
{
if (e.ChangedButton == MouseButton.Left) Dispatcher.Invoke(DragMove);
e.Handled = true;
}
}
class AnnoyingBlock : TextBlock
{
public AnnoyingBlock()
{
MouseLeftButtonUp += OnMouseUp;
Text = "Tweedle Dumb";
}
private void OnMouseUp(object sender, MouseButtonEventArgs e)
{
MessageBox.Show("Boom");
}
}
Run that code and click/drag the text Tweedle Dee. It works as expected and the window is moved with the mouse. Click on the text Tweedle Dumb. The message box shows up.
Now, from the AnnoyingBlock declaration in the xaml, remove the margin and run it again. Run it again and you should see that clicking anywhere in the Tweedle Dee text pops up the message box when you release the mouse. Remove the MouseDown event from the TextBlock in the xaml and run it again. The MouseUp event on the sibling control is no longer fired.
Why does an event on a sibling control get fired? Why does this only happen when you have an event on the control (even though it's a different event MouseDown vs MouseUp)? Why does adding a margin stop this from happening?
Related
I have trouble with my MouseEvents in WPF/C#.
I got some simple WPF structure:
<Grid Name="ID1" Grid.Column="1" Grid.Row="1">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<customUserControl:CustomUserControl:Name="MainDisplay" UseDefaultContextMenu="False" Grid.Row="0">
<customUserControl:CustomUserControl.ContextMenu>
<ContextMenu>
<MenuItem Header="Thing1" Click="Click1"/>
<MenuItem Header="Thing2" Click="Click2"/>
<MenuItem Header="Thing3" Click="Click3"/>
<MenuItem Header="Thing4" Click="Click4"/>
</ContextMenu>
</customUserControl:CustomUserControl.ContextMenu>
</customUserControl:CustomUserControl>
<Overlay1:OverlayS Name="Overlay_One" Grid.Row="0" HorizontalAlignment="Left" VerticalAlignment="Top" Background="Transparent"/>
</Grid>
As you may see I got standard Grid with two overlapping UserControls.
When rigth-click on CustomUserControl I get access to the ContextMenu.
The Overlay1 does show an overlay with transparent background. Still when this is displayed, I don't have access to the MouseEvents of customUserControl anymore. But that would be required.
My idea now was to simply implement an togglebutton to switch between MouseEvents on CustomUserControl or Overlay1 with both still displayed at the same time.
You can by-pass all the events catched by OverlayS to the control you need like:
private void OverlayS_Click(object sender, RoutedEventArgs e)
{
if (!e.Handled)
{
// e.Handled = true; //Set to true only when the mouse event must end here
var eventArg = new RoutedEventArgs(e.RoutedEvent);
eventArg.Source = sender;
var otherUIControl = CustomUserControl as UIElement;
otherUIControl.RaiseEvent(eventArg);
}
}
For other mouse events (previewMouseDown, MouseWheel, etc) you will have to create the appropiate EventArgs. For example:
private void OverlayS_PreviewMouseDown(object sender, MouseButtonEventArgs e)
{
if (!e.Handled)
{
// e.Handled = true;//Set to true only when the mouse event must end here
var eventArg = new MouseButtonEventArgs(e.MouseDevice,e.Timestamp, e.ChangedButton);
eventArg.RoutedEvent = e.RoutedEvent;
eventArg.Source = sender;
var otherUIControl = CustomUserControl as UIElement;
otherUIControl.RaiseEvent(eventArg);
}
}
Probably, you don't have to copy the EventArgs and just pass them on to otherUiControl.
private void OverlayS_Click(object sender, RoutedEventArgs e)
{
if (!e.Handled)
{
var otherUIControl = CustomUserControl as UIElement;
otherUIControl.RaiseEvent(e);
}
}
But be carefull as some mouse events might reach many controls and they can mess with e.Handled.
In my main page I try to show a list of stuff, and on this stuff a userControl as an overlay. In fact I never see it. (In the design view, my userControl is oaky)
XAML MainPage
<Grid>
<Grid x:name="MyPage">
<!-- All this part is visible -->
//Button
//Button
//nice Pic
//Button
</Grid>
<cat:CatPagecontrol x:Name="CatTool" Visibility="Visible" HorizontalAlignment="Center" VerticalAlignment="Center">
<cat:CatPagecontrol.Transitions>
<TransitionCollection>
<PopupThemeTransition/>
</TransitionCollection>
</cat:CatPagecontrol.Transitions>
</cat:CatPagecontrol>
<!-- EDIT I remove the Grid "CatGrid" And the ZIndex -->
</Grid>
I try to switch the ZIndex, no results.
C# File
public MainView()
{
this.CatTool = new CatPagecontrol();
//this.CatTool.Visibility = Visibility.Collapsed;
}
private void showCatSelector()
{
this.CatTool.Visibility = Visibility.Visible;
}
After that I need that one of my buttons show the overlay when clicked.
If you know how to show it, I'm yours. Thanks.
edit : solution find.
Voila !
I've find my problem :
public CatPagecontrol()
{
this.InitializeComponent();
}
I just Initialized in the correct section.
Considering that piece of code :
XAML:
<Grid x:Name="LayoutRoot">
<Border x:Name="brd1" Height="100" Width="100" Background="Blue"
MouseLeftButtonUp="brd1_MouseLeftButtonUp"
MouseLeave="brd1_MouseLeave" />
</Grid>
C# :
private void brd1_MouseLeftButtonUp(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
brd1.Visibility = System.Windows.Visibility.Collapsed;
}
private void brd1_MouseLeave(object sender, MouseEventArgs e)
{
MessageBox.Show("Mouse Leave");
}
Why is the MouseLeave not firing when setting Visibility = Collapsed (ie : when I click on the border)?
Is there a way to always catch the MouseLeave event even if the control disappears (or one of its parent)? I cannot listen to the MouseButtonUp event, since my control can appear/disappear asynchronously at any time.
(note : my application is far more complex than that, this was just a simple example of what I need to do)
I have the following XAML
<Window x:Class="SimpleAttahEvent.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow">
<Grid>
<StackPanel Margin="5" Name="stackButton" ButtonBase.Click="DoSomething">
<Button Name="cmd1" Tag="The first button"> Command 1</Button>
<Button Name="cmd2" Tag="The second button"> Command 2</Button>
<Button Name="cmd3" Tag="The third button"> Command 3</Button>
</StackPanel>
</Grid>
</Window>
...With the following code to handle the attached events.
namespace SimpleAttahEvent
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
stackButton.AddHandler(Button.ClickEvent, new RoutedEventHandler(DoSomething));
}
private void DoSomething(object sender, RoutedEventArgs e)
{
Console.WriteLine(Button.ClickEvent.RoutingStrategy);
Console.WriteLine(TextBox.PreviewKeyDownEvent.RoutingStrategy);
if (e.Source == cmd1)
{
MessageBox.Show("First button is clicked");
}
if (e.Source == cmd2)
{
MessageBox.Show("Second button is clicked");
}
if (e.Source == cmd3)
{
MessageBox.Show("Third button is clicked");
}
}
}
}
These produce a dialog box with 3 buttons stacked vertically. When I click one of the button, a messagebox comes up with an OK button. However, the OK button on the dialogue box won't close unless I clicked it twice. Did I do this implicitly from the code given above?
Edit - Additional Info:
When I do this instead
private void DoSomething(object sender, RoutedEventArgs e)
{
object tag = ((FrameworkElement)sender).Tag;
MessageBox.Show((string)tag);
}
..it still require 2 clicks to close the message box.
Your problem is that you are doubling your handler. You do not have to click twice on the same OK; you are clicking on OK, which closes the first message. Then, the event is handled again and you get another exact same message that you have to click OK on. If you add + DateTime.Now to your messages you will see that this is indeed a second message
I missed this line on my first glance:
stackButton.AddHandler(Button.ClickEvent, new RoutedEventHandler(DoSomething));
Which is the same as the ButtonBase.Click from this line
<StackPanel Margin="5" Name="stackButton" ButtonBase.Click="DoSomething">
Choose one way to attach to event handlers and stick to it. Mixing them up is just going to cause confusion.
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.