WPFユーザーコントロールの作成例(スライドメータ)

WPFでアプリを作るのだから、見やすく美しい画面を作らないと意味がないでしょう。
という事で、少し変わったメータをユーザーコントロールとして作ってみました。
普通のメータは針が動きますが、これは目盛りが動くタイプのメータです。
Visual Studio 2017 を使用します。

作成するユーザーコントロール

今回作成するユーザーコントロールは次のようなものです。

f:id:feynman911:20190318215654j:plain

背景の目盛りが左右に移動する事により数値を指示します。
このようなメーターは、可動範囲が大きいものを細かく見たい時に便利です。
メーターの動作にはアニメーションを付けることでアナログ的に動作させます。

プロジェクトの作成

まずユーザーコントロール用のプロジェクトを作成します。
Visual Studio 2017 を起動し、新規プロジェクトで「WPFユーザーコントロールライブラリ(.NET Framework)」を「WpfControlLibrary」という名前で作成します。

f:id:feynman911:20190318221628j:plain

作成されたプロジェクトからデフォルトの「UserControl.xaml」を削除し、新たに今回作成する「MeterSlide.xaml」を追加します。

f:id:feynman911:20190318221956j:plain

また、ソリューションにはユーザーコントロールのテスト用にWPFアプリのプロジェクトを「ControlTest」という名前で作成し、参照に WpfControlLibrary を追加しておきます。
スタートアッププロジェクトは ControlTest に設定します。
以上で、ソリューションの構成は次のようになります。

f:id:feynman911:20190318222857j:plain

コードはそれなりに長いので、ここに置いておきます。
github.com

ユーザーコントロール MeterSlide のXAML作成

XAML側の構成は、外枠Borderと、センターのライン用Grid(CenterMark)、目盛りを表記するGrid(Memoriban)、数字を表示するGrid(Mojiban)から成ります。
目盛りと文字をXAMLで作るのは大変なので、コードビハインドに記述します。
コードは長いのでGitHubソースコードを見てもらう事にして、簡単な説明のみ記述します。

f:id:feynman911:20190318224347j:plain

ユーザーコントロールのサイズが変わった時に目盛りと文字が追従できるようにするために、DisplayAreaのプロパティでDisplayArea_SizeChangedイベントを登録しておきます。

f:id:feynman911:20190319233929j:plain

SizeChangedの部分をダブルクリックすることで、コードビハインドに
private void DisplayArea_SizeChanged(object sender, SizeChangedEventArgs e)
が追加されます。

コードビハインドで目盛りとアニメーション設定

数値に従ってMemoriban と Mojiban をスライドさせてメーターにします。

private void memoribanInit()
MemoribanをDisplayAreaの1.6倍の幅に設定しRectangleで目盛りのラインを引きます。
表示はDisplayAreaでクリップされます。

private void mojibanInit()
MojibanをDisplayAreaの1.6倍の幅に設定しTextBlockで数字を表示します。
表示はDisplayAreaでクリップされます。

private void sliderAnimationInit()
アニメーション用のストーリーボードを作成し、DoubleAnimationを設定します。
DoubleAnimationと言うのはDouble型のPropertyを一定期間かけて変化させていくアニメーションです。
ここではDispValueというDependencyPropertyを変化させます。

private void MoveMemori()
Memoribanの表示位置を変更します。

private void MoveMoji()
Mojibanの表示位置を変更します。同時にTextBlockの表示文字を変更します。
TextBlockは作り直さずに再利用します。

private void SliderAnimation(double st, double end)
stからendまでアニメーションします。
ただし、アニメーションが終わる前に次が来た時の動きを滑らかにするために、
myDoubleAnimation.From = st;
コメントアウトしました。

public double DispValue
メータ表示用のDipendencyPropertyです。
[Category("MeterProperty")]
[Description("DispValue no Animation")]
と言うようにメタデータを付けることで、プロパティーを分かりやすくしてます。
DependencyPropertyはコード量が多いので、スニペットを使って作ります。

public double DispValueAnime
メータ表示値のDipendencyPropertyです。
SliderAnimation(double st, double end)に値を渡して、この値になるようにメータがアニメーションして動きます。

public double ScaleResolution
小目盛りの間隔のDipendencyPropertyです。

public double Duration
アニメーションする時間のDipendencyPropertyです。デフォルト0.5秒です。

このユーザーコントロールを貼り付けた時のプロパティーは次のように見えます。

f:id:feynman911:20190321165401j:plain

テスト用アプリの作成

テスト用アプリとしてはMeterSlideをViewに貼り付け、メータを動かすためのSliderを貼り付けて、MeterSlideのDispValueAnimeとバインドします。
他に、DispValueとDispValueAnimeの値が見れるようにTextBoxとTextBlockを貼り付けてバインドします。
メータを動かすにはSliderを操作しても良いですし、TextBoxに値を打ち込んでも良いです。

f:id:feynman911:20190321165543j:plain

TextBoxの値が反映されるのは、フォーカスが外れた時なので、値を打ち込んだ後にTABキーでフォーカスを外すとメータが動きます。
Sliderは頻繁に値が変わるので負荷が重いと感じる時には、バインディングを遅らせることができます。
デザインツールには出てこないのでXAMLを直接修正する必要があります。
Value="{Binding DispValueAnime, ElementName=meterSlide、Delay=300}"
の様にBindingの中にDelay=300を書くと、300ms停止した時に値が変化します。
こういう物が簡単に作れるというのがWPFの魅力でしょう。

まとめ

ソースコードを見てもらって修正すればオリジナルのメータが簡単にできると思います。
まずは実際に動かしてみて、楽しんでもらえれば良いかと思います。

ユーザーコントロールでもコードビハインドを書かないことにこだわる人がいます。
が、何のために書かないと言っているのかを考えれば、自ずと書いて良いのか悪いのかが明白になると思います。
すなわちコードビハインドに書かないのはロジック(プログラムの本質部分)だと言う事です。
逆に言えばユーザーインターフェースにかかわる部分は書いても構わないと思います。
まして、ユーザーコントロールは部品として使う物なので、ブラックボックスとして画面デザイナーに渡されるものです。
ですので、どんどん書きましょう。
ただし、コードビハインドのコードは画面のスレッドで動くので、制限が生じます
重い処理をしてしまうと、画面がフリーズしてしまいます。なので、

①重い処理はフリーズしないように別スレッドで行う。

別スレッドから画面のスレッドには直接アクセスできないので

②別スレッドの起動にはBackgroundworkerとかTaskを使い、ProgressChangedEventHandlerとかIprogressを使用してスレッドに途中経過を戻すようにする。

ユーザーコントロール特有のものとして

③ユーザーコントロールバインディング用プロパティはDependencyPropertyを使用する。

DependencyPropertyは名前がデータベースに登録され、バインディングスピードが上がるプロパティーです。
DependencyPropertyを書くことで、部品としてViewに貼った時にプロパティーに現れて、デザインツールでバインドできるようになります。

④DependencyPropertyの前にメタデータを書いてカテゴリー分けしておくとわかりやすい。

[Category("MeterProperty")]
[Description("DispValue with Aimation")]
Descriptionはマウスカーソルを持って行った時に表示される説明文です。


以上、自分の覚書も兼ねて簡単に要点をまとめました。