Event circularity - c#

I find myself quite often in the following situation:
I have a user control which is bound to some data. Whenever the control is updated, the underlying data is updated. Whenever the underlying data is updated, the control is updated. So it's quite easy to get stuck in a never ending loop of updates (control updates data, data updates control, control updates data, etc.).
Usually I get around this by having a bool (e.g. updatedByUser) so I know whether a control has been updated programmatically or by the user, then I can decide whether or not to fire off the event to update the underlying data. This doesn't seem very neat.
Are there some best practices for dealing with such scenarios?
EDIT: I've added the following code example, but I think I have answered my own question...?
public partial class View : UserControl
{
private Model model = new Model();
public View()
{
InitializeComponent();
}
public event EventHandler<Model> DataUpdated;
public Model Model
{
get
{
return model;
}
set
{
if (value != null)
{
model = value;
UpdateTextBoxes();
}
}
}
private void UpdateTextBoxes()
{
if (InvokeRequired)
{
Invoke(new Action(() => UpdateTextBoxes()));
}
else
{
textBox1.Text = model.Text1;
textBox2.Text = model.Text2;
}
}
private void textBox1_TextChanged(object sender, EventArgs e)
{
model.Text1 = ((TextBox)sender).Text;
OnModelUpdated();
}
private void textBox2_TextChanged(object sender, EventArgs e)
{
model.Text2 = ((TextBox)sender).Text;
OnModelUpdated();
}
private void OnModelUpdated()
{
DataUpdated?.Invoke(this, model);
}
}
public class Model
{
public string Text1 { get; set; }
public string Text2 { get; set; }
}
public class Presenter
{
private Model model;
private View view;
public Presenter(Model model, View view)
{
this.model = model;
this.view = view;
view.DataUpdated += View_DataUpdated;
}
public Model Model
{
get
{
return model;
}
set
{
model = value;
view.Model = model;
}
}
private void View_DataUpdated(object sender, Model e)
{
//This is fine.
model = e;
//This causes the circular dependency.
Model = e;
}
}

One option would be to stop the update in case the data didn't change since the last time. For example if the data were in form of a class, you could check if the data is the same instance as the last time the event was triggered and if that is the case, stop the propagation.
This is what many MVVM frameworks do to prevent raising PropertyChanged event in case the property didn't actually change:
private string _someProperty = "";
public string SomeProperty
{
get
{
return _someProperty;
}
set
{
if ( _someProperty != value )
{
_someProperty = value;
RaisePropertyChanged();
}
}
}
You can implement this concept similarly for Windows Forms.

What you're looking for is called Data Binding. It allows you to connect two or more properties, so that when one property changes others will be updated auto-magically.
In WinForms it's a little bit ugly, but works like a charm in cases such as yours. First you need a class which represents your data and implements INotifyPropertyChanged to notify the controls when data changes.
public class ViewModel : INotifyPropertyChanged
{
private string _textFieldValue;
public string TextFieldValue {
get
{
return _textFieldValue;
}
set
{
_textFieldValue = value;
NotifyChanged();
}
}
public void NotifyChanged()
{
if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(null));
}
public event PropertyChangedEventHandler PropertyChanged;
}
Than in your Form/Control you bind the value of ViewModel.TextFieldValue to textBox.Text. This means whenever value of TextFieldValue changes the Text property will be updated and whenever Text property changes TextFieldValue will be updated. In other words the values of those two properties will be the same. That solves the circular loops issue you're encountering.
public partial class Form1 : Form
{
public ViewModel ViewModel = new ViewModel();
public Form1()
{
InitializeComponent();
// Connect: textBox1.Text <-> viewModel.TextFieldValue
textBox1.DataBindings.Add("Text", ViewModel , "TextFieldValue");
}
}
If you need to modify the values from outside of the Form/Control, simply set values of the ViewModel
form.ViewModel.TextFieldValue = "new value";
The control will be updated automatically.

You should look into MVP - it is the preferred design pattern for Winforms UI.
http://www.codeproject.com/Articles/14660/WinForms-Model-View-Presenter
using that design pattern gives you a more readable code in addition to allowing you to avoid circular events.
in order to actually avoid circular events, your view should only export a property which once it is set it would make sure the txtChanged_Event would not be called.
something like this:
public string UserName
{
get
{
return txtUserName.Text;
}
set
{
txtUserName.TextChanged -= txtUserName_TextChanged;
txtUserName.Text = value;
txtUserName.TextChanged += txtUserName_TextChanged;
}
}
or you can use a MZetko's answer with a private property

Related

Moving methods from view to viewmodel - WPF MVVM

I have the following code in my code behind:
public partial class MainWindow
{
private Track _movieSkipSliderTrack;
private Slider sMovieSkipSlider = null;
private Label lbTimeTooltip = null;
private MediaElement Player = null;
public VideoPlayerViewModel ViewModel
{
get { return DataContext as VideoPlayerViewModel; }
}
public MainWindow()
{
InitializeComponent();
}
private void SMovieSkipSlider_OnLoaded(object sender, RoutedEventArgs e)
{
_movieSkipSliderTrack = (Track)sMovieSkipSlider.Template.FindName("PART_Track", sMovieSkipSlider);
_movieSkipSliderTrack.Thumb.DragDelta += Thumb_DragDelta;
_movieSkipSliderTrack.Thumb.MouseEnter += Thumb_MouseEnter;
}
private void Thumb_MouseEnter(object sender, MouseEventArgs e)
{
if (e.LeftButton == MouseButtonState.Pressed && e.MouseDevice.Captured == null)
{
var args = new MouseButtonEventArgs(e.MouseDevice, e.Timestamp, MouseButton.Left)
{
RoutedEvent = MouseLeftButtonDownEvent
};
SetPlayerPositionToCursor();
_movieSkipSliderTrack.Thumb.RaiseEvent(args);
}
}
private void Thumb_DragDelta(object sender, DragDeltaEventArgs e)
{
SetPlayerPositionToCursor();
}
private void SMovieSkipSlider_OnMouseEnter(object sender, MouseEventArgs e)
{
lbTimeTooltip.Visibility = Visibility.Visible;
lbTimeTooltip.SetLeftMargin(Mouse.GetPosition(sMovieSkipSlider).X);
}
private void SMovieSkipSlider_OnPreviewMouseMove(object sender, MouseEventArgs e)
{
double simulatedPosition = SimulateTrackPosition(e.GetPosition(sMovieSkipSlider), _movieSkipSliderTrack);
lbTimeTooltip.AddToLeftMargin(Mouse.GetPosition(sMovieSkipSlider).X - lbTimeTooltip.Margin.Left + 35);
lbTimeTooltip.Content = TimeSpan.FromSeconds(simulatedPosition);
}
private void SMovieSkipSlider_OnMouseLeave(object sender, MouseEventArgs e)
{
lbTimeTooltip.Visibility = Visibility.Hidden;
}
private void SetPlayerPositionToCursor()
{
Point mousePosition = new Point(Mouse.GetPosition(sMovieSkipSlider).X, 0);
double simulatedValue = SimulateTrackPosition(mousePosition, _movieSkipSliderTrack);
SetNewPlayerPosition(TimeSpan.FromSeconds(simulatedValue));
}
private double CalculateTrackDensity(Track track)
{
double effectivePoints = Math.Max(0, track.Maximum - track.Minimum);
double effectiveLength = track.Orientation == Orientation.Horizontal
? track.ActualWidth - track.Thumb.DesiredSize.Width
: track.ActualHeight - track.Thumb.DesiredSize.Height;
return effectivePoints / effectiveLength;
}
private double SimulateTrackPosition(Point point, Track track)
{
var simulatedPosition = (point.X - track.Thumb.DesiredSize.Width / 2) * CalculateTrackDensity(track);
return Math.Min(Math.Max(simulatedPosition, 0), sMovieSkipSlider.Maximum);
}
private void SetNewPlayerPosition(TimeSpan newPosition)
{
Player.Position = newPosition;
ViewModel.AlignTimersWithSource(Player.Position, Player);
}
}
I would like to follow the MVVM pattern and have this code moved to my ViewModel which at the moment has only few properties. I have read a lot of answer here and outside of StackOverflow on the topic, I've downloaded some github projects to check out how experienced programmers handle specific situations, but none of that seem to clear out the confusion for me. I'd like to see how can my case be refactored to follow the MVVM pattern.
Those are the extra extension methods and also the ViewModel itself:
static class Extensions
{
public static void SetLeftMargin(this FrameworkElement target, double value)
{
target.Margin = new Thickness(value, target.Margin.Top, target.Margin.Right, target.Margin.Bottom);
}
public static void AddToLeftMargin(this FrameworkElement target, double valueToAdd)
{
SetLeftMargin(target, target.Margin.Left + valueToAdd);
}
}
public class VideoPlayerViewModel : ViewModelBase
{
private TimeSpan _movieElapsedTime = default(TimeSpan);
public TimeSpan MovieElapsedTime
{
get { return _movieElapsedTime; }
set
{
if (value != _movieElapsedTime)
{
_movieElapsedTime = value;
OnPropertyChanged();
}
}
}
private TimeSpan _movieLeftTime = default(TimeSpan);
public TimeSpan MovieLeftTime
{
get { return _movieLeftTime; }
set
{
if (value != _movieLeftTime)
{
_movieLeftTime = value;
OnPropertyChanged();
}
}
}
public void AlignTimersWithSource(TimeSpan currentPosition, MediaElement media)
{
MovieLeftTime = media.NaturalDuration.TimeSpan - currentPosition;
MovieElapsedTime = currentPosition;
}
}
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([System.Runtime.CompilerServices.CallerMemberName] string propName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propName));
}
}
I have tried to make the code copy/paste ready as requested in the comments, all of the Controls in the View's code behind are created in the XAML, if you want to fully replicate it.
The idea is to have a property and command in your VM for every area of the UI that you'd like to update or event that needs to be handled, respectively.
Just glancing at your current code, I think you will have a much easier time (you'll be able to remove a few of your event handlers) if you hook directly into your slider's Value property and bind it (two-way) to a property on your VM. Whenever the user drags, you will be able to see when the value updates and you can handle accordingly.
As far as the "hidden" effect of your scrub bar goes, you may have a much easier time just hooking into the visual state of your slider. Here are the styles and visual states.
EDIT:
public class VideoPlayerViewModel : ViewModelBase
{
// your existing properties here, if you decide that you still need them
// this could also be long/double, if you'd like to use it with your underlying type (DateTime.TotalTicks, TimeSpan.TotalSeconds, etc.)
private uint _elapsedTime = 0; //or default(uint), whichever you prefer
public uint ElapsedTime
{
get { return _elapsedTime; }
set
{
if (_elapsedTime != value)
{
_elapsedTime = value;
//additional "time changed" logic here, if needed
//if you want to skip programmatically, all you need to do is set this property!
OnPropertyChanged();
}
}
}
private double _maxTime = 0;
public double MaxTime
{
// you get the idea, you'll be binding to the media's end time in whatever unit you're using (i.e. if I have a 120 second clip, this value would be 120 and my elapsed time would be hooked into an underlying TimeSpan.TotalSeconds)
}
}
and on your slider:
Value={Binding ElapsedTime, Mode=TwoWay}
Maximum={Binding MaxTime, Mode=OneWay} //could also be OneTime, depending on the lifecycle of the control
I recommend using Caliburn Micro.
If you use that library you can bind events like this:
<Button cal:Message.Attach="Save">
or like that
<Button cal:Message.Attach="[Event MouseEnter] = [Action Save]">
Check out their website for more advanced possibilities:
https://caliburnmicro.codeplex.com/wikipage?title=Cheat%20Sheet
I have some simple rules that I follow in XAML apps:
The ViewModel should not know about the View, so no UI related code will ever be found in the ViewModel
All UI related code is in the code behind(xaml.cs)
User controls and dependency properties are your best friends, so use them. The view should be made up of user controls, each with its own ViewModel.
Inject your dependencies through constructor injection so they can be mocked when you write unit tests
You should not have mouse handlers in your viewmodel. Those events belong to the UI and hence the view. Instead, move the bloated view code to an attached behavior. From the behavior you can optionally call into your viewmodel through interfaces. E.g.:
var vm = AssociatedObject.DataContext as IPlayerViewModel;
vm?.AlignTimersWithSource(...);
you can not use events in viewmodel. So you will have to create command pattern class and just create viewmodel class. After that can use name space of viewmodel in xml file or view file using "xmlns tag. And create resource for the class and provide meaning full key name. And set datacontext in
<Grid datacontext="nameofresource">. Now do the keybinding.
Note: If you need more clearification, reply

Passing Value on Property Change

I have 2 forms: Form A and Form B. I also have a property field class.
Form A contains the label I want changed when a property is changed. Form B contains code that will change the property field.
Property Class Code:
public class Controller
{
private static string _customerID;
public static string customerID
{
get { return _customerID; }
set
{
_customerID = value;
if (_customerID != "")
{
FormA.ChangeMe();
}
}
}
}
Form B Code:
private void something_Click(object sender, SomethingEventArgs e) {
Controller.customerID = "Cool";
}
Form A Code:
public static void ChangeMe()
{
var frmA = new FormA();
MessageBox.Show("Test: " + Controller.customerID); //This works! Shows Cool
frmA.lb2Change.Text = Controller.customerID; //This kind of works..
MessageBox.Show("Test2: " + frmA.lb2Change.Text); //This shows the correct value. Shows Cool
}
The property field value is passed (which I know from the MessageBox) however it does not update the value on the form label itself. Why is this? What am I doing wrong? I also believe there is a better alternative for achieving what ChangeMe() method is intended to achieve -- if so are there any suggestions?
You can do the following
To define a delegate
To Implement Property Change Notification
Delegate
public delegate void OnCustomerIDChanging(object sender,CancelEventArgs e);
public delegate void OnCustomerIDChanged(object sender,object value);
public class Controller
{
private static string _customerID;
public event OnCustomerIDChanging CustoerIDChanging;
public event OnCustomerIDChanged CustoerIDChanged;
public static string customerID
{
get { return _customerID; }
set
{
// make sure that the value has a `value` and different from `_customerID`
if(!string.IsNullOrEmpty(value) && _customerID!=value)
{
if(CustomerIDChanging!=null)
{
var state = new CancelEventArgs();
// raise the event before changing and your code might reject the changes maybe due to violation of validation rule or something else
CustomerIDChanging(this,state);
// check if the code was not cancelled by the event from the from A
if(!state.Cancel)
{
// change the value and raise the event Changed
_customerID = value;
if(CustomerIDChanged!=null)
CustomerIDChanged(this,value);
}
}
}
}
}
}
in your Form and when you are initiating the Controller Object
var controller = new Controller();
controller.CustomerIDChanging +=(sd,args) =>{
// here you can test if you want really to change the value or not
// in case you want to reject the changes you can apply
args.Cancel = true;
};
controller.CustomerIDChanged +=(sd,args) =>{
// here you implement the code **Changed already**
}
The above code will give you a great control over your code, also will make your controller code reusable and clean. Same
result you can get by implementing INotifyPropertyChanged interface
INotifyPropertyChanged
you might have a look on this article to get more information
In your static method ChangeMe you are creating a new Form every time, you want to Change the value. Instead of that you want to change the value of an existing form. Therefor your Controller needs an instance of this FormA. Try it like this:
public class Controller
{
//You can pass the form throught the constructor,
//create it in constructor, ...
private FormA frmA;
private string _customerID;
public string customerID
{
get { return _customerID; }
set
{
_customerID = value;
if (_customerID != "")
{
frmA.ChangeMe();
}
}
}
}
Now you donĀ“t need to be static in your FormA:
public void ChangeMe()
{
MessageBox.Show("Test: " + Controller.customerID);
this.lb2Change.Text = Controller.customerID;
}

MVP pattern in winforms - Handling events

I just started with C# and MVP design pattern.
I'm in doubt about concrete implementation when it comes to event handling. I'm aware of that, view shouldn't know about presenter and presenter should control a view through view interface.
Let's say I have 2 text boxes and would like to check for errors. If an error occurs I want to change text box Text property. Is it wrong approach to create one EventHandler and use sender object to verify witch text box is user currently using?
Something like:
IView:
interface IMainView
{
event KeyPressEventHandler KeyPressed;
}
View:
public partial class MainView : Form, IMainView
{
public frmInterakcija()
{
InitializeComponent();
this.textBox1.Name = "textBox1";
this.textBox2.Name = "textBox2";
new MainPresenter();
Bind();
}
private void Bind()
{
this.textBox1.KeyPress += KeyPressed;
this.textBox2.KeyPress += KeyPressed;
}
}
Presenter:
class MainPresenter
{
private IMainView _view;
public MainPresenter(IMainView view)
{
_view = view;
this.initialize();
}
public void initialize()
{
_view.KeyPressed += _view_textBoxKeyPressed;
}
public void _view_textBoxKeyPressed(object sender, EventArgs e)
{
if (sender.GetType() == typeof(TextBox))
{
TextBox textBox = (TextBox)sender;
if (textBox.Name.Equals("textbox1")
{...} // Do validation/changes on textbox1
else ...
}
}
}
Or instead of this above I should create event handler for every textbox I have and update/handle errors through properties? (this will make my code redundant I guess)
What would be right approach?
IMHO the presenter should be unaware of view specific objects (example textbox in your code). That kind of logic should not be in presenter. And presenter must not know about the Ids of controls in the UI, that's even worse. Remember one of the benefits of this should be that you can test the presenter by mocking the view, if you have UI specific code you won't be able to unit test the presenter.
It does seem like two different events to me since you are doing different logic. I'd raise two different events and one would do validation, the other would do its own logic. The presenter won't have to check if the sender is textbox or the id of the textbox. Also what if you have another textbox, you'll need another if condition in this current implementation.
Also, in the view, it should be new MainPresenter(this);
Your presenter should absolutely not have view-specific types in it (e.g. controls, events, etc.) since these are hard to fake when it comes time to test the presenter's logic. Instead, you should have something like the following.
IView:
interface IMainView
{
// give these better names based on what they actually represent (e.g. FirstName and LastName)
// you could also add setters if you needed to modify their values from the presenter
string Text1 { get; }
string Text2 { get; }
// provide a way to bubble up validation errors to the UI
string ErrorMessage { get; set; }
}
Presenter:
class MainPresenter
{
private IMainView _view;
public MainPresenter(IMainView view)
{
_view = view;
}
public void ValidateText1()
{
if (/* some validation is false */)
{
_view.ErrorMessage = "Text1 isn't valid";
}
}
public void ValidateText2()
{
if (/* some validation is false */)
{
_view.ErrorMessage = "Text2 isn't valid";
}
}
}
View:
public partial class MainView : Form, IMainView
{
var readonly MainPresenter _presenter;
public frmInterakcija()
{
InitializeComponent();
_presenter = new MainPresenter(this);
}
private void textBox1_KeyPress(object sender, KeyPressEventArgs eventArgs)
{
_presenter.ValidateText1();
}
private void textBox2_KeyPress(object sender, KeyPressEventArgs eventArgs)
{
_presenter.ValidateText2();
}
#region Implementation of IMainView
public string Text1
{
get { return textBox1.Text; }
}
public string Text2
{
get { return textBox2.Text; }
}
public string ErrorMessage
{
get { return labelErrorMessage.Text; }
set { labelErrorMessage.Text = value; }
}
#endregion
}

Bind "Enabled" properties of controls to a variable

I am running into an issue that I have found on some similar post, however, they are not quite the same and I am not quite sure how to apply it to my scenario. They may or may not be the same as my case. So, I am posting my own question here hopefully, I will get an answer to my specific scenario.
Basically, I have a window form with a bunch of controls. I would like to have the ability to bind their Enabled property to a Boolean variable that I set so that they can be enable or disable to my discretion.
public partial class MyUI : Form
{
private int _myID;
public int myID
{
get
{
return _myID;;
}
set
{
if (value!=null)
{
_bEnable = true;
}
}
}
private bool _bEnable = false;
public bool isEnabled
{
get { return _bEnable; }
set { _bEnable = value; }
}
public myUI()
{
InitializeComponent();
}
public void EnableControls()
{
if (_bEnable)
{
ctl1.Enabled = true;
ctl2.Enabled = true;
......
ctl5.Enabled = true;
}
else
{
ctl1.Enabled = false;
ctl2.Enabled = false;
......
ctl5.Enabled = false;
}
}
}
}
The method EnableControls above would do what I need but it may not be the best approach. I prefer to have ctrl1..5 be bound to my variable _bEnable. The variable will change depending on one field users enter, if the value in the field exists in the database, then other controls will be enabled for user to update otherwise they will be disabled.
I have found a very similar question here
but the data is bound to the text field. How do I get rid of the EnableControls method and bind the value of _bEnabled to the "Enabled" property in each control?
Go look into the MVVM (Model - View - ViewModel) pattern, specifically its implementation within Windows Forms. Its much easier to apply it to a WPF/Silverlight application, but you can still use it with Windows Forms without too much trouble.
To solve your problem directly, you will need to do 2 things:
Create some class that will hold your internal state (i.e. whether or not the buttons are enabled). This class must implement INotifyPropertyChanged. This will be your View Model in the MVVM pattern.
Bind an instance of the class from 1.) above to your Form. Your form is the View in the MVVM pattern.
After you have done 1 and 2 above, you can then change the state of your class (i.e. change a property representing whether a button is enabled from true to false) and the Form will be updated automatically to show this change.
The code below should be enough to get the concept working. You will need to extend it obviously, but it should be enough to get you started.
View Model
public class ViewModel : INotifyPropertyChanged
{
private bool _isDoStuffButtonEnabled;
public bool IsDoStuffButtonEnabled
{
get
{
return _isDoStuffButtonEnabled;
}
set
{
if (_isDoStuffButtonEnabled == value) return;
_isDoStuffButtonEnabled = value;
RaisePropertyChanged("IsDoStuffButtonEnabled");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
View
public class View : Form
{
public Button DoStuffButton { get; set; }
public void Bind(ViewModel vm)
{
DoStuffButton.DataBindings.Add("Enabled", vm, "IsDoStuffButtonEnabled");
}
}
Usage
public class Startup
{
public ViewModel ViewModel { get; set; }
public View View { get; set; }
public void Startup()
{
ViewModel = new ViewModel();
View = new View();
View.Bind(ViewModel);
View.Show();
ViewModel.IsDoStuffButtonEnabled = true;
// Button becomes enabled on form.
// ... other stuff here.
}
}
Maybe you can try this approach: in your isEnabled property's setter method, add an if statement:
if(_bEnable) EnableControls();
else DisableControls();
And if your control names are ctl1,ctl2... etc. you can try this:
EnableControls()
{
for(int i=1; i<6;i++)
{
string controlName = "ctl" + i;
this.Controls[controlName].Enabled = true;
}
}
And apply the same logic in DisableControls
If you have more controls in future this could be more elegant.

Return value of a Property from child ViewModel to parent ViewModel

In my WPF MVVM app, using Caliburn.Micro, I have a ViewModel, CreateServiceViewModel that, on a button click, opens a GridView in a seperate window for the User to chose a Row from.
I created another ViewModel for this, MemberSearchViewModel which has two properties:
private Member selectedMember;
public Member SelectedMember
{
get { return selectedMember; }
set { selectedMember = value; }
}
private IList<Member> members;
public IList<Member> Members
{
get { return members; }
set { members = value; }
}
How do I get that SelectedMember value back to the calling ViewModel? That ViewModel has a property of Service.SelectedMember.
EventAggregator is what you could use... One of many solutions I am sure.
public class MessageNotifier{
public object Content{get;set;}
public string Message {get;set;}
}
//MEF bits here
public class HelloWorldViewModel: Screen, IHandle<MessageNotifier>{
private readonly IEventAggregator _eventAggregator
//MEF constructor bits
public YourViewModel(IEventAggregator eventAggregator){
_eventAggregator = eventAggregator;
}
public override OnActivate(){
_eventAggregator.Subscribe(this);
}
public override OnDeactivate(){
_eventAggregator.UnSubscribe(this);
}
//I Handle all messages with this signature and if the message applies to me do something
//
public void Handle(MesssageNotifier _notifier){
if(_notifier.Message == "NewSelectedItem"){
//do something with the content of the selectedItem
var x = _notifier.Content
}
}
}
//MEF attrs
public class HelloWorld2ViewModel: Screen{
private readonly IEventAggregator _eventAggregator
//MEF attrs
public HelloWorld2ViewModel(IEventAggregator eventAggregator){
_eventAggregator = eventAggregator;
}
public someobject SelectedItem{
get{ return _someobject ;}
set{ _someobject = value;
NotifyOfPropertyChange(()=>SelectedItem);
_eventAggregator.Publish(new MessageNotifier(){ Content = SelectedItem, Message="NewSelectedItem"});
}
}
One option is to utilize NotifyPropertyChanged. Since you are working with ViewModels, they most likely implement INotifyPropertyChanged, which you can make use of just as the framework does.
When your CreateServiceViewModel creates the MemberSearchViewModel, it would just subscribe to the PropertyChanged event:
//This goes wherever you create your child view model
var memberSearchViewModel = new MemberSearchViewModel(); //Or using a service locator, if applicable
memberSearchViewModel.PropertyChanged += OnMemberSearchPropertyChanged;
private void OnMemberSearchPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if(e.PropertyName == "SelectedMember")
{
//Code to respond to a change in the Member
}
}
And then in your MemberSearchViewModel, you simply raise the NotifyPropertyChanged event when the user has selected a member from the grid.
EDIT:
As #DNH correctly notes in the comments, using event handlers like this can lead to memory leaks if not properly cleaned up. So when you are finished with the MemberSearchViewModel, make sure to unsubscribe to the PropertyChanged event. So for example, if you only need it until the user selects a member, you could put it inside the Property Changed Handler itself (I've switched it to use a class-level variable to hold the ViewModel):
private void OnMemberSearchPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if(e.PropertyName == "SelectedMember")
{
//Code to respond to a change in the Member
//Unsubscribe so the view model can be garbage collected
_memberSearchViewModel.PropertyChanged -= OnMemberSearchPropertyChanged;
_memberSearchViewModel = null;
}
}
One option would be to store MemberSearchViewModel as a field of CreateServiceViewModel and define CreateServiceViewModel.SelectedMember property as follows:
public Member SelectedMember
{
get
{
return _memberSearchViewModel.SelectedMember;
}
set
{
_memberSearchViewModel.SelectedMember = value;
}
}
How about?
public interface INotifyMe<T>
{
T ResultToNotify { get; set; }
}
public class CreateServiceViewModel : ViewModelBase, INotifyMe<Member>
{
// implement the interface as you like...
}
public class MemberSearchViewModel : ViewModelBase
{
public MemberSearchViewModel(INotifyMe<Member> toBeNotified)
{
// initialize field and so on...
}
}
Now you could let listen CreateServiceViewModel to its own property and you won't have to think about the removal of the event listener.
Well of course to do the more classical way you could alternatively use an interface like this.
public interface INotifyMe<T>
{
void Notify(T result);
}
As a follow-up to my comment, here's an example using Prism - I've never used Caliburn.
Create an event - the event's payload will be your SelectedMember:
public class YourEvent:CompositePresentationEvent<YourEventPayload>{}
Publish the event:
EventAggregator.GetEvent<YourEvent>().Publish(YourEventPayload);
Subscribe to the event:
EventAggregator.GetEvent<YourEvent>().Subscribe((i) => ...);

Categories