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

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

  1. Steven Davisworth

    Steven Davisworth Member

    Messages:
    65
    Likes Received:
    0
    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...
  2. @ruantec

    @ruantec Crazy GFX coder Emu Author

    Messages:
    15,103
    Likes Received:
    1,111
    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.
    Last edited: Aug 22, 2014
  3. Steven Davisworth

    Steven Davisworth Member

    Messages:
    65
    Likes Received:
    0
    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);
            }
  4. @ruantec

    @ruantec Crazy GFX coder Emu Author

    Messages:
    15,103
    Likes Received:
    1,111
    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.

    Yeah, but first i want you to understand the first two :p
  5. @ruantec

    @ruantec Crazy GFX coder Emu Author

    Messages:
    15,103
    Likes Received:
    1,111
    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.

    Attached Files:

    Last edited: Aug 27, 2014
  6. Steven Davisworth

    Steven Davisworth Member

    Messages:
    65
    Likes Received:
    0
    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...
    Last edited: Aug 29, 2014
  7. @ruantec

    @ruantec Crazy GFX coder Emu Author

    Messages:
    15,103
    Likes Received:
    1,111
    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.
  8. Steven Davisworth

    Steven Davisworth Member

    Messages:
    65
    Likes Received:
    0
    Just a quick one....what's the difference in using behavior as opposed to commands?
  9. @ruantec

    @ruantec Crazy GFX coder Emu Author

    Messages:
    15,103
    Likes Received:
    1,111
    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
    Last edited: Aug 29, 2014
  10. Steven Davisworth

    Steven Davisworth Member

    Messages:
    65
    Likes Received:
    0
    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);
                    }          
                }
            }
    Last edited: Sep 3, 2014
  11. @ruantec

    @ruantec Crazy GFX coder Emu Author

    Messages:
    15,103
    Likes Received:
    1,111
    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.

    Thanks! will keep that in mind ;)

    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.
    Last edited: Sep 4, 2014
  12. Steven Davisworth

    Steven Davisworth Member

    Messages:
    65
    Likes Received:
    0
    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?
    Last edited: Sep 4, 2014
  13. @ruantec

    @ruantec Crazy GFX coder Emu Author

    Messages:
    15,103
    Likes Received:
    1,111
    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 ;)

    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).

    Attached Files:

    Last edited: Sep 6, 2014
  14. Steven Davisworth

    Steven Davisworth Member

    Messages:
    65
    Likes Received:
    0
    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?
  15. @ruantec

    @ruantec Crazy GFX coder Emu Author

    Messages:
    15,103
    Likes Received:
    1,111
    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.
  16. Steven Davisworth

    Steven Davisworth Member

    Messages:
    65
    Likes Received:
    0
    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...
  17. Steven Davisworth

    Steven Davisworth Member

    Messages:
    65
    Likes Received:
    0
    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?
  18. @ruantec

    @ruantec Crazy GFX coder Emu Author

    Messages:
    15,103
    Likes Received:
    1,111
    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.

    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.
  19. Steven Davisworth

    Steven Davisworth Member

    Messages:
    65
    Likes Received:
    0
    I've got the datagrid and listbox in place already... its more on how to structure the collections... i.e. How does the listbox know to present its data according to parent row?
    Will play more today... thanks.
  20. Steven Davisworth

    Steven Davisworth Member

    Messages:
    65
    Likes Received:
    0
    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...
    [​IMG]

    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)...
    [​IMG]

    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!!!!)
    Last edited: Sep 23, 2014

Share This Page