ダブルクリックで編集可能になるTextBoxをつくる2
大昔にダブルクリックで編集可能になるTextBoxを実装した(ダブルクリックで編集可能になるTextBoxをつくる - 陰間茶屋)のだが、Drag操作で難があったので違う実装をしてみることにした。
今度はUserControlにLabelとTextBoxを置いて、編集操作で表示を切り替える作戦。
Labelって書いたけど"_"の表示ができなかったので実際にはTextBlockになった。
コードはこんな感じになった。
もうちょっとうまい書き方があるような気はする。
EditableLabel.xaml.cs
<UserControl x:Class="Railgun.Views.EditableLabel" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:local="clr-namespace:Railgun.Views" mc:Ignorable="d" d:DesignHeight="100" d:DesignWidth="200"> <Grid> <!-- ラベルだと_表記が省略される。TextBlockだとダブルクリックがとれない。そのため構造を挟む --> <ContentControl x:Name="PART_Label" Visibility="Visible"> <TextBlock x:Name="PART_LabelText" Text="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:EditableLabel}}, Path=Text}" Padding="5,3,5,3"/> </ContentControl> <!-- 最初はHiddenにしておかないと領域計算ができず、ダブルクリック後のカーソル位置の計算が上手くいかない --> <TextBox x:Name="PART_TextBox" Visibility="Hidden" AcceptsReturn="True" Background="{x:Null}" Text="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:EditableLabel}}, Path=Text}"/> </Grid> </UserControl>
EditableLabel.xaml
using Railgun.Helper; using System; using System.Windows; using System.Windows.Controls; using System.Windows.Input; namespace Railgun.Views { // 参考: https://stackoverflow.com/questions/32368931/universally-usable-editable-label /// <summary> /// EditableLabel.xaml の相互作用ロジック /// </summary> public partial class EditableLabel : UserControl { public EditableLabel() { EditLabelCommand = new EditLabelCommand(this); InitializeComponent(); // InputBindingのためにはKeyboardFocusが必要になる Focusable = true; } // bool IsEditing 編集可能か? public static readonly DependencyProperty IsEditingProperty = DependencyProperty.Register("IsEditing", typeof(bool), typeof(EditableLabel), new FrameworkPropertyMetadata( false, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnIsEditingChanged)); public bool IsEditing { get { return (bool)GetValue(IsEditingProperty); } set { SetValue(IsEditingProperty, value); } } static void OnIsEditingChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e) { EditableLabel self = obj as EditableLabel; if (self.IsEditing) { self.PART_Label.Visibility = Visibility.Collapsed; self.PART_TextBox.Visibility = Visibility.Visible; } else { self.PART_Label.Visibility = Visibility.Visible; self.PART_TextBox.Visibility = Visibility.Collapsed; } } // string Text 文字列 public static readonly DependencyProperty TextProperty = DependencyProperty.Register("Text", typeof(string), typeof(EditableLabel), new FrameworkPropertyMetadata("", FrameworkPropertyMetadataOptions.AffectsRender | FrameworkPropertyMetadataOptions.AffectsParentMeasure | FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnTextChanged)); public string Text { get { return (string)GetValue(TextProperty); } set { SetValue(TextProperty, value); } } static void OnTextChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e) { } // ICommand EditLabelCommand 編集コマンド public static readonly DependencyProperty EditLabelCommandProperty = DependencyProperty.Register("EditLabelCommand", typeof(ICommand), typeof(EditableLabel), new PropertyMetadata(null, OnEditLabelCommandChanged)); public ICommand EditLabelCommand { get { return (ICommand)GetValue(EditLabelCommandProperty); } set { SetValue(EditLabelCommandProperty, value); } } static void OnEditLabelCommandChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e) { EditableLabel self = obj as EditableLabel; // 古いコマンドを削除 ICommand old = e.OldValue as ICommand; self.RemoveInputBinding(old, Key.F2, ModifierKeys.None); // 新しいコマンドを登録 ICommand command = e.NewValue as ICommand; if (command != null) { self.InputBindings.Add(new KeyBinding(command, Key.F2, ModifierKeys.None)); } } // コマンド削除 void RemoveInputBinding(ICommand command, Key key, ModifierKeys modifier) { foreach (InputBinding binding in InputBindings) { KeyBinding kb = binding as KeyBinding; if (kb != null) { if (kb.Command == command && kb.Key == key && kb.Modifiers == modifier) { InputBindings.Remove(binding); return; } } } } /*-----------------------------------------------*/ public override void OnApplyTemplate() { base.OnApplyTemplate(); PART_Label.MouseDoubleClick += Label_MouseDoubleClick; PART_TextBox.LostKeyboardFocus += TextBox_LostKeyboardFocus; PART_TextBox.PreviewKeyDown += TextBox_PreviewKeyDown; // 他の要素をクリックしたときに編集モードを解除したいが難しい・・・ } // マウスクリック protected override void OnMouseUp(MouseButtonEventArgs e) { base.OnMouseUp(e); // InputBindingのためにはKeyboardFocusが必要になる Focus(); e.Handled = true; } // ラベルのダブルクリック void Label_MouseDoubleClick(object sender, MouseButtonEventArgs e) { IsEditing = true; // ダブルクリック後のカーソル位置を決める Point pos = Mouse.GetPosition(PART_TextBox); int textIndex = PART_TextBox.GetCharacterIndexFromPoint(pos, false); if (textIndex < 0) { textIndex = PART_TextBox.Text.Length; } PART_TextBox.Select(textIndex, 0); // BeginInvokeで実行しないとフォーカスが取れなかった Dispatcher.BeginInvoke(new Action(() => { PART_TextBox.Focus(); })); } // テキストボックスのキーボードフォーカスがなくなった void TextBox_LostKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e) { // 表示タイミングが合わないので明示的に入れる Text = PART_TextBox.Text; IsEditing = false; } // テキストボックスのキーが押されたときの前処理 void TextBox_PreviewKeyDown(object sender, KeyEventArgs e) { // Enter, Escapeが押されたら編集終了 bool isShift = ((Keyboard.Modifiers & ModifierKeys.Shift) == ModifierKeys.Shift); if (e.Key == Key.Enter && !isShift) { // 表示タイミングが合わないので明示的に入れる Text = PART_TextBox.Text; IsEditing = false; e.Handled = true; } else if (e.Key == Key.Escape) { // この時点ではTextは編集前の値なので、ここで戻せばキャンセルが間に合う PART_TextBox.Text = Text; IsEditing = false; e.Handled = true; } } } // 編集可能にするコマンド class EditLabelCommand : DelegateCommand { public EditLabelCommand(EditableLabel editableLabel) : base( () => { editableLabel.IsEditing = true; editableLabel.PART_TextBox.Focus(); } ) { } } }
ダブルクリックで編集モードになる。
F2キーを押したときも編集モードになる。
Enterを押すと編集完了。
Escapeを押したときも編集完了、だけど、編集前の値に戻る。
他の要素をクリックしたときに編集モードを解除させたかったんだけどやり方わからなくてできなかった。
フォーカスが違うところに行く場合なら編集モードが解除されるんだけど。
誰か教えて・・・。
WPF勉強中なんだけど分からんことが多い。
俺は雰囲気と勘と勢いでxamlを書いている。
デバッグは泣きながら行う。
参考にしたのはこのあたり
c# - Bind Command to KeyBinding in Code Behind - Stack Overflow
c# - UserControl InputBindings Only working after pressing a button first - Stack Overflow
wpf - Do mouse clicks bring keyboard focus to focusable controls by default? - Stack Overflow