2009-02-12 5 views
12

У меня есть приложение WPF с элементом управления Treeview.Как я могу отменить WPF TreeView пользователя?

Когда пользователь нажимает на узел на дереве, другие элементы управления TextBox, ComboBox и др. Заполняются соответствующими значениями.

Пользователь может внести изменения в эти значения и сохранить свои изменения, нажав кнопку «Сохранить».

Однако, если пользователь выбирает другой узел Treeview без сохранения его или ее изменений, я хочу отобразить предупреждение и возможность отменить этот выбор.

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 do I properly handle a PreviewMouseDown event with a MessageBox confirmation?

+9

Несвязанная напыщенная речь: Пожалуйста, у меня нет окна с сообщением о да/нет, но есть кнопки ОК/Отмена. – scwagner

ответ

1

Поскольку SelectedItemChanged событие запускается после SelectedItem уже изменилось, вы не можете отменить мероприятие в эта точка.

Что вы можете сделать, это прослушивать щелчки мыши и отменять их до изменения SelectedItem.

2

Вместо того, чтобы выбирать для Selected/Unselected, лучшим способом может быть подключение к PreviewMouseDown. Предбложка с обработкой события Selected и Unselected означает, что событие уже произошло, когда вы получили уведомление. Отменить нечего, потому что это уже произошло.

С другой стороны, просмотр событий отменяется. Это не то точное событие, которое вы хотите, но оно дает вам возможность избегать выбора другого узла.

+8

Как насчет того, когда они используют клавиатуру? Прикоснитесь? Стилус? Нужно ли мне все это обрабатывать? – Ray

+0

@Ray Установите свойство TreeView.Focusable на false, и больше невозможно получить фокус с помощью клавиатуры. –

2

Вы не можете отменить мероприятие, как, например, событие закрытия. Но вы можете отменить его, если кешировать последнее выбранное значение. Секрет в том, что вам нужно изменить выбор, не перепутывая событие SelectionChanged. Вот пример:

private object _LastSelection = null; 
    private void OnSelectionChanged(object sender, SelectionChangedEventArgs e) 
    { 
     if (IsUpdated) 
     { 
      MessageBoxResult result = MessageBox.Show("The current record has been modified. Are you sure you want to navigate away? Click Cancel to continue editing. If you click OK all changes will be lost.", "Warning", MessageBoxButton.OKCancel, MessageBoxImage.Hand); 
      switch (result) 
      { 
       case MessageBoxResult.Cancel: 
        e.Handled = true; 
        // disable event so this doesn't go into an infinite loop when the selection is changed to the cached value 
        PersonListView.SelectionChanged -= new SelectionChangedEventHandler(OnSelectionChanged); 
        PersonListView.SelectedItem = _LastSelection; 
        PersonListView.SelectionChanged += new SelectionChangedEventHandler(OnSelectionChanged); 
        return; 
       case MessageBoxResult.OK: 
        // revert the object to the original state 
        LocalDataContext.Persons.GetOriginalEntityState(_LastSelection).CopyTo(_LastSelection); 
        IsUpdated = false; 
        Refresh(); 
        break; 
       default: 
        throw new ApplicationException("Invalid response."); 
      } 
     } 

     // cache the selected item for undo 
     _LastSelection = PersonListView.SelectedItem; 
    } 
+1

Я пробовал что-то вроде этого (но с TreeView, а не в виде списка в вашем примере). И после того, как я снова включил событие, он снова выстрелил, пытаясь перейти на новый элемент. – Ray

+1

Также с помощью TreeView вы не можете установить выбранный элемент таким образом, вам нужно установить IsSelected на элемент. – Ray

2

CAMS_ARIES:

XAML:

код:

private bool ManejarSeleccionNodoArbol(Object origen) 
    { 
     return true; // with true, the selected nodo don't change 
     return false // with false, the selected nodo change 
    } 


    private void Arbol_PreviewMouseDown(object sender, MouseButtonEventArgs e) 
    {    
     if (e.Source is TreeViewItem) 
     { 
      e.Handled = ManejarSeleccionNodoArbol(e.Source); 
     } 
    } 

    private void Arbol_PreviewKeyDown(object sender, KeyEventArgs e) 
    { 
     if (e.Source is TreeViewItem) 
     { 
      e.Handled=ManejarSeleccionNodoArbol(e.Source); 
     } 
    }   
+0

Можем ли мы получить версию на английском языке? –

10

UPDATE

Реализованный я мог бы поставить логику в SelectedItemChanged вместо. Немного более чистого решения.

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> 

код позади. У меня есть некоторые классы, которые являются моими ItemsSource TreeView, поэтому я создал интерфейс (MyInterface), который предоставляет свойство IsSelected для всех из них.

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; 
    } 
} 

Я не нашел ситуации, когда он не возвращается к предыдущему элементу при нажатии «Нет».

1

Вы можете создать свой собственный элемент управления, который происходит из TreeView, а затем переопределить метод OnSelectedItemChanged.

Прежде чем позвонить в базу, вы можете сначала запустить настраиваемое событие с параметром CancelEventArgs. Если параметр.Cancel станет истинным, тогда не вызывайте базу, а вместо этого выберите старый элемент (будьте осторожны, чтобы OnSelectedItemChanged будет вызван снова).

Не лучшее решение, но по крайней мере это сохраняет логику внутри управления деревом, и нет никаких шансов, что событие смены выбора срабатывает больше, чем нужно. Кроме того, вам не нужно заботиться о том, что пользователь щелкнул дерево, использовал клавиатуру или, возможно, выбор был изменен программно.

1

Мне пришлось решить ту же проблему, но в нескольких treeviews в моем приложении. Я получил TreeView и добавил обработчики событий, частично используя решение Meleak, и частично используя методы расширения с этого форума: http://forums.silverlight.net/t/65277.aspx/1/10

Я думал, что поделюсь с вами своим решением, так что вот мое полное многократное дерево TreeView, которое обрабатывает «отменить узел» изменение ":

public class MyTreeView : TreeView 
{ 
    public static RoutedEvent PreviewSelectedItemChangedEvent; 
    public static RoutedEvent SelectionCancelledEvent; 

    static MyTreeView() 
    { 
     PreviewSelectedItemChangedEvent = EventManager.RegisterRoutedEvent("PreviewSelectedItemChanged", RoutingStrategy.Bubble, 
                      typeof(RoutedPropertyChangedEventHandler<object>), typeof(MyTreeView)); 

     SelectionCancelledEvent = EventManager.RegisterRoutedEvent("SelectionCancelled", RoutingStrategy.Bubble, 
                    typeof(RoutedEventHandler), typeof(MyTreeView)); 
    } 

    public event RoutedPropertyChangedEventHandler<object> PreviewSelectedItemChanged 
    { 
     add { AddHandler(PreviewSelectedItemChangedEvent, value); } 
     remove { RemoveHandler(PreviewSelectedItemChangedEvent, value); } 
    } 

    public event RoutedEventHandler SelectionCancelled 
    { 
     add { AddHandler(SelectionCancelledEvent, value); } 
     remove { RemoveHandler(SelectionCancelledEvent, value); } 
    } 


    private object selectedItem = null; 
    protected override void OnSelectedItemChanged(RoutedPropertyChangedEventArgs<object> e) 
    { 
     if (e.NewValue == selectedItem) 
     { 
      this.Focus(); 

      var args = new RoutedEventArgs(SelectionCancelledEvent); 
      RaiseEvent(args); 
     } 
     else 
     { 
      var args = new RoutedPropertyChangedEventArgs<object>(e.OldValue, e.NewValue, PreviewSelectedItemChangedEvent); 
      RaiseEvent(args); 

      if (args.Handled) 
      { 
       EventHandler eventHandler = null; 
       eventHandler = delegate 
       { 
        this.LayoutUpdated -= eventHandler; 

        var treeViewItem = this.ContainerFromItem(selectedItem); 
        if (treeViewItem != null) 
         treeViewItem.IsSelected = true; 
       }; 

       this.LayoutUpdated += eventHandler; 
      } 
      else 
      { 
       selectedItem = this.SelectedItem; 
       base.OnSelectedItemChanged(e); 
      } 
     } 
    } 
} 

public static class TreeViewExtensions 
{ 
    public static TreeViewItem ContainerFromItem(this TreeView treeView, object item) 
    { 
     if (item == null) return null; 

     var containerThatMightContainItem = (TreeViewItem)treeView.ItemContainerGenerator.ContainerFromItem(item); 

     return containerThatMightContainItem ?? ContainerFromItem(treeView.ItemContainerGenerator, treeView.Items, item); 
    } 

    private static TreeViewItem ContainerFromItem(ItemContainerGenerator parentItemContainerGenerator, ItemCollection itemCollection, object item) 
    { 
     foreach (var child in itemCollection) 
     { 
      var parentContainer = (TreeViewItem)parentItemContainerGenerator.ContainerFromItem(child); 
      var containerThatMightContainItem = (TreeViewItem)parentContainer.ItemContainerGenerator.ContainerFromItem(item); 

      if (containerThatMightContainItem != null) 
       return containerThatMightContainItem; 

      var recursionResult = ContainerFromItem(parentContainer.ItemContainerGenerator, parentContainer.Items, item); 
      if (recursionResult != null) 
       return recursionResult; 
     } 
     return null; 
    } 
} 

Ниже приведен пример использования (кода для окна, содержащего MyTreeView):

private void theTreeView_PreviewSelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e) 
    { 
     if (e.OldValue != null) 
      e.Handled = true; 
    } 

    private void theTreeView_SelectionCancelled(object sender, RoutedEventArgs e) 
    { 
     MessageBox.Show("Cancelled"); 
    } 

После выбора первого узла в TreeView, все другие изменения узла отменяется, и сообщение филиал x отображается.

2

Вы не можете поместить свою логику в метод OnSelectedItemChanged, если в логике есть выбранный элемент, который уже был изменен.

Как и было предложено другим плакатом, обработчик PreviewMouseDown является лучшим местом для реализации логики, однако предстоит еще немалая работа по ноге.

Ниже мои 2 цента:

Сначала TreeView, что я реализовал:

public class MyTreeView : TreeView 
{ 
    static MyTreeView() 
    { 
     DefaultStyleKeyProperty.OverrideMetadata(
      typeof(MyTreeView), 
      new FrameworkPropertyMetadata(typeof(TreeView))); 
    } 

    // Register a routed event, note this event uses RoutingStrategy.Tunnel. per msdn docs 
    // all "Preview" events should use tunneling. 
    // http://msdn.microsoft.com/en-us/library/system.windows.routedevent.routingstrategy.aspx 
    public static RoutedEvent PreviewSelectedItemChangedEvent = EventManager.RegisterRoutedEvent(
     "PreviewSelectedItemChanged", 
     RoutingStrategy.Tunnel, 
     typeof(CancelEventHandler), 
     typeof(MyTreeView)); 

    // give CLR access to routed event 
    public event CancelEventHandler PreviewSelectedItemChanged 
    { 
     add 
     { 
      AddHandler(PreviewSelectedItemChangedEvent, value); 
     } 
     remove 
     { 
      RemoveHandler(PreviewSelectedItemChangedEvent, value); 
     } 
    } 

    // override PreviewMouseDown 
    protected override void OnPreviewMouseDown(MouseButtonEventArgs e) 
    { 
     // determine which item is going to be selected based on the current mouse position 
     object itemToBeSelected = this.GetObjectAtPoint<TreeViewItem>(e.GetPosition(this)); 

     // selection doesn't change if the target point is null (beyond the end of the list) 
     // or if the item to be selected is already selected. 
     if (itemToBeSelected != null && itemToBeSelected != SelectedItem) 
     { 
      bool shouldCancel; 

      // call our new event 
      OnPreviewSelectedItemChanged(out shouldCancel); 
      if (shouldCancel) 
      { 
       // if we are canceling the selection, mark this event has handled and don't 
       // propogate the event. 
       e.Handled = true; 
       return; 
      } 
     } 

     // otherwise we want to continue normally 
     base.OnPreviewMouseDown(e); 
    } 

    protected virtual void OnPreviewSelectedItemChanged(out bool shouldCancel) 
    { 
     CancelEventArgs e = new CancelEventArgs(); 
     if (PreviewSelectedItemChangedEvent != null) 
     { 
      // Raise our event with our custom CancelRoutedEventArgs 
      RaiseEvent(new CancelRoutedEventArgs(PreviewSelectedItemChangedEvent, e)); 
     } 
     shouldCancel = e.Cancel; 
    } 
} 

некоторые методы расширения для поддержки TreeView найти объект под мышью.

public static class ItemContainerExtensions 
{ 
    // get the object that exists in the container at the specified point. 
    public static object GetObjectAtPoint<ItemContainer>(this ItemsControl control, Point p) 
     where ItemContainer : DependencyObject 
    { 
     // ItemContainer - can be ListViewItem, or TreeViewItem and so on(depends on control) 
     ItemContainer obj = GetContainerAtPoint<ItemContainer>(control, p); 
     if (obj == null) 
      return null; 

     // it is worth noting that the passed _control_ may not be the direct parent of the 
     // container that exists at this point. This can be the case in a TreeView, where the 
     // parent of a TreeViewItem may be either the TreeView or a intermediate TreeViewItem 
     ItemsControl parentGenerator = obj.GetParentItemsControl(); 

     // hopefully this isn't possible? 
     if (parentGenerator == null) 
      return null; 

     return parentGenerator.ItemContainerGenerator.ItemFromContainer(obj); 
    } 

    // use the VisualTreeHelper to find the container at the specified point. 
    public static ItemContainer GetContainerAtPoint<ItemContainer>(this ItemsControl control, Point p) 
     where ItemContainer : DependencyObject 
    { 
     HitTestResult result = VisualTreeHelper.HitTest(control, p); 
     DependencyObject obj = result.VisualHit; 

     while (VisualTreeHelper.GetParent(obj) != null && !(obj is ItemContainer)) 
     { 
      obj = VisualTreeHelper.GetParent(obj); 
     } 

     // Will return null if not found 
     return obj as ItemContainer; 
    } 

    // walk up the visual tree looking for the nearest ItemsControl parent of the specified 
    // depObject, returns null if one isn't found. 
    public static ItemsControl GetParentItemsControl(this DependencyObject depObject) 
    { 
     DependencyObject obj = VisualTreeHelper.GetParent(depObject); 
     while (VisualTreeHelper.GetParent(obj) != null && !(obj is ItemsControl)) 
     { 
      obj = VisualTreeHelper.GetParent(obj); 
     } 

     // will return null if not found 
     return obj as ItemsControl; 
    } 
} 

и последнее, но не в последнюю очередь пользовательские EventArgs, которые используют подсистему RoutedEvent.

public class CancelRoutedEventArgs : RoutedEventArgs 
{ 
    private readonly CancelEventArgs _CancelArgs; 

    public CancelRoutedEventArgs(RoutedEvent @event, CancelEventArgs cancelArgs) 
     : base(@event) 
    { 
     _CancelArgs = cancelArgs; 
    } 

    // override the InvokeEventHandler because we are going to pass it CancelEventArgs 
    // not the normal RoutedEventArgs 
    protected override void InvokeEventHandler(Delegate genericHandler, object genericTarget) 
    { 
     CancelEventHandler handler = (CancelEventHandler)genericHandler; 
     handler(genericTarget, _CancelArgs); 
    } 

    // the result 
    public bool Cancel 
    { 
     get 
     { 
      return _CancelArgs.Cancel; 
     } 
    } 
} 
0

Я решил эту проблему для 1 вида дерева и отображения 1 документа за раз.Это решение основано на присоединяемой поведение, которое может быть присоединено к нормальному TreeView:

<TreeView Grid.Column="0" 
     ItemsSource="{Binding TreeViewItems}" 
     behav:TreeViewSelectionChangedBehavior.ChangedCommand="{Binding SelectItemChangedCommand}" 
     > 
    <TreeView.ItemTemplate> 
     <HierarchicalDataTemplate ItemsSource="{Binding Children}"> 
      <StackPanel Orientation="Horizontal"> 
       <TextBlock Text="{Binding Name}" 
         ToolTipService.ShowOnDisabled="True" 
         VerticalAlignment="Center" Margin="3" /> 
      </StackPanel> 
     </HierarchicalDataTemplate> 
    </TreeView.ItemTemplate> 
    <TreeView.ItemContainerStyle> 
     <Style TargetType="{x:Type TreeViewItem}"> 
      <Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}" /> 
      <Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" /> 
     </Style> 
    </TreeView.ItemContainerStyle> 
</TreeView> 

и код для поведения заключается в следующем:

/// <summary> 
/// Source: 
/// http://stackoverflow.com/questions/1034374/drag-and-drop-in-mvvm-with-scatterview 
/// http://social.msdn.microsoft.com/Forums/de-DE/wpf/thread/21bed380-c485-44fb-8741-f9245524d0ae 
/// 
/// Attached behaviour to implement the SelectionChanged command/event via delegate command binding or routed commands. 
/// </summary> 
public static class TreeViewSelectionChangedBehavior 
{ 
#region fields 
/// <summary> 
/// Field of attached ICommand property 
/// </summary> 
private static readonly DependencyProperty ChangedCommandProperty = DependencyProperty.RegisterAttached(
    "ChangedCommand", 
    typeof(ICommand), 
    typeof(TreeViewSelectionChangedBehavior), 
    new PropertyMetadata(null, OnSelectionChangedCommandChange)); 

/// <summary> 
/// Implement backing store for UndoSelection dependency proeprty to indicate whether selection should be 
/// cancelled via MessageBox query or not. 
/// </summary> 
public static readonly DependencyProperty UndoSelectionProperty = 
    DependencyProperty.RegisterAttached("UndoSelection", 
    typeof(bool), 
    typeof(TreeViewSelectionChangedBehavior), 
    new PropertyMetadata(false, OnUndoSelectionChanged)); 
#endregion fields 

#region methods 
#region ICommand changed methods 
/// <summary> 
/// Setter method of the attached ChangedCommand <seealso cref="ICommand"/> property 
/// </summary> 
/// <param name="source"></param> 
/// <param name="value"></param> 
public static void SetChangedCommand(DependencyObject source, ICommand value) 
{ 
    source.SetValue(ChangedCommandProperty, value); 
} 

/// <summary> 
/// Getter method of the attached ChangedCommand <seealso cref="ICommand"/> property 
/// </summary> 
/// <param name="source"></param> 
/// <returns></returns> 
public static ICommand GetChangedCommand(DependencyObject source) 
{ 
    return (ICommand)source.GetValue(ChangedCommandProperty); 
} 
#endregion ICommand changed methods 

#region UndoSelection methods 
public static bool GetUndoSelection(DependencyObject obj) 
{ 
    return (bool)obj.GetValue(UndoSelectionProperty); 
} 

public static void SetUndoSelection(DependencyObject obj, bool value) 
{ 
    obj.SetValue(UndoSelectionProperty, value); 
} 
#endregion UndoSelection methods 

/// <summary> 
/// This method is hooked in the definition of the <seealso cref="ChangedCommandProperty"/>. 
/// It is called whenever the attached property changes - in our case the event of binding 
/// and unbinding the property to a sink is what we are looking for. 
/// </summary> 
/// <param name="d"></param> 
/// <param name="e"></param> 
private static void OnSelectionChangedCommandChange(DependencyObject d, DependencyPropertyChangedEventArgs e) 
{ 
    TreeView uiElement = d as TreeView; // Remove the handler if it exist to avoid memory leaks 

    if (uiElement != null) 
    { 
     uiElement.SelectedItemChanged -= Selection_Changed; 

     var command = e.NewValue as ICommand; 
     if (command != null) 
     { 
      // the property is attached so we attach the Drop event handler 
      uiElement.SelectedItemChanged += Selection_Changed; 
     } 
    } 
} 

/// <summary> 
/// This method is called when the selection changed event occurs. The sender should be the control 
/// on which this behaviour is attached - so we convert the sender into a <seealso cref="UIElement"/> 
/// and receive the Command through the <seealso cref="GetChangedCommand"/> getter listed above. 
/// 
/// The <paramref name="e"/> parameter contains the standard EventArgs data, 
/// which is unpacked and reales upon the bound command. 
/// 
/// This implementation supports binding of delegate commands and routed commands. 
/// </summary> 
/// <param name="sender"></param> 
/// <param name="e"></param> 
private static void Selection_Changed(object sender, RoutedPropertyChangedEventArgs<object> e) 
{ 
    var uiElement = sender as TreeView; 

    // Sanity check just in case this was somehow send by something else 
    if (uiElement == null) 
     return; 

    ICommand changedCommand = TreeViewSelectionChangedBehavior.GetChangedCommand(uiElement); 

    // There may not be a command bound to this after all 
    if (changedCommand == null) 
     return; 

    // Check whether this attached behaviour is bound to a RoutedCommand 
    if (changedCommand is RoutedCommand) 
    { 
     // Execute the routed command 
     (changedCommand as RoutedCommand).Execute(e.NewValue, uiElement); 
    } 
    else 
    { 
     // Execute the Command as bound delegate 
     changedCommand.Execute(e.NewValue); 
    } 
} 

/// <summary> 
/// Executes when the bound boolean property indicates that a user should be asked 
/// about changing a treeviewitem selection instead of just performing it. 
/// </summary> 
/// <param name="d"></param> 
/// <param name="e"></param> 
private static void OnUndoSelectionChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) 
{ 
    TreeView uiElement = d as TreeView; // Remove the handler if it exist to avoid memory leaks 

    if (uiElement != null) 
    { 
     uiElement.PreviewMouseDown -= uiElement_PreviewMouseDown; 

     var command = (bool)e.NewValue; 
     if (command == true) 
     { 
      // the property is attached so we attach the Drop event handler 
      uiElement.PreviewMouseDown += uiElement_PreviewMouseDown; 
     } 
    } 
} 

/// <summary> 
/// Based on the solution proposed here: 
/// Source: http://stackoverflow.com/questions/20244916/wpf-treeview-selection-change 
/// </summary> 
/// <param name="sender"></param> 
/// <param name="e"></param> 
private static void uiElement_PreviewMouseDown(object sender, MouseButtonEventArgs e) 
{ 
    // first did the user click on a tree node? 
    var source = e.OriginalSource as DependencyObject; 
    while (source != null && !(source is TreeViewItem)) 
     source = VisualTreeHelper.GetParent(source); 

    var itemSource = source as TreeViewItem; 
    if (itemSource == null) 
     return; 

    var treeView = sender as TreeView; 
    if (treeView == null) 
     return; 

    bool undoSelection = TreeViewSelectionChangedBehavior.GetUndoSelection(treeView); 
    if (undoSelection == false) 
     return; 

    // Cancel the attempt to select an item. 
    var result = MessageBox.Show("The current document has unsaved data. Do you want to continue without saving data?", "Are you really sure?", 
           MessageBoxButton.YesNo, MessageBoxImage.Question, MessageBoxResult.No); 

    if (result == MessageBoxResult.No) 
    { 
     // Cancel the attempt to select a differnet item. 
     e.Handled = true; 
    } 
    else 
    { 
     // Lets disable this for a moment, otherwise, we'll get into an event "recursion" 
     treeView.PreviewMouseDown -= uiElement_PreviewMouseDown; 

     // Select the new item - make sure a SelectedItemChanged event is fired in any case 
     // Even if this means that we have to deselect/select the one and the same item 
     if (itemSource.IsSelected == true) 
      itemSource.IsSelected = false; 

     itemSource.IsSelected = true; 

     // Lets enable this to get back to business for next selection 
     treeView.PreviewMouseDown += uiElement_PreviewMouseDown; 
    } 
} 
#endregion methods 
} 

В этом примере я показываю окно блокировки сообщений чтобы заблокировать событие PreviewMouseDown, когда оно происходит. Затем событие обрабатывается, чтобы сигнализировать о том, что выбор отменен или он не обрабатывается, чтобы позволить самому древовидному описанию обрабатывать событие, выбирая элемент, который будет выбран.

Поведение затем вызывает связанную команду в ViewModel, если пользователь решит продолжить в любом случае (PreviewMouseDown событие не обрабатываются прикрепленным поведением и связанной командой вызывается.

Я думаю, окно сообщения с указанием может быть сделано другими способами, но я думаю, что здесь важно блокировать событие, когда это происходит, поскольку в противном случае его невозможно отменить (?). Поэтому единственным улучшением, которое я мог бы подумать об этом коде, является привязка некоторых строк для отображения отображаемого сообщения Конфигурируемый.

Я написал статью, которая содержит загружаемый образец, поскольку в противном случае это трудная область для объяснения (нужно сделать так много sumptions о недостающих частей, которые и не всегда может быть общим для всех читателей)

Вот статья, которая содержит мои результаты: http://www.codeproject.com/Articles/995629/Cancelable-TreeView-Navigation-for-Documents-in-WP

Просьба прокомментировать это решение и дайте мне знать, если вы видите возможности для улучшения ,

Смежные вопросы