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);
}
}
コメント