views:

932

answers:

2

I've been trying to get my WPF application to prompt users to either discard their unsaved changes or to cancel when they navigate using a TreeView.

I think I've found a bug. The MessageBox does not play nice with PreviewMouseDown. It seems to "handle" a click regardless of how its e.Handled is set if there's a MessageBox shown.

For this XAML...

<TreeView Name="TreeViewThings"
    ...
    PreviewMouseDown="TreeViewThings_PreviewMouseDown"
    TreeViewItem.Expanded="TreeViewThings_Expanded"
    TreeViewItem.Selected="TreeViewThings_Selected" >

...compare these alternative methods...

Sub TreeViewNodes_PreviewMouseDown(...)
    e.Handled = False
End Sub

Sub TreeViewNodes_PreviewMouseDown(...)
    MessageBox.Show("Test", "Test", MessageBoxButton.OK)
    e.Handled = False
End Sub

These two methods behave differently. Without the MessageBox, TreeViewNodes_Selected() or TreeViewThings_Expanded() will execute. With the MessageBox, they won't.

Is this a bug or is there something going on here that I should understand?

+1  A: 

Hi Zack! I'm having precisely the same problem and you're right in thinking that MessageBox is screwing things up. To be honest, I've had other issues with MessageBox while working with Windows Forms before switching to WPF. Maybe it's just some century-old bug that became a feature (as often it is with Microsoft)?

In any case, the only solution I can offer you is the one that has worked for me. I was having problems with getting a similar situation to work with a ListBox - if there were changes to data in the form, when selection of the ListBox changed (either by clicking on new item or using keys "Up" or "Down"), I offered user a choice in the MessageBox whether to save, discard or cancel.

Naturally using the direct approach of handling ListBox's MouseDown or PreviewMouseDown events didn't work well with a MessageBox. Here's what worked.

I have a data template to display items in my ListBox (I'm almost expecting you to have the same):

<ListBox.ItemTemplate>
    <DataTemplate>
        <TextBlock Text="{Binding Path=NAME}" KeyDown="checkForChanges" MouseDown="checkForChanges"/>
    </DataTemplate>
</ListBox.ItemTemplate>

Note how I've moved the KeyDown and MouseDown event handlers to the TextBlock control instead. I kept the same code-behind:

// The KeyDown handler
private void checkForChanges(object sender, KeyEventArgs e) {
    e.Handled = checkForChanges();
}

// Method that checks if there are changes to be saved or discard or cancel
private bool checkForChanges() {
    if (Data.HasChanges()) {
        MessageBoxResult answer = MessageBox.Show("There are unsaved changes. Would you like to save changes now?", "WARNING", MessageBoxButton.YesNoCancel, MessageBoxImage.Question);
        if (answer == MessageBoxResult.Yes) {
            Data.AcceptDataChanges();
        } else if (answer == MessageBoxResult.Cancel) {
            return true;
        }
        return false;
    }
    return false;
}

// The MouseDown handler
private void checkForChanges(object sender, MouseButtonEventArgs e) {
    e.Handled = checkForChanges();
}

As a side note, it's odd how Binding always marks my DataRows as Modified when the selected item in the ListBox, which has ItemsSource bound to a DataTable, changes (I don't know if you're using DataTables/Sets). To battle that, I discard any unhandled changes once the selection has already been changed (because I handle anything that's necessary in the event MouseDown that occurs prior to that):

<ListBox IsSynchronizedWithCurrentItem="True" [...] SelectionChanged="clearChanges"> ... </ListBox>

And the code-behind for the handler:

private void clearChanges(object sender, SelectionChangedEventArgs e) {
    Data.cancelChanges();
}
Alexandra
A: 

This is what I've got. It works, but is less-desirable...

Sub TreeViewNodes_PreviewMouseDown(ByVal sender As Object, ByVal e As System.Windows.Input.MouseButtonEventArgs)
    If UnsavedChangesExist() Then
        MessageBox.Show("You have unsaved changes.", "Unsaved Changes", MessageBoxButton.OK, MessageBoxImage.Information, MessageBoxResult.OK)
        e.Handled = True
    End If
End Sub

This requires the user to click "OK", manually click a "Discard Changes" button (near the "Save" button), click through another "Are you sure?" message box, and only then navigate with the tree again.

Zack Peterson