午後わてんのブログ

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

WPF、カスタムコントロール使ってみた、マウスドラッグ移動できるTextBlockを作成

結果

カスタムコントロールの動作確認

Visual Studio起動からのCustomControl作成手順

プロジェクトを新規作成


WPFアプリケーションを選択


プロジェクト名を決定
今回は20241205_Customにした


フレームワークの選択
今なら.NET8.0で問題ないはず


いつもの画面
いつものデザイン画面、ここまではいつもと同じ


ソリューションエクスプローラーを表示
メニューにある表示からでも表示できるし、ctrl+alt+Lでも表示できる
プロジェクト名のところで右クリック

追加→新しい項目
メニューから追加→新しい項目をクリック

新しい項目の追加
一覧からWPFのカスタムコントロールを選択、名前をつけて追加する
今回の名前はCustomControl1にした

追加された
ソリューションエクスプローラーで確認すると、CustomControl1.csとThemesフォルダと、その中にGeneric.xamlが追加されている
どうやらこの2つが連携していて、カスタムコントロールになるみたい

CustomControl1.cs
わかっている人向けの使い方が書いてある

Generic.xaml
こっちはエラーを示す波線が表示されている
何のエラーなのか見てみると


エラー内容
CustomControl1が見つからないって言ってる、すぐ隣りにあるのに?
これはビルドすると認識してくれる

ビルド


エラー解消



マウスドラッグ移動できるTextBlock

その前に
2つのファイルの初期状態を見てみる

2つのファイルの初期状態
一応これでCustomControl1っていうカスタムコントロールはできているってことかな?
目立つ機能はないし、Generic.xamlを見ても、中身はBorderだけ
でも、Templateを指定しているので、これを真似して書いていけばできそう(できた)

Thumbコントロールはドラッグ移動系のイベントが用意されているので、これを継承して、見た目をTextBlockに変更する
見た目の変更はTemplateプロパティにTextBlockを指定する

TextThumb
CustomControl1.csにTextThumbクラスを追加したらビルド
TextThumbはThumbを継承
MyTextはDependencyProperty(依存関係プロパティ)
これは、TextBlockにあるような文字列を表示するTextプロパティが、Thumbにはないので追加した
これをTextBlockのTextプロパティとBindingする

staticとpublicなコンストラクタ?
staticの方は初期状態にあったのを真似て書いただけで、最初はここにBinding用のDataContext = this;を書いたらエラーになったので、publicなのを用意して書いた
言われてみればなんとなくわかるけど、まだ直感的には理解できない

これでCustomControl1.csのほうは完成、次はGeneric.xaml

入力候補に出てくる
TextThumbのほうをビルドしてあれば、入力候補に出てくる


書けたらビルド
こっちも初期状態で書いてあったのを見ながら書いた
ControlTemplateにTextBlockを指定して、TextプロパティをMyTextプロパティとBindingしただけ

これでCustomControlのTextThumb完成!


使ってみる
その前に確認

ツールボックス

デザイン画面
入力候補に出てくる

登録した依存関係プロパティ
これも入力候補に出てくる

できた!


DragDeltaイベント
Thumbコントロールを継承しているので、DragDeltaイベントも候補に出てくる

イベントハンドラー追加
F12キーで定義に移動して

MainWindow.xaml.cs
マウスドラッグ移動の処理を追加、11行目と、27から30行目
これでマウスドラッグ移動できるはず


背景色とかも指定したい

背景色とか指定
画像ではBackground(背景色)の指定が反映されているけど、一手間掛ける必要があった
ThumbにもBackgroundっていういかにも背景色を指定できそうなプロパティはあるんだけど、指定しても無視されるので、Templateで使っているTextBlockのBackgroundとBindingすることにした

Background以外のForegroundやFontSizeは何もしなくても反映された、プロパティによって違うんだねえ

Generic.xamlに戻ってBackgroundのBinding

TemplateBinding
25行目がそれ
TextBlockのBackgroundプロパティと
ThumbのBackgroundプロパティを
TemplateBindingってのを使ってBinding
これでBackgroundも反映される

同じようなプロパティが双方にある場合のBindingはTemplateBindingってのを使えば、わざわざ自前でDependencyPropertyを作らなくてもできるみたい


移動できて文字表示できるEllipse●

さっきのTextThumbを継承して、MaruThumbクラスを作成

MaruThumb
78行目から88行目がそれ

文字列を受け取るTextプロパティは、TextThumbから継承しているMyTextプロパティがあるので、新たに書く必要がない、楽ちん


Generic.xamlにMaruThumb用のStyleを追加

MaruThumbのXAML
31行目から44行目がそれ
Gridの中でEllipseにTextBlockを重ねている

36行目
EllipseのFillプロパティとTextThumbのBackgroundプロパティをTemplateBindingでBindingしている、プロパティの名前は違うけど型はどちらも同じBrush型なのでこれで問題ないみたい

37行目
同じようにTemplateBindingを使ってTextBlockのTextプロパティとTextThumbのMyTextプロパティをBinding
その下2行はTextBlockの中央寄せ表示

一つのファイルに複数のカスタムコントロール

プロジェクトにカスタムコントロールを新規作成で追加されたCustomControl1.csには、複数のカスタムコントロールを書いてもいいみたいで、期待通りに動いている


テストアプリのコード

2024WPF/20241205_Custom at master · gogowaten/2024WPF

github.com

CustomControl1.cs

using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;

namespace _20241205_Custom
{
    public class TextThumb : Thumb
    {

        public string MyText
        {
            get { return (string)GetValue(MyTextProperty); }
            set { SetValue(MyTextProperty, value); }
        }
        public static readonly DependencyProperty MyTextProperty =
            DependencyProperty.Register(nameof(MyText), typeof(string), typeof(TextThumb), new PropertyMetadata(string.Empty));

        static TextThumb()
        {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(TextThumb), new FrameworkPropertyMetadata(typeof(TextThumb)));
        }

        public TextThumb()
        {
            DataContext = this;
        }
    }

    public class MaruThumb : TextThumb
    {
        static MaruThumb()
        {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(MaruThumb), new FrameworkPropertyMetadata(typeof(MaruThumb)));
        }
        public MaruThumb()
        {
            DataContext = this;
        }
    }
}



Generic.xaml

<ResourceDictionary
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:_20241205_Custom">

  <Style TargetType="{x:Type local:TextThumb}">
    <Setter Property="Template">
      <Setter.Value>
        <ControlTemplate TargetType="{x:Type local:TextThumb}">
          <TextBlock Text="{Binding MyText}"
                     Background="{TemplateBinding Background}"/>
        </ControlTemplate>
      </Setter.Value>
    </Setter>
  </Style>

  <Style TargetType="{x:Type local:MaruThumb}">
    <Setter Property="Template">
      <Setter.Value>
        <ControlTemplate TargetType="{x:Type local:MaruThumb}">
          <Grid>
            <Ellipse Fill="{TemplateBinding Background}"/>
            <TextBlock Text="{TemplateBinding MyText}"
                       HorizontalAlignment="Center"
                       VerticalAlignment="Center"/>
          </Grid>
        </ControlTemplate>
      </Setter.Value>
    </Setter>
  </Style>

</ResourceDictionary>



MainWindow.xaml.cs

using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;

namespace _20241205_Custom
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        private void TextThumb_DragDelta(object sender, DragDeltaEventArgs e)
        {
            if (sender is TextThumb tt)
            {
                Canvas.SetLeft(tt, Canvas.GetLeft(tt) + e.HorizontalChange);
                Canvas.SetTop(tt, Canvas.GetTop(tt) + e.VerticalChange);
            }
        }
    }
}



MainWindow.xaml

<Window x:Class="_20241205_Custom.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:_20241205_Custom"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="400">
  <Grid>
    <Canvas>
      <local:TextThumb Canvas.Left="10" Canvas.Top="10"
                       MyText="TextBlockThumb"
                       Background="Tomato"
                       Foreground="LemonChiffon"
                       FontSize="30"
                       DragDelta="TextThumb_DragDelta"/>

      <local:MaruThumb Canvas.Left="100" Canvas.Top="100"
                       Width="100" Height="100"
                       Background="MediumAquamarine"
                       Foreground="Azure"
                       MyText="bbb" FontSize="30"
                       DragDelta="TextThumb_DragDelta"/>
    </Canvas>
  </Grid>
</Window>



作成と動作環境



参照したところ

カスタムコントロール(CustomControl)を作る - tera1707’s blog

tera1707.com

感想

ブログのアイキャッチ画像用
今までは移動できるTextBlockを作成するときには、ThumbのTemplateを改変するところをC#でのコード(コードビハインド)で書いていたけど、今回はTemplate改変をXAMLで書くことができるCustomControlで作成してみた
なんかねえ、Templateをコードビハインドで書くのは良くて、理由は、書いた通りにならないことがあるからってことらしい
でも今回ので解決できた

WPFのコントロール作成にはユーザーコントロールとカスタムコントロールがある
ユーザーコントロールでは、以前にNumericUpDownもどきを作ったことがあったので、これでできないか試したんだけど、Thumbを継承しての作成はできなかった
ユーザーコントロールっていうコントロールの中に別のコントロールを組み込んでいく感じで、どこまで行ってもユーザーコントロールが主体、基軸

一方のカスタムコントロールは難しそうな雰囲気があるので敬遠していたけど、もうこれしかないと思って試してみたらできたってわけ
カスタムコントロールは継承元を選べるので自由度が高い


関連記事

次、2日後
WPF、ItemsControlを使って要素を入れ子にできるカスタムコントロールThumb、グループ化みたいなもの - 午後わてんのブログ gogowaten.hatenablog.com

19日後、同じようなものをDataTemplateSelectorで表現できた
DataTemplateSelector使ってみた、実際便利 - 午後わてんのブログ gogowaten.hatenablog.com

1ヶ月後
WPF、この一ヶ月でのカスタムコントロールThumbのマウスドラッグ移動のまとめ - 午後わてんのブログ
gogowaten.hatenablog.com

1ヶ月後
WPF、サイズ可変+マウスドラッグ移動可能なCanvasを「できるだけ簡易」にカスタムコントロールで作ってみた - 午後わてんのブログ
gogowaten.hatenablog.com


2年前
WPF、マウスドラッグ移動できるTextBox、Templateを改変したThumbで作成 - 午後わてんのブログ gogowaten.hatenablog.com

3年前
WPF、Image(画像)をマウスドラッグ移動、ThumbのTemplateを変更して作成 - 午後わてんのブログ

gogowaten.hatenablog.com