2012-12-03 15 views
1

問題:如何在TreeViewItem在其TreeView外部單擊時離開編輯模式?

在下面的樣品,我有一個左列一個TreeView和在右列的列表框。 TreeView顯示一個小樣本項目列表。當用戶選擇一個TreeViewItem並按下F2時,該項目通過用TextBox替換它的TextBlock進入「編輯模式」。

現在,如果我選擇第一個TreeViewItem並將其置於編輯模式,然後左鍵單擊第二個TreeViewItem,第一個項目將離開編輯模式,如預期的那樣。

但是,如果我將第一個TreeViewItem放入編輯模式,然後單擊列表框內,TreeViewItem仍然處於編輯模式。

什麼是健壯當用戶在其TreeView之外單擊時,導致TreeViewItem離開編輯模式的方式?當然,請不要建議我只是將鼠標監聽器添加到列表框;我正在尋找一個強大的解決方案。


我最好的嘗試解決:

我嘗試添加一個IsKeyboardFocusWithinChanged在事件監聽到TreeView:

private static void IsKeyboardFocusWithinChanged(object sender, DependencyPropertyChangedEventArgs e) 
{ 
    var treeView = sender as TreeView; 
    if (treeView != null && !treeView.IsKeyboardFocusWithin) 
    { 
     EditEnding(treeView, false); 
    } 
} 

雖然這確實解決了我的問題,它有兩個不好的副作用:

  1. 當出現MessageBox時,TreeView項目被迫離開編輯模式。
  2. 如果我在編輯模式下右鍵單擊TreeViewItem,它會導致TreeViewItem離開編輯模式。這可以防止我在我的TreeViewItem的TextBox中使用上下文菜單。

樣品的編號:

(該樣品可以下載from Skydrive

MainWindow.xaml:

<Window 
x:Class="WpfApplication3.MainWindow" 
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
xmlns:wpfApplication3="clr-namespace:WpfApplication3" 
Title="MainWindow" Height="350" Width="525" 
> 
<Window.Resources> 
    <DataTemplate x:Key="viewNameTemplate"> 
     <TextBlock 
      Text="{Binding Name}" 
      FontStyle="Normal" 
      VerticalAlignment="Center" 
      /> 
    </DataTemplate> 

    <DataTemplate x:Key="editNameTemplate"> 
     <TextBox 
      Text="{Binding Name, UpdateSourceTrigger=PropertyChanged}" 
      VerticalAlignment="Center" 
      /> 
    </DataTemplate> 

    <Style x:Key="editableContentControl" 
     TargetType="{x:Type ContentControl}" 
     > 
     <Setter 
      Property="ContentTemplate" 
      Value="{StaticResource viewNameTemplate}" 
      /> 
     <Setter 
      Property="Focusable" 
      Value="False" 
      /> 
     <Style.Triggers> 
      <DataTrigger 
       Binding="{Binding Path=IsInEditMode}" 
       Value="True" 
       > 
       <Setter 
        Property="ContentTemplate" 
        Value="{StaticResource editNameTemplate}" 
        /> 
      </DataTrigger> 
     </Style.Triggers> 
    </Style> 
</Window.Resources> 
<Grid> 
    <Grid.ColumnDefinitions> 
     <ColumnDefinition Width="*"/> 
     <ColumnDefinition Width="*"/> 
    </Grid.ColumnDefinitions> 
    <TreeView 
     Grid.Column="0" 
     wpfApplication3:EditSelectedItemBehavior.IsEnabled="{Binding RelativeSource={RelativeSource Self}, Path=IsVisible}" 
     ItemsSource="{Binding RelativeSource={RelativeSource AncestorType={x:Type wpfApplication3:MainWindow}}, Path=Files}" 
     > 
     <TreeView.ItemTemplate> 
      <DataTemplate> 
       <ContentControl 
        Content="{Binding}" 
        Focusable="False" 
        Style="{StaticResource editableContentControl}" 
        /> 
      </DataTemplate> 
     </TreeView.ItemTemplate> 
    </TreeView> 
    <ListBox 
     Grid.Column="1" 
     ItemsSource="{Binding RelativeSource={RelativeSource AncestorType={x:Type wpfApplication3:MainWindow}}, Path=Files}" 
     /> 
</Grid> 
</Window> 

MainWindow.xaml.cs

public partial class MainWindow : Window 
{ 
    public MainWindow() 
    { 
     Files = new ObservableCollection<File>(); 
     Files.Add(new File("A.txt")); 
     Files.Add(new File("B.txt")); 
     Files.Add(new File("C.txt")); 
     Files.Add(new File("D.txt")); 

     InitializeComponent(); 
    } 

    public ObservableCollection<File> Files { get; private set; } 
} 

EditSelectedItemBehavior.cs

public static class EditSelectedItemBehavior 
{ 
    public static bool GetIsEnabled(DependencyObject obj) 
    { 
     return (bool)obj.GetValue(IsEnabledProperty); 
    } 

    public static void SetIsEnabled(DependencyObject obj, bool value) 
    { 
     obj.SetValue(IsEnabledProperty, value); 
    } 

    public static readonly DependencyProperty IsEnabledProperty = 
     DependencyProperty.RegisterAttached(
      "IsEnabled", 
      typeof(bool), 
      typeof(EditSelectedItemBehavior), 
      new UIPropertyMetadata(false, OnIsEnabledChanged)); 

    private static void OnIsEnabledChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e) 
    { 
     var treeView = obj as TreeView; 
     if (treeView == null) 
     { 
      return; 
     } 

     if (e.NewValue is bool == false) 
     { 
      return; 
     } 

     if ((bool)e.NewValue) 
     { 
      treeView.CommandBindings.Add(new CommandBinding(TransactionCommands.Cancel, CancelExecuted)); 
      treeView.CommandBindings.Add(new CommandBinding(TransactionCommands.Commit, CommitExecuted)); 
      treeView.CommandBindings.Add(new CommandBinding(TransactionCommands.Edit, EditExecuted)); 

      treeView.InputBindings.Add(new KeyBinding(TransactionCommands.Cancel, Key.Escape, ModifierKeys.None)); 
      treeView.InputBindings.Add(new KeyBinding(TransactionCommands.Commit, Key.Enter, ModifierKeys.None)); 
      treeView.InputBindings.Add(new KeyBinding(TransactionCommands.Edit, Key.F2, ModifierKeys.None)); 

      treeView.SelectedItemChanged += SelectedItemChanged; 
      treeView.Unloaded += Unloaded; 
     } 
     else 
     { 
      for (var i = treeView.CommandBindings.Count - 1; i >= 0; i--) 
      { 
       var commandBinding = treeView.CommandBindings[i]; 
       if (commandBinding != null && (commandBinding.Command == TransactionCommands.Cancel || commandBinding.Command == TransactionCommands.Commit || commandBinding.Command == TransactionCommands.Edit)) 
       { 
        treeView.CommandBindings.RemoveAt(i); 
       } 
      } 

      for (var i = treeView.InputBindings.Count - 1; i >= 0; i--) 
      { 
       var keyBinding = treeView.InputBindings[i] as KeyBinding; 
       if (keyBinding != null && (keyBinding.Command == TransactionCommands.Cancel || keyBinding.Command == TransactionCommands.Commit || keyBinding.Command == TransactionCommands.Edit)) 
       { 
        treeView.InputBindings.RemoveAt(i); 
       } 
      } 

      treeView.SelectedItemChanged -= SelectedItemChanged; 
      treeView.Unloaded -= Unloaded; 
     } 
    } 

    private static void SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e) 
    { 
     var treeView = sender as TreeView; 
     if (treeView != null) 
     { 
      EditEnding(treeView, true); 
     } 
    } 

    private static void Unloaded(object sender, RoutedEventArgs e) 
    { 
     var treeView = sender as TreeView; 
     if (treeView != null) 
     { 
      EditEnding(treeView, false); 
     } 
    } 

    private static void EditExecuted(object sender, ExecutedRoutedEventArgs e) 
    { 
     var treeView = sender as TreeView; 
     if (treeView != null) 
     { 
      EditExecuted(treeView); 
     } 
    } 

    private static void CommitExecuted(object sender, ExecutedRoutedEventArgs e) 
    { 
     var treeView = sender as TreeView; 
     if (treeView != null) 
     { 
      EditEnding(treeView, true); 
     } 
    } 

    private static void CancelExecuted(object sender, ExecutedRoutedEventArgs e) 
    { 
     var treeView = sender as TreeView; 
     if (treeView != null) 
     { 
      EditEnding(treeView, false); 
     } 
    } 

    private static void EditExecuted(TreeView treeView) 
    { 
     if (!TreeViewAttachedProperties.GetIsEditingObject(treeView)) 
     { 
      var editableObject = treeView.SelectedItem as IEditableObject; 
      TreeViewAttachedProperties.SetEditableObject(treeView, editableObject); 

      if (editableObject != null) 
      { 
       TreeViewAttachedProperties.SetIsEditingObject(treeView, true); 
       editableObject.BeginEdit(); 
      } 
     } 
    } 

    private static void EditEnding(TreeView treeView, bool commitEdit) 
    { 
     if (TreeViewAttachedProperties.GetIsEditingObject(treeView)) 
     { 
      TreeViewAttachedProperties.SetIsEditingObject(treeView, false); 

      var editableObject = TreeViewAttachedProperties.GetEditableObject(treeView); 
      if (editableObject != null) 
      { 
       if (commitEdit) 
       { 
        try 
        { 
         editableObject.EndEdit(); 
        } 
        catch (ArgumentOutOfRangeException aex) 
        { 
         // This is a hackaround for renaming a Biml file in Mist's project tree view, 
         // where committing an edit triggers an OutOfRange exception, despite the edit working properly. 
         Console.WriteLine(aex.Message + " " + aex.InnerException); 
        } 
       } 
       else 
       { 
        editableObject.CancelEdit(); 
       } 
      } 
     } 
    } 
} 

TreeViewAttachedProperties.cs

public static class TreeViewAttachedProperties 
{ 
    public static readonly DependencyProperty EditableObjectProperty = 
      DependencyProperty.RegisterAttached(
       "EditableObject", 
       typeof(IEditableObject), 
       typeof(TreeViewAttachedProperties)); 

    [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Justification = "Derived type is intentionally used to restrict the parameter type.")] 
    public static void SetEditableObject(TreeView treeView, IEditableObject obj) 
    { 
     treeView.SetValue(EditableObjectProperty, obj); 
    } 

    [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Justification = "Derived type is intentionally used to restrict the parameter type.")] 
    public static IEditableObject GetEditableObject(TreeView treeView) 
    { 
     return (IEditableObject)treeView.GetValue(EditableObjectProperty); 
    } 

    public static readonly DependencyProperty IsEditingObjectProperty = 
     DependencyProperty.RegisterAttached(
      "IsEditingObject", 
      typeof(bool), 
      typeof(TreeViewAttachedProperties)); 

    [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Justification = "Derived type is intentionally used to restrict the parameter type.")] 
    public static void SetIsEditingObject(TreeView treeView, bool value) 
    { 
     treeView.SetValue(IsEditingObjectProperty, value); 
    } 

    [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Justification = "Derived type is intentionally used to restrict the parameter type.")] 
    public static bool GetIsEditingObject(TreeView treeView) 
    { 
     return (bool)treeView.GetValue(IsEditingObjectProperty); 
    } 
} 

TransactionCommands。CS:

public static class TransactionCommands 
{ 
    private static readonly RoutedUICommand _edit = new RoutedUICommand("Edit", "Edit", typeof(TransactionCommands)); 

    public static RoutedUICommand Edit 
    { 
     get { return _edit; } 
    } 

    private static readonly RoutedUICommand _cancel = new RoutedUICommand("Cancel", "Cancel", typeof(TransactionCommands)); 

    public static RoutedUICommand Cancel 
    { 
     get { return _cancel; } 
    } 

    private static readonly RoutedUICommand _commit = new RoutedUICommand("Commit", "Commit", typeof(TransactionCommands)); 

    public static RoutedUICommand Commit 
    { 
     get { return _commit; } 
    } 

    private static readonly RoutedUICommand _delete = new RoutedUICommand("Delete", "Delete", typeof(TransactionCommands)); 

    public static RoutedUICommand Delete 
    { 
     get { return _delete; } 
    } 

    private static readonly RoutedUICommand _collapse = new RoutedUICommand("Collapse", "Collapse", typeof(TransactionCommands)); 

    public static RoutedUICommand Collapse 
    { 
     get { return _collapse; } 
    } 
} 

File.cs:

public class File : IEditableObject, INotifyPropertyChanged 
{ 
    private bool _editing; 
    private string _name; 

    public File(string name) 
    { 
     _name = name; 
    } 

    public string Name 
    { 
     get 
     { 
      return _name; 
     } 

     set 
     { 
      if (_name != value) 
      { 
       _name = value; 
       OnPropertyChanged("Name"); 
      } 
     } 
    } 

    #region IEditableObject 

    [Browsable(false)] 
    protected string CachedName 
    { 
     get; 
     private set; 
    } 

    [Browsable(false)] 
    public bool IsInEditMode 
    { 
     get { return _editing; } 
     private set 
     { 
      if (_editing != value) 
      { 
       _editing = value; 
       OnPropertyChanged("IsInEditMode"); 
      } 
     } 
    } 

    public virtual void BeginEdit() 
    { 
     // Save name before entering edit mode. 
     CachedName = Name; 
     IsInEditMode = true; 
    } 

    [EnvironmentPermission(SecurityAction.Demand, Unrestricted = true)] 
    public virtual void EndEdit() 
    { 
     CachedName = string.Empty; 
     IsInEditMode = false; 
    } 

    public void CancelEdit() 
    { 
     if (IsInEditMode) 
     { 
      if (CachedName != null) 
      { 
       Name = CachedName; 
      } 

      CachedName = string.Empty; 
      IsInEditMode = false; 
     } 
    } 

    public void SetCachedName(string cachedName) 
    { 
     CachedName = cachedName; 
    } 

    #endregion 

    #region INotifyPropertyChanged 

    public event PropertyChangedEventHandler PropertyChanged; 

    protected void OnPropertyChanged(string property) 
    { 
     if (PropertyChanged != null) 
     { 
      PropertyChanged(this, new PropertyChangedEventArgs(property)); 
     } 
    } 

    #endregion 
} 

回答

1

您可以當你失去重點樹型視圖添加事件處理程序。

事件處理方法在視圖模型(或數據上下文):

/// <summary> 
/// This is a template method to show that something occurs when you lose focus on the TreeViewItem 
/// </summary> 
/// <param name="sender">TreeViewItem</param> 
/// <param name="e">Routed Event arguments</param> 
public void treeView_FocusLoser(object sender, RoutedEventArgs e) { 
     MessageBox.Show("Argg!"); 
} 

XAML爲樹型視圖引發LostFocus:

<TreeView Name="myTreeView"> 
     <TreeView.ItemContainerStyle> 
      <Style TargetType="{x:Type TreeViewItem}"> 
       <EventSetter Event="TreeViewItem.LostFocus" Handler="treeView_FocusLoser" /> 
      </Style> 
     </TreeView.ItemContainerStyle> 
</TreeView> 

的XAML TreeView的引發LostFocus:

<TreeView Name="myTreeView"> 
     <TreeView.Style> 
      <Style TargetType="{x:Type TreeView}"> 
       <EventSetter Event="TreeView.LostFocus" Handler="treeView_FocusLoser" /> 
      </Style> 
     </TreeView.Style> 
</TreeView> 
+0

感謝您的回覆。回想起來,這似乎很明顯。 – Craig

0

我跑進同樣的問題,但即使用戶點擊框外,也需要引發焦點丟失一個不可聚焦的元素。我找到了一個解決方案,但它並不漂亮:

在UI的主容器元素上,爲PreviewMouseDown事件創建一個事件處理程序。然後在事件處理程序中,找出點擊來自何處以及是否需要處理:

private void GridPreviewMouseDown(object sender, MouseButtonEventArgs e) 
    { 
     var parent = FindVisualParent<StackPanel>((DependencyObject)e.OriginalSource); 
     if (parent != null && parent.Tag == "IgnoreClickPanel") 
     { 
      //ignore previewclicks from these controls 
     } 
     else 
     { 
      //prism eventaggregator will notify all user controls which care about this 
      eventAggregator.GetEvent<MouseDownEvent>().Publish(true); 
     } 
     e.Handled = false; 
    } 
相關問題