Arhn - архитектура программирования

Как я могу заставить TreeViewItem выходить из режима редактирования при щелчке вне его TreeView?

Проблема:

В приведенном ниже примере у меня есть TreeView в левом столбце и ListBox в правом столбце. TreeView отображает небольшой список образцов. Когда пользователь выбирает TreeViewItem и нажимает F2, элемент переходит в «режим редактирования», заменяя его TextBlock на TextBox.

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

Однако, если я переведу первый TreeViewItem в режим редактирования, а затем щелкну внутри ListBox, TreeViewItem останется в режиме редактирования.

Какой надежный способ заставить TreeViewItem выходить из режима редактирования, когда пользователь щелкает мышью за пределами его TreeView? Естественно, пожалуйста, не предлагайте мне просто добавлять прослушиватель мыши в ListBox; Я ищу надежное решение.


Моя лучшая попытка решить:

Я попытался добавить прослушиватель событий 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, TreeViewItem вынужден выйти из режима редактирования.
  2. Если я щелкну правой кнопкой мыши внутри TreeViewItem в режиме редактирования, это заставит TreeViewItem выйти из режима редактирования. Это не позволяет мне использовать контекстные меню в TextBox TreeViewItem.

Пример кода:

(Этот образец можно загрузить из 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
}
03.12.2012

Ответы:


1

Вы можете добавить обработчик событий, когда теряете фокус на TreeViewItem.

Метод обработчика событий в модели просмотра (или контексте данных):

 /// <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 для TreeViewItem 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>
04.12.2012
  • Спасибо за ответ. Оглядываясь назад, это кажется таким очевидным. 06.12.2012

  • 2

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

    В главном элементе контейнера вашего пользовательского интерфейса создайте обработчик событий для события 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;
        }
    
    04.12.2012
    Новые материалы

    Коллекции публикаций по глубокому обучению
    Последние пару месяцев я создавал коллекции последних академических публикаций по различным подполям глубокого обучения в моем блоге https://amundtveit.com - эта публикация дает обзор 25..

    Представляем: Pepita
    Фреймворк JavaScript с открытым исходным кодом Я знаю, что недостатка в фреймворках JavaScript нет. Но я просто не мог остановиться. Я хотел написать что-то сам, со своими собственными..

    Советы по коду Laravel #2
    1-) Найти // You can specify the columns you need // in when you use the find method on a model User::find(‘id’, [‘email’,’name’]); // You can increment or decrement // a field in..

    Работа с временными рядами спутниковых изображений, часть 3 (аналитика данных)
    Анализ временных рядов спутниковых изображений для данных наблюдений за большой Землей (arXiv) Автор: Рольф Симоэс , Жильберто Камара , Жильберто Кейрос , Фелипе Соуза , Педро Р. Андраде ,..

    3 способа решить квадратное уравнение (3-й мой любимый) -
    1. Методом факторизации — 2. Используя квадратичную формулу — 3. Заполнив квадрат — Давайте поймем это, решив это простое уравнение: Мы пытаемся сделать LHS,..

    Создание VR-миров с A-Frame
    Виртуальная реальность (и дополненная реальность) стали главными модными терминами в образовательных технологиях. С недорогими VR-гарнитурами, такими как Google Cardboard , и использованием..

    Демистификация рекурсии
    КОДЕКС Демистификация рекурсии Упрощенная концепция ошеломляющей О чем весь этот шум? Рекурсия, кажется, единственная тема, от которой у каждого начинающего студента-информатика..