Using custom RoutedEvent in WPF causes TargetInvocationException - wpf

I am trying out custom routed events, but I get a TargetInvocationException when compiling with an Attached Event Handler.
I have the following code inside custom control EventRaiserControl:
public static readonly RoutedEvent KickedEvent = EventManager.RegisterRoutedEvent("KickedEvent", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(EventRaiserControl));
public event RoutedEventHandler Kicked
{
add
{ this.AddHandler(KickedEvent, value); }
remove
{ this.RemoveHandler(KickedEvent, value); }
}
private void btn1_Click(object sender, RoutedEventArgs e)
{
RaiseEvent(new RoutedEventArgs(KickedEvent));
}
I then have the following XAML in my main window:
<StackPanel local:EventRaiserControl.Kicked="StackPanel_Kicked">
<local:EventRaiserControl Kicked="EventRaiserControl_Kicked"/>
</StackPanel>
With the following handlers in the MainWindow code behind:
private void StackPanel_Kicked(object sender, RoutedEventArgs e)
{
Console.WriteLine("Caught Kicked Event at Panel level.");
}
private void EventRaiserControl_Kicked(object sender, RoutedEventArgs e)
{
Console.WriteLine("Caught Kicked Event at Control level.");
}
My code works fine with this handler:
<local:EventRaiserControl Kicked="EventRaiserControl_Kicked"/>
But fails with TargetInvocationException the moment I add the attached handler:
<StackPanel local:EventRaiserControl.Kicked="StackPanel_Kicked">
Can somebody help? What am I missing / misusing?
Many thanks

At first I was surprised why it is, but I saw the reason after coding as your code. Just change
EventManager.RegisterRoutedEvent("KickedEvent"....
to
EventManager.RegisterRoutedEvent("Kicked"....

Related

wpf stylus mouse event

When button1 gets tapped by the stylus test method gets called twice, even though I am setting the Handled property in the stylusdown event. Is there a way to have the stylus event not propegate a secondary button click event?
namespace DialogTest
{
/// <summary>
/// Interaction logic for Window1.xaml
/// </summary>
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
}
private void button1_Click(object sender, RoutedEventArgs e)
{
test(sender, e);
}
private void button1_StylusDown(object sender, StylusDownEventArgs e)
{
test(sender, e);
e.Handled = true;
}
private void test(object e, EventArgs env)
{
Console.WriteLine(e.ToString(), env.ToString());
Console.WriteLine("clicking");
}
}
If you look at this MSDN Input overview documentation. you will see the fact the both events are called.
From above link:
Because the stylus can act as a mouse, applications that support only mouse input can still obtain some level of stylus support automatically. When the stylus is used in such a manner, the application is given the opportunity to handle the appropriate stylus event and then handles the corresponding mouse event. In addition, higher-level services such as ink input are also available through the stylus device abstraction.
Since it does give you the order the events are called you can create a Boolean variable, set it in the StylusDown EventHandler, then check in your Button_Click EventHandler if it is true, set it to false then exit the Handler.
something like this.
public partial class Window1 : Window
{
bool StylusDown;
public Window1()
{
InitializeComponent();
}
private void button1_Click(object sender, RoutedEventArgs e)
{
if(StylusDown)
{
StylusDown=false;
return;
}
test(sender, e);
}
private void button1_StylusDown(object sender, StylusDownEventArgs e)
{
StylusDown =true;
test(sender, e);
}
private void test(object e, EventArgs env)
{
Console.WriteLine(e.ToString(), env.ToString());
Console.WriteLine("clicking");
}
}
There may be a better way of accomplishing this, but this is was the first thing that came to mind.
MouseEventArgs has a property called StylusDevice that will be not null if the event originated from a stylus or touch event.
private void button1_Click(object sender, MouseButtonEventArgs e)
{
if (e.StylusDevice != null) return;
...
}

How do i wrap a standard wpf event with an event of my own?

I have a user control that contains a ListBox.
I want to expose a SelectionChanged event on my user control that wraps the listBox.SelectionChanged event.
So that, when the listbox item selection changes, my own custom event on the user control also gets fired after that...
How would I do that?
Any sample would be appreciated.
Thanks!
I'm not sure wrapping is the best approach, even if you could wrap it. I'd suggest just defining your own event, and fire your own event in the handler hooked to listBox.SelectionChanged. You can then pass on any data from the original listbox event to your own event.
Added sample:
public partial class MainWindow : Window
{
public delegate void CustomSelectionChangedEventHandler(object sender, SelectionChangedEventArgs e);
public event CustomSelectionChangedEventHandler CustomSelectionChanged;
public MainWindow()
{
InitializeComponent();
listBox1.SelectionChanged += delegate(object sender, SelectionChangedEventArgs e)
{
OnCustomSelectionChanged(e);
};
}
void listBox1_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
OnCustomSelectionChanged(e);
}
//We'll use the system defined SelectionChangedEventArgs type instead of creating a derived EventArgs class
protected virtual void OnCustomSelectionChanged(SelectionChangedEventArgs e)
{
if (CustomSelectionChanged != null)
CustomSelectionChanged(this, e);
}
}
Further reading:
http://msdn.microsoft.com/en-us/library/edzehd2t.aspx
http://msdn.microsoft.com/en-us/library/17sde2xt.aspx
If you want your custom event on your UserControl to bubble up the visual tree you should expose it as a RoutedEvent. In your .xaml.cs file you'll need to register the event as a routed event and then implement a custom handler and event args class.
XAML:
<UserControl x:Class="WpfApplication1.MyUserControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid>
<ListView Name="myListView" SelectionChanged="OnSelectionChanged_"/>
</Grid>
</UserControl>
Code:
public partial class MyUserControl : UserControl
{
public delegate void CustomSelectionChangedEventHandler(object sender, SelectionChangedRoutedEventArgs args);
public static readonly RoutedEvent CustomSelectionChangedEvent = EventManager.RegisterRoutedEvent(
"CustomSelectionChanged", RoutingStrategy.Bubble, typeof(CustomSelectionChangedEventHandler), typeof(MyUserControl));
public event RoutedEventHandler CustomSelectionChanged
{
add { AddHandler(CustomSelectionChangedEvent, value); }
remove { RemoveHandler(CustomSelectionChangedEvent, value); }
}
public MyUserControl()
{
InitializeComponent();
}
private void OnSelectionChanged_(object sender, SelectionChangedEventArgs e)
{
RaiseEvent(new SelectionChangedRoutedEventArgs(myListView, CustomSelectionChangedEvent, e.AddedItems, e.RemovedItems));
}
}
public class SelectionChangedRoutedEventArgs : RoutedEventArgs
{
public IList AddedItems { get; set; }
public IList RemovedItems { get; set; }
public SelectionChangedRoutedEventArgs(object source, RoutedEvent routedEvent, IList addedItems, IList removedItems)
: base(routedEvent, source)
{
AddedItems = addedItems;
RemovedItems = removedItems;
}
}
The caller of your control would then provide an event handler for the CustomSelectionChanged event with the signature of:
private void OnCustomSelectionChanged(object sender, SelectionChangedRoutedEventArgs e) { }

Using button event in user control from main window

I have wpf user control
<StackPanel Orientation="Horizontal">
<TextBox Name="txbInterval" Text="5"/>
<Image Name="imgStart" Source="Images/start.png"/>
<Image Name="imgStop" Source="Images/stop.png"/>
</StackPanel>
I use this control in my application many times. This control can start/stop executing tasks in my own scheduler. When imgStart is clicked, it should create new instance of some task with txbInterval.Text argument. I have this in my MainWindow.xaml
<wp:TaskManager x:Name="tmToolsArMail"/>
<wp:TaskManager x:Name="tmToolsArSail"/>
and I need in Mainwindow.xaml.cs something like this
tmToolsArMail_imgStart_mouseUp(...)
... new MyTask(tmToolsArMail.txbInterval.Text) ...
tmToolsArSail_imgStart_mouseUp(...)
... new MyAnotherTask(tmToolsArSail.txbInterval.Text) ...
How?
IMO, the easiest way to implement this is to create 2 RoutedEvent (Start and Stop) and 1 DependencyProperty (Interval) on your UserControl and then subscribe those events on your parent control (MainWindow)
What I would do would be to put RoutedEvents in the user control like this:
public MyUserControl()
{
InitializeComponent();
imgStart.MouseUp += imgStart_MouseUp;
imgStop.MouseUp += imgStop_MouseUp;
}
// Create custom routed events by first registering a RoutedEventID
// These events use the bubbling routing strategy
public static readonly RoutedEvent StartEvent = EventManager.RegisterRoutedEvent(
"Start", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(MyUserControl));
public static readonly RoutedEvent StopEvent = EventManager.RegisterRoutedEvent(
"Stop", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(MyUserControl));
// Provide CLR accessors for the events
public event RoutedEventHandler Start
{
add { AddHandler(StartEvent, value); }
remove { RemoveHandler(StartEvent, value); }
}
// Provide CLR accessors for the events
public event RoutedEventHandler Stop
{
add { AddHandler(StopEvent, value); }
remove { RemoveHandler(StopEvent, value); }
}
// This method raises the Start event
void RaiseStartEvent()
{
RoutedEventArgs newEventArgs = new RoutedEventArgs(MyUserControl.StartEvent);
RaiseEvent(newEventArgs);
}
// This method raises the Stop event
void RaiseStopEvent()
{
RoutedEventArgs newEventArgs = new RoutedEventArgs(MyUserControl.StopEvent);
RaiseEvent(newEventArgs);
}
private void imgStart_MouseUp(object sender, MouseButtonEventArgs e)
{
RaiseStartEvent();
}
private void imgStop_MouseUp(object sender, MouseButtonEventArgs e)
{
RaiseStopEvent();
}
Then any code which calls into this UserControl can subscribe to those Start and Stop events and do the handling you require.
I like to implement attached commands, That way if you wanted to do a click style command
you can then attach it to any control at a later stage (its all very MVVM).
This is a very nice article on the subject
Here is a Stack Overflow discussion that shows alternatives

When Raising an MouseButtonEvent from a UserControl the MainWindow have no access to MouseButtonEventArgs

Still climbing up the steep WPF Mountain, and suffering.
I have defined a UserControl, and my MainWindow needs to retrieve the MouseButtonEventArgs coming from a control inside the UserControl (like the mouse e.GetPosition for instance)
In the UserControl code behind, I have done the Registrations and I Raise the bubbling event.
public static readonly RoutedEvent MyButtonDownEvent = EventManager.RegisterRoutedEvent("MyMouseButtonDown", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(MyUserControl));
public event RoutedEventHandler MyButtonDown {
add { AddHandler(MyButtonDownEvent, value); }
remove { RemoveHandler(MyButtonDownEvent, value); }
}
private void MyMouseButtonDownHandler(object sender, MouseButtonEventArgs e) {
RaiseEvent(new RoutedEventArgs(MyButtonDownEvent ));
}
Now in my MainWindow I declare the UserControl like this:
<local:MyUserControl MouseDown="MyUserControl_MouseDown"/>
And this code behind
private void MyUserControl_MouseDown(object sender, RoutedEventArgs e)
And I receive the events from the UserControl, but the Args are RoutedEventArgs (which is normal) but I dont have access to the MouseButtonEventArgs that I need to get the mouse e.GetPosition.
What elegant solution would you suggest in this case ?
Why do you define your own MouseDown event while UserControl already has a normal MouseDown event?
Anyway, if you define an event to use a RoutedEventHandler it is hardly surprising that you'll end up being stuck with a RoutedEventHandler. You declared it like this:
public static readonly RoutedEvent MyButtonDownEvent = EventManager.RegisterRoutedEvent("MyMouseButtonDown", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(MyUserControl));
Notice the bit where it says typeof(RoutedEventHandler)?
If i am not mistaken your code should look like this instead:
public static readonly RoutedEvent MyButtonDownEvent =
EventManager.RegisterRoutedEvent
("MyButtonDown",
RoutingStrategy.Bubble,
typeof(MouseButtonEventHandler),
typeof(MyUserControl));
public event MouseButtonEventHandler MyButtonDown
{
add { AddHandler(MyButtonDownEvent, value); }
remove { RemoveHandler(MyButtonDownEvent, value); }
}
Example of how to propagate an existing MouseDown event to the custom event:
InitializeComponent();
this.MouseDown += (s, e) => {
RaiseEvent(new MouseButtonEventArgs(e.MouseDevice, e.Timestamp, e.ChangedButton)
{
RoutedEvent = MyButtonDownEvent
});
};
I think I finally got it (at least I hope):
If I write in the code behind:
public event EventHandler<MouseButtonEventArgs> MyRightButtonDownHandler;
public void MyRightButtonDown(object sender, MouseButtonEventArgs e) {
MyRightButtonDownHandler(sender, e);
}
Then in the consumer (MainWindow) XAML:
<local:GlobalDb x:Name="globalDb" MyRightButtonDownHandler="globalDb_MyRightButtonDownHandler"/>
And in the consumer code behind:
private void globalDb_MyRightButtonDownHandler(object sender, MouseButtonEventArgs e) {
Console.WriteLine("x= " + e.GetPosition(null).X + " y= " + e.GetPosition(null).Y);
}
Please tell me if you have a better solution (By design policy - rules established where I work - all the event handling of my application MUST appear in the XAML).
Thanks again for your help,

Problems with event bubbeling (ScrollViewer)

I have a problem with bubbeling events. I manage to bubble events in borders, grid, stackpanel, but not in a ScrollViewer
If you look at the example below you will notice that when you click the TextBlock the event is bubbeled up to the Grid. But when I include the ScrollViewer the event stops here and is not sent up to the Grid.
Does anyone now whay this happends and if it can be fixed? I really need to be able to bubble events through a ScrollViewer as I use it all the time.
<Grid MouseLeftButtonDown="Grid_MouseLeftButtonDown">
<!--<ScrollViewer MouseLeftButtonDown="ScrollViewer_MouseLeftButtonDown">-->
<StackPanel Orientation="Vertical" MouseLeftButtonDown="StackPanel_MouseLeftButtonDown">
<TextBlock Text="Click me to bubble an event" MouseLeftButtonDown="TextBlock_MouseLeftButtonDown"/>
</StackPanel>
<!--</ScrollViewer>-->
</Grid>
public partial class MainPage : UserControl
{
public MainPage()
{
InitializeComponent();
}
private void Grid_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
MessageBox.Show("LayoutRoot clicked");
}
private void ScrollViewer_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
MessageBox.Show("ScrollViewer clicked");
e.Handled = false;
}
private void StackPanel_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
MessageBox.Show("StackPanel clicked");
e.Handled = false;
}
private void TextBlock_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
MessageBox.Show("Textblock clicked");
e.Handled = false;
}
}
use AddHandler(yourDelegate, True); syntax for adding event handlers, which will ignore Handled flag set by other controls in the visual tree.
I had this problem and the fix posted by user572559 fixed my issue. For those that need it, below is what I did (modified for posting):
_scrollViewer = new ScrollViewer();
_scrollViewer.AddHandler(
ScrollViewer.MouseLeftButtonDownEvent,
new MouseButtonEventHandler(OnMouseLeftButtonDown),
true);
_scrollViewer.AddHandler(
ScrollViewer.MouseLeftButtonUpEvent,
new MouseButtonEventHandler(OnMouseLeftButtonUp),
true);
...
void OnMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
...
}
void OnMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
...
}
Also note that if you hare handling these you may be handling MouseMove as well. MouseMove worked for me without needing to do this, and it also does not seem to be supported in this fashion (not a bubbling event).
You can prevent e.Handled on MouseButtonEventArgs by overriding ScrollViewer like this
public sealed class ClickScrollViewer : ScrollViewer
{
protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
{
this.Focus();
}
}

Resources