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
- public static bool IsVisualChild(DependencyObject parent, DependencyObject child)
- {
- if (parent == null || child == null)
- return false;
- bool result = false;
- for (int i = 0; i < VisualTreeHelper.GetChildrenCount(parent); i++)
- {
- var visualChild = VisualTreeHelper.GetChild(parent, i);
- if (child != visualChild)
- {
- result = IsVisualChild(visualChild, child);
- if (result)
- break;
- }
- else
- {
- result = true;
- break;
- }
- }
- return result;
- }
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
- public static void UnloadedEvent(object sender, RoutedEventArgs args)
- {
- if (elements.Count > 0)
- {
- var dpSender = elements.Find(p => p.Key == sender);
- if (dpSender.Value != null)
- {
- var parent = dpSender.Value.Content as DependencyObject;
- var child = sender as DependencyObject;
- if (!IsVisualChild(parent, child))
- {
- if (elements.Count == 1)
- {
- elements[0].Value.SetValue(IsTabValidProperty, true);
- }
- ((FrameworkElement)sender).Unloaded -= UnloadedEvent;
- elements.RemoveAll(p => p.Key == sender);
- }
- }
- }
- }
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
- public static readonly DependencyProperty DisplayModeProperty =
- DependencyProperty.RegisterAttached("DisplayMode", typeof(DisplayMode),
- typeof(TabItemValidation),
- 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
- private static void DisplayModeChanged(DependencyObject dobj, DependencyPropertyChangedEventArgs args)
- {
- if((DisplayMode)args.NewValue == DisplayMode.ReadOnly &&
- ((DisplayMode)args.OldValue != DisplayMode.ReadOnly))
- {
- foreach (var tabItem in elements.Select(p => p.Value))
- {
- tabItem.SetValue(IsTabValidProperty, true);
- }
- elements.Clear();
- }
- }
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.