Фокусирующий элемент, созданный в триггере стиля через ContentTemplate - PullRequest
0 голосов
/ 10 марта 2012

Работа с WPF Я пытаюсь создать TextBlock, который при двойном щелчке «превращается» в TextBox, готовый для редактирования.После этого клавиша Enter, клавиша Esc или потеря фокуса приводят к завершению редактирования, а TextBox возвращается в TextBlock.

Решение, которое я нашел, в основном работает.Моя проблема в том, что я не могу сфокусировать TextBox на «преобразовании», что заставляет пользователя явно щелкнуть еще раз по элементу, чтобы сфокусировать его и начать редактирование.

Объяснение кода

Я выбрал для реализации этого поведения работу с шаблонами и стилями DataTriggers для изменения шаблона элемента.В примере, который я показываю, элемент представляет собой простой ContentControl, хотя реальный вариант использования, в котором я пытаюсь сделать это, немного сложнее (у меня есть ListBox, где каждый элемент редактируется через это поведение, по одному за раз).

Идея заключается в следующем:

  • У ContentControl есть связанный стиль
  • С соответствующим стилем определяется ContentTemplate как TextBlock.
  • У объекта модели есть свойство InEditing, которое становится истинным, когда я хочу отредактировать элемент управления
  • С помощью привязки мыши к TextBlock для свойства InEditing модели устанавливается значение True
  • .Стиль имеет DataTrigger, который слушает InEditing, и в этом случае устанавливает ContentTemplate на TextBox
  • . Через EventSetters я перехватываю Enter, Esc и LostFocus, чтобы отменить сохранение изменений и вернуться к предыдущему стилю.Обратите внимание: я не могу напрямую прикреплять события к TextBox, в противном случае я получаю Событие не может быть указано для тега Target в стиле.Вместо этого используйте EventSetter.

Хотя это и не оптимально (есть определенное сочетание видов и моделей поведения - особенно в свойстве InEditing), и я не люблю существенно повторно реализовывать фиксациюлогика изменений в модели TextBox через различные обработчики для KeyDown и LostFocus), система фактически работает без проблем.

Неудачная идея реализации

Сначала я подумал о подключении к IsVisibleChangedсобытие TextBox, и установите фокус там.Невозможно сделать это из-за вышеупомянутого Событие не может быть указано для тега Target в стиле.Вместо этого используйте EventSetter.

Решение, предложенное из-за ошибки, не может быть использовано, поскольку такое событие не является перенаправленным событием и, следовательно, не может использоваться в EventSetter.

код

Код разделен на четыре файла.

Model.cs:

using System.Windows;

namespace LeFocus
{
    public class Model: DependencyObject
    {
        public bool InEditing
        {
            get { return (bool)GetValue(InEditingProperty); }
            set { SetValue(InEditingProperty, value); }
        }

        // Using a DependencyProperty as the backing store for InEditing.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty InEditingProperty =
            DependencyProperty.Register("InEditing", typeof(bool), typeof(Model), new UIPropertyMetadata(false));


        public string Name
        {
            get { return (string)GetValue(NameProperty); }
            set { SetValue(NameProperty, value); }
        }

        // Using a DependencyProperty as the backing store for Name.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty NameProperty =
            DependencyProperty.Register("Name", typeof(string), typeof(Model), new UIPropertyMetadata("Hello!"));
    }
}

App.xaml:

<Application x:Class="LeFocus.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:lefocus="clr-namespace:LeFocus"
             StartupUri="MainWindow.xaml">
    <Application.Resources>
        <lefocus:Model x:Key="Model"/>
    </Application.Resources>
</Application>

MainWindow.xaml:

<Window x:Class="LeFocus.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:lefocus="clr-namespace:LeFocus"
        Title="MainWindow" Height="350" Width="525"
        DataContext="{Binding Source={StaticResource Model}}" 
        Name="mainWindow">
    <Window.Resources>
        <Style x:Key="SwitchingStyle"
               TargetType="{x:Type ContentControl}">
            <Setter Property="ContentTemplate">
                <Setter.Value>
                    <DataTemplate DataType="{x:Type lefocus:Model}">
                        <TextBlock Text="{Binding Path=Name}">
                            <TextBlock.InputBindings>
                                <MouseBinding MouseAction="LeftDoubleClick"
                                              Command="lefocus:MainWindow.EditName"
                                              CommandParameter="{Binding}"/>
                            </TextBlock.InputBindings>
                        </TextBlock>
                    </DataTemplate>
                </Setter.Value>
            </Setter>
            <EventSetter Event="TextBox.KeyDown" Handler="TextBox_KeyDown"/>
            <EventSetter Event="TextBox.LostFocus" Handler="TextBox_LostFocus"/>
            <Style.Triggers>
                <DataTrigger Binding="{Binding Path=InEditing}" Value="True">
                    <Setter Property="ContentTemplate">
                        <Setter.Value>
                            <DataTemplate DataType="{x:Type lefocus:Model}">
                                <TextBox Text="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=lefocus:MainWindow, AncestorLevel=1}, Path=NameInEditing, UpdateSourceTrigger=PropertyChanged}" TextChanged="TextBox_TextChanged" KeyDown="TextBox_KeyDown_1" />
                            </DataTemplate>
                        </Setter.Value>
                    </Setter>
                </DataTrigger>
            </Style.Triggers>
        </Style>
    </Window.Resources>
    <Window.CommandBindings>
        <CommandBinding Command="lefocus:MainWindow.EditName" Executed="setInEditing"/>
    </Window.CommandBindings>
    <Grid>
        <ContentControl Style="{StaticResource SwitchingStyle}" Content="{Binding}"/>
    </Grid>
</Window>

MainWindow.xaml.cs

using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;

namespace LeFocus
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }


        public string NameInEditing
        {
            get { return (string)GetValue(NameInEditingProperty); }
            set { SetValue(NameInEditingProperty, value); }
        }

        // Using a DependencyProperty as the backing store for NameInEditing.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty NameInEditingProperty =
            DependencyProperty.Register("NameInEditing", typeof(string), typeof(MainWindow), new UIPropertyMetadata(null));


        public static readonly RoutedUICommand EditName =
            new RoutedUICommand("EditName", "EditName", typeof(MainWindow));


        private void setInEditing(object sender, ExecutedRoutedEventArgs e)
        {
            var model = ((Model)e.Parameter);
            NameInEditing = model.Name;
            model.InEditing = true;
        }


        private void TextBox_KeyDown(object sender, KeyEventArgs e)
        {
            if (e.Key == Key.Enter)
            {
                var model = getModelFromSender(sender);
                model.Name = NameInEditing;
                NameInEditing = null;
                model.InEditing = false;
            }
            else if (e.Key == Key.Escape)
            {
                var model = getModelFromSender(sender);
                model.InEditing = false;
            }
        }

        private void TextBox_LostFocus(object sender, RoutedEventArgs e)
        {
            var model = getModelFromSender(sender);
            model.Name = NameInEditing;
            NameInEditing = null;
            model.InEditing = false;
        }

        private static Model getModelFromSender(object sender)
        {
            var contentControl = (ContentControl)sender;
            var model = (Model)contentControl.DataContext;
            return model;
        }
    }
}

Ответы [ 2 ]

0 голосов
/ 11 марта 2012

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

<Page.Resources>
    <ResourceDictionary>

        <Style x:Key="NullSelectionStyle" TargetType="ListBoxItem">
            <Style.Resources>
                <!-- SelectedItem with focus -->
                <SolidColorBrush x:Key="{x:Static SystemColors.HighlightBrushKey}" Color="Transparent" />
                <!-- SelectedItem without focus -->
                <SolidColorBrush x:Key="{x:Static SystemColors.ControlBrushKey}" Color="Transparent" />
                <!-- SelectedItem text foreground -->
                <SolidColorBrush x:Key="{x:Static SystemColors.HighlightTextBrushKey}" Color="{DynamicResource {x:Static SystemColors.ControlTextColorKey}}" />
            </Style.Resources>
            <Setter Property="FocusVisualStyle" Value="{x:Null}" />
        </Style>

        <Style x:Key="ListBoxSelectableTextBox" TargetType="{x:Type TextBox}">
            <Setter Property="IsHitTestVisible" Value="False" />
            <Style.Triggers>
                <DataTrigger Binding="{Binding IsSelected, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListBoxItem}, AncestorLevel=1}}" Value="True">
                    <Setter Property="IsHitTestVisible" Value="True" />
                </DataTrigger>
            </Style.Triggers>
        </Style>
    </ResourceDictionary>
</Page.Resources>
<Grid>
    <ListBox ItemsSource="{Binding Departments}" HorizontalContentAlignment="Stretch">
        <ListBox.ItemTemplate>
            <DataTemplate>
                <TextBox Margin="5" Style="{StaticResource ListBoxSelectableTextBox}" Text="{Binding Name}" BorderBrush="{x:Null}"/>
            </DataTemplate>
        </ListBox.ItemTemplate>
    </ListBox>
</Grid>
0 голосов
/ 10 марта 2012

Один метод, который может работать с этой настройкой, обрабатывает Loaded на TextBox, а затем Keyboard.Focus это (sender).

...