午後わてんのブログ

ベランダ菜園とWindows用アプリ作成(WPFとC#)

WPF、マウスドラッグ移動できるTextBox、Templateを改変したThumbで作成

結果

Animation20220615_112928.gif



コード

2022WPF/20220615_TextBoxThumb0/20220615_TextBoxThumb0 at master · gogowaten/2022WPF
github.com


MainWindow.xaml

<Window x:Class="_20220615_TextBoxThumb0.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:_20220615_TextBoxThumb0"
        mc:Ignorable="d"
        Title="MainWindow" Height="250" Width="400">

  <Canvas Name="MyCanvas"/>
</Window>



MainWindow.xaml.cs

using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Controls.Primitives;

namespace _20220615_TextBoxThumb0
{
    public partial class MainWindow : Window
    {
        private TBThumb MyTBThumb;
        private TBThumb2 MyTBThumb2;
        public MainWindow()
        {
#if DEBUG
            Left = 100; Top = 100;
#endif
            InitializeComponent();

            MyTBThumb = new TBThumb();
            MyCanvas.Children.Add(MyTBThumb);
            MyTBThumb.TextBox.Text = "移動できないTextBoxThumb";
            MyTBThumb.DragDelta += Thumb_DragDelta;

            MyTBThumb2 = new TBThumb2();
            MyCanvas.Children.Add(MyTBThumb2);
            MyTBThumb2.TextBox.Text = "ダブルクリックで\nテキスト編集と移動を切り替え";
            MyTBThumb2.TextBox.AcceptsReturn = true;//改行を有効
            MyTBThumb2.DragDelta += Thumb_DragDelta;
        }

        private void Thumb_DragDelta(object sender, DragDeltaEventArgs e)
        {
            if (sender is not FrameworkElement elem) { return; }
            Canvas.SetLeft(elem, Canvas.GetLeft(elem) + e.HorizontalChange);
            Canvas.SetTop(elem, Canvas.GetTop(elem) + e.VerticalChange);
        }
    }

    //TemplateをTextBoxにしたThumb、マウスドラッグ移動できない
    public class TBThumb : Thumb
    {
        private const string TEXTBOX_NAME = "ttt";
        public TextBox TextBox;
        public TBThumb()
        {
            this.Template = MakeControlTemplate();
            //Templateの更新、必要
            ApplyTemplate();
            //Templateの中のTextBoxを検索、取得
            this.TextBox = (TextBox)Template.FindName(TEXTBOX_NAME, this);
            Canvas.SetLeft(this, 0); Canvas.SetTop(this, 0);
        }

        //TextBoxをベースにしたControlTemplateを作成
        private ControlTemplate MakeControlTemplate()
        {
            FrameworkElementFactory textF = new(typeof(TextBox), TEXTBOX_NAME);
            ControlTemplate template = new();
            template.VisualTree = textF;
            return template;
        }
    }

    //TemplateをGridを乗せたTextBoxにしたThumb、マウスドラッグ移動できる
    //ダブルクリックで移動モードとText編集モードを切り替える
    public class TBThumb2 : Thumb
    {
        private const string COVER_NAME = "cover";
        private const string TEXTBOX_NAME = "ttt";
        public Grid CoverGrid;
        public TextBox TextBox;
        public TBThumb2()
        {
            this.Template = MakeControlTemplate();
            //Templateの更新、必要
            ApplyTemplate();
            //Templateの中のTextBoxと蓋用のGridを検索、取得
            this.TextBox = (TextBox)Template.FindName(TEXTBOX_NAME, this);
            this.CoverGrid = (Grid)Template.FindName(COVER_NAME, this);
            //表示位置を指定
            Canvas.SetLeft(this, 0); Canvas.SetTop(this, 0);
            //ダブルクリック時の動作
            MouseDoubleClick += TBThumb2_MouseDoubleClick;
        }
        //ControlTemplate作成
        private static ControlTemplate MakeControlTemplate()
        {
            FrameworkElementFactory coverF = new(typeof(Grid), COVER_NAME);//蓋
            FrameworkElementFactory textF = new(typeof(TextBox), TEXTBOX_NAME);
            FrameworkElementFactory underF = new(typeof(Grid));//ベースGrid
            //蓋の背景は透明色
            coverF.SetValue(Panel.BackgroundProperty, Brushes.Transparent);
            //ベースGridに要素追加、順番はTextBox、蓋Gridの順
            underF.AppendChild(textF);
            underF.AppendChild(coverF);
            //テンプレート作成、VisualTreeにベースを指定
            ControlTemplate template = new();
            template.VisualTree = underF;
            return template;
        }

        //ダブルクリックでテキスト編集状態の切り替え
        private void TBThumb2_MouseDoubleClick(object sender, MouseButtonEventArgs e)
        {
            //蓋の背景が透明色ならTextBoxを編集状態にする、背景をnullにする
            if (CoverGrid.Background == Brushes.Transparent)
            {
                CoverGrid.Background = null;
                Keyboard.Focus(TextBox);
            }
            //それ以外なら編集状態終了、背景を透明色にしてキーボードのフォーカスを外す
            else
            {
                CoverGrid.Background = Brushes.Transparent;
                Keyboard.ClearFocus();//キーボードのフォーカスを外す
            }
        }
    }
}



最初は失敗した
TemplateをTextBoxに変更したThumbを作れば、
ドラッグ移動可能なTextBoxになると思って作ったクラスが↓

TBThumb
Thumbを継承したクラスにして、41行目
TextBoxだけで構成したTemplateを作成、57行目
これを自身のTemplateに指定する、47行目
で、これをMainWindowのほうで
MainWindow
ドラッグ移動イベント(DragDelta)で移動するようにしたんだけど、これが動かない
どうやらドラッグ移動イベント自体が発生していないみたい
デバッグ
要素の選択
要素の選択を有効にするとクリックした要素が判別できるから、これで見てみると
クリックした要素?
TBThumbの中のTextBoxのそれも、かなり下の方にあるTextBoxViewってのが選択されていた
ドラッグ移動イベントはもっと上の方で発生するのに、それをスルーしてTextBoxViewってのが選択されてしまうから移動しないのかも?
じゃあTextBoxの上に何かの要素を乗せて蓋をしておけば、TextBoxViewより蓋が選択されてドラッグ移動イベントが発生するじゃないかと考えてできたのがTBThumb2↓

TBThumb2
蓋にはGrid要素を使った
TextBoxと蓋を重ねるから、それを詰め込むためのPanel要素にもGridを使用

  • Grid
    • TextBox
    • Grid(蓋)

GridやCanvas要素は背景色を指定しない(null)と、クリックが無視されるので透明色を指定
これで見た目は普通のTextBoxになるし、ドラッグ移動もできるようになる、けどこのままだと
今度はクリックが蓋に邪魔されてTextBoxまで到達できないので、テキスト編集ができない
これは蓋の背景色をnullにすれば蓋をスルーするようになるので、
要は蓋の背景色を透明とnullの切り替えができればいい
今回はダブルクリックで切り替えるようにしてみた

ダブルクリック時の動作
キーボードのフォーカスを外すKeyboard.ClearFocus()
これをしないと編集状態を終了して移動モードになってもカーソルが残ったままになる
この方法は
WPFでコントロールからフォーカスを外す方法 - Qiita
qiita.com
こちらから

要素の選択:移動時

移動時
蓋用のGridが選択されるのかとも思っていたけど、選択されていたのは一番上のTBThumb2だった
テキスト編集状態のときは?
テキスト編集時
これは普通にTextBoxViewが選択されていた
この辺の動作はさっぱりわからんし、TextBoxって11個もの要素で構成されているのは意外だった、へー


あとは文字の装飾したい!

関連記事

次回のWPF記事
gogowaten.hatenablog.com

前回のWPF記事は昨日
gogowaten.hatenablog.com

8年前
gogowaten.hatenablog.com

gogowaten.hatenablog.com