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
}
}
Related
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.
I am trying to display the selection from a combobox in a textbox on a different page when a button is clicked. I thinking of using NavigationService, but I am not sure if that is the right way to go or not. In this part of the code I am getting the correct value and for testing I am displaying in messagebox, and that is working.
private void Button_Click(object sender, RoutedEventArgs e)
{
itemSelection = SubItemBox.Text;
NavigationService.Navigate(
new Uri("/Display.xaml?Message=" + itemSelection,
UriKind.Relative)
);
MessageBox.Show(itemSelection);
}
I am having an issue figuring out where to go next, I can't figure out how to get the itemSelection to dispaly in Display.xaml
namespace CateringDisplay
{
public partial class Display : Page
{
string itemSelection;
public Display()
{
InitializeComponent();
}
private void Page_Loaded(object sender, RoutedEventArgs e)
{
}
}
}
Any help would be appreciated as I am trying to learn WPF
Instead of using navigation, you could try the Event Aggregator (https://msdn.microsoft.com/en-us/library/ff921122.aspx):
You define an event:
public class SelectionChangedEvent : PubSubEvent<string> { }
You subscribe to the event in the Display Page
public partial class Display : Page
{
string itemSelection;
public Display()
{
InitializeComponent();
IEventAggregator eventAggregator = Locator.GetInstance<IEventAggregator>();
eventAggregator.GetEvent<SelectionChangedEvent>().Subscribe(OnSelectionChanged);
}
private void OnSelectionChanged(string obj)
{
itemSelection = obj;
}
private void Page_Loaded(object sender, RoutedEventArgs e)
{
}
}
The event handler updates your item selection using the event payload. Finally you fire the event from the button click event handler:
private void Button_Click(object sender, RoutedEventArgs e)
{
itemSelection = SubItemBox.Text;
IEventAggregator eventAggregator = Locator.GetInstance<IEventAggregator>();
eventAggregator.GetEvent<SelectionChangedEvent>().Publish(itemSelection);
MessageBox.Show(itemSelection);
}
Hope it helps.
I got a problem with usercontrol like this:
I have a formA contain 1 usercontrol with name UC_wrap
and UC_wrap contain a usercontrol with name UC_child
In UC_child contain : a button add, a button edit, a button delete and 1 textbox
but I don't know how do I create event for each button on formA?
please, Somebody help me !!!!
You can rig UC_Wrap with an event that forwards any events it receives from the button to whoever is subscribed to it.
partial class UC_Wrap : Control
{
public event EventHandler AddButtonClick
{
add { addButton.Click += value; }
remove { addButton.Click -= value; }
}
// etc
}
Then the UC_Control can forward those events
partial class UC_Control : Control
{
public event EventHandler AddButtonClickedInWrap
{
add { ucWrap.AddButtonClick += value; }
remove { ucWrap.AddButtonClick -= value; }
}
// etc
}
Then finally at the FormA level you can subscribe to the event and handle it.
partial class FormA : Form
{
protected override void OnLoad()
{
ucControl.AddButtonClickedInWrap += ActuallyDoSomething;
}
private void ActuallyDoSomething(object sender, EventArgs e)
{
// do something
}
}
That's probably the best way to do it. The only simpler way I can think of is to make each sub control public, but that has the major downside of exposing far more than is needed.
Try this
//UC_child - child user control code
public event EventHandler addClick;
public event EventHandler editClick;
public event EventHandler deleteClick;
//call above event in each button click ie.
private void btnAdd_Click(object sender, EventArgs e)
{
if (addClick != null)
addClick(sender, e);
}
//Do same for other edit and delete button
//UC_wrap- UC_wrap usercontrol code
//Hand UC_Child event in UC_wrap
//Create event again in UC_wrap
public event EventHandler addClick;
public event EventHandler editClick;
public event EventHandler deleteClick;
private void UC_Child_Load(object sender, EventArgs e)
{
UC_Child1.addClick += new EventHandler(add_Click);
//Do same for other edit and delete button
}
private void add_Click(object sender, EventArgs e)
{
if (addClick != null)
addClick(sender, e);
}
//formA-This is your form code
private void formA_Load(object sender, EventArgs e)
{
UC_wrap1.addClick += new EventHandler(add_Click);
//Do same for other edit and delete button
}
private void add_Click(object sender, EventArgs e)
{
//Place your code here.
}
This is my current App.xaml.cs
Its looks simple for one or two, but I have 7-8 windows.
Is there a clever way to make this a little more general and better?
public App()
{
_ViewModel = new MyAppViewModel();
_ViewModel.OpenXXXWindowEvent += new EventHandler(ViewModel_OpenXXXWindow);
_ViewModel.OpenYYYWindowEvent += new EventHandler(ViewModel_OpenYYYWindow);
...
}
private void ViewModel_OpenXXXWindow(object sender, EventArgs e)
{
_XXXWindow = new XXXWindow();
_XXXWindow.DataContext = _ViewModel;
_XXXWindow.ShowDialog();
}
private void ViewModel_CloseXXXWindow(object sender, EventArgs e)
{
if (_XXXWindow != null)
_XXXWindow.Close();
}
private void ViewModel_OpenYYYWindow(object sender, EventArgs e)
{
_YYYWindow = new YYYWindow();
_YYYWindow.DataContext = _ViewModel;
_YYYWindow.ShowDialog();
}
private void ViewModel_CloseYYYWindow(object sender, EventArgs e)
{
if (_YYYWindow != null)
_YYYWindow.Close();
}
...
Too much XAML code-behind is a signal that you're somehow breaking the MVVM pattern. A ViewModel receiving EventArgs is a no-no too.
To open/close dialogs I tend to use a messaging system, for example, the one provided by MVVM Light.
With a messaging system (using MVVM Light) you do something like this:
In your ViewModel:
private void SomeMethodThatNeedsToOpenADialog()
{
Messenger.Default.Send(new OpenDialogXMessage());
}
And in your View:
Messenger.Default.Register<OpenDialogXMessage>(this, (msg) => {
new DialogX().ShowDialog();
});
Some relevant links:
How to open a new window using MVVM Light Toolkit
Show dialog with MVVM Light toolkit
Well it's not a event handler solution, but how about a Binding solution? Unfortunately, you cannot bind Window.DialogResult, which causes the window to close, when the value is set. But you could create an AttachedProperty which can be bound to a property on the underlying ViewModel and sets the not bindable property, when its value is set. The AttachedProperty looks like this.
public class AttachedProperties
{
public static readonly DependencyProperty DialogResultProperty =
DependencyProperty.RegisterAttached("DialogResult", typeof (bool?), typeof (AttachedProperties), new PropertyMetadata(default(bool?), OnDialogResultChanged));
private static void OnDialogResultChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var wnd = d as Window;
if (wnd == null)
return;
wnd.DialogResult = (bool?) e.NewValue; //here the not bindable property is set and the windows is closed
}
public static bool? GetDialogResult(DependencyObject dp)
{
if (dp == null) throw new ArgumentNullException("dp");
return (bool?)dp.GetValue(DialogResultProperty);
}
public static void SetDialogResult(DependencyObject dp, object value)
{
if (dp == null) throw new ArgumentNullException("dp");
dp.SetValue(DialogResultProperty, value);
}
}
The AttachedProperty can be used like this
<Window x:Class="AC.Frontend.Controls.DialogControl.Dialog"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:hlp="clr-namespace:AC.Frontend.Helper"
hlp:AttachedProperties.DialogResult="{Binding DialogResult}">
<!-- put your content here -->
</Window>
Now you can use a Command to set the DialogResult property of the VM, which is the DataContext of the Window.
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) { }