レガシーコードからの脱却

レガシーな職場でも独学でどうにか頑張っていくブログです。

MaterialDesignInXamlToolkit でデザインかっこよくできました。

MaterialDesignInXamlToolkit

Windowsのデスクトップアプリを作成してみて思っていたこと…それは出来上がったもののデザインがくそダサいということ。

WPFでBootstarp的なものとかないのかなぁと調べてみたところ、MaterialDesignInXamlToolkitとかいうライブラリのQiitaの記事がありました。 とても参考になりました。ありがとうございます。

Material Design In XAML ToolkitでWPFアプリにモダンなUIを! - Qiita

というわけで、前回作ってみたTodoアプリにさっそく導入してみました。

写真は日付を選ぶところです。

f:id:Gappory:20170910172038p:plain

ちょちょっと数行書くだけで、いろいろ格好良くしてくれました。 画像ではわかりませんが、マウスが当たった際のアニメーションなんかも良いです。 上の記事でも書かれてる通り、Gitからデモアプリを落として、実行してみるとどんなことができるのかすぐにわかります。

人のソース読むの大事

デモアプリについて、ライブラリでできることの多さに感動することもそうなんですが、WPFでここまでかっこいいものが作れるのかと感動しています。 また、こういったデザインを適応するのに適したXAMLの書き方もあるよなぁとおぼろげながら思いました。 MVVM系のライブラリを色々検討している際にも思ったのですが、やっぱり公開されているソースを読むのってかなりお勉強になるなと感じています。

最後に

Visual Studio2017を入れるのにめちゃくちゃ時間と容量を食ってしまいました。 MVVMのライブラリはPrismを勉強していくことに決めました。会社の先輩に相談したところ、Prismを使っているとのことだったので、わからないこと聞きやすいなと思ったのが決め手です。

【お勉強記録】WPF + MVVMでTodoアプリを作ってみた。

先週コマンドバインディングを学び、WPFバインディングに関しては一応基礎的な部分はお抑えられたのかなと思い、今度はMVVMにのっとって実際に何か作ってみようということで今回Todoアプリを簡単に書いてみました。

MVVMパターン

MVVMは役割に応じてModel・View・ViewModelの3つにアプリケーションを分割するパターンで、ロジックを担当するModelと画面の表示などを担当するView、それぞれを橋渡しするViewModelという役割をそれぞれ担当しているとのこと。

とはいったものの、現状、Viewの部分をXamlが担当しており、ViewModelを担当するクラスとXamlバインディングされて、ViewModel内でModelのクラスを使うというイメージしか持てていません。

利点として役割ごとに分けることで、再利用性やテストしやすさが高まったり、デザインとロジックでの担当者(コーディングする人)の分離が可能であったりするようです。

これは結構わかります。日常業務ではバリバリイベント駆動で全てをコードビハインドに書き込むような状況で開発を行っていて、依存度がすごすぎて、再利用性・テスト可能性・可読性が損なわれている状況を体験しているので、役割を分けることでこれらの問題に対処しようというのは理解しやすかったです。(レガシー環境にいてよかった~)

ViewModelとModelの役割の違いが現時点でいまいちな部分が多いのですが、今の時点でもとりあえず書いてみようということで今回遣ってみました。このあと色々他のサンプルなどを参考にして、自分の中に落とし込んでいくつもりです。

Todoアプリ

タスクと期限を登録して、終わったら消すというだけのシンプルなアプリ。

f:id:Gappory:20170902172946p:plain

「追加ボタン」で追加したタスクが下に表示され、完了したものにはチェックを付けて「完了ボタン」押下で一覧から消えます。

まずはModelから。タスクを管理するのに必要なプロパティのみ持っています。

using System;

namespace TodoMVVM
{
    public class Task
    {
        public string Title { get; set; }
        public DateTime Deadline { get; set; }
        public bool Done { get; set; }
    }
}

Modelってこんなにシンプルで平気なの?心配です。

続いてViewModelです。

using System;
using System.Collections.ObjectModel;
using System.ComponentModel;


namespace TodoMVVM
{
    public class ViewModel:INotifyPropertyChanged
    {
        #region INotifyPropertyChanged
        public event PropertyChangedEventHandler PropertyChanged;

        private void OnPropertyChanged(string name)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(name));
            }
        }
        #endregion              

        public ViewModel()
        {
            taskList = new ObservableCollection<Task>();
            addCommand = new Command(ExecuteAdd);
            doneCommand = new Command(ExecuteDone);
            deadline = DateTime.Today;
        }

        private ObservableCollection<Task> taskList;
        public ObservableCollection<Task> TaskList
        {
            get { return taskList; }
            set 
            {
                taskList = value;
                OnPropertyChanged("TaskList");
            }
        }
        private Command  addCommand;
        public Command  AddCommand
        {
            get { return addCommand; }
        }
        private Command doneCommand;
        public Command DoneCommand
        {
            get { return doneCommand; }
        }


        private string title;
        public string Title
        {
            get { return title; }
            set
            {
                title = value;
                OnPropertyChanged("Title");
            }
        }        
        private DateTime deadline;
        public DateTime Deadline
        {
            get { return deadline; }
            set
            {
                deadline = value;
                OnPropertyChanged("Deadline");
            }
        }
        
        /// <summary>
        /// 追加処理。
        /// </summary>
        private void ExecuteAdd()
        {
            var task = new Task();
            task.Title = title;
            task.Deadline = deadline;
            task.Done = false;
            taskList.Add(task);
        }
        /// <summary>
        /// 完了済みのタスクの削除
        /// </summary>
        private void ExecuteDone()
        {
            for (int i = taskList.Count - 1; i >= 0 ; i--)
            {
                if (taskList[i].Done)
                {
                    taskList.RemoveAt(i);
                }
            }
        }
    }    
}

Commandクラスは別に書いていて、コンストラクタでActionデリゲートに実処理を渡しています。

using System;
using System.Windows.Input;

namespace TodoMVVM
{
    public class Command : ICommand
    {
        #region ICommand
        public bool CanExecute(Object parameter) { return true; }

        public event EventHandler CanExecuteChanged;

        public void Execute(object parameter)
        {
            action();
        }
        #endregion

        private Action action;
        public Command(Action action)
        {
            this.action = action;
        }
    }
}

Viewです、ViewModelをDataContextに指定して、Bindingしています。

<Window x:Class="TodoMVVM.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:vm ="clr-namespace:TodoMVVM"
        Title="MainWindow" Height="400" Width="400">
    <Window.DataContext>
        <vm:ViewModel x:Name="viewModel"></vm:ViewModel>
    </Window.DataContext>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="auto"/>
            <RowDefinition Height="auto"/>
            <RowDefinition Height="16"/>
            <RowDefinition Height="auto"/>
            <RowDefinition Height="16"/>
            <RowDefinition Height="auto"/>
            <RowDefinition Height="auto"/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="70"></ColumnDefinition>
            <ColumnDefinition Width="300"></ColumnDefinition>
        </Grid.ColumnDefinitions>
        <TextBlock Grid.Row="0" Grid.Column="0" VerticalAlignment="Center" Background="#FFEEAA00" Text="TODO"/>
        <TextBox Grid.Row="0" Grid.Column="1" Width="250" HorizontalAlignment="Left" Text="{Binding Title}"></TextBox>
        <TextBlock Grid.Row="1" Grid.Column="0" Text="DeadLine" VerticalAlignment="Center" Background="#FFEEAA00"></TextBlock>
        <DatePicker  Grid.Row="1" Grid.Column="1"  Width="200" HorizontalAlignment="Left" SelectedDate="{Binding Deadline}"></DatePicker>
        <Button Grid.Row="3" Grid.Column="1" Command="{Binding AddCommand}" Height="20" Width="70"  Content="追加" HorizontalAlignment="Right"></Button>
        
        <ListBox Grid.Row="5" Grid.Column="0" Grid.ColumnSpan="2" x:Name="listbox" Height="200" Width="350" HorizontalAlignment="Center" ItemsSource="{Binding TaskList}">
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <Grid>
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition Width="250"></ColumnDefinition>
                            <ColumnDefinition Width="auto"></ColumnDefinition>
                        </Grid.ColumnDefinitions>
                        <CheckBox Grid.Column="0" IsChecked="{Binding Done}" Content="{Binding Title}"/>
                        <TextBlock Grid.Column="1" Text="{Binding Deadline ,StringFormat=yyyy/MM/dd}" />
                    </Grid>
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>
        <Button Grid.Row="6" Grid.Column="1" Command="{Binding DoneCommand}" Height="20" Width="70"  Content="完了" HorizontalAlignment="Right"></Button>
    </Grid>
</Window>

Xamlも結構難しいですねぇー 各コントロールがどのような機能やプロパティをもっていてどのように記述するのか、まだまだ勉強です。

最後に

ということで、一応Todoアプリを作成してみたのですが、いまいちまだまだわかりません! Viewとその他の分離はまぁわかってきたのですが、ViewModelとModelでの役割の分担の理解がいまいちです。

上のままでいくとさらに機能を盛り込んでいくことになると、どんどんViewModelが肥大していき容易にスパゲッティになりそうです。 MVVMのためのフレームワークもあるようなので、上にも書きましたが、色々自分で試しつつ、サンプルコードを当たりつつ、色々な人の意見を聞きつつ、自分の中で理解を進めていきます。

あ、あとアプリケーションを一から作る際のフォルダ階層のいい感じのルール見たいのも学びたい…。