だるろぐ

明日できることは、今日しない。

UWP:ユーザーコントロールを作ってみる

f:id:daruyanagi:20160412165802p:plain

今回作るのは、文字数カウンター付きのテキストボックス。まずはユーザーインターフェイス。

<UserControl
    x:Class="Hateboo.UserControls.TextBoxWithCounter"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:Hateboo.UserControls"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    d:DesignHeight="300"
    d:DesignWidth="400">

    <StackPanel>
        <TextBox x:Name="textBox" TextChanged="textBox_TextChanged" />

        <StackPanel Orientation="Horizontal" HorizontalAlignment="Right">
            <TextBlock x:Name="textBlockCurrent">0</TextBlock>
            <TextBlock>/</TextBlock>
            <TextBlock x:Name="textBlockMax">0</TextBlock>
        </StackPanel>
    </StackPanel>
</UserControl>

単にテキストボックスとカウンターラベル(0/100 みたいな表示)を配置しただけ。

(コードを張り付けてから気づいたが、0/100 の / を表示するためだけに TextBlock 使ってるのはアレだな。Run とか使えばよかった)

このコントロールにほしいプロパティは、

  • Text:TextBox の内容
  • Current:現在の TextBox 文字数
  • Max:TextBox に入力できる最大の文字数。これをオーバーすると、ラベルが赤くなる

ぐらいかな。名前がいまいちなのは気にしないでくれ(Max は Limit とかのほうがよさげやな。英語わからんから知らんけど)。

というわけで、こいつらを依存関係プロパティとして実装する。依存関係プロパティというのはいまだによくわからんが、バインディングがいい感じに動くように CLR プロパティ(フツーの C# のプロパティ)をクラスに登録しておく仕組みって感じだろうか。

基本的にはこんな感じ。

// 依存関係プロパティ
public static readonly DependencyProperty MaxProperty = DependencyProperty.Register(
    "Max", // Max という名前の……
    typeof(int), // int 型の CLR プロパティを……
    typeof(TextBoxWithCounter), // クラスに登録するやで―
new PropertyMetadata(0));

// CLR プロパティ
public int Max
{
    get { return (int)GetValue(MaxProperty); }
    set
    {
        SetValue(MaxProperty, value);
        textBlockMax.Text = value.ToString();
    }
}

だいたいはこれでいいのだけど、今回の Text プロパティだけは値が変わったときにいろいろごちゃごちゃしなきゃいけないので、コールバックを設定する。

public static readonly DependencyProperty TextProperty = DependencyProperty.Register(
    "Text",
    typeof(string),
    typeof(TextBoxWithCounter),
    // コールバックの追加
    new PropertyMetadata(string.Empty, new PropertyChangedCallback(TextPropertyChanged))
);

private static void TextPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
    var self = d as TextBoxWithCounter;
    var value = e.NewValue as string;

    // テキストボックスとラベルの値を更新しとく
    self.textBox.Text = value;
    self.Current = value.Length;

    // アカんときはアカくする
    if (self.Current > self.Max)
    {
        self.IsOverflow = true;
        self.textBlockCurrent.Foreground = new SolidColorBrush(Colors.Red);
    }
    else
    {
        self.IsOverflow = false;
        self.textBlockCurrent.Foreground = (デフォのブラシ);
    }
}

かずきさんのおかげで、案外簡単に実装できた……WPF と UWP の違いでちょっとハマったけど。