WPFのスライダーのサンプルコードを使いデータバインディングの使い方を確認する

コンピュータ

この記事はスライダーコントロール(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の良さだと思います。

実行例

スクリーンショット

コメント