Wpf – How to cancel a user’s WPF TreeView click

savechangestreeviewwpf

I've got a WPF application with a Treeview control.

When the user clicks a node on the tree, other TextBox, ComboBox, etc. controls on the page are populated with appropriate values.

The user can then make changes to those values and save his or her changes by clicking a Save button.

However, if the user selects a different Treeview node without saving his or her changes, I want to display a warning and an opportunity to cancel that selection.

MessageBox: Continue and discard your unsaved changes? OK/Cancel http://img522.imageshack.us/img522/2897/discardsj3.gif

XAML…

<TreeView Name="TreeViewThings"
    ...
    TreeViewItem.Unselected="TreeViewThings_Unselected"
    TreeViewItem.Selected="TreeViewThings_Selected" >

Visual Basic…

Sub TreeViewThings_Unselected(ByVal sender As System.Object, _
                              ByVal e As System.Windows.RoutedEventArgs)
    Dim OldThing As Thing = DirectCast(e.OriginalSource.DataContext, Thing)
    If CancelDueToUnsavedChanges(OldThing) Then
        'put canceling code here
    End If
End Sub

Sub TreeViewThings_Selected(ByVal sender As System.Object, _
                            ByVal e As System.Windows.RoutedEventArgs)
    Dim NewThing As Thing = DirectCast(e.OriginalSource.DataContext, Thing)
    PopulateControlsFromThing(NewThing)
End Sub

How can I cancel those unselect/select events?


Update: I've asked a follow-up question…
How do I properly handle a PreviewMouseDown event with a MessageBox confirmation?

Best Answer

UPDATE

Realized I could put the logic in SelectedItemChanged instead. A little cleaner solution.

Xaml

<TreeView Name="c_treeView"
          SelectedItemChanged="c_treeView_SelectedItemChanged">
    <TreeView.ItemContainerStyle>
        <Style TargetType="{x:Type TreeViewItem}">
            <Setter Property="IsSelected" Value="{Binding Path=IsSelected, Mode=TwoWay}" />
        </Style>
    </TreeView.ItemContainerStyle>
</TreeView>

Code behind. I have some classes that is my ItemsSource of the TreeView so I made an interface (MyInterface) that exposes the IsSelected property for all of them.

private MyInterface m_selectedTreeViewItem = null;
private void c_treeView_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
{
    if (m_selectedTreeViewItem != null)
    {
        if (e.NewValue == m_selectedTreeViewItem)
        {
            // Will only end up here when reversing item
            // Without this line childs can't be selected
            // twice if "No" was pressed in the question..   
            c_treeView.Focus();   
        }
        else
        {
            if (MessageBox.Show("Change TreeViewItem?",
                                "Really change",
                                MessageBoxButton.YesNo,
                                MessageBoxImage.Question) != MessageBoxResult.Yes)
            {
                EventHandler eventHandler = null;
                eventHandler = new EventHandler(delegate
                {
                    c_treeView.LayoutUpdated -= eventHandler;
                    m_selectedTreeViewItem.IsSelected = true;
                });
                // Will be fired after SelectedItemChanged, to early to change back here
                c_treeView.LayoutUpdated += eventHandler;
            }   
            else
            {
                m_selectedTreeViewItem = e.NewValue as MyInterface;
            }        
        }
    }
    else
    {
        m_selectedTreeViewItem = e.NewValue as MyInterface;
    }
}

I haven't found any situation where it doesn't revert back to the previous item upon pressing "No".