Работа с 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;
}
}
}