This is a migrated thread and some comments may be shown as answers.

Who should notify the GUI?

12 Answers 156 Views
GridView
This is a migrated thread and some comments may be shown as answers.
Clas Ericson
Top achievements
Rank 2
Clas Ericson asked on 27 Sep 2011, 02:46 PM
Hello,
I'm rather new to Silverlight and have a question about the notifying-mechanism. My solution is an MVVM-application stacked like this:
VIEW
Contains a RadGridView bound to a property in the viewmodel, data is an entitycollection. The GridView's SelectedItem is bound to corresponding property in the viewmodel.
VIEWMODEL
Holds the properties below that the GridView is bound to and implements INotifyPropertyChanged.
  • SelectList - an entitycollection that inherits ObservableCollection. When SelectList is set, it runs a notify-call.
  • SelectedItem - an entity that also implements INotifyPropertyChanged for its own properties. When SelectedItem is set, it runs a notify-call.

My question is, who should make the notify-call so that the GridView knows value has changed?

Regards, Clas

12 Answers, 1 is accepted

Sort by
0
Daní
Top achievements
Rank 1
answered on 28 Sep 2011, 01:00 PM
Hello Clas,

I'm not sure I've understood your scenario. However, it seems your problem is you don't know how to notify UI property change when inner ViewModel properties are changed. The answer is really Straightforward. You MVVM classes not only must implement the INotifyPropertyChanged interface, they must raise the PropertyChanged event whenever a property is modified. I'm pasting a very simple example:
public class ViewModel : INotifyPropertyChanged
    {
        private int _myField;
 
        public int MyProperty
        {
            get { return _myField; }
            set
            {
                if (_myField != value)
                {
                    _myField = value;
                    OnPropertyChanged("MyProperty");
                }
            }
        }
 
        #region INotifyPropertyChanged Members
 
        public event PropertyChangedEventHandler PropertyChanged;
 
        #endregion
 
        private void OnPropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }

Every time a MyProperty property changes it raises the PropertyChanged event. You must raise this event in all your properties in your MVVM (at less those you are interested in binding operations).

Every time you perform a binding on a silverlight control, the silverlight binding systems checks if the binding source (tipycally control's DataContext) implements INotifyPropertyChanged. If so, it subscribes to PropertyChanged event, whenever the event is raised and the PropertyName value present in the PropertyChangedEventArgs is the same present in binding's path it updates the Control's property.

Telerik offers the ViewModelBase class, present in Telerik.Windows.Contols.dll, that serves as a base for all ViewModel classes. It just offers a protected methos "OnPropertyChanged" that raises the PropertyChanged event and avoids you to have to write the same code along all your ViewModel classes.

Hope this helps.
0
Clas Ericson
Top achievements
Rank 2
answered on 28 Sep 2011, 01:20 PM
Thanks for your example that clarifies a bit for me. A little bit of detective work led me to the model. The entity derives from an abstract base entity and I thought it took care of the implementation of notification mechanism. However, it didn't so I need to implement in my entity class as in your example. Now I get a strange error though. My entity class is as below:
public class MyEntity : EntityBase
 
        //private members
       public MyEntity() : base()
       {
        }
 
        //public properties, like
        public DateTime counterDateStart
        {
            get
            {
                return _counterDateStart;
            }
            set
            {
                if (_counterDateStart == value)
                    return;
                this.SendPropertyChanged("counterDateStart", _counterDateStart, value);
                _counterDateStart = value;
            }
        }
          
          
        protected override void SendPropertyChanged(string propertyName, object oldValue, object newValue)
        {
            if (base.PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
The event is declared in the base class. But I'm getting the following error inside SendPropertyChanged-method "The event PropertyChanged can only appear on the left hand side of += or -=. How come? Isn't this exactly what the code should look like? Gahhh... I'm getting tired of this now...

0
Daní
Top achievements
Rank 1
answered on 28 Sep 2011, 01:21 PM
Hi Clas

I've seen your previous post GUI doesn't reflect changes and the code snippets you provided. It's a bit confusing but I think I know why your UI doesn't reflect changes, Let's see:

Having a look to the MonthReportViewModel class I've seen the following method:
public void SetAutomaticReadingDate()
        {
            if ((NewReadingDate.HasValue) && (!SelectedItem.curDate.HasValue))
            {
                SelectedItem.curDate = NewReadingDate;
                Notify(() => this.SelectedItem.curDate);
            }
        }

You are trying to notify the curDate has changed. But you are doing it on MonthReportViewModel!!! The control in UI that is bound to curDate property is listening for PropertyChanged event on the SelectedItem instance (if it implement INotifyPopertyChanged) not in you Model (MonthReportViewModel) instance!!



0
Clas Ericson
Top achievements
Rank 2
answered on 28 Sep 2011, 01:25 PM
Dani,
you're right! I've been trying every imagineable possibility until you're previous post led me to the entity class itself. Please, see my previous post for the last error I got.

Thanks again!
0
Daní
Top achievements
Rank 1
answered on 28 Sep 2011, 01:33 PM
Hi Clas

In an inherited class you cannot directly raise an event defined in the base class. I'm sure EntityBase you are deriving on offers a protected method that raises the event. Usually this method is called "OnPropertyChanged" or "RaisePropertyChanged".
0
Clas Ericson
Top achievements
Rank 2
answered on 28 Sep 2011, 01:41 PM
Oh, I see. The base class looks like this. It pretty much doesn't do anything for me in this case.
public abstract class BusinessBase : ValidationBase, INotifyPropertyChanged
{
    public BusinessBase();
    public BusinessBase(IBusinessCollection Parent);
 
    public event PropertyChangedEventHandler PropertyChanged;
    protected virtual void SendPropertyChanged(string propertyName, object oldValue, object newValue);
     
    // + more that doesn't have something to do with property changes.
}

0
Accepted
Daní
Top achievements
Rank 1
answered on 28 Sep 2011, 01:50 PM
You already got it. But you need to modify a bit your MyEntity class.

You must set the value before raising the event. When PropertyChanged event is raised, UI wil be notified and it will read the property value again (an access to the property getter method). If you raise the event before updating the property value UI will not detect the change. Your actual code looks like this:
public class MyEntity : EntityBase
  
        //private members
       public MyEntity() : base()
       {
        }
  
        //public properties, like
        public DateTime counterDateStart
        {
            get
            {
                return _counterDateStart;
            }
            set
            {
                if (_counterDateStart == value)
                    return;
                this.SendPropertyChanged("counterDateStart", _counterDateStart, value);
                _counterDateStart = value;
            }
        }
           
           
        protected override void SendPropertyChanged(string propertyName, object oldValue, object newValue)
        {
            if (base.PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }

It should look similar to this one:
public class MyEntity : EntityBase
  
        //private members
       public MyEntity() : base()
       {
        }
  
        //public properties, like
        public DateTime counterDateStart
        {
            get
            {
                return _counterDateStart;
            }
            set
            {
                if (_counterDateStart == value)
                    return;
                    _counterDateStart = value;
                this.SendPropertyChanged("counterDateStart", _counterDateStart, value);
                
            }
        }
           
          

As you can see, "_counterDateStart" field is set prior raising the event!!!
0
Clas Ericson
Top achievements
Rank 2
answered on 28 Sep 2011, 02:10 PM
Hmm, it might be me not understanding but the abstract base class doesn't implement the virtual method SendPropertyChanged so I still need to do the implementation in the derived class, or?
0
Daní
Top achievements
Rank 1
answered on 28 Sep 2011, 02:57 PM
That's a matter Clas, I don't know where did youget the EntityBase class, may be a third party framework? It has no sense providing a base class implementing INotifyPropertyChanged and not offering a protected method for inheritors to raise the PropertyChanged event. 

You should reconsider using EntityBase class. If you own this class it would be as easy as add a protected method that raises the event. Another solution would be create your own EntityBase class copying the implementation (if you don't have the source code you can use .Net Reflector tool to get it). Finally, you might look for another framework. As I said before, is unacceptable to have a base class implementig INotifyPropertyChanged without offering a protected method to raise the event.
0
Daní
Top achievements
Rank 1
answered on 28 Sep 2011, 04:32 PM
Hi Clas,

Watching the EntityBase class you are using as ViewModel base class I've seen it inherits from another class called ValidationBase. Of course I haven't seen ValidationBase implementation but its name is suggesting me it's implementing the INotifyDataErrorInfo interface. If so, you should take very care of using it, mainly when you are binding to ItemControls like a GridView. The reason is that Silverlight core has a known memory leak that manifests when binding an object that implements INotifyDataErrorInfo property. This interface exposes an event called ErrorsChanged, this event is tipically raised whenever a validation is performed on the object. When binding, Silverlight core checks if the binding's source implements this interface, if so, it subscribes to ErrorsChanged event, this subscription is leaking the control. You can observe this memory leak easily, just bind a RadGridView ItemSource property to a collection of 50 objects implementing INotifyDataErrorInfo interface, then use a RadDataPager with a page size of 10 to page the results. Memory usage will increase each time you change the page. If you have a MemoryProfiler like "Memory Ants Profile" o "JustTrace" (developped by Telerik) you will be able to see that each time you change the page 10 new GridViewRow instances are added, and they never get destroyed!!!

So be carefull when using INotifyDataErrorInfo interface. Don't use it if you don't need client side validation. Don't use it in ItemsControl or derivded controls if you are paging them or they are suitable to be refreshed or reloaded.
0
Clas Ericson
Top achievements
Rank 2
answered on 04 Oct 2011, 01:11 PM
Hello Dani,
sorry for not being able to reply earlier! Thank you for efforts in helping me out. Your replies made the day for me.
First, the imlementation of iNotifyPropertyChanged was made correctly in the entity base class. In my frustration I didn't see that I was watching metadata about the class.
Secondly, when I reordered the settings in the public properties as you gave an example of, the GUI finally began to get the notifications when the data changed. Although I had to alter the value of the property by using a temporary variable because we needed both the old and the new value when calling SendPropertyChanged for better tracing possibilities.

About the memoryleak, I'll take that in consideration if I run into that situation.

Again, thank you very much!

Regards, Clas
0
Daní
Top achievements
Rank 1
answered on 04 Oct 2011, 01:17 PM
Hi Clas,

I'm glad to know you have solved all your issues!

Regards, Daní
Tags
GridView
Asked by
Clas Ericson
Top achievements
Rank 2
Answers by
Daní
Top achievements
Rank 1
Clas Ericson
Top achievements
Rank 2
Share this question
or