Next Generation Emulation banner

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

7428 Views 41 Replies 2 Participants Last post by  @ruantec
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 - 20 of 42 Posts
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.
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.
... Na... Just not getting it... but it's late for me here. Going to zzzzzzz and will try again in the morning. See ya!!!
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?
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
See less See more
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 :)
See less See more
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!
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.
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? :)
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.
See less See more
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...
See less See more
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);
        }
See less See more
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...
Just a quick one....what's the difference in using behavior as opposed to commands?
Hi Ruantec... I'm trying not to bug you too often... (but from all my postings, I don't seem to be succeeding in that :D )
Just a word on the English language... to use the term "and my control is binded" is not perfect English, but it seems to be used all over the net. I wish people would use "Is bound" or "I've bound it to..."... (as you are trying to learn English, do you find this a bit strange or have you ever questioned it?)... but anyway that's just my personal view...

Help... again. I've a problem with a datagrid which seems strange. I've buttons to move selected item up or down. Moving up works like a charm... moving down I get "ArgumentOutOfRangeException" (Specified argument was out of the range of valid values.\r\nParameter name: index). This error is not consistent... It could be on the 3rd click of down btn or 5th... pretty random. I think what's happening is that on the remove of item from current position my indexing goes out of whack and when on the insert of item into new row it fails, but I can't seem to fathom why its giving error and why it's random... any ideas?
Code:
// Up
        void StepMoveUp()
        {
            if (SelectedJobStepItem != null)
            {
                if (this.EventType == EvenTypes.MouseLeftButtonUp)
                {
                    var selectedUser = SelectedJobStepItem;
                    int currentIndex = jobStepItems.IndexOf(SelectedJobStepItem);

                    if (currentIndex == 0) return;

                    JobStepItems.RemoveAt(currentIndex);

                    currentIndex--;

                    JobStepItems.Insert(currentIndex, selectedUser);

                    SelectedJobStepItem = jobStepItems.FirstOrDefault(selectedItem => selectedItem.JobSafetyAnalysisStepID == selectedUser.JobSafetyAnalysisStepID);
              }        
            }
        }

        // Down
        void StepMoveDown()
        {
            if (SelectedJobStepItem != null)
            {
                if (this.EventType == EvenTypes.MouseLeftButtonUp)
                {
                    var selectedUser = SelectedJobStepItem;
                    int currentIndex = jobStepItems.IndexOf(SelectedJobStepItem);

                    if (currentIndex == JobStepItems.Count - 1) return;

                    JobStepItems.RemoveAt(currentIndex);

                    currentIndex++;

                    JobStepItems.Insert(currentIndex, selectedUser);

                    SelectedJobStepItem = jobStepItems.FirstOrDefault(selectedItem => selectedItem.JobSafetyAnalysisStepID == selectedUser.JobSafetyAnalysisStepID);
                }          
            }
        }
See less See more
Thanks Ruantec.

Tried code and played with it but got varying results. The closest I could get was to switch your two lines around “JobStepItems[currentIndex] = myItem;” above “JobStepItems[currentIndex -1] = JobStepItems[currentIndex];”

This seemed to work, it selected the appropriate row but you had to reselect it with mouse click to get focus. When moving same item back up to original row position it would jump 2 rows. The StepMoveUp seems to have it's own problems... (spent 2 days on this already :confused: )

PS. I’ve also changed my code to reflect SelectedIndex property.

A question on the side... when referencing "selectedJobStepIndex" should it be the "selectedJobStepIndex" or "SelectedJobStepIndex"?

Code:
        void StepMoveDown()
        {
            if (SelectedJobStepItem != null)
            {
                if (this.EventType == EvenTypes.MouseLeftButtonUp)
                {
                    var copyJobStepItem = JobStepItems[SelectedJobStepIndex];

                    ++selectedJobStepIndex;

                    JobStepItems[selectedJobStepIndex] = copyJobStepItem;
                    JobStepItems[selectedJobStepIndex - 1] = JobStepItems[selectedJobStepIndex];

                    InvokePropertyChanged("JobStepItems");
                }
            }
        }
PS. Along with moving rows up or down I need to re-number my "StepNumber" column accordingly. What would be the best way to do this?
See less See more
Hi Ruantec... managed to get code to work.
I noticed that your step code will not work with ObservableList... I used ObservableCollection as in your code. I also came across the _myCollection.move method which seemed to work well (no use of your SwapItems method). Not sure which is best to implement... any comments?

Question: I've also added the below code to your SwapItems method...
Code:
private void SwapJobStepItems(int indexToUse, bool increase = false)
        {
            // Make a copy of the selected item
            var copy = SelectedJobStepItem;
            // Make a copy of the selected index
            var indexcopy = SelectedJobStepIndex;
            // Swap items
            JobStepItems[indexcopy] = JobStepItems[indexToUse];
            JobStepItems[indexToUse] = copy;

            for (int i = 0; i < JobStepItems.Count; i++ )
            {
                JobStepItems[i].StepNumber = i + 1;
            }

            InvokePropertyChanged("JobStepItems");

            // Set the new index
            SelectedJobStepIndex = increase ? ++indexcopy : --indexcopy;
        }
What I'm trying to do here is to auto re-number my StepNumber according to index (I've used count -1 :) )... is there a better way of doing this. I'm also having difficulty refreshing my DataGrid StepNumber... correct me if I'm wrong but I thought that as I'm altering bound values in viewmodel all I need to do is call "InvokePropertyChanged("JobStepItems");"... or have I got this wrong?
See less See more
Regarding StepNumber... yes its a UI thing and displays the step order to the user as these are jobs to be done in specific order...
Lol... looks like we need a totally dedicated site to help me and all my posts... Will try and do this myself but I'm stuck with how to structure the collections for a listbox within a datagrid row. Table structure is one -> many... so each row in dg will have multiple items in listbox. I need to have the dg item editable (already doing this) and along with each dg row I need to edit/add/delete any listbox items...

I managed to do this in code behind pre MVVM version and that took some time and searching... I don't stand a chance finding anything in MVVM... any pointers in how to structure my vm with above scenario ... guess its a nested observable collection or something?

PS. Guess you are pretty busy with your Mobile MVVM stuff? How is it going?
1 - 20 of 42 Posts
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