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.
Related
I want to execute certain code in view if something happens in view model . I have looked into Prism event aggregator but I havent got success with prism 5. If there is more easier method to do so it will be helpful.Any blog or same code regarding this will also work
As Ed Plunkett says, the thing to do is listen for DataContextChanged in your view, as this is how View's are connected to ViewModels.
Here's an example:
public partial class MyView : UserControl
{
public MyView ()
{
DataContextChanged += MyView_DataContextChanged;
}
private void MyView_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
{
//new ViewModel has been set.
MyViewModel myViewModel = e.NewValue as MyViewModel;
if (myViewModel != null)
{
//check for property changes
myViewModel.PropertyChanged += MyViewModel_PropertyChanged;
//custom event for specific update
myViewModel.MyCustomEventTriggered += MyViewModel_MyCustomEventTriggered
}
}
private void MyViewModel_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
//do your logic
}
private void MyViewModel_MyCustomEventTriggered(object sender, MyCustomEventArgs e)
{
//do your logic
}
}
I have a listbox that is bound to a list of custom objects. I can get the listbox items to display correctly using the ListBox.ItemTemplate in xaml. The custom objects for the listbox are all of the same base class outlined below.
public class HomeViewMenuItem : UIElement
{
private Uri _uri;
private IRegionManager _manager;
public HomeViewMenuItem(string text, Uri uri, IRegionManager manager)
{
this.PreviewMouseDown += HomeViewMenuItem_PreviewMouseDown;
this.PreviewKeyDown += HomeViewMenuItem_PreviewKeyDown;
_manager = manager;
Text = text;
_uri = uri;
ClickCommand = new DelegateCommand(this.Click, this.CanClick);
}
void HomeViewMenuItem_PreviewKeyDown(object sender, System.Windows.Input.KeyEventArgs e)
{
if (e.Key == System.Windows.Input.Key.Enter)
{
e.Handled = true;
this.ClickCommand.Execute();
}
}
void HomeViewMenuItem_PreviewMouseDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
e.Handled = true;
this.ClickCommand.Execute();
}
private void Click()
{
_manager.Regions[RegionNames.MainRegion].RequestNavigate(_uri);
}
private bool CanClick()
{
return true;
}
public DelegateCommand ClickCommand { get; set; }
public string Text { get; set; }
}
The problem I am having is the HomeViewMenuItem_PreviewKeyDown method is not getting called. I believe this is because the method is getting called on the ListBoxItem itself first and getting handled there. I was able to verify this by obtaining a reference to the ListBoxItem object through listBox.ItemContainerGenerator.ContainerFromIndex(0) after the ItemContainerGenerator status changes to ContainersGenerated and adding an event handler there. This event handler correctly fired. Normally this would be an ok solution on a small project but I plan on having more listboxes with the same sort of functionality and would like to have a simpler/better solution. Is there a way that I can get my base class previewkeydown method to work?
The only solution I could think of is to have the base class inherit from ListBoxItem instead of UIElement then get the ListBox to create my items instead of ListBoxItems. But I dont think that is really possible without creating my own ListBox implementation.
You seem to be somewhat confused. In WPF, we create data items and declare DataTemplates to define what those items should look like in the UI. Our data items do not extend UI classes. If you have to handle the PreviewKeyDown event, then attach a handler to the UI element in the DataTemplate instead:
<DataTemplate>
<Grid PreviewKeyDown="HomeViewMenuItem_PreviewKeyDown">
...
</Grid>
</DataTemplate>
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
I need to know when a WPF Datagrid has been sorted by the user. Why is there no Sorted event? I can only find a Sorting event.
I also investigated the CollectionView and ListCollectionView that is exposing the objects to the View, without any luck.
I am quite surprised as this should come out of the box.
Any ideas?
You can still subscribe to the DataGrid Sorting Event:
<local:CustomDataGrid x:Name="datagrid" Sorting="datagrid_Sorted;"/>
but to make sure that your actions happen after the sorting is done use Dispatcher :
private void DataGrid_Sorting(object sender, DataGridSortingEventArgs e)
{
this.Dispatcher.BeginInvoke((Action)delegate()
{
//runs after sorting is done
}, null);
}
This way, there's no need of a custom Datagrid class.
I've taken an example from MSDN documentation and adjusted it to raise a Sorted event when the Sorting event is done.
public class CustomDataGrid : DataGrid
{
// Create a custom routed event by first registering a RoutedEventID
// This event uses the bubbling routing strategy
public static readonly RoutedEvent SortedEvent = EventManager.RegisterRoutedEvent(
"Sorted", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(CustomDataGrid));
// Provide CLR accessors for the event
public event RoutedEventHandler Sorted
{
add { AddHandler(SortedEvent, value); }
remove { RemoveHandler(SortedEvent, value); }
}
// This method raises the Sorted event
void RaiseSortedEvent()
{
RoutedEventArgs newEventArgs = new RoutedEventArgs(CustomDataGrid.SortedEvent);
RaiseEvent(newEventArgs);
}
protected override void OnSorting(DataGridSortingEventArgs eventArgs)
{
base.OnSorting(eventArgs);
RaiseSortedEvent();
}
}
Then you can use it either in codebehind.
datagrid.Sorted += new RoutedEventHandler(datagrid_Sorted);
or in XAML
<local:CustomDataGrid x:Name="datagrid" Sorted="datagrid_Sorted"/>
And here the method that will get triggered when the datagrid finishing sorting:
private void datagrid_Sorted(object sender, RoutedEventArgs args)
{
var datagrid = (CustomDataGrid)sender;
var sortedItems = datagrid.Items;
}
datagrid has "Sorting" event, subscribe to it!
XAML:
<DataGrid ItemsSource="{Binding YourItems}" AutoGenerateColumns="True" anUserSortColumns="True"
Sorting="DataGrid_Sorting"/>
.cs code:
private void DataGrid_Sorting(object sender, System.Windows.Controls.DataGridSortingEventArgs e)
{
Console.WriteLine(string.Format("sorting grid by '{0}' column in {1} order", e.Column.SortMemberPath, e.Column.SortDirection));
}
i want to do add custom event handlers to default framework elements using DependencyProperties.
Something like the following:
<Border custom:MyProps.HandleMyEvent="someHandler">...</Border>
Here is the code behind for the control that contains the Border element:
public class MyPage : Page{
public void someHandler(object sender, EventArgs e){
//do something
}
}
Here is rough sample of how i imagine the class that defines the property:
public class MyProps{
public event EventHandler MyInternalHandler;
public static readonly DependencyProperty HandleMyEventProperty = ...
public void SetHandleMyEvent(object sender, EventHandler e){
MyInternalHandler += e;
}
}
The problem is that I don't know/didn't find any hints how to combine DependencyProperties with events/delegates and EventHandlers.
Do you have a clue?
I'm going to assume this has nothing to do with WPF, this is a silverlight question.
First of all you can't simply add an Event to an existing control. After all you are adding attached Properties whereas events are handled differently, they're not properties.
You need to create a new type which has this event then create an attached property of this type.
Here is a basic type that simply has an event:-
public class MyEventer
{
public event EventHandler MyEvent;
// What would call this??
protected void OnMyEvent(EventArgs e)
{
if (MyEvent != null)
MyEvent(this, e);
}
}
Now we create an attached property which has MyEventer as its property, I prefer to place these in a separate static class.
public static class MyProps
{
public static MyEventer GetEventer(DependencyObject obj)
{
return (MyEventer)obj.GetValue(EventerProperty );
}
public static void SetEventer(DependencyObject obj, MyEventer value)
{
obj.SetValue(EventerProperty , value);
}
public static readonly DependencyProperty EventerProperty =
DepencencyProperty.RegisterAttached("Eventer", typeof(MyEventer), typeof(MyProps), null)
}
}
Now you attach this to a control like this:-
<Border ...>
<custom:MyProps.Eventer>
<custom:MyEventer MyEvent="someHandler" />
</custom:MyProps.Eventer>
</Border>
If you compile the project before writing this xaml you'll note that Visual Studio will offer you the option for it to create the event handler in the code behind for you.
Of course this still leaves a significant question: How did you intend to cause the event to fire?