WPFユーザーコントロールの作成例(LedControl)

WPF用のユーザーコントロールです。 状態を表示する為のLEDのようなコントロールです。 表示色の設定とOn時の点滅機能も設けています。 [Visual Studio 2017、.NET4.5.2]

f:id:feynman911:20191118173052j:plain

XAML

Blendでユーザーコントロールを作成します。 左側にLED形状の楕円と右側にテキストボックスを配置します。 楕円を配置するグリッドは、幅を自身の高さにバインドすることで正方形にします。それによって楕円は常に円となります。

f:id:feynman911:20191118174046j:plain

楕円は3重にしてあります。最下層はLEDの色表示でLightという名前にします。(名前を付けることで、コードからアクセスできるようになります。)

2層目は点滅用に配置して、透過度で点滅を表現します。

最上層はLEDの周りのリングを表示するのと立体感をだすに使用しています。

<Grid Width="{Binding ActualHeight, ElementName=Light}">
    <Ellipse x:Name="Light" StrokeThickness="0" Fill="#FFB0B0B0"/>
    <Ellipse x:Name="FlickerCover" Fill="#00000000" Stroke="Black" StrokeThickness="0"/>
    <Ellipse StrokeThickness="3">
        <Ellipse.Fill>
            <RadialGradientBrush GradientOrigin="0.3,0.3" Center="0.3,0.3" RadiusX="0.4" RadiusY="0.4">
                <GradientStop Offset="1"/>
                <GradientStop Color="White"/>
            </RadialGradientBrush>
        </Ellipse.Fill>
        <Ellipse.Stroke>
            <RadialGradientBrush>
                <GradientStop Color="Black" Offset="0"/>
                <GradientStop Color="Black" Offset="1"/>
                <GradientStop Color="#FFC7C7C7" Offset="0.748"/>
                <GradientStop Color="White" Offset="0.923"/>
                <GradientStop Color="#FF292929" Offset="0.679"/>
            </RadialGradientBrush>
        </Ellipse.Stroke>
    </Ellipse>
</Grid>

アニメーション

FlickerStory と言う名前で、ストーリーボードを作成します。 その為には、Blend で、オブジェクトとタイムラインの (ストーリーボードは開いていません)の右にある+を押します。

f:id:feynman911:20191118200906j:plain

Storyboard リソースの作成 ダイアログが開きます。

f:id:feynman911:20191118201135j:plain

開いたダイアログで、Storyboard の名前を FlickerStory に変更してストーリーボードを作成します。

タイムラインの時刻を変えながら、プロパティーを変更してキーフレームを記録していくことで、ストーリーボードを作ります。

f:id:feynman911:20191118174406j:plain

出来たストーリーボードは次のようなものになります。

<Storyboard x:Key="FlickerStory">
    <ColorAnimationUsingKeyFrames Storyboard.TargetProperty="(Shape.Fill).(SolidColorBrush.Color)" Storyboard.TargetName="FlickerCover">
        <EasingColorKeyFrame KeyTime="0" Value="Transparent">
            <EasingColorKeyFrame.EasingFunction>
                <CubicEase EasingMode="EaseInOut"/>
            </EasingColorKeyFrame.EasingFunction>
        </EasingColorKeyFrame>
        <EasingColorKeyFrame KeyTime="0:0:0.5" Value="#7FFFFFFF"/>
        <EasingColorKeyFrame KeyTime="0:0:1" Value="Transparent"/>
    </ColorAnimationUsingKeyFrames>
</Storyboard>

Dependency Property

ユーザーコントロールとして使用する時のプロパティーを作成します。
これらはコードビハインドに配置します。
View に作成するプロパティーDependency Propertyです。

FlickerOn: 点滅のOn/Off
FlickerRatio: 点滅のスピード
LedLightBrush: LightOnの時の色
LedOffBrush: LightOffの時の色
LightOn: Light On/Off
Title: Ledの右側の文字列

プロパティーが変更された時に呼ばれる ChangeFunc で表示の変更を行っています。

#region ******************************* LedLightBrush
[Category("LED")]
[Description("Led Light Brush")]
public Brush LedLightBrush
{
    get { return (Brush)this.GetValue(LedLightColorProperty); }
    set { this.SetValue(LedLightColorProperty, value); }
}

public static readonly DependencyProperty LedLightColorProperty =
    DependencyProperty.Register("LedLightBrush", typeof(Brush),
        typeof(LedControl),
        new FrameworkPropertyMetadata(Brushes.Red,
            FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
            LedLightColorChangeFunc));

static void LedLightColorChangeFunc(DependencyObject target,
    DependencyPropertyChangedEventArgs e)
{
    var of = (Brush)e.OldValue;
    var nf = (Brush)e.NewValue;
    var obj = (LedControl)target;
    if (obj.LightOn) obj.Light.Fill = nf;
}
#endregion

#region ******************************* LedOffBrush
[Category("LED")]
[Description("Led off Brush")]
public Brush LedOffBrush
{
    get { return (Brush)this.GetValue(LedOffBrushProperty); }
    set { this.SetValue(LedOffBrushProperty, value); }
}

public static readonly DependencyProperty LedOffBrushProperty =
    DependencyProperty.Register("LedOffBrush", typeof(Brush),
        typeof(LedControl),
        new FrameworkPropertyMetadata(Brushes.Gray,
            FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
#endregion

#region ******************************* LightOn
[Category("LED")]
[Description("Led Light On")]
public bool LightOn
{
    get { return (bool)this.GetValue(LightOnProperty); }
    set { this.SetValue(LightOnProperty, value); }
}

public static readonly DependencyProperty LightOnProperty =
    DependencyProperty.Register("LightOn", typeof(bool),
        typeof(LedControl),
        new FrameworkPropertyMetadata(false,
            FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
            LightOnChangeFunc));

static void LightOnChangeFunc(DependencyObject target,
    DependencyPropertyChangedEventArgs e)
{
    var of = (bool)e.OldValue;
    var nf = (bool)e.NewValue;
    var obj = (LedControl)target;
    if (nf)
    {
        obj.Light.Fill = obj.LedLightBrush;
        if (obj.FlickerOn)
        {
            obj.board.RepeatBehavior = RepeatBehavior.Forever;
            obj.board.Begin();
            obj.board.SetSpeedRatio(obj.FlickerRatio);
        }
    }
    else
    {
        obj.Light.Fill = obj.LedOffBrush;
        obj.board.Stop();
    }
}
#endregion

#region ******************************* Title
[Category("LED")]
[Description("Led Title")]
public string Title
{
    get { return (string)this.GetValue(TitleProperty); }
    set { this.SetValue(TitleProperty, value); }
}

public static readonly DependencyProperty TitleProperty =
    DependencyProperty.Register("Title", typeof(string),
        typeof(LedControl),
        new FrameworkPropertyMetadata("LedControl",
            FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
            TitleChangeFunc));

static void TitleChangeFunc(DependencyObject target,
    DependencyPropertyChangedEventArgs e)
{
    var of = (string)e.OldValue;
    var nf = (string)e.NewValue;
    var obj = (LedControl)target;
    obj.title.Text = nf;
}
#endregion

#region ******************************* FlickerOn
[Category("LED")]
[Description("Flicker On")]
public bool FlickerOn
{
    get { return (bool)this.GetValue(FlickerOnProperty); }
    set { this.SetValue(FlickerOnProperty, value); }
}

public static readonly DependencyProperty FlickerOnProperty =
    DependencyProperty.Register("FlickerOn", typeof(bool),
        typeof(LedControl),
        new FrameworkPropertyMetadata(false,
            FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
            FlickerOnChangeFunc));

static void FlickerOnChangeFunc(DependencyObject target,
    DependencyPropertyChangedEventArgs e)
{
    var of = (bool)e.OldValue;
    var nf = (bool)e.NewValue;
    var obj = (LedControl)target;

    if(nf && obj.LightOn)
    {
        obj.board.RepeatBehavior = RepeatBehavior.Forever;
        obj.board.Begin();
        obj.board.SetSpeedRatio(obj.FlickerRatio);
    }
    if (!nf)
    {
        obj.board.Stop();
    }
}
#endregion

#region ******************************* FlickerRatio
[Category("LED")]
[Description("Flicker Ratio")]
public double FlickerRatio
{
    get { return (double)this.GetValue(FlickerRatioProperty); }
    set { this.SetValue(FlickerRatioProperty, value); }
}

public static readonly DependencyProperty FlickerRatioProperty =
    DependencyProperty.Register("FlickerRatio", typeof(double),
        typeof(LedControl),
        new FrameworkPropertyMetadata(1.0,
            FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
            FlickerRatioChangeFunc,
            FlickerRatioCoerceFunc));

static void FlickerRatioChangeFunc(DependencyObject target,
    DependencyPropertyChangedEventArgs e)
{
    var of = (double)e.OldValue;
    var nf = (double)e.NewValue;
    var obj = (LedControl)target;
    obj.board.SetSpeedRatio(nf);
}

static object FlickerRatioCoerceFunc(DependencyObject target, object baseValue)
{
    var obj = (LedControl)target;
    var val = (double)baseValue;
    return val;
}
#endregion

このユーザーコントロールを貼り付けると、次のようなプロパティーが使用できるようになります。

f:id:feynman911:20191120223815j:plain

点滅用のストーリーボードをコードビハインドから使用する為に、View のコンストラクターに、ストーリーボードを探すために次のようなコードを追加しておきます。

board = (Storyboard)FindResource("FlickerStory");

サンプルの置き場所

作成したサンプルは次の場所に置いてあります。

github.com

f:id:feynman911:20191120223915j:plain