メインコンテンツまでスキップ

「wpf」タグの記事が2件件あります

全てのタグを見る

MVVMモデルでWPFプログラムを作ってみる

土井
ディアシステム(株)開発一部第2課

こんにちは。開発 1 部2課の土井です。
今回は WPF を MVVM モデル(Model-View-ViewModel)を使って、作ってみたいと思います。
自分自身、このモデルを使いながら作ることもあるのですが、作りながらこれってどんな仕組みで動いてるんだっけ?とか思うこともあり、ここで一旦内容をまとめたいと思います。

動作環境

  • Visual Studio Community 2022
  • .NET8.0

まずそもそも MVVM モデル(Model-View-VIewModel)とは?
アプリケーションを作る際に、Model-View-VIewModel の三つに分けて整理する考え方です。それぞれの役割を分割させることで、作りやすくて、直しやすいアプリができます。

  • Model(モデル)

データや計算など、アプリの中身の部分です。
例えば、「売上データを計算する」「ファイルから情報を読む」などを行う部分です。

  • View(ビュー)

ユーザーが見る画面の部分です。
こちらは XAML で作成されます。

  • ViewModel(ビューモデル)

View と Model の繋ぎ役です。
View からの操作(ボタンを押すなど)を受け取って、Model に伝えたり、Model からのデータを View に渡したりします。

MVVM モデルのメリット

  1. 画面と処理を分けられる → 直すときに楽!
  2. テストしやすい → 画面を動かさなくても、処理だけ確認できる!
  3. チームで作りやすい → 役割が分かれているから、分担しやすい!

では、早速実際のプログラムではどのようになるのか、お見せしようと思います。
「画面のボタンを押すと、テキストが変わる」というシンプルなものを作ってみます。

次のような構成で作成します。

画像1

  • View
MainWindow.Xaml
<Window x:Class="MvvmPractice.View.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:MvvmPractice.ViewModel"
mc:Ignorable="d"
Title="MainWindow" Height="200" Width="300">

<Window.DataContext>
<local:MainViewModel />
</Window.DataContext>

<StackPanel VerticalAlignment="Center" HorizontalAlignment="Center">
<TextBlock Text="{Binding Message}" FontSize="10" Margin="10"/>
<Button Content="メッセージ表示" Command="{Binding ShowMessageCommand}" FontSize="10" />
</StackPanel>

</Window>

「Window.DataContext」は View と ViewModel を紐づけるための部分です。
「local:MainViewModel」と記載していますが、これは Window タグ内の xmlns:local の部分で、ViewModel までの名前空間を指定してあげる必要があります。
また、今回のプログラムでは XAML 内で記載していますが、MainWindow.xaml.cs 内で ViewModel を紐づけることも可能です。

TextBlock タグの「Binding Message」と書かれている部分ですが、これは「ViewModel の”Message”って名前のプロパティと紐づけるよー!」といった感じです。よくバインディングすると言ったりもします。なので、ここには ViewModel 側にある、Message プロパティの値が表示されます。

  • ViewModel
MainViewModel.cs
using MvvmPractice.Model;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;

namespace MvvmPractice.ViewModel
{
public class MainViewModel : INotifyPropertyChanged
{
private string _message;
private MessageModel _model;

public string Message
{
get => _message;
set
{
_message = value;
OnPropertyChanged(nameof(Message));
}
}

public ICommand ShowMessageCommand { get; }

public MainViewModel()
{
_model = new MessageModel();
ShowMessageCommand = new RelayCommand(ShowMessage);
}

private void ShowMessage()
{
Message = _model.GetMessage();
}

public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string name) =>
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));

}
}

これが ViewModel の中身ですが、知らないものが多くあるかと思いますので、一つずつ解説していきます。

  • INotifyPropertyChanged

View にデータが変わったことを教えてくれる機能を持ったインターフェースです。今回だとこのクラスの「Message」のプロパティの値が変わった時に、View へとそのことを教える役割があります。WPF で MVVM モデルを使うときは、ほぼ必ず使うインターフェースです。

  • ICommand

UI 操作を ViewModel に伝えるためのインターフェースです。
今回で言うと、View 側の button タグ内で「Command="{Binding ShowMessageCommand}"」と記載し、ViewModel 側と紐づけています。

  • PropertyChangedEventHandler PropertyChanged

これは INotifyPropertyChanged インターフェースの中で定義されているイベントです。
INotifyPropertyChanged インターフェースを使う際には必ず、このイベントを実装する必要があります。
プロパティの値が変わったときは、この PropertyChanged を通じて View へと通知する必要があります。

  • protected void OnPropertyChanged(string name) =>
    PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));

ここが少し複雑ですが、一つ一つご説明します。

「=>」は式形式メソッドと呼ばれる記法です。
これを通常の構文に直すと以下のようになります。

protected void OnPropertyChanged(string name)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}

PropertyChanged?は null 条件演算子と呼ばれるものです。この PropertyChanged が null でない場合だけ呼び出すということができます。

Invoke は「このイベントに登録されているメソッドを実行する」という意味です。
今回で言うと、

PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));

・PropertyChanged イベントに登録されているメソッドがあれば
・this(通知元)と PropertyChangedEventArgs(names)(変更されたプロパティ名)を渡して、
・そのメソッドを実行する(=通知する) という処理になります。

PropertyChangedEventArgs は PropertyChanged イベントが発火されたときに、「どのプロパティが変更されたか」を伝えるための情報を持つ PropertyChanged イベント専用の引数クラスです。引数に変更されたプロパティ名が入ります。

RelayCommand.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;

namespace MvvmPractice.ViewModel
{
public class RelayCommand : ICommand
{
private readonly Action _execute;

public RelayCommand(Action execute) => _execute = execute;

public event EventHandler CanExecuteChanged;

public bool CanExecute(object parameter) => true;

public void Execute(object parameter) => _execute();
}

}

こちらはボタンなどの操作を ViewModel に簡単に伝えるためのクラスです。
CanExecute、Execute、CanExecuteChanged はこのインターフェースを使うときには実装する必要があります。

  • CanExecute このコマンドが実行可能かどうかを返すメソッドです。
    true を返せば、ボタンなどの UI 要素は有効になります。

  • Execute 実際の処理を実行するメソッドです。
    parameter には、XAML から渡された値が入ります。

  • CanExecuteChanged CanExecute の結果が変わったときに通知するイベントです。

先ほどの MainViewModel.cs に戻ると次のように書かれている箇所があるかと思います。

ShowMessageCommand = new RelayCommand(ShowMessage);

ここで Model 内で行っている処理を ViewModel のプロパティと紐づけています。
ShowMessageCommand は View の button タグ内でバインディングされているので、ボタンを押すと ShowMessageCommand が呼び出されて、ShowMessage メソッドが実行されるといった動きになります。

  • Model
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace MvvmPractice.Model
{
public class MessageModel
{
public string GetMessage()
{
return "腹筋6LDKかい!";
}

}
}

こうして画面を起動して動かしてみましょう。

この画面でボタンを押すと、

画像2

こうなります。

画像3

実際にこの MVVM モデルを使用するときですが、INotifyPropertyChanged と ICommand を継承した ViewModel の Base のようなインターフェースを用意しておいて、ViewModel のクラスではそれを継承して使っていました。そうすることで ViewModel を作成するたびにインターフェースを継承するたびに、メソッドを実装しなくても良くなり、ViewModel 全体として共通する処理があれば、その中に記述できます。

このような形で MVVM モデルというものが活用できます。
ちなみにこの MVVM モデルでは画面とロジックを分離するという目的があるため、View のコードビハインド(MainWindow.xaml.cs など)には基本的には何も書かないという考えがあるのですが、新しい画面を別ウィンドウで開きたいときや、画面を閉じたいときなんかはどうしてもコードビハインドで実装しないといけなかったりします。(いいやり方あれば教えてほしい)
そこらへんがちょっと不便で、プログラムが複雑になったりもするので注意が必要です。

WPFで棒グラフを表示する

土井
ディアシステム(株)開発一部第2課

プロジェクトでWPF内での棒グラフの実装を行うことになり、苦労したことですし、せっかくなので調査した内容等をこちらで紹介します。
WPFで棒グラフを表示するためには様々な方法がありますが、
その中の一つとして、WPF内でWindows Form用のコントロールを使う方法があります。
今回MVVMモデルで記載しております。

動作環境:.NetFramework4.7.2 + xaml

ソリューションエクスプローラーのプロジェクトを右クリックし、「追加」→「参照」をクリックしてください。その後表示される「参照マネージャー」内の「アセンブリ」から以下の項目の参照を追加してください。

  • WindowsFormIntegration
  • System.Windows.Forms
  • System.Windows.Forms.DataVisualization

画像1

※上記の方法でWindowsFormIntegrationの参照ができなかった場合
「参照マネージャー」を開き、参照ボタンを押下します。
その後、以下のフォルダ内にある「WindowsFormsIntegration.dll」を選択してください。
C:\Windows\Microsoft.NET\Framework64\v4.0.30319\WPF

XAMLに以下のコードを追記します。

MainWindow.xaml
<Grid>
<WindowsFormsHost x:Name="Chart1" />
</Grid>

コードビハインド(MainWindow.xaml.cs)には以下のコードを追加します。

MainWindow.xaml.cs
public MainWindow()
{
InitializeComponent();
MainWindowViewModel vm = new MainWindowViewModel();
this.DataContext = vm;

Chart1.Child = vm.chart1;
}

ViewModelには以下のコードを追加します。

MainWindowViewModel.cs
public class MainWindowViewModel
{
public Chart chart1;
Title title;
ChartArea chartArea1;
Series series1;

public MainWindowViewModel()
{
chart1 = new Chart();
title = new Title("タイトル");
chartArea1 = chart1.ChartAreas.Add("Area1");
series1 = new Series();

// 数値
Random rdm = new Random();

// X軸表示用の名前
string[] memberList = { "Aさん", "Bさん", "Cさん", "Dさん", "Eさん" };

// グラフの設定
SettingChart();

// グラフ要素の追加
for(int i = 0; i < 5; i++)
{
series1.Points.AddY(rdm.NextDouble());
series1.Points[i].AxisLabel = memberList[i];
}
}

private void SettingChart()
{
// グラフタイトル、軸ラベルの設定
title.DockedToChartArea = "Area1";
chartArea1.AxisX.Title = "X軸ラベル";
chartArea1.AxisY.Title = "Y軸ラベル";

// グラフの種類
series1.ChartType = SeriesChartType.Column;

// ChartにTitle,Seriesを追加
chart1.Titles.Add(title);
chart1.Series.Add(series1);
}
}

以下の棒グラフが表示されます。

画像2

またViewModelにSeriesを追加すると各個人の二つのデータを比較するということも行うことができます。

MainWindowViewModel.cs
        public MainWindowViewModel()
{
chart1 = new Chart();
title = new Title("タイトル");
chartArea1 = chart1.ChartAreas.Add("Area1");
series1 = new Series();
series2 = new Series();

・・・

// グラフ要素の追加
for(int i = 0; i < 5; i++)
{
series1.Points.AddY(rdm.NextDouble());
series2.Points.AddY(rdm.NextDouble());
series1.Points[i].AxisLabel = memberList[i];
}

・・・

// ChartにTitle,Seriesを追加
chart1.Titles.Add(title);
chart1.Series.Add(series1);
chart1.Series.Add(series2);

画像3

またグラフの上に数値や文字を表示する場合はseries1.Points[0から始まるグラフの順番].labelに表示させたい文字を入れることで可能です。
以下はグラフに自身の値をグラフの上に表示させている内容となります。

double value1;
double value2;
// グラフ要素の追加
for(int i = 0; i < 5; i++)
{
value1 = rdm.NextDouble();
value2 = rdm.NextDouble();
series1.Points.AddY(value1);
series1.Points[i].Label = Math.Round(value1,2).ToString();
series2.Points.AddY(value2);
series2.Points[i].Label = Math.Round(value2,2).ToString();
series1.Points[i].AxisLabel = memberList[i];
}

画像4

このグラフを使用する際の注意点ですが、2点あります。

  • コントローラが画面の最前面に表示されるため、グラフの前面に要素を配置することができない。
  • X軸の数を10個以上にすると全てのX軸の各グラフ名が表示されなくなる。

以上の2点です。

特に二つ目の点ですが、例えば以下のように グラフを10人分表示させようとした場合です。

            string[] memberList = { "Aさん", "Bさん", "Cさん", "Dさん", "Eさん","Fさん","Gさん","Hさん","Iさん","Jさん" };

// グラフの設定
SettingChart();

double value1;
double value2;
// グラフ要素の追加
for(int i = 0; i < 10; i++)
{
value1 = rdm.NextDouble();
value2 = rdm.NextDouble();
series1.Points.AddY(value1);
series1.Points[i].Label = Math.Round(value1,2).ToString();
series2.Points.AddY(value2);
series2.Points[i].Label = Math.Round(value2,2).ToString();
series1.Points[i].AxisLabel = memberList[i];
}

表示は以下のようになり、5人の名前しか表示されなくなってしまいます。

画像5

このX軸の表示ですが、10以上からはグラフの数が増えるほど表示するX軸の数も減っていくようです。

未経験から始める
システムエンジニア

一生モノのITスキルを身につけよう

あなたの経験とスキルを
ディアシステムで発揮してください!