この記事はスライダーコントロール(Slider)の使い方を学ぶためのサンプルコードを元に、
さらに、データバインディングの使い方を使ったアプリの基本的コードを考えてみました。
正直スライダーで値を表示するくらいの振る舞いしかしないサンプルコードにデータバインディングを行う必要はありません。
ただ、実際スライダーをアプリに組み込む場合を想定すると、
スライダーの値はアプリケーションで必要となる値であり、
C#側で使える必要があります。
それを踏まえて、スライダーの値をデータバインディングで連動するサンプルコードにしてみました。
ソースコード
ファイル名:SliderDemo.csproj
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net10.0-windows</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<UseWPF>true</UseWPF>
</PropertyGroup>
</Project>
ファイル名:AppCommands.cs
using System.Windows;
using System.Windows.Input;
static class AppCommands
{
public static void Bind(
Window w,
ICommand cmd,
ExecutedRoutedEventHandler exec,
CanExecuteRoutedEventHandler? can = null)
{
w.CommandBindings.Add(
new CommandBinding(
cmd,
exec,
can ?? ((_, e) => e.CanExecute = true)
));
}
}
コマンド(ICommand)のバインディング用のヘルパー。
このコマンドはViewModelとバインディングする為というより、
メインメニューなどに登録される、アプリで提供する機能と考えると良さそう。
コードは回りくどく鳴りますが、アプリの機能設計を考えると、
コマンドを中心に組み立てることが出来て良いと感じます。
ファイル名:MainWindow.xaml.cs
using System.Windows;
using System.Windows.Input;
namespace SliderDemo;
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new MainWindowViewModel();
// =====================================
// アプリ機能(Command)の登録
// =====================================
AppCommands.Bind(
this,
ApplicationCommands.Open,
Open_Executed);
// =====================================
// 起動時に Open を実行
// =====================================
Loaded += (_, __) =>
{
ApplicationCommands.Open.Execute(null, this);
};
}
// Open コマンド実行時の処理
void Open_Executed(object sender, ExecutedRoutedEventArgs e)
{
var vm = DataContext! as MainWindowViewModel;
if (vm == null) throw new InvalidOperationException(); // 初期化されていないときはエラー
// スライダーの初期設定
vm!.SliderMin = 0;
vm!.SliderMax = 200;
vm!.SliderValue = 50;
vm!.IsSliderEnabled = true;
}
}
コードビハンド部分です。
最期ほどのAppCommandsヘルパーでコマンドを登録しています。
肝心のコマンドは出来合いのApplicationCommands.Openを使い、
一般的なアプリでファイルを開く機能を想定しています。
また、割り当てられる機能は、スライダーのプロパティに値をセットする処理を行います。
そして、スライダーの初期値がセットされていないと、
アプリとして成立しないので、ロード時にコマンドが実行される用にしています。
ファイル名:MainWindowViewModel.cs
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace SliderDemo;
public class MainWindowViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler? PropertyChanged;
void Raise([CallerMemberName] string? name = null)
=> PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
int _sliderMin;
public int SliderMin
{
get => _sliderMin;
set
{
if (_sliderMin == value) return;
_sliderMin = value;
Raise();
}
}
int _sliderMax;
public int SliderMax
{
get => _sliderMax;
set
{
if (_sliderMax == value) return;
_sliderMax = value;
Raise();
}
}
int _sliderValue;
public int SliderValue
{
get => _sliderValue;
set
{
if (_sliderValue == value) return;
_sliderValue = value;
Raise();
}
}
bool _isSliderEnabled;
public bool IsSliderEnabled
{
get => _isSliderEnabled;
set
{
if (_isSliderEnabled == value) return;
_isSliderEnabled = value;
Raise();
}
}
}
INotifyPropertyChangedを実装したMainWindowのデータソースとなるクラスです。
バインディングするためのプロパティを生やしています。
コードビハンドからは、DataContextを介してアクセスすることが出来ますが、
ViweModelからはコードビハインド(View)へのアクセスするパスは基本無しです。
(Application経由で出来なくは無さそうですが、やらないのが作法)
ファイル名:MainWindow.xaml
<Window x:Class="SliderDemo.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:SliderDemo"
mc:Ignorable="d"
Title="Slider Demo"
Height="200"
Width="500">
<Grid Margin="20">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<!-- 値表示 -->
<TextBlock x:Name="ValueText"
Grid.Row="0"
FontSize="28"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="{Binding ElementName=SeekSlider,
Path=Value,
StringFormat=現在値: {0:F0}}" />
<!-- スライダーの値をバインディングし表示 -->
<!-- スライダー -->
<Slider x:Name="SeekSlider"
Grid.Row="1"
Height="30">
<!-- ============================================
制約値(ViewModel → View)
ユーザー操作では変更されない
============================================ -->
<Slider.Minimum>
<!-- 最小値:アプリケーション状態から与えられる -->
<Binding Path="SliderMin"
Mode="OneWay" />
</Slider.Minimum>
<Slider.Maximum>
<!-- 最大値:動画読み込みなどで動的に変化する -->
<Binding Path="SliderMax"
Mode="OneWay" />
</Slider.Maximum>
<!-- ============================================
入力値(View ⇄ ViewModel)
ユーザー操作によって直接変更される
============================================ -->
<Slider.Value>
<Binding Path="SliderValue"
Mode="TwoWay"
UpdateSourceTrigger="PropertyChanged" />
</Slider.Value>
<!-- ============================================
状態表示(ViewModel → View)
============================================ -->
<Slider.IsEnabled>
<Binding Path="IsSliderEnabled"
Mode="OneWay" />
</Slider.IsEnabled>
</Slider>
</Grid>
</Window>
XAMLで書かれたViewになります。
ViewModelのプロパティとバインドしていますが、スライダーの値はC#側で使うので双方向(TwoWay)でバインド。
それ以外のプロパティは、C#側から、コントロールへ値がセット出来るだけで良いのでOneWayでバインドしています。
また、スライダーの値をTextBlockで表示するため、XAML内でバインディングをして値を同期しています。
C#を使わずにXAMLだけでバインディング出来る点はWPFの良さだと思います。
実行例
スクリーンショット


コメント