I have 3 Controls - ContractSelection, ContractInfo and ContractClips
I have an additional class - ContractStructureService. This contains a property - SelectedContract
The contract selection control displays a list of contracts to the user in a combo box
When the user selects a contract, the SelectedContract on the service is changed.
I know how to do all that - the tricky bit is that I want both ContractInfo and ContractClips to be told when the selected contract has been changed
public class FlexContractStructureService : IFlexContractStructureService
{
#region IFlexContractStructureService Members
private Contract _selectedContract;
public ViewModels.Contract SelectedContract
{
get { return _selectedContract; }
set
{
_selectedContract = value;
OnSelectedContractChanged(new SelectedContractChangedEventArgs(SelectedContract));
}
}
public event EventHandler SelectedContractChanged;
protected virtual void OnSelectedContractChanged(SelectedContractChangedEventArgs e)
{
if (SelectedContractChanged != null)
{
SelectedContractChanged(this, e);
}
}
How do I do this with Silverlight?
I would assume that if I have code in both ContractInfo and ContractClipInfo like
service.SelectedContractChanged += ContractChanged
Wouldn't this mean that when I call OnSelectedContract changed, only 1 object is notified?
Paul
There are several questions rolled into one here.
For the last part, an event is meant to be multicast, ie to be able to notify a list of delegates. So, no, not only 1 object notified.
Now, for the main question, in Silverlight, you have several options, depending on what you're already using:
You can use events and delegates as you propose, if you can get direct references to the relevant objects. This causes a coupling that might not be desirable, and also, poses a leak/delegate spam risk. What this mean is: you have to be very careful to unsubscribe each delegate that was added to an event.
You can use a non-coupling communication pattern, for instance a Messenger (MVVM Light), or something similar (broadcaster/subscribers). You have a point of broadcast here: OnSelectedContractChanged where you can send a notification (in MVVM Light again: Messenger.Default.Send(new SelectedContractChangedNotification(_selectedContract));). This notification can be received wherever you need elsewhere in your application (Messenger.Default.Register<SelectedContractChangedNotification>(this, OnSelectedContractChanged);) with as many receivers as you need.
Related
See the next post. This original one question content has been removed, as doesn't have any sense. Briefly, I asked how to bind XML (which I generated by mistake while parsing DLL assembly) to TreeView using XmlDataProvider in MVVM way. But later I understood that this approach was wrong, and I switched to generation of data entity model (just write classes which represent all the entities I would like to expose in the tree) instead of XML.
So, the result in the next post. Currently from time to time I update this "article", so F5, and
Enjoy reading!
Introduction
The right way I had found reading this article
It's a long story, most of you just can skip it :) But those, who want to understand the problem and solution, must read this all !
I'm QA, and some time ago had become responsible for Automation of the product I clicks. Fortunately, this automaton takes place not in some Testing Tool, but in Visual Studio, so it is maximally close to development.
For our automation we use a framework which consist of MbUnit (Gallio as runner) and of MINT (addition to MbUnit, which is written by the customer we work with). MbUnit gives us Test Fixtures and Tests, and MINT adds additional smaller layer -- Actions inside tests. Example. Fixture is called 'FilteringFixture'. It consist of amount of tests like 'TestingFilteringById', or 'TestingFilteringWithSpecialChars', etc. Each test consist of actions, which are atomic unit of our test. Example of actions are - 'Open app (parameter)', 'OpenFilterDialog', etc.
We already have a lot of tests, which contain a lot of actions, it's a mess. They use internal API of the product we QA. Also, we start investigation a new Automation approach - UI automation via Microsoft UI Automation (sorry for tautology). So the necessity of some "exporter", or "reporter" tool became severe for managers.
Some time ago I have got a task to develop some application, which can parse a DLL (which contains all the fixtures, tests and actions), and export its structure in the human readable format (TXT, HTML, CSV, XML, any other). But, right after that, I went to vacation (2 weeks).
It happens so, that my girlfriend went to her family until vacation (she also got it), and I remained at home so alone. Thinking what me to do all this time (2 weeks), I remember about that "write exporter tool task" and how long I have been planning to start learning WPF. So, I decided to make my task during vacation, and also dress a application to WPF. At that time I heard something about MVVM, and I decided to implement it using pure MVVM.
DLL which can parse DLL with fixrtures etc had been written rather fast (~1-2 days). After that I had started with WPF, and this article will show you how it ended.
I have spent a major part of my vacation (almost 8 days!), trying to sorted it out in my head and code, and finally, it is done (almost). My girlfriend would not believe what I was doing all this time, but I have a proof!
Sharing my solution step by step in pseudo code, to help others avoid similar problems. This answer is more looks like tutorial =) (Really?). If you are interested what were the most complicated things while learning WPF from scratch, I would say -- make it all really MVVM and f*g TreeView binding!
If you want an archived file with solution, I can give it a bit later, just when I have made a decision, that it is worth of that. One limitation, I'm not sure I may share the MINT.dll, which brings Actions, as it has been developed by the customer of our company. But I can just remove it, and share the application, which can display information about Fixtures and Tests only, but not about actions.
Boastful words. With just a little C# / WinForms / HTML background and no practice I have been able to implement this version of the application in almost 1 week (and write this article). So, impossible is possible! Just take a vacation like me, and spend it to WPF learning!
Step by step tutorial (w/o attached files yet)
Short repetition of the task:
Some time ago I have got a task to develop an application, which can parse a DLL (which contains test fixtures, test methods and actions - units of our unit testing based automation framework), and export its structure in the human readable format (TXT, HTML, CSV, XML, any other). I decided to implement it using WPF and pure MVVM (both were absolutely new things for me). The 2 most difficult problems for me became MVVM approach itself, and then MVVM binding to TreeView control. I skip the part about MVVM division, it's a theme for separate article. The steps below are about binding to TreeView in MVVM way.
Not so important: Create DLL which can open DLL with unit tests and finds fixtures, test methods and actions (more smaller level of unit test, written in our company) using reflection. If you are interested in how it had been done, look here: Parsing function / method content using Reflection
DLL: Separated classes are created for both fixtures, tests and actions (data model, entity model?).We'll use them for binding. You should think by yourself, what will be an entity model for your tree. Main idea - each level of tree should be exposed by appropriate class, with those properties, which help you to represent the model in the tree (and, ideally, will take right place in your MVVM, as model or part of the model). In my case, I was interested in entity name, list of children and ordinal number. Ordinal number is a number, which represents order of an entity in the code inside DLL. It helps me show ordinal number in the TreeView, still not sure it's right approach, but it works!
public class MintFixutre : IMintEntity
{
private readonly string _name;
private readonly int _ordinalNumber;
private readonly List<MintTest> _tests = new List<MintTest>();
public MintFixutre(string fixtureName, int ordinalNumber)
{
_name = fixtureName;
if (ordinalNumber <= 0)
throw new ArgumentException("Ordinal number must begin from 1");
_ordinalNumber = ordinalNumber;
}
public List<MintTest> Tests
{
get { return _tests; }
}
public string Name { get { return _name; }}
public bool IsParent { get { return true; } }
public int OrdinalNumber { get { return _ordinalNumber; } }
}
public class MintTest : IMintEntity
{
private readonly string _name;
private readonly int _ordinalNumber;
private readonly List<MintAction> _actions = new List<MintAction>();
public MintTest(string testName, int ordinalNumber)
{
if (string.IsNullOrWhiteSpace(testName))
throw new ArgumentException("Test name cannot be null or space filled");
_name = testName;
if (ordinalNumber <= 0)
throw new ArgumentException("OrdinalNumber must begin from 1");
_ordinalNumber = ordinalNumber;
}
public List<MintAction> Actions
{
get { return _actions; }
}
public string Name { get { return _name; } }
public bool IsParent { get { return true; } }
public int OrdinalNumber { get { return _ordinalNumber; } }
}
public class MintAction : IMintEntity
{
private readonly string _name;
private readonly int _ordinalNumber;
public MintAction(string actionName, int ordinalNumber)
{
_name = actionName;
if (ordinalNumber <= 0)
throw new ArgumentException("Ordinal numbers must begins from 1");
_ordinalNumber = ordinalNumber;
}
public string Name { get { return _name; } }
public bool IsParent { get { return false; } }
public int OrdinalNumber { get { return _ordinalNumber; } }
}
BTW, I also created an interface below, which implement all the entities. Such interface can help you in the future. Still not sure, should I was also add there Childrens property of List<IMintEntity> type, or something like that?
public interface IMintEntity
{
string Name { get; }
bool IsParent { get; }
int OrdinalNumber { get; }
}
DLL - building data model: DLL has a method which opens DLL with unit tests and enumerating data. During enumeration, it builds a data model like below. Real method example is given, reflection core + Mono.Reflection.dll are used, don't be confused with complexity. All that you need - look how the method fills _fixtures list with entities.
private void ParseDllToEntityModel()
{
_fixutres = new List<MintFixutre>();
// enumerating Fixtures
int f = 1;
foreach (Type fixture in AssemblyTests.GetTypes().Where(t => t.GetCustomAttributes(typeof(TestFixtureAttribute), false).Length > 0))
{
var tempFixture = new MintFixutre(fixture.Name, f);
// enumerating Test Methods
int t = 1;
foreach (var testMethod in fixture.GetMethods().Where(m => m.GetCustomAttributes(typeof(TestAttribute), false).Length > 0))
{
// filtering Actions
var instructions = testMethod.GetInstructions().Where(
i => i.OpCode.Name.Equals("newobj") && ((ConstructorInfo)i.Operand).DeclaringType.IsSubclassOf(typeof(BaseAction))).ToList();
var tempTest = new MintTest(testMethod.Name, t);
// enumerating Actions
for ( int a = 1; a <= instructions.Count; a++ )
{
Instruction action = instructions[a-1];
string actionName = (action.Operand as ConstructorInfo).DeclaringType.Name;
var tempAction = new MintAction(actionName, a);
tempTest.Actions.Add(tempAction);
}
tempFixture.Tests.Add(tempTest);
t++;
}
_fixutres.Add(tempFixture);
f++;
}
}
DLL: Public property Fixtures of the List<MintFixutre> type is created to return just created data model ( List of Fixtures, which contain lists of tests, which contains lists of Actions ). This will be our binding source for TreeView.
public List<MintFixutre> Fixtures
{
get { return _fixtures; }
}
ViewModel of MainWindow (with TreeView inside): Contains object / class from DLL which can parse unit tests DLLs. Also exposes Fixtures public property from the DLL of List<MintFixutre> type. We will bind to it from XAML of MainWindow. Something like that (simplified):
var _exporter = MySuperDllReaderExporterClass ();
// public property of ViewModel for TreeView, which returns property from #4
public List<MintFixture> Fixtures { get { return _exporter.Fixtures; }}
// Initializing exporter class, ParseDllToEntityModel() is called inside getter
// (from step #3). Cool, we have entity model for binding.
_exporter.PathToDll = #"open file dialog can help";
// Notifying all those how are bound to the Fixtures property, there are work for them, TreeView, are u listening?
// will be faced later in this article, anticipating events
OnPropertyChanged("Fixtures");
XAML of MainWindow - Setup data templates: Inside a Grid, which contains TreeView, we create <Grid.Resources> section, which contains a set of templates for our TreeViewItems. HierarchicalDataTemplate (Fixtures and Tests) is used for those who have child items, and DataTemplate is used for "leaf" items (Actions). For each template, we specify which its Content (text, TreeViewItem image, etc.), ItemsSource (in case of this item has children, e.g. for Fixtures it is {Binding Path=Tests}), and ItemTemplate (again, only in case this item has children, here we set linkage between templates - FixtureTemplate uses TestTemplate for its children, TestTemplate uses ActionTemplate for its children, Action template does not use anything, it is a leaf!). IMPORTANT: Don't forget, that in order to "link" "one" template to "another", the "another" template must be defined in XAML above the "one"! (just enumerating my own blunders :) )
XAML - TreeView linkage: We setup TreeView with: linking with data model from ViewModel (remember public property?) and with just prepared templates, which represent content, appearance, data sources and nesting of tree items! One more important note. Don't define your ViewModel as "static" resource inside XAML, like <Window.Resources><MyViewModel x:Key="DontUseMeForThat" /></Window.Resources>. If you do so, then you won't be able to notify it on property changed. Why? Static resource is static resource, it initializes ones, and after that remains immutable. I might be wrong here, but it was one of my blunders. So for TreeView use ItemsSource="{Binding Fixtures}" instead of ItemsSource="{StaticResource myStaticViewModel}"
ViewModel - ViewModelBase - Property Changed: Almost all. Stop! When user opens an application, then initially TreeView is empty of course, as user hasn't opened any DLL yet! We must wait until user opens a DLL, and only then perform binding. It is done via OnPropertyChanged event. To make life easier, all my ViewModels are inherited from ViewModelBase, which right exposes this functionality to all my ViewModel.
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
}
protected virtual void OnPropertyChanged(PropertyChangedEventArgs args)
{
var handler = PropertyChanged;
if (handler != null)
handler(this, args);
}
}
XAML - OnPropertyChanged and commanding. User clicks a button to opens DLL which contains unit tests data. As we using MVVM, then click is handled via commanding. At the end of the OpenDllExecuted handler OnPropertyChanged("Fixtures") is executed, notifying the Tree, that the property, to which it is bind to has been changed, and that now is time to refresh itself. RelayCommand helper class can be taken for example from there). BTW, as I know, there are some helper libraries and toolkits exist Something like that happens in the XAML:
And ViewModel - Commanding
private ICommand _openDllCommand;
//...
public ICommand OpenDllCommand
{
get { return _openDllCommand ?? (_openDllCommand = new RelayCommand(OpenDllExecuted, OpenDllCanExecute)); }
}
//...
// decides, when the <OpenDll> button is enabled or not
private bool OpenDllCanExecute(object obj)
{
return true; // always true for Open DLL button
}
//...
// in fact, handler
private void OpenDllExecuted(object obj)
{
var openDlg = new OpenFileDialog { ... };
_pathToDll = openDlg.FileName;
_exporter.PathToDll = _pathToDll;
// Notifying TreeView via binding that the property <Fixtures> has been changed,
// thereby forcing the tree to refresh itself
OnPropertyChanged("Fixtures");
}
Final UI (but not final for me, a lot of things should be done!). Extended WPF toolkit was used somewhere: http://wpftoolkit.codeplex.com/
I am using the following code.
public partial class SettingApp
{
public SettingApp()
{
InitializeComponent();
Parallel.Invoke(SetDataInTextBox);
}
private void SetDataInTextBox()
{
txtIncAns.Text = Properties.Settings.Default.IncludeAN;
txtIncAuthor.Text = Properties.Settings.Default.IncludeAutt;
txtIncQuo.Text = Properties.Settings.Default.IncludeQU;
txtIncSpBegin.Text = Properties.Settings.Default.IncludeSP;
}
}
The program gives the following error
The calling thread cannot access this object because a different
thread owns it.
Which is the right way
update :
Whether this is right :
public partial class SettingApp
{
private delegate void SetDataInTextBoxDelegate();
public SettingApp()
{
InitializeComponent();
txtIncAns.Dispatcher.BeginInvoke(DispatcherPriority.Normal, new SetDataInTextBoxDelegate(SetDataInTextBox));
}
private void SetDataInTextBox()
{
txtIncAns.Text = Properties.Settings.Default.IncludeAN;
txtIncAuthor.Text = Properties.Settings.Default.IncludeAutt;
txtIncQuo.Text = Properties.Settings.Default.IncludeQU;
txtIncSpBegin.Text = Properties.Settings.Default.IncludeSP;
}
}
Only the UI thread can access UI elements, which I'm guessing is what those txt things are. Parallel.Invoke is in your case not the UI thread, so the exception is thrown when you try to access the .Text property on the controls.
You need to marshal the call across to the UI thread. In WinForms, controls have various ways to help you do this:
if (myControl.InvokeRequired)
{
myControl.Invoke(...);
}
else
{
myControl.Text = "something";
}
MSDN has an article with examples on it here (VS2010):
http://msdn.microsoft.com/en-us/library/757y83z4(v=VS.100).aspx
Update 1:
For WPF the model is similar, but includes the Dispatcher:
myControl.Dispatcher.Invoke(...);
MSDN Forum Post
MSDN Article
Update 2: Of course, it looks like you don't even need to use multi-threaded code here. I would guess the overhead of using the multi-threaded portion is more than the code you eventually call. Simply remove the use of multiple threads from this section and set the properties directly:
public SettingApp()
{
InitializeComponent();
SetDataInTextBox();
}
private void SetDataInTextBox()
{
txtIncAns.Text = Properties.Settings.Default.IncludeAN;
txtIncAuthor.Text = Properties.Settings.Default.IncludeAutt;
txtIncQuo.Text = Properties.Settings.Default.IncludeQU;
txtIncSpBegin.Text = Properties.Settings.Default.IncludeSP;
}
As Adam said, only the UI thread can access UI elements. In the case of WPF, you'd use myControl.Dispatcher.Invoke().
Since these calls are all going to be invoked on the UI thread anyway, you should remove the Parallel.Invoke() and call the method directly.
Just an alternate suggestion. I would move toward databinding your textboxes to properties of a class, even if you don't want to go the full MVVM design, databinding is your friend in WPF. Then if you need the threading, WPF will handle updating controls on UI thread when the property changes, even when the property is changed on another thread.
I have a page where you basically select a set of options (configuration), and then you go to a next page, where you do some stuff
Using the MVVM Light toolkit, I have a viewmodel that binds to the view of the first page. when the user hits a button, it redirects to another view, which would be the 2nd page
i.e.:
Page2Command = new DelegateCommand((obj) =>
Messenger.Default.Send<Uri>(new Uri("/DoStuffView.xaml", UriKind.Relative),
Common.CommonResources.GoToDoStuffRequest)) });
The problem is, the viewmodel for the 2nd view (the way that I see it) has a couple of parameters in the constructor, which are basically the dependencies on the configuration that was set on the first page.
i.e. :
public DoStuffViewModel(ICollection<Note> availableNotes, SoundMappers soundType)
{
}
The problem lies here.. How can I instantiate the viewmodel with this data that was dynamically selected by the user on the 1st page?.
I can't use the ViewModelLocator pattern that MVVM light provides, since those viewmodels don't have any dependencies, they are just by themselves (or they can retrieve data from a db, file or whatever, but they don't have any dynamic input data). I could do it through the view's constructor, instantiate there the viewmodel, and assign to the view's DataSource the newly created viewmodel, but I think that's not very nice to do.
suggestions?
As I see you send messsage using Messenger class so you are familiar with messaging in MVVM light. You have to define your own message type that should accept your parameters from page 1:
public class Page2ViewModelCreateMessage : MessageBase
{
public ICollection<Note> AvailableNotes{get;set;}
public SoundMappers SoundType{get;set;}
public Page2ViewModelCreateMessage ()
{
}
public Page2ViewModelCreateMessage(ICollection<Note> availableNotes, SoundMappers soundType)
{
this.AvailableNotes = availableNotes;
this.SoundType = soundType;
}
}
You have to send an Page2ViewModelCreateMessage instance with you parameters and send it on navigating:
var message = new Page2ViewModelCreateMessage(myAvailableNotes, mySoundType)
Messenger.Default.Send(message);
On Page2 you have to register for recieving message of type Page2ViewModelCreateMessage:
Messenger.Default.Register<Page2ViewModelCreateMessage>(this, OnPage2ViewModelCreateMessage);
..
public void OnPage2ViewModelCreateMessage(Page2ViewModelCreateMessage message)
{
var page2ViewModel = new Page2ViewModel(messsage.AvailableNotes, message.SoundType);
}
As you can see I have replace your DoStuffViewModel with Page2ViewModel to be more clear.
I hope this will help you.
NOTE:I dont guarantee that code will work as its written in notepad.
The way I do this is to have a central controller class that the ViewModels all know about, via an interface. I then set state into this before having the phone perform the navigation for me. Each ViewModel then interrogates this central class for the state it needs.
There are a number of benefits to this for me:
It allows me to have non-static ViewModels.
I can use Ninject to inject the concrete implementation of the controller class and have it scoped as a singleton.
Most importantly, when tombstoning, I only need to grab the current ViewModel and the controller class.
I ran into a problem with messaging where my ViewModel was the registered listener, because I was View First and not ViewModel First, I was forced to use static ViewModel references. Otherwise the ViewModel wasn't created in time to receive the message.
I use the controller class in conjunction with messages (it is basically the recipient of all messages around the UI) so in future if I refactor, I don't need to change much, just the recipients of the messages.
Come to think of it, the controller class is also my navigation sink - as I have some custom navigation code that skips back paging on certain pages etc.
Here's an example of my current set up:
public interface IController
{
Foo SelectedFoo { get; }
}
public class ViewModel
{
private IController _controller;
public ViewModel(IController controller)
{
_controller = controller;
}
private void LoadData()
{
// Using selected foo, we load the bars.
var bars = LoadBars(_controller.SelectedFoo);
}
}
You could use PhoneApplicationService dictionary to save data you need when navigation from first event, and parse it when you navigateTo second page. you can also use that data in your ViewModels.
Something like this:
PhoneApplicationService.Current.State["DatatFromFirstPage"] = data;
and when navigating to second page:
if (PhoneApplicationService.Current.State.ContainsKey("DatatFromFirstPage"))
{
var dataUsedOnSeconPage= PhoneApplicationService.Current.State["DatatFromFirstPage"];
}
you can use this data globally in entire app
I am implementing a WPF based application using MVVMfor the UI.
I have a ViewModel that wraps each editable Model that can be edited. The VM contains all the logic for handling error notifications, "is dirty" management and so forth ..
This design supports well CRUD schenarios for simple domain Model objects that are anemic, that is, do not contain any logic.
Now, I am facing a more tricky problem cause I have a domain Model that contains logic and that logic can change the internal state of the domain Model.
Do someone have already faced this scenario ? If so, do you have some advices to handle this correctly ?
Riana
Here is how I usually deal with it:
The ViewModel layer is made of types that belong to this layer, meaning I don't ever directly use my business objects inside of a ViewModel. I map my business objects to ViewModel objects that may or may not be the exact same shape minus the behaviors. It can be argued that this violates Don't Repeat Yourself, but doing so allows you to adhere to the Single Responsibility Principle. In my opinion, SRP should usually trump DRY. The ViewModel exists to serve the view, and the model exists to serve business rules / behavior.
I create a facade/service layer that takes and returns ViewModels as arguments, but maps the ViewModels to-and-from their corresponding business object versions. This, way the non-anemic objects won't impose non view logic on the ViewModel
The dependencies would look like this:
ViewModel <--> Facade/ServiceLayer --> Business Objects
I think it is important to keep this in mind if you want to unleash the full potential of MVVM: The ViewModel is the model/abstraction of the view, not the model presented to the view.
Try using Command pattern. Your screen should be design not to edit an entity but to perform an action (command) on an entity. If you follow that principle when designing your screens, your ViewModel will have properties that should be mapped to a command object. Then, the command will be send to an (remote) facade of the domain model.
ViewModels for displaying the data could be mapped directly to the database (bypassing the domain model altogether) so that you don't need to put nasty getters in the domain model classes.
If the domain model is non-anemic, you will need to use events to communicate internal changes in the Model back to the ViewModel. That way you don't have to worry about keeping track of what operations could potentially make your VM out-of-sync with the model.
Here's a simple example:
First, a sample model:
public class NonAnemicModel
{
private string _name;
public string Name
{
get { return _name; }
set
{
if (_name == value)
return;
_name = value;
OnNameChanged(EventArgs.Empty);
}
}
public event EventHandler NameChanged;
protected virtual void OnNameChanged(EventArgs e)
{
if (NameChanged != null)
NameChanged(this, e);
}
public void PerformNameCalculation(int chars)
{
//example of a complex logic that inadvertently changes the name
this.Name = new String('Z', chars); //makes a name of Z's
}
}
And here's a sample ViewModel:
public class MyViewModel : INotifyPropertyChanged
{
private NonAnemicModel _model;
public NonAnemicModel Model
{
get { return _model; }
set
{
_model = value;
_model.NameChanged += (sender, args) => NotifyPropertyChanged("UserName");
}
}
public string UserName
{
get { return this.Model.Name; }
set { this.Model.Name = value; }
}
//this command would call out to the PerformNameCalculation method on the Model.
public ICommand PerformNameCalculation { get; private set; }
}
Notice that the PropertyChanged event is raised when the Name on the model changes. That way, regardless of whether the UserName setter was used, or the PerformNameCalculation command was used, the ViewModel stays in sync. The big downside to this is that you have to add many more events to your Model, but I've found that having these events in place is usually very helpful in the long run. Just be careful about memory leaks with events!
Typically in the property setter of an object we may want to raise a PropertyChanged event such as,
public event PropertyChangedEventHandler PropertyChanged;
protected void Notify(string property)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
public string UserNote
{
get { return _userNote; }
set
{
_userNote = value;
Notify("UserNote");
}
}
In our existing code base I see instances where PropertyChangedEventArgs is being sent null in order to indicate that all properties of the object have changed. This seems inefficient and seems to lead to far more events being triggered than is needed. It also seems to causes issues where objects update each other in a circular fashion.
Is this ever a good practice?
A comment in the code tries to justify it ...
//The purpose of this method is to wire up clients of NotificationBase that are also
//NotificationBases to *their* clients. Consider the following classes:
public class ClassA : NotificationBase
{
public int Foo
{
get { return 123; }
set { Notify("Foo"); }
}
}
public class ClassB : NotificationBase
{
ClassA A = new ClassA();
public ClassB()
{
A.PropertyChanged += AllChanged;
}
public void SetFoo()
{
A.Foo = 456;
}
}
public class ClassC
{
ClassB B = new ClassB();
public ClassC()
{
B.PropertyChanged += delegate { dosomething(); };
B.SetFoo(); // causes "dosomething" above to be called
}
}
/// ClassB.SetFoo calls ClassA.Foo's setter, which calls ClassA.Notify("Foo").
/// The event registration in ClassB's ctor causes ClassB.AllChanged to be called, which calls
/// ClassB.Notify(null) - implying that ALL of ClassB's properties have changed.
/// The event registration in ClassC's ctor causes the "dosomething" delegate to be called.
/// So a Notify in ClassA is routed to ClassC via ClassB's PropertyChanged event.
protected void AllChanged(Object sender, PropertyChangedEventArgs e)
{
Notify(null);
}
Any thoughts much appreciated.
Regards,
Fzzy
This is actually a problem with the design (or its documentation) of PropertyChangedEventArgs. Setting PropertyName to null means "all properties on this object have changed." But unless the class is sealed, or you're using reflection, you can't actually know that all properties on the object have changed. The most you can say is that all of the properties in the object's base class have changed.
This is reason enough to not use this particular convention, in my book, except in the vanishingly small number of cases where I create sealed classes that implement property-change notification.
As a practical matter, what you're really trying to do is just raise one event that tells listeners "a whole bunch of properties on this object have changed, but I'm not going to bother to tell you about them one by one." When you say:
I see instances where PropertyChangedEventArgs is being sent null in order to indicate that all properties of the object have changed. This seems inefficient and seems to lead to far more events being triggered than is needed.
...the actual intent is the exact opposite. If a method changes the Foo, Bar, Baz, and Bat properties on an object, and the object has only four or five properties, raising one event is probably better than raising four. On the other hand, if the object has sixty properties, raising four events is probably better making every one of the object's listeners - even those that aren't looking at those four properties - do whatever they do when the properties that they care about change, because those properties didn't.
The problem is that the property-change notification system, as designed, isn't a fine-grained enough tool for every single job. It's designed to be completely generic, and has no knowledge of a particular application domain built into it.
And that, it seems to me, is what's missing from your design: application domain knowledge.
In your second example, if a Fixture object has (say) ten properties that depend on the value of FixtureStatus, raising ten property-change events may seem a little excessive. Maybe it is. Maybe the object should raise a FixtureStatusChanged event instead. Then classes with knowledge of your application domain can listen to this one event and ignore the PropertyChanged event. (You still raise the PropertyChanged event on the other properties, so that objects that don't know what a FixtureStatusChanged event means can stay current - that is, if it's still necessary for your class to implement INotifyPropertyChanged once you've implemented FixtureStatusChanged.)
A secondary comment: Most classes in the C# universe, if they implement a method that raises the Foo event, call that method OnFoo. This is an important convention: it makes the relationship between the method and the event explicit, and it makes the fact that the code that's calling the method is raising an event easy to recognize. Notify is a weak name for a method in general - notify who? of what? - and in this case it actually obfuscates something that should be made explicit. Property-change notification is tricky enough without your naming convention concealing the fact that it's happening.
Ignoring the other stuff, I'd say the Notify(null) alone is a bad practice. It's not inherently clear what that means, and to a developer working the code 5 years down the line would probably assume that it meant something else unless they happened upon the comments.
I have come across situations wherein computed properties (without setters) need to fire PropertyChangeNotification when some other property i set via a setter.
eg
double Number
{
get { return num;}
set
{
num=value;
OnPropertyChanged("Number");
OnPropertyChanged("TwiceNumber");
}
}
double TwiceNumber
{
get {return _num * 2.0;}
}
As a rule I only do it with get only properties and I don't see why in this case a property firing a change notification on the other is bad. But I think if I do it for any other case I most likely don't know what I am doing!