【C# WPF】WPFを理解したいC#初心者の話【MVVMパターン編その4】

C# WPFについて学んでいく過程を備忘録として残して置きたいと思います。

1. 電卓系アプリケーション開発の続き


前回は、アプリケーションのGUIをコントロールを配置することで作成しました。

また、テキスト表示エリアの作成も完了しました。今回は数字入力のボタンの実装からやってみようと思います。

2. 処理を考えた結果


数字ボタンの実装について、現状ではボタンのプロパティを取得して処理を行うことがなかなか難しそうだな、という感じがします。

MVVMパターンとWPFアプリケーション開発を独学で行う弊害ですね。


とりあえず色々調べながらコードをいじったりしてみて、何とか実装にこぎつけられないかを試してみます。

今回はあちこち変更を加える必要があり、すべてを書いていくと記事が冗長化してしまいそうなので、最後にまとめてコードを載せます。

3. コマンドとViewModel、Modelを考える


すでに実装している中でもコマンド処理に関するところで、Viewのオブジェクトを取得できそうな部分がありました。

ここをうまく使えばボタンのプロパティを取得できそうな感じがします。

コマンド処理とViewModelの処理をうまく嚙ませられるように参照設定を変えてみたりします。

ついでにModelクラスも作り、具体的な計算や数値の保持をここで行うことにします。


色々と変更を考えると、最初に考えていたことの実現が難しくなりそうですが、パワープレイ的な実装は出来るだけ避けて、実務でも最低限通用するような仕組みを作りたいのでここは思い切って変更を加えます。


まず、DataContextの設定をMainWindow.xamlで行い、MainWindow.xaml.csは最初に生成された状態のままに戻します。

これでView側の実装にC#のコードが関わらず、XAMLのコードのみで成立するものにします。


ViewModelの方でコマンド系とModelで作成する予定のクラスを作成し、管理を行うように変更します。

ここで一元管理を行うことで内部処理で考えることがシンプルになる想定です。


コマンドの処理に関しても大きく変更を加えてみます。これまではボタンが押されるとViewModelで作った処理関数を呼び出して実行するような想定で作ろうとしていましたがここを変更します。

ViewModelの処理関数を呼び出すところは変わりませんが、ここに引数を渡してボタンのプロパティの値をなんとか渡せないか試行錯誤してみます。

そして、実際の数値足しこみの処理や計算の処理はViewModelを経由してModelのクラスで行います。


Modelでは、実際の計算を行う処理、入力された数値を保持しておく変数やリセットの処理を記述していきます。

基本的にModelクラスの実装はシンプルに収まるように考えます。


前回行った、TextBoxの値もModelクラスに保持している値をViewModel経由でバインディングして管理を行うように変更していきます。

4. 実際に組んでみたが…

これらの変更をなんとかコーディングして、ビルドエラーも発生しないところまで漕ぎつける事ができました。

想定通り実装できていれば、起動時にテキスト表示エリアの数値が「0」になり、数字ボタン「1」を押すことで表示エリアの数値に変更が加わるようになっているはずです。

早速実行してみます。


起動直後の挙動は想定通りです。

テキスト表示エリアの数値のバインディングは変更を加えても問題なさそうです。

次は数字ボタン「1」を押してみます。


ここで例外処理となり、動作が停止してしまいました。

どうやらボタンのオブジェクトからNameプロパティを取得しようとしたタイミングでエラーが発生したようです。

どうもボタンのオブジェクト自体が取得できておらず、結果Nullの参照が発生しているようです。

5. 今回のコード

実行エラーが発生してしまっているので参考にはならないと思いますが、ここまでのコードを貼っておきます。

5-1. View:MainWindow.xaml

<Window x:Class="CalcApp.Views.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:CalcApp" xmlns:viewmodels="clr-namespace:CalcApp.ViewModels" d:DataContext="{d:DesignInstance Type=viewmodels:MainViewModel}"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="400">
    <Grid>
        <TextBox HorizontalAlignment="Center" Height="60" Margin="0,35,0,0" TextWrapping="Wrap" Text="{Binding TextArea}" VerticalAlignment="Top" Width="251" TextAlignment="Center" FontSize="36" Background="#FFD9D9D9" IsReadOnly="True">
        </TextBox>
        <Button Content="1" HorizontalAlignment="Center" Height="45" Margin="-120,172,0,0" VerticalAlignment="Top" Width="45" FontSize="16" Command="{Binding RelayCommand}"/>
        <Button Content="2" HorizontalAlignment="Center" Height="45" Margin="0,172,0,0" VerticalAlignment="Top" Width="45" FontSize="16"/>
        <Button Content="3" HorizontalAlignment="Center" Height="45" Margin="120,172,0,0" VerticalAlignment="Top" Width="45" FontSize="16"/>
        <Button Content="3" HorizontalAlignment="Center" Height="45" Margin="-120,234,0,0" VerticalAlignment="Top" Width="45" FontSize="16"/>
       

5-2. ViewModel:MainViewModel.cs

using CalcApp.Common;
using CalcApp.Views;
using CalcApp.Models;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Controls;
using System.Windows.Input;

namespace CalcApp.ViewModels
{
    public class MainViewModel:INotifyPropertyChanged
    {
        private MainModel m_model;
        private RelayCommand m_relay_command;

        public MainViewModel()
        {
            m_model = new MainModel();
            m_relay_command = new RelayCommand(this);
        }

        // テキスト表示エリアのバインドプロパティ
        public int TextArea
        {
            get { return m_model.Value1; }
            set
            {
                m_model.Value1 = value;
                NotifyPropertyChanged("TextArea");
            }
        }

        // 変数の変更通知用
        public event PropertyChangedEventHandler PropertyChanged;

        private void NotifyPropertyChanged(String info)

5-3. コマンド:RelayCommand.cs

using CalcApp.ViewModels;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Controls;
using System.Windows.Input;


namespace CalcApp.Common
{
    public class RelayCommand:ICommand
    {
        MainViewModel m_vm;
     
        public RelayCommand(MainViewModel vm)
        {
            m_vm = vm;
        }

        public event EventHandler? CanExecuteChanged
        {
            add { CommandManager.RequerySuggested += value; }
            remove { CommandManager.RequerySuggested -= value; }
        }

        public bool CanExecute(object? parameter)
        {
            return
                !string.IsNullOrEmpty(m_vm.TextArea.ToString());
        }

        public void Execute(object parameter)
        {
            string temp = ((Button)parameter).Name;
            m_vm.ValueButton_Command(temp);
        }
    }
}

5-4. Model:MainModel.cs

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

namespace CalcApp.Models
{
    public class MainModel
    {
        public MainModel() { }

        public void InputValue(string str)
        {
            int temp = int.Parse(str);
            Value1 = Value1 * 10 + temp;
        }

        public int Value1 { get; set; } = 0;
        public int Value2 { get; set; } = 0;
    }
}

6. まとめ

今回は電卓系のアプリケーションの中身の実装を試みましたが、うまくいきませんでした。

次回でこれを改善し、動作するモノを何とか仕上げられるよう、調査を進めたいと思います。