ボタンを作る for Silverlight

とりあえずSilverlightを軽くさわってみたけど、標準的なコントロールが一切用意されていないので、普通のアプリがまったく組めない。四角形とか円とか描画しても仕方ないし。
将来的には標準コントロールが用意されるようなので、それまで待っておけということか。

じゃあ仕方がないからSilverlightの勉強がてら標準的なコントロールを作ってみる。

プロジェクトの作成

コントロールのライブラリを作るので、「Silverlight Class Library」プロジェクトを作る。プロジェクト名は「SilverlightControls」にする。

「Add」->「New Item」で「Silverlight User Control」を選択して「Button.xaml」というファイルを追加する。ユーザコントロールも他と同じ用にXAMLファイルとそのコードビハインドファイルという構成になっている。

コンポーネントの仕様

プロパティ

Width
ボタンの横幅
Height
ボタンの高さ
Text
ボタンのテキスト
ForeColor
テキストの文字色

イベント

Click
ボタンがクリックされた時

その他

  • マウスのカーソルが置かれたら、ボタンの色を変える。

XMALの記述

まずは「Button.xmal」ファイルから

<Canvas xmlns="http://schemas.microsoft.com/client/2007" 
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        MouseEnter="canvas_MouseEnter"
        MouseLeave="canvas_MouseLeave"
        MouseLeftButtonDown="canvas_Click"
        >
    <Canvas.Resources>
        <Storyboard x:Name="mouseEnter">
            <ColorAnimation To="Gray"
                            Storyboard.TargetName="brush"
                            Storyboard.TargetProperty="Color"
                            SpeedRatio="5"
                            />
        </Storyboard>
    </Canvas.Resources>

    <Rectangle x:Name="rectButton"
               Width="100" Height="40"
               RadiusX="15" RadiusY="15" Stroke="Gray">
        <Rectangle.Fill>
            <LinearGradientBrush StartPoint="0,0" EndPoint="0,1">
                <GradientStop Color="White" Offset="0" />
                <GradientStop x:Name="brush" Color="Black" Offset="0.5" />
                <GradientStop Color="White" Offset="1" />
            </LinearGradientBrush>
        </Rectangle.Fill>
    </Rectangle>
    <TextBlock x:Name="tbLabel" Foreground="White">
    </TextBlock>
</Canvas>

Rectangleで四角形を描画して、RadiusXとRadiusYで角を丸める。
中身をLinearGradientBrushで塗りつぶす。黒と白のグラジエント。TextBlockでボタンのテキストを描画する。

ボタンにマウスカーソルが置かれた時にボタンの色を変える為のアニメーションをCanvas.Resourcesで定義する。
マウスカーソルが置かれたらすぐにボタンの黒をグレイに変えるというアニメーション。本来ならこのアニメーションをどこで実行させるかは、EventTriggerで設定できるらしいが、現状EventTriggerで指定できるイベントは「Loaded」だけらしいので、ここではCanvasのMouseEnter、MouseLeaveイベントでアニメーションを実行/停止させるようにする。

後はボタンがクリックされた事を感知する為にCanvasのMouseLeftButtonDownイベントを利用する。

コードビハインドファイルの記述

次はXMALに対応する「Button.xaml.cs」ファイル

using System;
using System.IO;
using System.Windows;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Windows.Controls;
using System.ComponentModel;
using System.Diagnostics;

namespace SilverlightControls {
    public class Button : System.Windows.Controls.Control {
        private FrameworkElement implementationRoot;

        private Rectangle rectButton;
        private TextBlock tbLabel;

        private Storyboard mouseEnter;

        public virtual new double Width {
            get { return rectButton.Width; }
            set {
                rectButton.Width = value;

                UpdateLayout();
            }
        }
        public virtual new double Height {
            get { return rectButton.Height; }
            set {
                rectButton.Height = value;

                UpdateLayout();
            }
        }
        public string Text {
            get { return tbLabel.Text; }
            set {
                tbLabel.Text = value;

                UpdateLayout();
            }
        }
        public Brush ForeColor {
            get { return tbLabel.Foreground; }
            set { tbLabel.Foreground = value; }
        }

        public event EventHandler Click;

        public Button() {
            using(StreamReader sr = new StreamReader(
                this.GetType().Assembly.GetManifestResourceStream("SilverlightControls.Button.xaml"))) {

                implementationRoot = InitializeFromXaml(sr.ReadToEnd());
                implementationRoot.Loaded += (sender, e) => UpdateLayout();;
            }
            rectButton = (Rectangle)implementationRoot.FindName("rectButton");
            tbLabel = (TextBlock)implementationRoot.FindName("tbLabel");
        }

        protected virtual void UpdateLayout() {
            tbLabel.SetValue<double>(Canvas.TopProperty,
                (rectButton.Height / 2) - (tbLabel.ActualHeight / 2)
            );
            tbLabel.SetValue<double>(Canvas.LeftProperty,
                (rectButton.Width / 2) - (tbLabel.ActualWidth / 2)
            );
        }

        protected virtual void OnClick(EventArgs e) {
            if(Click != null) Click(this, e);
        }

        private void canvas_MouseEnter(object sender, MouseEventArgs e) {
            if(mouseEnter == null) {
                mouseEnter = (Storyboard)implementationRoot.FindName("mouseEnter");
            } else {
                mouseEnter.Stop();
            }
            mouseEnter.Begin();
        }
        
        private void canvas_MouseLeave(object sender, EventArgs e) {
            if(mouseEnter != null) mouseEnter.Stop();
        }

        private void canvas_Click(object sender, EventArgs e) {
            OnClick(EventArgs.Empty);
        }
    }
}

Silverlightのユーザコントロールは「System.Windows.Controls.Control」クラスから継承する。

コンストラクタでは必ず「InitializeFromXaml」メソッドでXAMLファイルを読み込む必要がある。
このメソッドの戻り値としてXAMLのルート要素を表す「FrameworkElement」が取得できるので、これをprivate変数の「implementationRoot」に格納しておく(必須ではない)

using(StreamReader sr = new StreamReader(
    this.GetType().Assembly.GetManifestResourceStream("SilverlightControls.Button.xaml"))) {

    implementationRoot = InitializeFromXaml(sr.ReadToEnd());
    implementationRoot.Loaded += (sender, e) => UpdateLayout();;
}

ユーザコントロールでは「x:Name」属性で名前を付けたコントロールに暗黙的にはアクセスできないので、「FindName」メソッドでコードからアクセスする必要のあるコントロールの参照を取得しておく。

rectButton = (Rectangle)implementationRoot.FindName("rectButton");
tbLabel = (TextBlock)implementationRoot.FindName("tbLabel");

TextBlockはボタンの中央に配置する必要があるので、「UpdateLayout」というメソッドを定義して、その中で中央に配置するようにする。これも本来ならStackPanelなんかのレイアウトマネージャにまかせればいいけど、現在のバージョンではまだサポートされていない。

TextBlockを中央に配置するには、「Left」と「Top」プロパティを変更するわけだが、これらはCanvas
依存関係プロパティ?の為、

tbLabel.SetValue<double>(Canvas.TopProperty,
    (rectButton.Height / 2) - (tbLabel.ActualHeight / 2)
);

のように実装する必要がある。

後は「canvas_MouseEnter」でアニメーションを開始して、「canvas_MouseLeave」で停止する。
これでボタンコントロールの完成。

使い方

Silverlight Project」でプロジェクトを作成して、「Page.xaml」を以下のように変更する。

<Canvas x:Name="parentCanvas"
        xmlns="http://schemas.microsoft.com/client/2007"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:My="clr-namespace:SilverlightControls;assembly=ClientBin/SilverLightControls.dll"
        Loaded="Page_Loaded"
        x:Class="SilverlightProject1.Page;assembly=ClientBin/SilverlightProject1.dll"
        Width="500"
        Height="500"
        >
    <My:Button My:Text="Hello"
               Canvas.Left="5" Canvas.Top="5" Click="button_Click"
               />
</Canvas>

「My」という名前空間を宣言して、「My:Button」というタグで使える。

これのコードビハインドファイル

using System;
using System.Diagnostics;

namespace SilverlightProject1 {
    public partial class Page : System.Windows.Controls.Canvas {
        public void Page_Loaded(object o, EventArgs e) {
            InitializeComponent();
        }

        private void button_Click(object sender, EventArgs e) {
            // なんかする!
        }
    }
}

たかがボタンを作るだけなのに、このめんどくささ。早く標準コントロール作ってよ!