205 – Base classes

Let’s be lazy efficient – working smarter instead of harder – through inheritance.

If you’ve done any amount of C# or other OOP language, then you know that inheritance is the mechanism of having one or more classes inherit from a base class. By putting as much common functionality in your base class as possible you don’t have to duplicate it in all the inherited classes. If this is a foreign concept to you then stop and pick up a “Learn C# in 30 days” book and work it cover-to-cover
before going any further because you’ll need those foundation concepts.

We’re going to apply the concept of inheritance to our ViewModels by starting with a ViewModelBase… Well… We’re really going to start even smaller, with a ModelBase class. There will be several things that models and ViewModels have in common such as notifying listeners of property changes. So, we might as well put as much common code in the ‘base-est’ class we can. Then our ViewModelBase will inherit from ModelBase

Reminder: Models are our small data bits. Model = Class = blueprint for an instantiated object = All the same thing. If you’re making a phone book it might be an address class or a person class.

Things that do NOT go in the model or base include:

  • The actual saving and loading from database or file. An object should be ignorant of such things. Its up to the storage/database layer to know the mechanisms of this; not the object.
  • Same goes for network updates. Objects don’t know about sending themselves to a server.
  • Anything to do with the UI. An object doesn’t know a darned thing about the display… or if there even is a display.

Shortly after making the solution, we made folders for Controls, Converters and Icons. We might as well make a couple more right now: Models, ViewModels and Pages.

ModelBase.cs

If you didn’t know this about Visual Studio, its fairly smart and will try to do what it thinks you intended. If you select the folder for Models then choose ‘New Class’ from the context menu (keystroke Shift-Alt-C) it will make a new class for you AND put it in that folder AND give it a namespace path to match. RpxOne.Models.ModelBase.cs Do that now.

Doesn’t look like much right now, does it?

What are some of the things we do with objects, that would go well here?

  • Access them from other classes
  • Serialize them (for an xml file, for network upload, for a local database record etc.)
  • Get notification of changes so our app can react – usually by updating the UI, marking the object dirty so we know to save it before closing, etc.

Lets add support for those things

  1. Access from other classes – So we have to make this class Public
  2. Serializable
    1. We mark the class with the [Serializable] attribute
    2. To be serializable a class must have a constructor that takes no parameters. Add one now.
  3. Notifications to other classes. What do our other classes really want to know about?
    1. When a property changes value – so we can update a label for example
    2. When an object is disposed of,
    3. When the contents of a collection changes – So our ListViews can update. Judging by forum questions this is missed by a lot of people. The question usually sounds like:
      “When my page first comes up the ListView has all the items, but when I add or remove items the ListView doesn’t show the changes. What do I have to do to the ListView to get it to update?” The problem isn’t the ListView: It’s the lack of event notifications on the data bound as the source. The ListView is bound to a Collection property. The property raises a PropetyChangedEvent when the entire property is replaced with a new Collection. If you add one item the Collection is not replaced. That’s where the CollectionChangedEvent comes in.

So lets add inheritance for the interfaces that provide these. At this point your code should look about like this…

and have red squiggles under the interface notations because we haven’t added the required methods to support the interfaces. That’s next.

We’re going to need to raise a couple events for when these changes take place:

The next bit will be a little longer, but not bad. We’re going to add all the methods required by those interfaces. Remember I said at the start of this series that we’re going to do more work up front to make our every day, over and over work easier. This is one of those moments. You will make hundreds of properties in most applications. If an extra few minutes in the base class makes dealing with all of those properties just a minute faster you’ve more than made up for the time spent here. If you need to top off your Mt. Dew, now would be a good time. Go ahead… I’ll wait for ya.

INotifyPropertyChanged

Most intro examples around the ‘net show something like OnPropertyChanged(“FirstName”); because it’s easy to read and doesn’t require the setup we’re about to do. As a result even experienced developers continue to do this for years. Here’s the problem: It violates best practices by using ‘magic strings’. So long as the text in the quotes exactly matches the name of the property you’re golden. But if you (or a team mate) refactor the code so the property becomes “NameFirst” but forgets to look around for all those magical strings all the event notification for this property breaks; and you may not see that right away. It could be on a view that only gets shown when the user has two middle names and a hyphenated last name – or some other one in a thousand case that you don’t check every time you do a quick test. Leading to the update getting released. Three days later support gets flooded with calls from mad customers – Usually resulting in you getting calls on the weekend while you’re trying to relax.

We’re going to create a method in the ModelBase that takes an expression instead of a string and uses that to raise the PropertyChangedEvent. Why an expression? Because that’s what the property itself is. We’re going to send the actual property to this method and let it pull out the name for us. If we can send an expression instead of a string it makes our code easier to read, friendlier to changes, find and replace doesn’t break it, and keeps us from goofing up with typos.

For example this
OnPropertyChanged(() => NameFirst);
is more robust for long term use and maintainability than this
OnPropertyChanged(“NameFirst”);
because if you rename the property you don’t have to search for all
the uses of the magic string “NameFirst”… you don’t have typical human mistakes of typing “FirstName”, “Firstname”, “Namefirst”, “NameFrist” etc.


INotifyCollectionChanged

As mentioned earlier, this is so your classes are notified about changes to elements within a collection, and not just when the entire collection as a whole property is set.

Property – IsDirty

Let’s do one other thing to support this: When ever any property is changed let’s mark the object as dirty. For example, when you change the name of a Person, and the Person is automatically marked dirty, without you having to expressly do it every time on every property of every object.


ViewModelBase : ModelBase

Make a new class in the ViewModels folder and call it ViewModelBase. Just like before, it doesn’t look like much to start with. Make it public, inherit from ModelBase, and serializable.

Hit F6 and make sure everything still builds. We’ve done nothing that should have broken our app, but it costs nothing to double check. You can even hit F5 sending it to your device just to make sure you’re still in a good place. You should still see our very simple starting app that looks similar to this, depending on how much you played with styles etc. earlier.

Testing it

So far this is all theoretical until we actually see it in operation and confirm it all works. In a bit we’ll get into the right way to handle commands and navigating to new pages. But we need these to work first. For now we’re going to use the MainPage for a quick test. We’re going to mock up a PetInfo page, ViewModel to back it with, and of course we’ll need a Pet model. When we’re done it will look like this:

Yes, it is ugly. But it also shows us exactly where every element is from page (red), to ListView (fuscia), to each of the 3 elements in the collection (yellow), to each property on the element (black). I tend to do this sort of thing when I am first making a view because honestly it never goes right the first time and if everything has its own color you can see what you have and what you’re missing, to help point you in the right direction. Later you just take the colors out or make them something more attractive.

  • In the Models folder, make a new Pet class inheriting from ModelBase.
  • Give it properties for
    • Name : string
    • Dob : DateTime
    • Coloring : string
  • In the ViewModels folder, make a new PetsViewModel inheriting from ViewModelBase
  • Give it properties for
    • Pets : ObservableCollection
    • SelectedPet : Pet

So we can have some dummy data add a #if DEBUG block in the constructor to add some values:

#if DEBUG
            Pets.Add(new Pet()
            {
                Name = "Fido",
                Coloring = "Tan",
                Dob = new DateTime(1776, 7, 4)
            });
            Pets.Add(new Pet()
            {
                Name = "Snowball",
                Coloring = "White",
                Dob = new DateTime(1968, 7, 20)
            });
            Pets.Add(new Pet()
            {
                Name = "Santa's Little Helper",
                Coloring = "Brown",
                Dob = new DateTime(1989, 4, 12)
            });
            OnPropertyChanged(()=>Pets);
            NotifyPropertyChanged("Pets");
#endif

To your MainPage code behind we are going to add just one property:
MyViewModel : PetsViewModel

        #region MyViewModel (PetsViewModel)
        private PetsViewModel _MyViewModel = new PetsViewModel();
        public PetsViewModel MyViewModel
        {
            [DebuggerStepThrough]
            get
            {
                if (_MyViewModel == null) MyViewModel = new PetsViewModel();
                return _MyViewModel;
            }

            [DebuggerStepThrough]
            set
            {
                if (_MyViewModel == value) return;
                OnPropertyChanging();
                _MyViewModel = value;
                OnPropertyChanged();
            }
        }
        #endregion MyViewModel (PetsViewModel )

NOTE: This is not how to do things in a finished view, but it will suffice to whack out a quick test. It is NOT an example from MVVM best practices. We’ll cover that in a coming lesson.

Next you need to update the MainPage.XAML to build the UI.  Sadly WordPress is terrible at letting me add XAML in the same way as the CSharp blocks.  There is a big screen capture at the end of this page to show you the XAML.

Now run it on your device!

Play:

Tap on one of the pets in the ListView. Their details populate the right side of the display. If you tap on the name or coloring, you can change it AND IT UPDATES IN THE LISTVIEW IN REAL TIME. That’s the magic of binding – no code behind making explicit updates to various UI fields. The models and view models don’t know a thing about the UI.

Select an item then tap on the DOB field – A DatePicker pops up so you can pick a date. Cool eh?

Source Code

I’m not going to provide full code everyplace. I think there is a lot of learning that takes place when you can’t just copy/paste your way to a completed project. Reading, typeing, debugging, watching Intellisense pop up suggestions, is an invaluable learning process that really drives it all into your brain.

But in this case, since this lesson will be the basis of the follow lessons its important to get it right. So just in case I missed anything during my explanation here’s the full code. (Sorry in advance for how WordPress managles some of this and extends the spacing between lines).

ModelBase.cs

using System;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Xml.Serialization;

namespace RpxOne.Models
{
    [Serializable]
    public class ModelBase : INotifyPropertyChanged,
                             INotifyCollectionChanged,
                             IDisposable
    {
        public ModelBase()
        {
            //Required for serialization
            PropertyChanged += OnPropertyChanged;

        }


        #region Events
        public event NotifyCollectionChangedEventHandler CollectionChanged;
        public event PropertyChangedEventHandler PropertyChanged;
        #endregion Events


        #region Properties
        #region IsDirty
        private bool _IsDirty;
        [XmlIgnore]
        public bool IsDirty
        {
            [DebuggerStepThrough]
            get
            {
                return _IsDirty;
            }

            [DebuggerStepThrough]
            set
            {
                if (_IsDirty == value) return;
                OnPropertyChanging(() => IsDirty);
                _IsDirty = value;
                OnPropertyChanged(() => IsDirty);
            }
        }
        #endregion IsDirty
        #endregion Properties

        #region INotifyPropertyChanged Members
        public static class PropertyHelper
        {
            public static PropertyInfo GetProperty(
                Expression<Func<T, TValue>> selector)
            {
                Expression body = selector;
                if (body is LambdaExpression)
                {
                    body = ((LambdaExpression)body).Body;
                }
                switch (body.NodeType)
                {
                    case ExpressionType.MemberAccess:
                        return (PropertyInfo)((MemberExpression)body).Member;
                    default:
                        throw new InvalidOperationException();
                }
            }
        }
        protected virtual void OnPropertyChanged(Expression e)
        {
            try
            {
                PropertyChangedEventHandler handler = this.PropertyChanged;
                if (handler == null) return;

                // The fully qualified property is really  RpxOne.Models.SomeModel.SomeProperty
                // so we need to strip that down to just the property name.
                var ts = e.ToString();
                var lineage = ts.Split('.');
                if (!lineage.Any()) return;

                var name = lineage[lineage.Length - 1];
                if (name == null) return;

                handler?.Invoke(this, new PropertyChangedEventArgs(name));
            }
            catch (Exception ex)
            {
                //TODO: Log exception
            }
        }
        public void NotifyPropertyChanged(string propertyName)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }


        protected virtual void OnPropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            try
            {
                IsDirty = true;
            }
            catch (Exception ex)
            {
                Console.WriteLine(string.Format("Error: {0}", ex.Message));
            }
        }

        #endregion INotifyPropertyChanged Members

        #region INotifyPropertyChanging Members
         public event NotifyCollectionChangedEventHandler PropertyChanging;
        protected virtual void OnPropertyChanging(Expression property)
        {
            //PropertyChangedEventHandler handler = this.PropertyChanged;
            //if (handler != null)
            //    handler(this, property.CreateChangeEventArgs());
        }

        #endregion INotifyPropertyChanging Members

        #region IDisposable Members
        [XmlIgnore]
        private bool _disposed = false;

        ~ModelBase()
        {
            OnDispose(false);
        }

        public void Dispose()
        {
            OnDispose(true);
            GC.SuppressFinalize(this);//Prevent redundant call on an already released object.
            //https://msdn.microsoft.com/en-us/library/system.gc.suppressfinalize%28v=vs.110%29.aspx?f=255&MSPPError=-2147217396
        }

        protected virtual void OnDispose(bool disposing)
        {
            string output = String.Format("The Dispose({0}) method.\n", disposing);

            // Execute if resources have not already been disposed.
            if (!_disposed)
            {
                // If the call is from Dispose, free managed resources.
                if (disposing)
                {
                    Console.Error.WriteLine("Disposing of managed resources.");
                    //TODO: Dispose of managed objects
                }
                // Free unmanaged resources.
                output = "Disposing of unmanaged resources.";
                //TODO: Dispose of UNmanaged objects
            }
            _disposed = true;
        }

        #endregion IDisposable Members

        #region OnCollectionChanged Members
        protected virtual void OnCollectionChanged(Expression e)
        {
            var handler = this.CollectionChanged;
            handler?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
        OnPropertyChanged(() => e);//If the contents of the property changed, then the property itself changed as far as binding is concerned. But will cause a listview to reload. Uncomment if you are having issues
        }
        #endregion OnCollectionChanged Member

    }
}</code></pre>
<h3>ViewModelBase.cs</h3>
<pre><code class="language=csharp">using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace RpxOne.ViewModels
{
    [Serializable]
    public class ViewModelBase : Models.ModelBase
    {
        public ViewModelBase()
        {
            
        }
    }
}</code></pre>
<h3>Pet.cs</h3>
<pre><code class="language=csharp">using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Serialization;

namespace RpxOne.Models
{
    [Serializable]
    public class Pet : ModelBase
    {
        #region Name (string )
        private string _Name;
        [XmlIgnore]
        public string Name
        {
            [DebuggerStepThrough]
            get
            {
                //if (_Name == null) Name = new string();
                return _Name;
            }

            [DebuggerStepThrough]
            set
            {
                if (_Name == value) return;
                OnPropertyChanging(() => Name);
                _Name = value;
                OnPropertyChanged(() => Name);
            }
        }
        #endregion Name (string )


        #region Dob (DateTime )
        private DateTime _Dob;
        [XmlIgnore]
        public DateTime Dob
        {
            [DebuggerStepThrough]
            get
            {
                //if (_Dob == null) Dob = new DateTime();
                return _Dob;
            }

            [DebuggerStepThrough]
            set
            {
                if (_Dob == value) return;
                OnPropertyChanging(() => Dob);
                _Dob = value;
                OnPropertyChanged(() => Dob);
            }
        }
        #endregion Dob (DateTime )


        #region Coloring (string )
        private string _Coloring;
        [XmlIgnore]
        public string Coloring
        {
            [DebuggerStepThrough]
            get
            {
                //if (_Coloring == null) Coloring = new string();
                return _Coloring;
            }

            [DebuggerStepThrough]
            set
            {
                if (_Coloring == value) return;
                OnPropertyChanging(() => Coloring);
                _Coloring = value;
                OnPropertyChanged(() => Coloring);
            }
        }
        #endregion Coloring (string )
    }
}</code></pre>
<h3>PetViewModel.cs</h3>
<pre><code class="language=csharp">using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Serialization;
using RpxOne;
using RpxOne.Models;

namespace RpxOne.ViewModels
{
    public class PetsViewModel : ViewModelBase
    {
        public PetsViewModel()
        {
#if DEBUG
            Pets.Add(new Pet()
            {
                Name = "Fido",
                Coloring = "Tan",
                Dob = new DateTime(1776, 7, 4)
            });
            Pets.Add(new Pet()
            {
                Name = "Snowball",
                Coloring = "White",
                Dob = new DateTime(1968, 7, 20)
            });
            Pets.Add(new Pet()
            {
                Name = "Santa's Little Helper",
                Coloring = "Brown",
                Dob = new DateTime(1989, 4, 12)
            });
            OnPropertyChanged(()=>Pets);
            NotifyPropertyChanged("Pets");
#endif
        }

        #region Pets (ObservableCollection )
        private ObservableCollection _Pets;
        [XmlIgnore]
        public ObservableCollection Pets
        {
            [DebuggerStepThrough]
            get
            {
                if (_Pets == null) Pets = new ObservableCollection();
                return _Pets;
            }

            [DebuggerStepThrough]
            set
            {
                if (_Pets == value) return;
                OnPropertyChanging(() => Pets);
                _Pets = value;
                OnPropertyChanged(() => Pets);
            }
        }
        #endregion Pets (ObservableCollection )


        #region SelectedPet (Pet )
        private Pet _SelectedPet;
        [XmlIgnore]
        public Pet SelectedPet
        {
            [DebuggerStepThrough]
            get
            {
                //if (_SelectedPet == null) SelectedPet = new Pet();
                return _SelectedPet;
            }

            [DebuggerStepThrough]
            set
            {
                if (_SelectedPet == value) return;
                OnPropertyChanging(() => SelectedPet);
                _SelectedPet = value;
                OnPropertyChanged(() => SelectedPet);
            }
        }
        #endregion SelectedPet (Pet )
    }
}</code></pre>
<h3>MainPage.xaml.cs</h3>
<pre><code class="language=csharp">using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Serialization;
using RpxOne.Controls;
using RpxOne.Models;
using RpxOne.ViewModels;
using Xamarin.Forms;

namespace RpxOne
{
    public partial class MainPage : ContentPage
    {
        #region Constructor(s)
        public MainPage()
        {
            InitializeComponent();
        }
        #endregion Constructor(s)


        #region MyViewModel (PetsViewModel)
        private PetsViewModel _MyViewModel = new PetsViewModel();
        public PetsViewModel MyViewModel
        {
            [DebuggerStepThrough]
            get
            {
                if (_MyViewModel == null) MyViewModel = new PetsViewModel();
                return _MyViewModel;
            }

            [DebuggerStepThrough]
            set
            {
                if (_MyViewModel == value) return;
                OnPropertyChanging();
                _MyViewModel = value;
                OnPropertyChanged();
            }
        }
        #endregion MyViewModel (PetsViewModel )
    }
}

MainPage.xaml

Here’s where WordPress really falls down – sorry.  Its hard to present XAML in WordPress because it keeps trying to parse it as part of how the page should be rendered.  Here’s a screenshot of the XAML for making the app page shown above.

mainpage-xaml

2 thoughts on “205 – Base classes

  1. How are you using the Serializable Attribute in the PCL? I get an error that it is inaccessible due to it’s protection level and all the answers on SO say that it isn’t available Xamarin projects.

    • There’s nothing special here. They are just C# classes. Plane old .NET. Do you have a link to the post you say said its not doable? Serialization by XML or JSON is very typical. Its how everything is either sent to a service or saved to a local .xml file.

Comments are closed.