Hopefully the question makes sense. What I would like to do is place an Ellipse (or styled button to look like an Ellipse with an icon inside in a user control that is dynamically populated in my code behind, and then be able to determine when the Ellipse was tapped on the user control and perform a separate action than when the user control is tapped. The sample I'm using actually comes from a Nokia Imaging SDK sample with a few tweaks.
PhotoThumbnail.xaml //The UserControl
<Ellipse Grid.Row="0" Grid.Column="1" Stroke="LightGray" StrokeThickness="3"
VerticalAlignment="Top" HorizontalAlignment="Right" Width="50" Height="50" Margin="7"/>
PhotoThumbnail.xaml.cs
public event PropertyChangedEventHandler PropertyChanged;
public PhotoThumbnail()
{
InitializeComponent();
DataContext = this;
}
Page.xaml.cs
//Creae PhotoThumbnail
PhotoThumbnail photoThumbnail = new PhotoThumbnail()
{
..
};
photoThumbnail.Tap += (object sender, System.Windows.Input.GestureEventArgs e) =>
{
// do something
};
panel.Children.Add(photoThumbnail);
}
}
}
}
The photoThumbnail.Tap event above performs an action, but how can I determine if the user tapped the Ellipse on the PhotoThumbnail UserControl as opposed to the control itself?
I just added the following in Page.xaml.cs and named the Ellipse in the User Control x:Name="EditableEllipse"
photoThumbnail.EditableEllipse.Tap += (object sender, System.Windows.Input.GestureEventArgs e) =>
{
if (sender != null)
{
.. do something ..
}
};
Related
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 .......
}
}
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.
I recently created a Silverlight 3 app in which I created some UI elements in the code behind and added them at run-time dynamically.
I was hoping to just use the built-in MouseButtonEventArgs or the sender object to get a reference to the instance that was clicked, however I noticed once I started that this was not the case. I was not able to access any properties of the object that triggered the event and program against it.
void myFunc(object sender, MouseButtonEventArgs e)
{
//Can't do this :(
sender.someProperty = someValueToUpdate;
//or this
MyClass foo = sender as MyClass;
foo.someProperty = someValueToUpdate;
}
I ended up just writing a CustomEventArgs object to pass an instance, but it surprised me that this wasn't a default behavior.
Can anyone shed some light as to WHY the sender object doesn't contain a reference to the object that triggered the event?
Also, here is what I did to get that instance.
myObject.myEvent += new CustomEvent(myFunc);
...
void myFunc(object sender, CustomEventArgs e)
{
e.MyProperty = someValueToUpdate;
}
...
public class MyClass
{
public MyProperty = 0;
public event CustomEvent myEvent;
protected virtual void MyEventMethod(CustomEventArgs e)
{
if (myEvent != null){myEvent(this, e);}
}
public MyClass ()
{
this.MouseLeftButtonDown += new MouseButtonEventHandler(this_MouseLeftButtonDown);
}
void rect_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
CustomEventArgs e2 = new CustomEventArgs(this);
MyEventMethod(e2);
}
}
public class CustomEventArgs : EventArgs
{
private readonly MyClass myProperty;
public CustomEventArgs(MyClass myProperty) { this.myProperty = myProperty; }
public MyClass MyProperty { get { return myProperty; } }
}
public delegate void CustomEvent(object sender, CustomEventArgs e);
The MouseEventArgs has a OriginalSource property. Its this property which holds a reference to the object that originally triggered it.
The sender argument quite rightly is set to the instance of the object against which you attached the event handler. Perhaps a simple experiment will make how this hangs together clearer. In Visual Studio create a Silverlight Application. Make the content of the MainPage.xaml look like this:-
<UserControl x:Class="SilverlightApplication1.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
>
<Grid x:Name="LayoutRoot" Background="White" MouseLeftButtonDown="MouseHandler">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<StackPanel x:Name="OuterPanel" MouseLeftButtonDown="MouseHandler" Margin="5">
<StackPanel x:Name="TopPanel" MouseLeftButtonDown="MouseHandler">
<TextBlock Text="First Top Item" />
<TextBlock Text="Second Top Item" />
</StackPanel>
<StackPanel x:Name="BottomPanel" MouseLeftButtonDown="MouseHandler">
<TextBlock Text="First Bottom Item" />
<TextBlock Text="Second Bottom Item" />
</StackPanel>
</StackPanel>
<ListBox x:Name="lstOutput" Grid.Column="1" Margin="5" />
</Grid>
</UserControl>
And in MainPage.xaml.cs add this code:-
private void MouseHandler(object sender, MouseButtonEventArgs e)
{
FrameworkElement s = sender as FrameworkElement;
TextBlock o = e.OriginalSource as TextBlock;
string text = (o != null) ? o.Text : "Not from a text block";
lstOutput.Items.Add(String.Format("Sender: {0}, Text block: {1}", s.Name, text));
}
Note how this same handler is attached to three different items in the XAML but not to the TextBlocks themselves. Clicking the "First Top Item" gets you this:-
Sender: TopPanel, Text block: First Top Item
Sender: OuterPanel, Text block: First Top Item
Sender: LayoutRoute, Text block: First Top Item
The handler fires 3 times once for each item it is attached to as can be seen by the sender being different for each one. However the OrignalSource it the TextBlock that was actually clicked on despite it not having any handler attached. Also note that the OriginalSource remains the same as it bubbles up the ancestor elements.
Click on the area below the Stack panels. You only get:-
Sender: LayoutRoot, Text block: Not from a text block
Of interest also is that clicking in the Listbox results in no items being added at all, you might expect to the same ase the above line. Clearly ListBox handles the mouse down and therefore sets the event args Handled property to True preventing further bubbling.
From the msdn documentation:
For a bubbling event, the sender
parameter identifies the object where
the event is handled, not necessarily
the object that actually received the
input condition that initiated the
event.
I.e. since it's a bubbling event, maybe you should try something like
void myFunc(object sender, MouseButtonEventArgs e)
{
var theUIElement = sender as TheUIElementOfWhichImInterested;
if (theUIElement != null)
{
// set properties on the element
}
}