Part 5 - New WPF/MVVM Tutorial(C#)

Discussion in 'Web development / Programming' started by @ruantec, Jul 23, 2014.

  1. @ruantec

    @ruantec Crazy GFX coder Emu Author

    Messages:
    14,658
    Likes Received:
    1,035
    After quite a long time i've finally found some time to continue writing the tutorial serie. This time i'm going to keep updating this thread in the next few days in order to complete the part #5 so keep checking it if you're interested.

    This time i'm going to show several topics that will help you to share information between views and viewmodels and those are:

    - ViewModel Manager
    - Converters
    - Binding behaviour
    - PropertyChanged listener
    - Global exception handling(bonus :p)

    The ViewModel Manager:
    Since i started to write this tutorials you have probably come across the ViewModelManager class and the IManagable interface. As of now both of them are included in the projects provided by me but never used. First of all the ViewModelManager class is a basic implementation to share information between your viewmodel classes. A well written application usually doesn't necessary need to share information between viewmodels if the proper logic is used. However by experience i know that isn't the case in a hell bunch of complex applications as there is a time when you just need to share information between viewmodels.

    How is that done? quite simple. If you check the ViewModel base class you will quickly spot this code in the constructor:

    Code:
    var parseable = this as IManageable;
    if(parseable != null)
    {
          parseable.InstanceID = Guid.NewGuid();
          ViewModelManager.AddViewModel(this);
    }
    What we do here is cast the current viewmodel as IManageable. If the current viewmodel implements the IManageable interface it's going to automatically be added to the ViewModelManager list. The interface itself is very basic as well since it should serve as a template for your further implementations. Now if you check the ViewModelManager class you will find out that as mentioned is also a basic implementation of what a manager should be. The class is very straightforward and as you can see it implements a list of viewmodelbase items and offer few methods to get or add viemodels. Remember, the base of each viewmodel is the viewmodelbase ;)

    The ViewModelManager class as you can notice is static and i did that on porpuse. Being static means you can access it anytime as long as the namespace is included on your viewmodel. Now that we know what the porpuse of the ViewModelManager class is let's now explain how it works. The manager in order to work properly needs to be implemented correctly and for that you will have to keep this in mind:

    manager.jpg

    In the scenario above we have an application that has several viewmodels. Each viewmodel is unique and represent the main logic of each element(main, item list control, child control, child control of child). Elements provided by controls such as items are obviously viewmodels too but those should not implement the IManageable interface but only the main ones. If you check the ViewModelManager implementation you can see that each element added to the manager must be unique and not of the same type(at least in my basic implementation).

    Now let's make the following scenario... the Child control of child viewmodel is called "ChildOfChildViewModel" and the Child control viewmodel is called "ChildViewModel". Let's say i'm currently in the ChildOfChildViewModel class and want to access a property of the ChildViewModel class. There are different ways of doing that but the simple one is done as follows:

    Code:
    var childVM = ViewModelManager.GetViewModel(typeof(ChildViewModel)) as ChildViewModel;
    if(childVM != null)
    {
        // Voila! we have a reference to the parent viewmodel and can now access it's property.
    }
    By adding the code above you get access to the ChildViewModel if it implements the IManageable interface. The same can be done from any item viewmodel as well. There are also other ways to accomplish that but this is the simpliest one. I will cover other ways later in this tutorial when i talk about Binding behaviour and PropertyChanged listener.

    Converters:
    Logic between your code and UI. Basically what they do is provide a pre-determined information at binding time. How do you create one? pretty simple. Start by creating a class that implements the IValueConverter interface like this:

    Code:
    public class ManagerVMConverter : IValueConverter
        {
            public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
            {
                Type type = Type.GetType(string.Format("MVVMTutorial.ViewModels.{0}", parameter.ToString()));
                return ViewModelManager.GetViewModel(type);
            }
    
            public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
            {
                return null;
            }
        }
    As you can see the interface contains two methods which are "Convert" and "ConvertBack". This time we are going to focus in the "Convert" method only. The method itself is very straightforward and provide you all the information coming from the UI object that implements it. How do we implement the ManagerVMConverter class in your UI? again, pretty simple:

    Start by adding the namespace where the converter is located to your XAML code like for example:
    Code:
    xmlns:converters="clr-namespace:MVVMTutorial.Converters"
    Now add the following to your Window or Control XAML resource like for example:
    Code:
    <Window.Resources>
            <converters:ManagerVMConverter x:Key="ManagerConverter"/>
    </Window.Resources>
    What we do here is declare a key to use the converter anytime. For example... if you want to add a certain viewmodel that was already registered in the ViewModelManager class you can do it directly on your XAML now without the need of code behind. Let's say you have a simple button and you want to use the "ChildViewModel" as DataContext. Normally you will just go to codebehind and do it but let's do it directly in XAML now:

    Code:
    <Button DataContext="{Binding Converter={StaticResource ManagerConverter}, ConverterParameter=ChildViewModel}"/>
    As you can see here i'm using a button and directly binding the DataContext property to the Converter which use the previously created resource key. By adding the ConverterParameter of "ChildViewModel" i'm providing the type name of the viewmodel registered to use. After that the button is going to use the "ChildViewModel" class automatically. The same can be done on every single element and you can use the same viewmodel on all UI elements of your need without to modify the default viewmodel given to the main control. This step is very important when you want to display information that is stored in a viewmodel other than the one you are currently using on your current view. Since your button DataContext binding has changed you can now bind other properties of the control as usual using the given viewmodel property.

    Isn't that cool??? this method saves you from having to get instances of foreign classes or even worst... having to write huge loops and methods just to access a single property. If used properly this method could bring a huge speed up by avoiding unnecessary code. At the end of this tutorial i will provide a project to show what i just explained. For the time being try to figure out yourself using my explanation.


    To be continued in the next few days......... enjoy!
    Last edited: Jul 23, 2014
  2. NGEmu.com Advertisement

  3. Steven Davisworth

    Steven Davisworth Member

    Messages:
    65
    Likes Received:
    0
    Thanks Ruantec... playtime!!!!
  4. Steven Davisworth

    Steven Davisworth Member

    Messages:
    65
    Likes Received:
    0
    Hmmm... seem that my "childVM" is coming back null even though it maps to the property... will play more and try and get it to work.
  5. @ruantec

    @ruantec Crazy GFX coder Emu Author

    Messages:
    14,658
    Likes Received:
    1,035
    There are few reasons for that so make sure to check it out:

    1. ViewModel doesn't implement the IManageable interface.
    2. ViewModel instance is created after the UI binding.
    3. If you use the converter check the convert method and check the namespace string if correct for your needs.
  6. Steven Davisworth

    Steven Davisworth Member

    Messages:
    65
    Likes Received:
    0
    Me thinks it's maybe your point 2... I've added the IManageable interface to VM... but am unsure where to place the code snippet to be before UI binding, but will play around with it. Thanks for reply.... enjoy your weekend.
  7. @ruantec

    @ruantec Crazy GFX coder Emu Author

    Messages:
    14,658
    Likes Received:
    1,035
    To prevent such scenarios from happening you can instanciate required Viewmodels in your main application entry(where you instanciate your main viewmodel for example). Alternatively new instances can be created in the main viewmodel itself by just writing this:

    new MyChildViewModel();

    Usually you just have to check your project structure to avoid using stuff you haven't created yet. In case you run into such a situation just create a new instance as i said above or another way could be by implementing that line in your viewmodel manager.

    When your binding use the converter to get the viewmodel it check if is already registered after the instance creation. If that isn't the case you can create the instance yourself by using the Activator to create a instance of the Child viewmodel. Remember that you already have the type name so it shouldn't be hard to implement in either the converter or viewmodel manager. In case you are having some hard time doing so i can show you later today how to do that ;)
  8. Steven Davisworth

    Steven Davisworth Member

    Messages:
    65
    Likes Received:
    0
    ... Na... Just not getting it... but it's late for me here. Going to zzzzzzz and will try again in the morning. See ya!!!
  9. @ruantec

    @ruantec Crazy GFX coder Emu Author

    Messages:
    14,658
    Likes Received:
    1,035
    After checking it myself i think i will implement that in the GetViewModel method of the ViewModel Manager to prevent such situations from happening. At the same time i will make the need of "DataContext = new MyViewModel()" in codebehind useless as viewmodels can be always defined in XAML and that would be cool. See? even i learn in the process :D. I will toy around later and will upload a project to show that with the updated ViewModelManager class.
  10. Steven Davisworth

    Steven Davisworth Member

    Messages:
    65
    Likes Received:
    0
    Thanks Ruantec... Just a quick confirmation from you.
    Scenario:
    My Contentcontrol is bound to a datasource. I have a combobox with its itemsource set to another datacontext within same contentcontrol. True of false? ...Said combobox will not populate as you can't set datasource within another datasource?
    If so do I use a datatemplate for the itemsource to get the item list while maintaining the current datacontext of the contentcontrol as the selecteditem?

    Just reading the above sends me into a spin... hope you get the gist of what I'm asking?
  11. @ruantec

    @ruantec Crazy GFX coder Emu Author

    Messages:
    14,658
    Likes Received:
    1,035
    Each control can have separate datacontexts in fact every single UI control can have a different datacontext and it doesn't matter where is located. However when you have a combobox control for example you bind the datasource to a list which contains items. Each item on that list is the datacontext of each combobox item. If you want controls inside those items to use a different datacontext you have to define those in your item template by using my example of binding datacontext with the converter in XAML. The reason why you may get some issues at the beginning is because each item automatically gets the item as datacontext so you have to override that by defining a new datacontext on child elements. I will try to find time today or tomorrow to write a small sample application to show you that.

    I don't know if that helps you but each component on your UI is a independent element that can have own logic behind a viewmodel. By default each UI element gets the parent datacontext if no other is defined.
  12. @ruantec

    @ruantec Crazy GFX coder Emu Author

    Messages:
    14,658
    Likes Received:
    1,035
    After re-reading your post i can't figure out why your content control is bound to a datasource... it shouldn't be that way as a content control serves only as a presenter. With that in mind you either have a defined class in your viewmodel to represent what is going to be displayed through templates or you have different classes that represents different templates to be shown but never a datasource. Somehow i don't quite get what you want to do here.
  13. Steven Davisworth

    Steven Davisworth Member

    Messages:
    65
    Likes Received:
    0
    Nuts... you mean I'm still getting it wrong? I set the datacontext on my content control to save me having to repeat it on all the child controls, hence whenever I have a child control within same content control that has a different datacontext I get blank results in combobox. With that being said should I be setting datacontext on every single control separately?
    Sample Code the way I'm doing it at moment:
    Code:
    <ContentControl Grid.Row="3" Margin="0,10,0,17" DataContext="{Binding WorkPermitDetailItems}">
    <Grid>
    <Label Content="Applicant Name" HorizontalAlignment="Left" VerticalAlignment="Top" />
    <ComboBox ItemsSource="{Binding ApplicantLookupItems}"
    DisplayMemberPath="FullName"
    SelectedValuePath ="UserID"
    SelectedValue="{Binding SelectedApplicantItem, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
    Height="23" Width="200" HorizontalAlignment="Left" VerticalAlignment="Top" Margin="0,23,0,0" />
    
    Then in my VM:

    Code:
    public WorkPermitViewModel()
    {
          getWorkPermitDetails();
          getApplicantLookupItems();
    }
    
    private ApplicantLookupList selectedApplicantItem;
    public ApplicantLookupList SelectedApplicantItem
    {
          get { return selectedApplicantItem; }
          set { SetProperty(ref selectedApplicantItem, value); }
    }
    
    private void getApplicantLookupItems()
    {
          var query = from u in db.Users
          select new
                      {
                            UserID = (int?)u.UserID ?? 0,
                            FullName = u.FirstName + " " + u.LastName
                      };
    
          applicantLookupItems = new List<ApplicantLookupList>();
    
          foreach (var user in query)
         {
               var _user = newApplicantLookupList();
               _user.UserID = user.UserID;
               _user.FullName = user.FullName;
    
               applicantLookupItems.Add(_user);
          }
    
          InvokePropertyChanged("ApplicantLookupItems");
    
          SelectedApplicantItem = applicantLookupItems.FirstOrDefault(selectedItem =>  selectedItem.UserID == WorkPermitDetailItems.FirstOrDefault().ApplicantID);
    
    }
    
    Data context "WorkPermitDetailItems" set on content control causes ComboBox not to display any items. If I remove the "WorkPermitDetailItems" off of the content control it works, but then I can't get my SelectedValue to work. Please advise on correct code... Thanks
    Last edited: Aug 6, 2014
  14. Steven Davisworth

    Steven Davisworth Member

    Messages:
    65
    Likes Received:
    0
    AAARRRGH!!! Just figured it out... had this wrong...
    Code:
    private ApplicantLookupList selectedApplicantItem;
    public ApplicantLookupList SelectedApplicantItem
    {
        get { return selectedApplicantItem; }
        set { SetProperty(ref selectedApplicantItem, value); }
    
    }
    
    Re-coded to ...
    Code:
    private int selectedApplicantID;
            public int SelectedApplicantID
            {
                get { return selectedApplicantID; }
                set { SetProperty(ref selectedApplicantID, value); }
            }
    and changed ...
    Code:
    SelectedApplicantID = applicantLookupItems.FirstOrDefault(selectedItem => selectedItem.UserID == WorkPermitDetailItems.FirstOrDefault().ApplicantID).UserID;
    PS. Please correct me if you see anything that looks dodgy...

    AAARGH!!!! again.... I see my error.... due to the fact that I have bound my DataContext in my "Code Behind", I shouldn't have to rebind it within my UI... I've tested this with a single text field outside of my content control and it works... but still seem to be having an issue in my content control... hence the only way I could get it to work was to set it within the Content Controls datacontext... obviously from what you were saying this is wrong... will play some more and see if I can get it "cleaned up"... :)

    AAARG!!! no 3... I've bound my db values to an ObservableList and I'm guessing the only way to iterate through this is to add datacontext to content control which snookers me regarding original issue... I think I've confused myself again... back to drawing board...

    Sorry for all the posting I've done on this tutorial esp when it's not related to Part - 5...

    PS. Ruantec.... slap me over the head if you think I need it :)
    Last edited: Aug 6, 2014
  15. Steven Davisworth

    Steven Davisworth Member

    Messages:
    65
    Likes Received:
    0
    Hi Ruantec... Hope you are keeping well.
    Above all sorted. I realised my mistake and corrected it.

    PS.Will you be including field validation in your next upload?
    Cheers!
  16. @ruantec

    @ruantec Crazy GFX coder Emu Author

    Messages:
    14,658
    Likes Received:
    1,035
    I've been terribly busy lately setting up my hybrid OSX/Windows/Linux enviroment for iOS/OSX/Android/Windows development. I will try to take some free time and complete the tutorial and include validation. The good news is that i may bring my MVVM concept to mobile as well so that everybody can use my concept and develope universal apps that runs the same code across all plattforms.... this is going to be fun :)
  17. Steven Davisworth

    Steven Davisworth Member

    Messages:
    65
    Likes Received:
    0
    Fantastic to hear about iOS/OSX/Android/Win dev. Funny though, I was going to ask you about developing in MVVM across those platforms. I played with a plugin to vs 2010 C# android dev but found it clunky. Would this still use WPF and C#?
    Keep us posted as to this project.
  18. @ruantec

    @ruantec Crazy GFX coder Emu Author

    Messages:
    14,658
    Likes Received:
    1,035
    I use Xamarin studio for mobile development. In the past it was not that good but they added XAML and MVVM support a while ago and it's been a bless so far(porting my concept over). Of course is not as advanced as WPF but is looking great :) i posted this picture here about a week ago:

    [​IMG]
  19. Steven Davisworth

    Steven Davisworth Member

    Messages:
    65
    Likes Received:
    0
    Hi Ruantec... please keep me posted as to how you go... ps what version of Xamarin you using... I see the free one does not support VS... bugger.

    Are you any closer to finishing part 5? :)
  20. Steven Davisworth

    Steven Davisworth Member

    Messages:
    65
    Likes Received:
    0
    Hi again Ruantec... 3 questions...
    1. With the behaviour class you have OnLeftMouseUp event... for a combobox what would I use... I've tried using MouseLeave but I think this is clunky

    2. With the above behaviour class you have used the MethodParameter method should I be creating all that code to create a new on for the MouseLeave event

    3. I've got a datagrid with an id and a description in it... my combobox has an items list and selectedvalue is taken from dg id. when selecting a new item in combo I get the ID to change but not the description. If I fiddle with it I can get the reverse i.e. I get the description but not the ID. I know why its doing it but don't know how to resolve. I've coded 101 ways to get one or the other but not both :)

    4. If all else fails I can try and update the DataGrid ObservableCollection to reflect the description change. I know how to get selectedvalue of DG but am unable to update the description cell... ie.
      Code:
      SelectedIsolationListItem = isolationDetailListItems.FirstOrDefault(selectedItem => selectedItem.IsolationListID == IsolationDetailListItems.FirstOrDefault().IsolationListID);
      ... but as I said how do I set the value of the selected item
    Hope these q's make sense to you.
    Last edited: Aug 21, 2014
  21. @ruantec

    @ruantec Crazy GFX coder Emu Author

    Messages:
    14,658
    Likes Received:
    1,035
    Ok, let's go with the first two points since i worked all day and my brain is a bit tired today :p

    1. You can actually add all the events you want in the Behaviour class... just look for this part:

    Code:
    // Create events here
    element.PreviewMouseUp += element_PreviewMouseUp;
    Create your events there. In the event itself don't forget to add this line just as in the mouse up one:

    Code:
    InvokeViewModelMethod(sender, AruanTuber_DarkMoon.ViewModels.ViewModelBase.EvenTypes.MouseLeftButtonUp);
    All i do here is pass the message to the viewmodel hooked to the UI. Add the required entries to the EvenTypes enum to expand it and replace "MouseLeftButtonUp" with your new one. That's all required. Basically you just have all the required events in one place.

    2. No, you can expand the Behaviour class as it serves as a base only so is there to get expanded as much as you want as i described on point #1.

    As i can see you have improved.... i like that :)

Share This Page