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.
Related
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?
In my app, a user can select an Image and drag it onto a Grid, to play with it. I do this by handling the PointerEntered event of the Grid. Here I detect if the user had an image selected and if the user is holding the mouse button.
Now I want to place the Image on the grid, and pass on the (still pressed down) pointer to my Image, so the Image uses its own ManipulationStarted, ManipulationDelta and ManipulationCompleted events. This should let the user drag the image in one smooth movement from the list of images to the Grid, instead of having to release and click on the element.
I have tried releasing the pointer from the sender in PointerEntered, and capturing it using CapturePointer, but that doesn't seem to work, even though the CapturePointer returns true.
Here is the code I use for the PointerEntered event:
private void DrawingArea_OnPointerEntered(object sender, PointerRoutedEventArgs e)
{
// If we enter the grid while dragging and we have an image that was dragged
if (e.Pointer.IsInContact && CurrentDraggedImage != null)
{
DrawingArea.Children.Add(CurrentDraggedImage);
// Move it to the location we're currently at
var transform = (CurrentDraggedImage.RenderTransform as CompositeTransform);
transform.TranslateX += e.GetCurrentPoint(DrawingArea).RawPosition.X - DrawingArea.ActualWidth / 2;
transform.TranslateY += e.GetCurrentPoint(DrawingArea).RawPosition.Y - DrawingArea.ActualHeight/2;
// This works (I think)
(sender as UIElement).ReleasePointerCaptures();
// This doesn't work (or it isn't what I need), but returns true
CurrentDraggedImage.CapturePointer(e.Pointer);
// Get ready for a new image
CurrentDraggedImage = null;
}
}
My manipulation code is in this answer:
https://stackoverflow.com/a/32230733/1009013
Why don't you just use drag-n-drop? Create a grid containing your toolbar (e.g. a list of images to drag) and a target grid that responds to dragdrop commands:
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<ListBox Background="AliceBlue" MouseMove="OnMouseMove">
<ListBox.Resources>
<Style TargetType="{x:Type Image}">
<Setter Property="Width" Value="64" />
<Setter Property="Height" Value="64" />
</Style>
</ListBox.Resources>
<Image Source="http://www.wpclipart.com/recreation/games/chess/chess_set_1/chess_piece_white_pawn_T.png" />
<Image Source="http://www.wpclipart.com/recreation/games/chess/chess_set_1/chess_piece_white_rook_T.png" />
<Image Source="http://www.wpclipart.com/recreation/games/chess/chess_set_1/chess_piece_white_knight_T.png" />
<Image Source="http://www.wpclipart.com/recreation/games/chess/chess_set_1/chess_piece_white_bishop_T.png" />
<Image Source="http://www.wpclipart.com/recreation/games/chess/chess_set_1/chess_piece_white_queen_T.png" />
<Image Source="http://www.wpclipart.com/recreation/games/chess/chess_set_1/chess_piece_white_king_T.png" />
<Image Source="http://www.wpclipart.com/recreation/games/chess/chess_set_1/chess_piece_black_pawn_T.png" />
<Image Source="http://www.wpclipart.com/recreation/games/chess/chess_set_1/chess_piece_black_rook_T.png" />
<Image Source="http://www.wpclipart.com/recreation/games/chess/chess_set_1/chess_piece_black_knight_T.png" />
<Image Source="http://www.wpclipart.com/recreation/games/chess/chess_set_1/chess_piece_black_bishop_T.png" />
<Image Source="http://www.wpclipart.com/recreation/games/chess/chess_set_1/chess_piece_black_queen_T.png" />
<Image Source="http://www.wpclipart.com/recreation/games/chess/chess_set_1/chess_piece_black_king_T.png" />
</ListBox>
<GridSplitter Grid.Column="1" Width="5" Background="LightGray" />
<Grid x:Name="targetGrid" Grid.Column="2" AllowDrop="True" DragEnter="OnDragEnter" DragOver="OnDragMove" DragLeave="OnDragLeave" Drop="OnDrop" Background="Transparent"/>
</Grid>
Your listbox needs a MouseMove handler to detect when an image is being dragged and your command handlers simply respond to the various events as required, cloning the require image and dragging them across the face of the grid accordingly:
public partial class MainWindow : Window
{
private Image DragImage = null;
public MainWindow()
{
InitializeComponent();
}
private void OnMouseMove(object sender, MouseEventArgs e)
{
// make sure we have an image
var image = e.OriginalSource as Image;
if (image == null)
return;
// make sure we've started dragging
if (e.LeftButton != MouseButtonState.Pressed)
return;
DragDrop.DoDragDrop(image, image, DragDropEffects.Copy);
}
private void OnDragEnter(object sender, DragEventArgs e)
{
// make sure we have an image
if (!e.Data.GetDataPresent(typeof(Image)))
{
e.Effects = DragDropEffects.None;
return;
}
// clone the image
var image = e.Data.GetData(typeof(Image)) as Image;
e.Effects = DragDropEffects.Copy;
this.DragImage = new Image { Source = image.Source, Width=64, Height=64 };
var position = e.GetPosition(this.targetGrid);
this.DragImage.SetValue(Grid.MarginProperty, new Thickness(position.X-32, position.Y-32, 0, 0));
this.DragImage.SetValue(Grid.HorizontalAlignmentProperty, HorizontalAlignment.Left);
this.DragImage.SetValue(Grid.VerticalAlignmentProperty, VerticalAlignment.Top);
this.DragImage.IsHitTestVisible = false; // so we don't try and drop it on itself
// add it to the target grid
targetGrid.Children.Add(this.DragImage);
}
private void OnDragMove(object sender, DragEventArgs e)
{
var position = e.GetPosition(this.targetGrid);
this.DragImage.SetValue(Grid.MarginProperty, new Thickness(position.X - 32, position.Y - 32, 0, 0));
}
private void OnDragLeave(object sender, DragEventArgs e)
{
targetGrid.Children.Remove(this.DragImage);
this.DragImage = null;
}
private void OnDrop(object sender, DragEventArgs e)
{
this.DragImage.IsHitTestVisible = true;
this.DragImage = null;
}
}
Result:
I've done things the horrible and ugly WPF way here instead of clean and elegant MVVM, but you get the idea. I also don't get why you want to drag things around a Grid instead of a Canvas?
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)
This may be a very basic question. I have a canvas, which I put different Elements on in runtime (TextBoxes, Shapes, Buttons). Now I want to be able to drag and drop those elements to another location on the same canvas.
Can anyone provide me with some code, how to implement something like onDrag in runtime?
You can use for example the MouseDown, MouseMove and MouseUp events and then the MouseEventArgs's GetPosition(element) that returns you the coordinates relative to element (there are more events that expose this method).
Also, take advantage of the RoutedEvent's OriginalSource to check which element inside the canvas was clicked (in this case it's only the rectangle).
Here's an example:
<Grid>
<Canvas Name="MyCanvas" PreviewMouseLeftButtonDown="OnMouseDown" MouseMove="OnMouseMove">
<Rectangle Width="20" Height="20" Canvas.Left="10" Canvas.Top="10" Fill="Blue"/>
<Rectangle Width="20" Height="20" Canvas.Left="50" Canvas.Top="10" Fill="Red"/>
</Canvas>
</Grid>
Code-behind:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
AddHandler(Mouse.MouseUpEvent, new MouseButtonEventHandler(OnMouseUp), true);
}
private bool _isBeingDragged;
public void OnMouseDown(object sender, MouseEventArgs args)
{
if (!(args.OriginalSource is Canvas))
{
_isBeingDragged = true;
}
}
public void OnMouseMove(object sender, MouseEventArgs args)
{
if (_isBeingDragged)
{
var elementBeingDragged = (FrameworkElement) args.OriginalSource;
var position = args.GetPosition(MyCanvas);
Canvas.SetLeft(elementBeingDragged, position.X - elementBeingDragged.ActualWidth / 2);
Canvas.SetTop(elementBeingDragged, position.Y - elementBeingDragged.ActualHeight / 2);
}
}
public void OnMouseUp(object sender, MouseEventArgs args)
{
_isBeingDragged = false;
}
}
I had to use the UIElement's AddHandler method to register MouseUp because somehow it has being marked as Handled (the AddHandler method allows you to register an event handler for events that have been handled previously, i.e. e.Handled = true)
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 .......
}
}