Using button event in user control from main window - wpf

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

Related

Mixing events from CustomControls, in an adorner, with a Viewmodel

I've been going back and forth over the pros and cons of the two following approaches to Events from custom controls. My debate basically revolves around how much "logic" should be placed within a custom (not user) control and to best get events into a viewmodel.
The "control", DataGridAnnotationControl, resides within an adorner to my data grid. The goal here is to respond to the user selecting an item from a combobox displayed within the custom control.
The first example, Example #1, uses a pretty standard custom event in the DataGridAnnotationControl
which is then mapped by way of the adorner to the target AppointmentEditor (viewmodel). My biggest complaint with this is the obvious dependency to the (AppointmentEditor) from the adorner to achieve proper event routing.
♦ Example #1:
♦ CustomControl DataGridAnnotationControl
public override void OnApplyTemplate()
{
......
_cboLastName.SelectionChanged += _cboLastName_SelectionChanged;
}
private void _cboLastName_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
RaiseSelectionChanged();
}
public event Action SelectionChanged;
public void RaiseSelectionChanged()
{
SelectionChanged?.Invoke();
}
♦ Adorner DataGridAnnotationAdorner
public DataGridAnnotationAdorner(DataGrid adornedDataGrid)
: base(adornedDataGrid)
{
......
Control = new DataGridAnnotationControl();
this.SelectionChanged += ((AppointmentEditor)adornedDataGrid.DataContext).SelectionChanged; <--This requires a reference to Patient_Registration.Editors. THIS IS FORCING
A DEPENDENCY ON THE PATIENT_REGISTRATION PROJECT.
}
public event Action SelectionChanged
{
add { Control.SelectionChanged += value; }
remove { Control.SelectionChanged -= value; }
}
♦ AppointmentEditor
public void SelectionChanged()
{
throw new NotImplementedException();
}
Example #2 This example uses pretty standard event routing up to the mainwindow from which an event aggregator is being used to hit the AppointmentEditor as a subscriber to the event. My biggest complaint here is all the additional code needed (over Example #1). In addition, it seems like a complicating factor to climb the visual tree just to jump into the one viewmodel designed to support this customcontrol.
Example #2:
♦ CustomControl DataGridAnnotationControl
public override void OnApplyTemplate()
{
.....
_cboLastName.SelectionChanged += _cboLastName_SelectionChanged;
}
private void _cboLastName_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
RaisePatientNameSelectionChangedEvent();
}
public static readonly RoutedEvent PatientNameSelectionChangedEvent = EventManager.RegisterRoutedEvent(
"PatientNameSelectionChanged", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(DataGridAnnotationControl));
// Provide CLR accessors for the event
public event RoutedEventHandler PatientNameSelectionChanged
{
add { AddHandler(PatientNameSelectionChangedEvent, value); }
remove { RemoveHandler(PatientNameSelectionChangedEvent, value); }
}
protected virtual void RaisePatientNameSelectionChangedEvent()
{
RoutedEventArgs args = new RoutedEventArgs(DataGridAnnotationControl.PatientNameSelectionChangedEvent);
RaiseEvent(args);
}
♦ public partial class MainWindow : Window
{
public MainWindow(IMainWindowViewModel mainWindowViewModel, EventAggregator eventAggregator)
{
InitializeComponent();
EventAggregator = eventAggregator;
DataContext = mainWindowViewModel;
....
AddHandler(DataGridAnnotationControl.PatientNameSelectionChangedEvent, new RoutedEventHandler(PatientNameSelectionChangedHandler));
}
private void PatientNameSelectionChangedHandler(object sender, RoutedEventArgs e)
{
EventAggregator.PublishEvent( new PatientNameSelected() );
}
}
♦ public class AppointmentEditor : INotifyPropertyChanged, ISubscriber<PatientNameSelected>
public void OnEventHandlerAsync(PatientNameSelected e)
{
throw new NotImplementedException();
}
Is there a preferred way of doing this?
TIA
Ideally, your custom control should have no knowledge of your view-models.
Using MVVM, you would bind an event in your custom control to a command in your view-model.
I author and maintain tons of custom controls that are used by a lot of other teams. I always expose an associated ICommand with any event to make it easy for MVVM users to use my controls in the easiest way possible.

MahApps Metro capture close window event

I'm using the MahApps Metro window style, and I want to capture the event when the user clicks on the close button of the window.
I've set my ShutdownMode to OnExplicitShutdown so I need to call Application.Current.Shutdown(); when that button is clicked
How can I do this ?
I believe that I am also trying to do the same thing as you (bind to the close window button) using WPF and MahApps.Metro. I wasn't able to find a way yet to bind to that command explicitly, but I was able to accomplish this by setting the ShowCloseButton property to false (to hide it) and then created my own close window command button and handled the logic in my viewmodel. Took me some digging, but I found that you can easily add your own window command controls in the command bar with MahApps.Metro just add similar markup to your XAML:
<Controls:MetroWindow.WindowCommands>
<Controls:WindowCommands>
<Button Content="X" Command="{Binding CancelCommand}" />
</Controls:WindowCommands>
</Controls:MetroWindow.WindowCommands>
You need to create a DependencyProperty to handle the close window behavior:
DependencyProperty
namespace MyApp.DependencyProperties
{
public class WindowProperties
{
public static readonly DependencyProperty WindowClosingProperty =
DependencyProperty.RegisterAttached("WindowClosing", typeof(RelayCommand), typeof(WindowProperties), new UIPropertyMetadata(null, WindowClosing));
public static object GetWindowClosing(DependencyObject depObj)
{
return (RelayCommand)depObj.GetValue(WindowClosingProperty);
}
public static void SetWindowClosing(DependencyObject depObj, RelayCommand value)
{
depObj.SetValue(WindowClosingProperty, value);
}
private static void WindowClosing(DependencyObject depObj, DependencyPropertyChangedEventArgs e)
{
var element = (Window)depObj;
if (element != null)
element.Closing += OnWindowClosing;
}
private static void OnWindowClosing(object sender, CancelEventArgs e)
{
RelayCommand command = (RelayCommand)GetWindowClosing((DependencyObject)sender);
command.Execute((Window)sender);
}
}
}
In your ViewModel
public RelayCommand WindowClosedCommand { get; set; }
private void WindowClose()
{
Application.Current.Shutdown();
}
In Constructor of ViewModel
this.WindowCloseCommand = new RelayCommand(WindowClose);
In your XAML
<mah:MetroWindow x:Class="MyApp.Views.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mah="clr-namespace:MahApps.Metro.Controls;assembly=MahApps.Metro"
xmlns:dp="clr-namespace:MyApp.DependencyProperties"
dp:WindowProperties.WindowClosing="{Binding WindowClosedCommand}" />
Since the solution from gotapps.net did't worked for me because I did't found how to programatically do that (My window does not have the Xaml file, it's just a base class). I found another workaround to use the same button to close the Window as follows:
internal class BaseWindow : MetroWindow
{
public BaseWindow()
{
this.Loaded += BaseWindow_Loaded;
}
void BaseWindow_Loaded(object sender, EventArgs e)
{
Button close = this.FindChild<Button>("PART_Close");
close.Click += close_Click;
}
void close_Click(object sender, RoutedEventArgs e)
{
Application.Current.Shutdown(0);
}
}
You can use "Closing" or "Closed" event
Closing handler gets triggered when user clicks on the Close button. This also gives you control on whether the application should be closed.
Similarly, Closed handler gets triggered just before the window is closed
<Controls:MetroWindow x:Class="MyClass.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:Controls="clr-namespace:MahApps.Metro.Controls;assembly=MahApps.Metro"
Title="My Class"
Closing="MainWindow_OnClosing">
</Controls:MetroWindow>

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) { }

Why doesn't this class handler attached to a tunneling event fire before an instance handler?

According to this MSDN article (among others),
Class handlers are invoked before any instance listener handlers that
are attached to an instance of that class, whenever a routed event
reaches an element instance in its route.
I'm quite new to RoutedEvents so there is a chance that I have a mistake in my code, but it seems as though a class handler attached to a RoutedEvent that is declared as RoutingStrategy.Tunnel does not always fire before the instance handlers attached to the same event.
In my example below, I have created a TouchButton control class with a tunneling RoutedEvent and a bubbling RoutedEvent. I have registered class handlers for each. I then created an instance of the class in a window and handle each event in the code behind. I attached the same handler for the tunneling event on both the class element and the Grid that contains it. All four handlers display their name in a MessageBox so you can clearly see the order of execution.
Grid Instance PreviewTouch
Class TouchButton_PreviewTouch
TouchButton Instance PreviewTouch
Class TouchButton_Touch
TouchButton Instance Touch
This means that if I call e.Handled = true; in the class PreviewTouch event handler, I can stop execution from reaching all of the other event handlers except for the one attached to the Grid element. Is this supposed to be like this, or have I made a mistake somewhere? Otherwise, how can I stop execution from reaching every instance event handler?
Here is the class:
public class TouchButton : Button
{
static TouchButton()
{
EventManager.RegisterClassHandler(typeof(TouchButton), PreviewTouchEvent,
new RoutedEventHandler(TouchButton_PreviewTouch), true);
EventManager.RegisterClassHandler(typeof(TouchButton), TouchEvent,
new RoutedEventHandler(TouchButton_Touch), true);
}
private static void TouchButton_PreviewTouch(object sender, RoutedEventArgs e)
{
MessageBox.Show("Class TouchButton_PreviewTouch");
}
private static void TouchButton_Touch(object sender, RoutedEventArgs e)
{
MessageBox.Show("Class TouchButton_Touch");
}
public static RoutedEvent TouchEvent = EventManager.RegisterRoutedEvent("Touch",
RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(TouchButton));
public event RoutedEventHandler Touch
{
add { AddHandler(TouchEvent, value); }
remove { RemoveHandler(TouchEvent, value); }
}
public static RoutedEvent PreviewTouchEvent = EventManager.RegisterRoutedEvent(
"PreviewTouch", RoutingStrategy.Tunnel, typeof(RoutedEventHandler),
typeof(TouchButton));
public event RoutedEventHandler PreviewTouch
{
add { AddHandler(PreviewTouchEvent, value); }
remove { RemoveHandler(PreviewTouchEvent, value); }
}
protected override void OnClick()
{
RaiseTouchEvent();
}
private void RaiseTouchEvent()
{
RoutedEventArgs touchEventArgs = new RoutedEventArgs(PreviewTouchEvent);
RaiseEvent(touchEventArgs);
if (!touchEventArgs.Handled) RaiseEvent(new RoutedEventArgs(TouchEvent));
}
}
Here are the instance handlers in the window code behind:
private void TouchButton_PreviewTouch(object sender, RoutedEventArgs e)
{
MessageBox.Show(string.Format("{0} Instance PreviewTouch",
((FrameworkElement)sender).Name));
}
private void TouchButton_Touch(object sender, RoutedEventArgs e)
{
MessageBox.Show(string.Format("{0} Instance Touch",
((FrameworkElement)sender).Name));
}
Here is the control XAML:
<Grid Name="Grid" Controls:TouchButton.PreviewTouch="TouchButton_PreviewTouch">
<Controls:TouchButton x:Name="TouchButton" Width="200" Height="45" FontSize="24"
Content="Touch me" Touch="TouchButton_Touch" PreviewTouch="TouchButton_PreviewTouch" />
</Grid>
I do understand that the tunneling event is handled by the Grid element before 'tunneling' down to the TouchButton element, but I thought that the class handlers were always supposed to fire before the instance handlers. If not, how can I achieve this?
UPDATE >>>
Thanks to #sanguine's answer, I managed to find a way to stop all instance handlers from handling the event. If instead of replacing the declared class handling type of TouchButton with Grid as sanguine suggested, I replace it with FrameworkElement, then it will catch all FrameworkElement-derived controls.
EventManager.RegisterClassHandler(typeof(FrameworkElement), PreviewTouchEvent,
new RoutedEventHandler(TouchButton_PreviewTouch), true);
MSDN article means - When a traversing event finds an element(in tree) which has provision of both Class and instance handler then it invokes class handler before the instance handler. Therefore in this case when event is fired and tunneled from out to in, it encounters grid but the Grid class does not have any Class handler so it merely calls the instance handler used by the "Grid" instance. If this line is added in toggle button-
EventManager.RegisterClassHandler(typeof(Grid), PreviewTouchEvent,
new RoutedEventHandler(TouchButton_PreviewTouch), true);
then before Grid's instance handler, the corresponding Class handler will be called.

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,

Resources