Next Generation Emulation banner

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

8K views 41 replies 2 participants last post by  @ruantec 
#1 · (Edited)
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:

Text Line Font Design Parallel


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!
 
See less See more
1
#4 ·
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 ·
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 ·
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.
 
#9 ·
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?
 
#10 ·
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.
 
#11 ·
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.
 
#12 · (Edited)
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
 
#13 · (Edited)
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 :)
 
#15 ·
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 ·
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:

 
#19 · (Edited)
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.
 
#20 ·
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 :)
 
#21 ·
Ok... I've got as coded...
Code:
private static void IsMethodChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var element = d as FrameworkElement;
            if (element != null)
            {
                // Create events here
                element.PreviewMouseUp += element_PreviewMouseUp;
                element.MouseLeave += element_MouseLeave;
            }
        }

        static void element_PreviewMouseUp(object sender, System.Windows.Input.MouseButtonEventArgs e)
        {
            InvokeViewModelMethod(sender, PTWSys.ViewModels.ViewModelBase.EvenTypes.MouseLeftButtonUp);
        }

        static void element_MouseLeave(object sender, System.Windows.Input.MouseEventArgs e)
        {
            InvokeViewModelMethod(sender, PTWSys.ViewModels.ViewModelBase.EvenTypes.MouseLeftButtonUp);
        }
and on my ComboBox
Code:
behaviours:EventBehaviour.MethodParameter="IsolationTypeChanged"
... this results in both events fireing...
 
#22 · (Edited)
Of course is firing both and that's good. That's the reason why i told you in my previous post to expand the EvenTypes enum and add a MouseLeave item. In the viewmodel you have access to that property and you can check which event has been fired. I made that to show how things work and what could happen if you register several events on your behaviour. As i said this is just a basic implementation and as such is great to see the problems you have to deal with. Alternatively you can add a second parameter to the behaviour to determine which event should be generated instead. However is not that necessary i believe but i can show you how to do that if needed.
 
#23 ·
Thanks for that Ruantec... I've tried to go with your 1st option of checking which event fired and came up with the below... it works but I feel its clunky and not the correct way to do it... please advise. (yes you may slap me over the head :) )

ps... When you get a chanse can you advise on question 3 and 4 prev post?)

Code:
        private static void IsMethodChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var element = d as FrameworkElement;
            var events = EventManager.GetRoutedEvents();

            if (element != null)
            {
                foreach (var routedEvent in events)
                {
                    string thisControlType = element.GetType().FullName.ToString();

                    switch (routedEvent.Name)
                    {
                        case "PreviewMouseUp":
                            if (thisControlType != "System.Windows.Controls.ComboBox") element.PreviewMouseUp += element_PreviewMouseUp;
                            break;

                        case "MouseLeave":
                            if (thisControlType == "System.Windows.Controls.ComboBox") element.MouseLeave += element_MouseLeave;
                            break;
                    }
                }
            }
        }


        static void element_PreviewMouseUp(object sender, System.Windows.Input.MouseButtonEventArgs e)
        {
            InvokeViewModelMethod(sender, PTWSys.ViewModels.ViewModelBase.EvenTypes.MouseLeftButtonUp);
        }

        static void element_MouseLeave(object sender, System.Windows.Input.MouseEventArgs e)
        {
            InvokeViewModelMethod(sender, PTWSys.ViewModels.ViewModelBase.EvenTypes.None);
        }
 
#24 ·
Ok, that's indeed cluncky.... what i meant was to extend the enum of the viewmodel base and add a new event entry. The idea is to fire the invoke method and provide the new enum item. The downside of this method is that the method is gonna get fired several times depending on the events triggered. However you can check from the viewmodel which one is firing using the enum. There is another method using a secondary parameter flag in your behaviour to determine which event is gonna get fired. This is done by using the enum as a property flag. Will see if i can get some time today to show you how to do that.

ps... When you get a chanse can you advise on question 3 and 4 prev post?)
Yeah, but first i want you to understand the first two :p
 
#25 · (Edited)
So.... it seems like you are having some hard times trying to figure out how to deal with it so let me give you a hand. First of all as i said on my previous post you should focus in the EvenTypes enum and expand it(i just realized that i have a typo lol! it should be called "EventTypes"). The enum is located in the ViewModelBase class:

Code:
/// <summary>
        /// Handle the different types of events
        /// </summary>
        public enum EvenTypes
        {
            None = 0,
            MouseLeftButtonUp = 1,
            MouseLeftButtonDown = 2,
            MouseDoubleClick = 3,
            Enter = 4,
            Drop = 5,
            Delete = 6,
            MouseRightButtonUp = 7,
            MouseRightButtonDown = 8,
            MouseLeave = 9,
            AltLeftUp = 10
        }
As you can see i added the "MouseLeave = 9," entry so i'm ready to handle that in my behaviour now.

Code:
private static void IsMethodChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
     var element = d as FrameworkElement;
     if (element != null)
     {
         // Create events here
         element.PreviewMouseUp += element_PreviewMouseUp;
         element.MouseLeave += element_MouseLeave;
     }
}
Code:
static void element_MouseLeave(object sender, System.Windows.Input.MouseEventArgs e)
{
      InvokeViewModelMethod(sender, AruanTuber_DarkMoon.ViewModels.ViewModelBase.EvenTypes.MouseLeave);
}
Now we have all setup but this method have a downside still and that's the fact that our method is going to be called on all events registered. This is fine as the basic idea is to work like the commands on windows where commands go through the same place...now is time to determine what we need. In this case i'm using the "Test" method i added to the tutorial project. If you check the Control3ViewModel.cs you will notice that i added the following:

Code:
class Control3ViewModel : ViewModelBase
    {
        void Test()
        {
            switch(this.EventType)
            {
                case EvenTypes.MouseLeave:
                    break;
                case EvenTypes.MouseLeftButtonUp:
                    break;
            }
        }
    }
As you an see each viewmodel gets the events from the behaviour but each time the EventType property of the baseclass has the current event that called the method. In this way you can determine what todo next.

The other method to register the necessary event only is done by using a secondary parameter so i will show you that in the next few days when i get some time. In the mean time try to understand a little more what is happening here so that you can get a clear picture of it and find your own ways. This method is not the best but also not bad and at certain scenarios probably the only way so is important to comprehend what's going on in order to deal with future problems you may get. The alternative method with the extra parameter may help to register one event only but is going to limit you to that event as well so you will quickly realize the pros and cons.
 

Attachments

#26 · (Edited)
Thanks for that Ruantec. Been playing and getting more of an understanding of how things work. Thanks to you ;-)

I see there is no FrameworkElement for MouseDoubleClick... is this one of those cons? I've come across a post that said to get the "ClickCount" on the event but that doesn't seem to fit anywhere... anyway will play and learn some more...
 
#27 ·
There's no MouseDoubleClick but just as you found out it's done by getting the "ClickCount". I know it sounds weird but it works just as good by checking the click count of 2. As weird as it may sound is already a default practice when doing MVVM as you basically work with base components where in this case is the FrameworkElement which is the base of all components. The reason why is important to know how things work is because it's very important when dealing with MVVM as you may face scenarios that require you to build your own solution at times.
 
#29 · (Edited)
There are many differences between them but the biggest one is the fact that you will end up having to define your commands on your viewmodel over and over just to handle a single click not to mention handling from different controls. In case you need more features you will end up having some ugly code that do the job or having to add a third party dll that you probably don't need at least 90% of it. Using the behaviour you have a relatively clean way to handle events and all them are re-usable meaning that you just declare the event once in your entire project not to mention the behaviour is also re-usable with any other future project you may have.

I will recommend you to check commands if you wish but after a while you will see how much your code is gonna grow and how bloated is gonna get ;). Of course my solution isn't perfect either but after many years trying to figure out the best way to handle such scenarious i've learn that behaviours are probably the best way so i stay away from commands as much as i can this days. Of course there are people out there that may not agree with my view of things :p
 
This is an older thread, you may not receive a response, and could be reviving an old thread. Please consider creating a new thread.
Top