Monday 11 June 2012

TabControl's TabItem validation Part2

In the previous part we've added some basic functionality to our TabItem validation. There are 2 main scenerios I've found, that add certain difficulty.

First one occurs when we are using DataGrid or its derivative. In DataGrid, when we remove an invalid row or row that has got children in invalid state (for example a validated textbox) despite the "invalid" row deletion, invalid state is retained and will stay, unless we clear all elements in the datagrid and repopulate it.

Second one is related to "revert to previous state" mechanisms often found in solutions that involve accepting and cancelling changes. When we have several TabItems in invalid state and we want to press the "Cancel" button to revert changes we've made, we want all TabItem's to receive information about that change.

To start with, we'll add a helper method, which we've already used in the part1.
IsVisualChild checks, if the control we pass as the second parameter in is places inside of the parent control passed as the first parameter.

Code Snippet
  1. public static bool IsVisualChild(DependencyObject parent, DependencyObject child)
  2. {
  3.     if (parent == null || child == null)
  4.         return false;
  5.  
  6.     bool result = false;
  7.     for (int i = 0; i < VisualTreeHelper.GetChildrenCount(parent); i++)
  8.     {
  9.         var visualChild = VisualTreeHelper.GetChild(parent, i);
  10.         if (child != visualChild)
  11.         {
  12.             result = IsVisualChild(visualChild, child);
  13.             if (result)
  14.                 break;
  15.         }
  16.         else
  17.         {
  18.             result = true;
  19.             break;
  20.         }
  21.     }
  22.     return result;
  23. }

Now we can add the Unloaded event handling method, you have probably already noticed, that in the ValidationOccured method in the Part1 we've added Unloaded method handler to the control that caused validation errors. Unloaded event is fired when the control is being disconnected from the visual object tree.

We need to know of two particular cases - when the validation row is being deleted from a datagrid and when the tab is being switched to another one in the tabcontrol (the tabitem with our control is no longer visible - the control's getting unloaded). When the former happens, we have to remove the control from our elements collection, contrary we should omit removing when the control is not visible due to the parent tab being deselected - that's why in code below we need to check if the control is still a visual child of the tab, before we can sefaly remove it from the erroneous elements list.

Code Snippet
  1. public static void UnloadedEvent(object sender, RoutedEventArgs args)
  2. {
  3.     if (elements.Count > 0)
  4.     {
  5.         var dpSender = elements.Find(p => p.Key == sender);
  6.         if (dpSender.Value != null)
  7.         {
  8.             var parent = dpSender.Value.Content as DependencyObject;
  9.             var child = sender as DependencyObject;
  10.             if (!IsVisualChild(parent, child))
  11.             {
  12.                 if (elements.Count == 1)
  13.                 {
  14.                     elements[0].Value.SetValue(IsTabValidProperty, true);
  15.                 }
  16.                 ((FrameworkElement)sender).Unloaded -= UnloadedEvent;
  17.                 elements.RemoveAll(p => p.Key == sender);
  18.             }
  19.         }
  20.     }
  21. }

Last part is adding changes cancellation support. Different programmers do it differently, in my case my ViewModel has got a Mode property which changes accordingly to the state of properties. When I start changing values in textboxes my ViewModel goes into Edit state. When I cancel changes it goes back to Normal state - and properties go back to their former values (using entity framework in my case). So depending on your implementation you might want to do things differently.
Property, which matches my ViewModel's property Mode:

Code Snippet
  1. public static readonly DependencyProperty DisplayModeProperty =
  2.     DependencyProperty.RegisterAttached("DisplayMode", typeof(DisplayMode), 
  3.     typeof(TabItemValidation),
  4.     new UIPropertyMetadata(DisplayMode.ReadOnly, DisplayModeChanged));

And the DisplayModeChanged callback method. Depending on the mode to which my ViewModel changes to, we can clear elements in our list and switch TabItem to valid state.

Code Snippet
  1. private static void DisplayModeChanged(DependencyObject dobj, DependencyPropertyChangedEventArgs args)
  2. {
  3.     if((DisplayMode)args.NewValue == DisplayMode.ReadOnly &&
  4.         ((DisplayMode)args.OldValue != DisplayMode.ReadOnly))
  5.     {
  6.         foreach (var tabItem in elements.Select(p => p.Value))
  7.         {
  8.             tabItem.SetValue(IsTabValidProperty, true);
  9.         }
  10.         elements.Clear();
  11.     }
  12. }

It's the first this elaborate tutorial I've posted, so I beg your understanding, and I'd be happy to answer to any questions.






10 comments:

  1. Thank you Simon, this is exactly what I'm looking for.
    But I have a question, would you provide some xaml code to show the use of the tabcontrol validation?

    ReplyDelete
    Replies
    1. Hi,

      I'll try to find the code sample I've used for the above. I'll get back to you once I've found it.

      Regards.

      Delete
    2. Thank you, I appreciate that.
      I will check every day this page :)

      Delete
    3. This comment has been removed by the author.

      Delete
    4. Hi Anonymous,

      I'm really sorry it has taken so much time for me to prepare a sample, if you are still around you can check the sample here:
      http://simonkatanski.blogspot.com/2016/04/tabitem-validation-sample.html

      Regards,
      Simon

      Delete
  2. This comment has been removed by the author.

    ReplyDelete
  3. Hi.
    I think I figure out how to do it, but I have a problem - It doesn't work for a dynamically added TabItems for the first time - so if I write something in control, validation error dissapear and I delete it (to show again validation error) then it works. If I add TabItem on first place then it works fine...
    Can You help me?

    ReplyDelete
    Replies
    1. Ok I think I have it:
      Instead of working in VisualTreeHelper I change it to LogicalTreeHelper and it Works! ;)

      Delete
    2. Hi Andrzej,

      Similarly, sorry for the hold up. I've prepared a rough code sample in github, urls in the post below:
      http://simonkatanski.blogspot.com/2016/04/tabitem-validation-sample.html

      The sample doesn't have dynamic tabs. Please let me know if you still need help with the dynamic case, I can try putting some example together.

      Best regards,
      Simon

      Delete
    3. Thank You, I've got it.
      As i wrote, I change VisualTreeHelper to LogicalTreeHelper and it works. Also I've add extra Foreground DependencyProperty in behavior class and I only bind Header Foreground to it and it works as I want.
      Thank You

      Delete