XAMLを使わないWPF入門43「値表示機能付きスライダー」

コンピュータ

SliderとTextBlockを組み合わせたカスタムコントロールを作成してみます。

サンプルコードのプロジェクトの作成

ソースコード

ファイル名:NoXAML43SliderWithTextblock.csproj

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>WinExe</OutputType>
    <TargetFramework>net8.0-windows</TargetFramework>
    <Nullable>enable</Nullable>
    <ImplicitUsings>enable</ImplicitUsings>
    <UseWPF>true</UseWPF>
  </PropertyGroup>

</Project>

ファイル名:AppEntry.cs

using System.Windows;

namespace NoXAML43SliderWithTextblock;

using System.Windows;
public class AppEntry : Application
{
    [STAThread] public static void Main() => new AppEntry().Run(new MainWindow());
}

ファイル名:SliderWithValueView.cs

using System.Collections;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;

namespace NoXamlSample;

public class SliderWithValueView : Control
{
    private readonly Slider _slider;
    private readonly TextBlock _text;
    private readonly UIElement _content;

    // Value(双方向バインディング既定、Min/Maxでクランプ)
    public static readonly DependencyProperty ValueProperty =
        DependencyProperty.Register(
            nameof(Value),
            typeof(double),
            typeof(SliderWithValueView),
            new FrameworkPropertyMetadata(
                0.0,
                FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
                OnValueChanged,
                CoerceValueInRange));

    public double Value
    {
        get => (double)GetValue(ValueProperty);
        set => SetValue(ValueProperty, value);
    }

    // Minimum / Maximum(変更時に Value を再クランプ)
    public static readonly DependencyProperty MinimumProperty =
        DependencyProperty.Register(
            nameof(Minimum),
            typeof(double),
            typeof(SliderWithValueView),
            new PropertyMetadata(0.0, OnRangeChanged));

    public double Minimum
    {
        get => (double)GetValue(MinimumProperty);
        set => SetValue(MinimumProperty, value);
    }

    public static readonly DependencyProperty MaximumProperty =
        DependencyProperty.Register(
            nameof(Maximum),
            typeof(double),
            typeof(SliderWithValueView),
            new PropertyMetadata(100.0, OnRangeChanged));

    public double Maximum
    {
        get => (double)GetValue(MaximumProperty);
        set => SetValue(MaximumProperty, value);
    }

    // 内部スライダーの見た目だけ個別指定したい場合
    public static readonly DependencyProperty SliderWidthProperty =
        DependencyProperty.Register(
            nameof(SliderWidth),
            typeof(double),
            typeof(SliderWithValueView),
            new PropertyMetadata(150.0, OnSliderWidthChanged));

    public double SliderWidth
    {
        get => (double)GetValue(SliderWidthProperty);
        set => SetValue(SliderWidthProperty, value);
    }

    public SliderWithValueView()
    {
        var panel = new StackPanel { Orientation = Orientation.Horizontal, Margin = new Thickness(10) };

        _slider = new Slider { VerticalAlignment = VerticalAlignment.Center };
        _text   = new TextBlock
        {
            Width = 50,
            Margin = new Thickness(10, 0, 0, 0),
            VerticalAlignment = VerticalAlignment.Center
        };

        // 現在のDP値を内部に反映
        _slider.Minimum = Minimum;
        _slider.Maximum = Maximum;
        _slider.Width   = SliderWidth;
        _slider.Value   = (double)CoerceValueInRange(this, Value);
        _text.Text      = _slider.Value.ToString("F0");

        // 内部→外部(DP)反映
        _slider.ValueChanged += (s, e) => Value = _slider.Value;

        panel.Children.Add(_slider);
        panel.Children.Add(_text);

        AddVisualChild(panel);
        AddLogicalChild(panel);
        _content = panel;
    }

    private static void OnValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var c = (SliderWithValueView)d;
        if (c._slider == null) return;
        var v = (double)e.NewValue;
        // クランプ後の実値で表示更新
        v = (double)CoerceValueInRange(c, v);
        if (c._slider.Value != v) c._slider.Value = v;
        c._text.Text = v.ToString("F0");
    }

    private static object CoerceValueInRange(DependencyObject d, object baseValue)
    {
        var c = (SliderWithValueView)d;
        var v = (double)baseValue;
        var min = c.Minimum;
        var max = c.Maximum;
        if (v < min) v = min;
        if (v > max) v = max;
        return v;
    }

    private static void OnRangeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var c = (SliderWithValueView)d;
        if (c._slider != null)
        {
            c._slider.Minimum = c.Minimum;
            c._slider.Maximum = c.Maximum;
        }
        // 範囲変更に合わせてValueを再クランプ
        c.CoerceValue(ValueProperty);
    }

    private static void OnSliderWidthChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var c = (SliderWithValueView)d;
        if (c._slider != null) c._slider.Width = (double)e.NewValue;
    }

    // Visual/Logical ツリー最小実装
    protected override int VisualChildrenCount => 1;
    protected override Visual GetVisualChild(int index) => _content;
    protected override IEnumerator LogicalChildren
    {
        get { yield return _content; }
    }
    protected override Size MeasureOverride(Size constraint)
    {
        _content.Measure(constraint);
        return _content.DesiredSize;
    }
    protected override Size ArrangeOverride(Size arrangeBounds)
    {
        _content.Arrange(new Rect(arrangeBounds));
        return arrangeBounds;
    }
}

ファイル名:MainWindow.cs

// メインウィンドウ
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media.Imaging;
using static UI;
using NoXamlSample;

public sealed class MainWindow : Window
{
    public MainWindow()
    {
        var grid = Grd();
        var slider = new SliderWithValueView()
        {
            HorizontalAlignment = HorizontalAlignment.Center,
            VerticalAlignment = VerticalAlignment.Center,
        }.PlaceIn(grid);


        this.Init("NoXAML Custom Control Sample").Content(grid);
    }
}

実行例

コメント