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
#30 · (Edited)
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);
                }          
            }
        }
 
#31 · (Edited)
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 )
You don't bug me at all my friend. In fact... i'm really liking your determination and i'm willed to help as much as i can. I wish i had more time this days to spend in the tutorials but sadly have found myself busy with other stuff.

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...
Thanks! will keep that in mind ;)

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?
Looking at your code i can't really say what's wrong there except for the way you are getting the index. Usually controls offer the SelectedIndex property for bindings so you can use that instead on your viewmodel. Other than that have you ever thought on swapping rather than remove and insert? it has a small overhead by creating a copy but is way faster at the end as the list doesn't get re-organized and no items are inserted or removed. Basically this:

var myItem = JobStepItems[currentIndex]; // Copy
// Increase your index
++currentIndex;
// Swap items
JobStepItems[currentIndex -1] = JobStepItems[currentIndex];
JobStepItems[currentIndex] = myItem;

I haven't tested myself since is quite late here but if i'm not mistaken the only thing required would be to update using an invokepropertychanged on your list in case is not done automatically.
 
#32 · (Edited)
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?
 
#33 · (Edited)
I checked the problem to see what's exactly the reason for you getting the exception. After writing a small example myself the problem was clear. Each time you change items on your list the whole list gets updated and because of that the index resets and obviously your property too. At the moment you want to insert the new item your index has the value -1 causing the argument out of range exception. To solve that problem you have to keep a copy of both the item and the current index before swapping items. I made a small project for you to show you how to solve your problem easily.

I wrote this method in the attached sample to handle both ways:
Code:
private void MoveUp()
        {
            if (SelectedItemIndex == 0 || SelectedItem == null)
                return;
            SwapItems(SelectedItemIndex -1, false);
        }

        private void MoveDown()
        {
            if (SelectedItemIndex >= items.Count -1 || SelectedItem == null)
                return;
            SwapItems(SelectedItemIndex +1, true);
        }

        private void SwapItems(int indexToUse, bool increase = false)
        {
            // Make a copy of the selected item
            var copy = SelectedItem;
            // Make a copy of the selected index
            var indexcopy = SelectedItemIndex;
            // Swap items
            Items[indexcopy] = Items[indexToUse];
            // At this point in time your SelectedItemIndex is -1 because the list was rearranged
            Items[indexToUse] = copy;
            // Set the new index
            SelectedItemIndex = increase ? ++indexcopy : --indexcopy;
        }
Voila!!!! swap works for both ways ;)

A question on the side... when referencing "selectedJobStepIndex" should it be the "selectedJobStepIndex" or "SelectedJobStepIndex"?
If you are refering to items bound to a property then it should always be "SelectedJobStepIndex". In fact... you should never use the selectedJobStepIndex variable if not necessary. The only case when it makes sense is when you want to modify the content without updating the information on your UI(which is kinda weird but it could make sense in some scenarios).
 

Attachments

#34 ·
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?
 
#35 ·
There is a small bug in the ObservableList which i found recently. "InvokePropertyChanged("JobStepItems");" won't work because you are altering a property of an item but not the item itself. Because of that it thinks that nothing has changed and obviously no update is going to be triggered. There is a dirty way to solve that problem until i find a solution for that and that is by setting the list again so basically doing this:

Code:
JobStepItems = new YourCollectionType(JobStepItems);
As i said it's a dirty way of doing it but there is no other way around it right now(i'm looking for a solution). That's the reason why my code didn't work with the ObservableList.

Now one thing that confuse me is why do you need the StepNumber(which seems to be a property of your class) and what is the use of it. Are you using it as a kind of index to display or something? if there is another reason you can also swap indexes the same way you swap content in the same function without going through the whole list again.
 
#37 ·
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?
 
#38 ·
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?
Well, you have to start thinking on how your datagrid is built. Each row represents an element which is a viewmodel obviously. On that viewmodel you have a observableList/collection/collections etc. that holds the elements to be displayed on your listbox within the datagrid. The itemsource obviously points to your viewmodel list which is where the data is coming from. Now the tricky part is to use an item template and place the listbox on your grid. Obviously the listbox is going to be invisible and with the use of triggers you can check the datagrid edit states and make your combobox visible when necessary. There are other alternatives as well but it could be tricky.

PS. Guess you are pretty busy with your Mobile MVVM stuff? How is it going?
I'm actually not busy with the mobile version of MVVM but more with my real life job ;) atm i'm writing a project using C++, MVVM and C#. I've also faced a new issue and found myself a new way to speed up heavy UI updates simulating something like the UI lock in windows forms but using the manager and invoke property mechanism of MVVM.
 
#40 · (Edited)
Hi Ruantec
Hope you are keeping well.
Been playing for 3 full days now and managed to get some sort of solution in regards to my last post. Each solution work to a point but then I get stuck.
Each JobStepItem has one or many PotIncHazItems

Solution 1: Nested Collections
Master Detail Collection holds Child Collection
... Sample code
Code:
public class JobStepList
        {
            public int JobSafetyAnalysisStepID { get; set; }
            public int StepNumber { get; set; }
            public string StepDescription { get; set; }
            public ObservableCollection<PotIncHazList> PotIncHazItems { get; set; }
            public ObservableCollection<PotIncHazLookupList> PotIncHazLookupItems { get; set; }
        }
I've used the JobStepList datagrid element to populate my child list
... with the above I get that dredded null exception error... see Image...


Solution 2: Free Standing Collections - PS I'm not displaying my PotIncHaz's in same datagrid as JobStepList (as in Solution 1) I.e. Dropped the "Potential Incidents or Hazards" column.

Both JobStepList and PotIncHazList have their own collections.
I base my select query and filter by SelctedJobStepItem... all works great.I can remove items from my PotIncHazList no probs, but can't get my PotIncHazList to change out when I select another JobStepItem from above grid (as per image)...


I have similar function on the "Add To List" Expander where I update the lookup with the removed SelectedPotIncHazItem and visa-versa (to maintain my lookup and selected items list)

... Let me know if you need more info (just spent more time searching internet... getting frustrated with how little "well coded" samples are out there... my little rant!!!!)
 
#41 ·
I'm not quite sure what you are trying to do but as far as my sleepy brain can understand you have lists nested to each JobStepList right? then are you trying to display the content of the nested collection in your row or something???. In the "Add To List" are you trying to add the selected item to the selected item nested collection? and what is the null exception you mentioned? i can't see anything on your picture.

I just need more imput to completely understand what you are doing and provide you a solution. However if the scenario i described is what you are trying to do then i guess the selected item is your JobStepList class so you can use it to fill it with the selected item on your "Add To List" list.

Btw i used to work on a Safety project as well many years ago. Your pictures and code reminds me a lot to what i had to deal with :p

getting frustrated with how little "well coded" samples are out there... my little rant!!!!
Samples on the net are usually messy and in my opinion nothing you should make a big use of. However they are quite useful sometimes as they can give you some ideas on how you can solve your problems.
 
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