問題:如何在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);
}
}
雖然這確實解決了我的問題,它有兩個不好的副作用:
- 當出現MessageBox時,TreeView項目被迫離開編輯模式。
- 如果我在編輯模式下右鍵單擊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
}
感謝您的回覆。回想起來,這似乎很明顯。 – Craig