やる気駆動型エンジニアの備忘録

WPF(XAML+C#)の話題を中心に.NET/Android/CI やたまに趣味に関するブログです

DataGridColumnにデータバインドするには

業務アプリを制作する際にDataGridのある列の表示非表示を切り替えたいケースがあると思います。
表示だけでなく、ヘッダーや幅を変えたいなどの場合も同様です。
しかし、少し手を加えなければDataGridColumn のバインディングは不可能です。
(なぜかは後述します。)

このような場合、上記の仕組みを実現するには主に以下の2通りがあります。

1.画面(View)側でDataGridColumn コントロールにアクセス
2.ViewModel から表示プロパティをバインド

1の場合は対応する画面が一つだけなら問題無いでしょう。
しかし、画面が複数あったりした場合はどうでしょう?
いやいやWPFらしくデータバインドを使いたい、と思う方もいらっしゃるでしょう。
ということで、今回はケース2.の方法を実現しようと思います。


■参考資料
http://www.thomaslevesque.com/2011/03/21/wpf-how-to-bind-to-data-when-the-datacontext-is-not-inherited/

DataGridColumn には間をとりもつ存在が必要

参考資料より引用

I don’t know the exact mechanism that enables this behavior, but we’re going to take advantage of it to make our binding work…


オチから説明すると、なぜDataGridColumn とデータバインドできない原因は明確にはわかっていません。
上記Blogを読むに、論理ツリー内にDataGridColumn が含まれていないからでは?ということみたいです。
DataGridColumn を論理ツリー内に含むにはFreezable オブジェクトとバインドするのだそうです。

以下は、ViewModel の間をとりもつBindingProxy クラスの実装です。

< BindingProxy.cs >

 using System.Windows;

namespace DataGridColumnBinding
{
    /// <summary>
    /// ViewModelのバインディングソースの代理として働くクラスです。
    /// </summary>
    public class BindingProxy : Freezable
    {
        /// <summary>
        /// Freezableオブジェクトのインスタンスを生成します。
        /// </summary>
        /// <returns></returns>
        protected override Freezable CreateInstanceCore()
        {
            return new BindingProxy();
        }
        
        /// <summary>
        /// 間をとりもつプロパティ
        /// データバインドした場合は、このプロパティがViewModelの代わりになる。
        /// </summary>
        public object Data
        {
            get { return (object) GetValue(DataProperty); }
            set { SetValue(DataProperty, value); }
        }
        
        /// <summary>
        /// Data の依存関係プロパティ定義
        /// </summary>
        public static readonly DependencyProperty DataProperty =
            DependencyProperty.Register("Data", typeof (object), typeof (BindingProxy), new UIPropertyMetadata(null));
    }
}


これに対し、Xaml とViewModel の定義は次のようになります。< MainWindow.xaml >

<Window x:Class="DataGridColumnBinding.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:local="clr-namespace:DataGridColumnBinding"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        Title="MainWindow"
        Width="525"
        Height="350"
        Loaded="MainWindow_OnLoaded"
        d:DataContext="{d:DesignInstance {x:Type local:MainWindowViewModel}, IsDesignTimeCreatable=True}"
        mc:Ignorable="d">
    <Window.Resources>
        <BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter" />
    </Window.Resources>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>
        <StackPanel Margin="12" Orientation="Horizontal">
            <CheckBox Content="VisibleDate" IsChecked="{Binding IsVisibleDate, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
            <CheckBox Content="VisibleTime" IsChecked="{Binding IsVisibleTime, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
        </StackPanel>
        <Border Grid.Row="1">
            <DataGrid AutoGenerateColumns="False" ItemsSource="{Binding Times, Mode=OneWay}">
                <DataGrid.Resources>
                    <!--
                        BindingProxy のリソースを定義
                        Data には、MainWindowViewModel がバインドされる。
                    -->
                    <local:BindingProxy x:Key="Proxy" Data="{Binding}" />
                </DataGrid.Resources>
                <DataGrid.Columns>
                    <DataGridTextColumn Binding="{Binding Today, Mode=OneWay, StringFormat=\{0:d\}}"
                                        Header="Date"
                                        Visibility="{Binding Data.IsVisibleDate, Converter={StaticResource BooleanToVisibilityConverter}, Mode=OneWay, Source={StaticResource Proxy}}" />
                    <DataGridTextColumn Binding="{Binding Mode=OneWay, StringFormat=\{0:hh:mm:ss\}}"
                                        Header="Time"
                                        Visibility="{Binding Data.IsVisibleTime, Converter={StaticResource BooleanToVisibilityConverter}, Mode=OneWay, Source={StaticResource Proxy}}" />
                    <DataGridTextColumn Binding="{Binding Mode=OneWay, StringFormat=\{0:G\}}" Header="DateTime" />
                </DataGrid.Columns>
            </DataGrid>
        </Border>
    </Grid>
</Window>

< MainWindowViewModel.cs >

 using System;
using System.Collections.ObjectModel;
using System.Linq;
using Microsoft.Practices.Prism.Mvvm;

namespace DataGridColumnBinding
{
    public class MainWindowViewModel : BindableBase
    {
        public MainWindowViewModel()
        {
            IsVisibleDate = true;
            IsVisibleTime = true;

            Times = new ObservableCollection<DateTime>();

            foreach (var value in Enumerable.Range(0, 20))
            {
                Times.Add(DateTime.Now.AddHours(value));
            }
        }

        /// <summary>
        /// 日付を表示するかどうか
        /// </summary>
        private bool _isVisibleDate;

        /// <summary>
        /// 日付を表示するかどうか
        /// </summary>
        public bool IsVisibleDate
        {
            get { return _isVisibleDate; }
            set { SetProperty(ref _isVisibleDate, value); }
        }

        /// <summary>
        /// 時刻を表示するかどうか
        /// </summary>
        private bool _isVisibleTime;

        /// <summary>
        /// 時刻を表示するかどうか
        /// </summary>
        public bool IsVisibleTime
        {
            get { return _isVisibleTime; }
            set { SetProperty(ref _isVisibleTime, value); }
        }

        public ObservableCollection<DateTime> Times { get; private set; }
    }
}


ポイントは以下の2箇所
1.BindingProxy リソースの定義

<local:BindingProxy x:Key="Proxy" Data="{Binding}" />


2.データバインドの定義

<DataGridTextColumn Binding="{Binding Today, Mode=OneWay, StringFormat=\{0:d\}}"
                    Header="Date"
                    Visibility="{Binding Data.IsVisibleDate, Converter={StaticResource BooleanToVisibilityConverter}, Mode=OneWay, Source={StaticResource Proxy}}" />

Visibility の設定を上記のように定義することでデータバインドが可能になります。
画面を表示すると以下のようになります。

f:id:iyemon018:20160130234226p:plain

f:id:iyemon018:20160130234229p:plain

f:id:iyemon018:20160130234230p:plain



このようにDataGridColumn のプロパティもデータバインドが可能になっています。
今回のサンプルソースは以下から↓↓↓
github.com

シリアライザ~BinaryFormatter~

前回リアライザについての説明を軽くしました。
今回はその中の一つ、BinaryFormatterについて説明していこうと思います。

特徴

MSDNのページはこちら↓↓↓
BinaryFormatter クラス (System.Runtime.Serialization.Formatters.Binary)
BinaryFormatterはその名の通り、オブジェクトをバイナリに書式化します。
さらに、たいていの型に対応しているため、とりあえずシリアライズするならこれを使用する場合が多いでしょう。
パフォーマンス的にも高速です。が、あるケースによっては速度が激遅になるんだとか…。
詳しくは以下のページをご覧いただけるとわかるはずです。
neue cc - .NET(C#)におけるシリアライザのパフォーマンス比較

使い方

    public static T DeepCopy<T>(this T self)
    {
        T result;

        using (var ms = new MemoryStream())
        {
            var formatter = new BinaryFormatter();
            formatter.Serialize(ms, self);
            ms.Position = 0;
            result = (T)formatter.Deserialize(ms);
        }

        return result;
    }

これは拡張メソッドで何でもコピー可能にしていますが、シリアライズしたいオブジェクトとStreamでシリアライズし、その結果をデシリアライズします。
そうすることでオブジェクトのコピーが可能になっています。

さらに、シリアライズするクラスにはSerializableAttributeを定義する必要があります。もちろん基底クラスにもです。

シリアライズ元となるクラスは以下のようにしました。
TimeTracerは処理速度を出力するための機能です。
<ViewModelBase.cs>

    /// <summary>
    /// すべてのViewModelの基底クラスです。
    /// </summary>
    [Serializable]
    public abstract class ViewModelBase : BindableBase
    {
        /// <summary>
        /// オブジェクトの状態を保存するためのスナップショットデータです。
        /// </summary>
        private ViewModelBase _snapshot;

        /// <summary>
        /// スナップショットデータを保存します。
        /// </summary>
        public void SaveSnapshotData()
        {
            using (new TimeTracer("オブジェクトのコピー"))
            {
                _snapshot = this.DeepCopy();
            }
        }

        /// <summary>
        /// オブジェクトが更新されたかどうかを判定します。
        /// </summary>
        /// <returns>
        /// オブジェクトが更新されている場合は、true を
        /// 更新されていない場合は、false を返します。
        /// </returns>
        public bool IsUpdate()
        {
            using (new TimeTracer("更新チェック"))
            {
                if (_snapshot == null)
                {
                    return true;
                }

                return !this.DeepCopy().Equals(_snapshot);
            }
        }

        /// <summary>
        /// オブジェクトの状態を元に戻します。
        /// </summary>
        public void Reset()
        {
            using (new TimeTracer("オブジェクトにリセット"))
            {
                if (_snapshot != null)
                {
                    UpdateObject(_snapshot);
                }
            }
        }

        /// <summary>
        /// オブジェクトを更新します。
        /// </summary>
        /// <param name="obj">オブジェクトデータ</param>
        protected virtual void UpdateObject(ViewModelBase obj)
        {

        }
    }

<BinaryFormatterViewModel.cs>

    /// <summary>
    /// BinaryForamtterView用のViewModelクラスです。
    /// </summary>
    [Serializable]
    public class BinaryFormatterViewModel : ViewModelBase
    {
        private int _age;

        private string _name;

        private DateTime _birthday;

        private BloodType _bloodType;

        private Gender _gender;

        public BinaryFormatterViewModel()
        {
            Age = 20;
            Name = string.Empty;
            Birthday = new DateTime(1990, 1, 1);
            BloodType = SelializeSample.BloodType.TypeA;
            Gender = SelializeSample.Gender.Male;
        }

        /// <summary>年齢</summary>
        public int Age
        {
            get { return _age; }
            set { SetProperty<int>(ref _age, value); }
        }

        /// <summary>名前</summary>
        public string Name
        {
            get { return _name; }
            set { SetProperty<string>(ref _name, value); }
        }

        /// <summary>誕生日</summary>
        public DateTime Birthday
        {
            get { return _birthday; }
            set { SetProperty<DateTime>(ref _birthday, value); }
        }

        /// <summary>血液型</summary>
        public BloodType BloodType
        {
            get { return _bloodType; }
            set { SetProperty<BloodType>(ref _bloodType, value); }
        }

        /// <summary>性別</summary>
        public Gender Gender
        {
            get { return _gender; }
            set { SetProperty<Gender>(ref _gender, value); }
        }

        /// <summary>
        /// オブジェクトを更新します。
        /// </summary>
        /// <param name="obj">オブジェクトデータ</param>
        protected override void UpdateObject(ViewModelBase obj)
        {
            base.UpdateObject(obj);

            var vm = obj as BinaryFormatterViewModel;
            if (vm != null)
            {
                Age       = vm.Age;
                Name      = vm.Name;
                Birthday  = vm.Birthday;
                BloodType = vm.BloodType;
                Gender    = vm.Gender;
            }
        }

        /// <summary>
        /// オブジェクト同士を比較します。
        /// </summary>
        /// <param name="obj">比較対象のオブジェクト</param>
        /// <returns>true:一致, false:不一致</returns>
        public override bool Equals(object obj)
        {
            var other = obj as BinaryFormatterViewModel;
            if (other == null)
            {
                return false;
            }
            return Age == other.Age
                 & Name.Equals(other.Name)
                 & Birthday == other.Birthday
                 & BloodType == other.BloodType
                 & Gender == other.Gender;
        }
    }

画面で使用する場合はこのようにしました。
<BinaryFormatterView.xaml.cs>

    /// <summary>
    /// BinaryFormatterView.xaml の相互作用ロジック
    /// </summary>
    public partial class BinaryFormatterView : Window
    {
        /// <summary>
        /// 当画面のViewModel
        /// </summary>
        private BinaryFormatterViewModel _vm;

        public BinaryFormatterView()
        {
            InitializeComponent();
        }

        /// <summary>
        /// リセットボタンをクリックした際に呼ばれるイベントハンドラです。
        /// </summary>
        /// <param name="sender">イベント発行元オブジェクト</param>
        /// <param name="e">イベント引数オブジェクト</param>
        private void ResetButton_Click(object sender, RoutedEventArgs e)
        {
            // リセット
            _vm.Reset();
        }

        /// <summary>
        /// 戻るボタンをクリックした際に呼ばれるイベントハンドラです。
        /// </summary>
        /// <param name="sender">イベント発行元オブジェクト</param>
        /// <param name="e">イベント引数オブジェクト</param>
        private void BackButton_Click(object sender, RoutedEventArgs e)
        {
            if (_vm.IsUpdate())
            {
                var result = MessageBox.Show("データが更新されています。\r\n入力内容を破棄しますか?"
                                            , "確認"
                                            , MessageBoxButton.YesNo
                                            , MessageBoxImage.Question
                                            , MessageBoxResult.No);
                if (result != MessageBoxResult.Yes)
                {
                    return;
                }
            }
            Close();
        }

        /// <summary>
        /// 当ウィンドウがロードされた際に呼ばれるイベントハンドラです。
        /// </summary>
        /// <param name="sender">イベント発行元オブジェクト</param>
        /// <param name="e">イベント引数オブジェクト</param>
        private void Window_Loaded(object sender, RoutedEventArgs e)
        {
            _vm = DataContext as BinaryFormatterViewModel;
            if (_vm != null)
            {
                // これ以降に変更されたら変更ダイアログを表示します。
                _vm.SaveSnapshotData();
            }
        }
    }

画面イメージはこんな感じです。
f:id:iyemon018:20151223181836p:plain

これに値を入力し、
f:id:iyemon018:20151223181912p:plain

戻るボタンをクリックすると、ダイアログが表示されます。
f:id:iyemon018:20151223181940p:plain


さて、シリアライズの話と言いつつ話の本筋はオブジェクトのコピーになっていますがそれはさておき。
このように属性を付加するだけでオブジェクトのコピーが可能になるのがBinaryFormatterの強みです。

今回使用したサンプルはこちら↓↓↓
github.com

C#でシリアライズ

この前仕事でシリアライズについていろいろ調べてみたんですが、今後も使うことは多いと思うのでメモ
内容的には、画面で入力された情報を保持しておき、変更があれば確認メッセージボックスを出力して入力内容を破棄するかどうかを選択するというもの。
WPFアプリケーションでViewModelの内容をシリアライズして記録する、という仕組みで作りました。

シリアライズの種類

さて、シリアライズと言っても幾つか種類が存在します。
調べた限り、以下の3つがあります。

  1. BinaryFormatter
  2. XmlSerializer
  3. DataContractSerializer

どの方法でシリアライズするかはケースバイケースですが、調べた結果以下の方向性で使用するのが妥当かと思います。

  • パフォーマンスを重視したい、とにかく高速に

 ⇒BinaryFormatter

  • ファイルへの入出力、デバッグの容易さ重視

 ⇒XmlSerializer

 ⇒DataContractSerializer

私の印象なんで実際は少し異なると思いますが、概ねこの概念です。
以下のページでそれぞれのシリアライザの特徴が挙げられています。
stackoverflow.com
一覧はこちら↓↓↓
f:id:iyemon018:20151223161347p:plain

このようにシリアライザにも一長一短がありますので、開発でベストな選択ができるといいですね。

ちなみに、私が関わった開発ではXmlSerializerを使用しました。
単純にデバッグがし易いというのが1番の目的でした。
画面数が多く、ファイルの比較を実施した際に本当に正しいのかどうかその判定基準を明確にするにはXmlSerializerが妥当だと思ったからです。

さて、次回以降にそれぞれのシリアライザについての記事を書いていこうと思います。
ただ、シリアライザについてのブログなんて山程あります。
そんなもの見てももう知っているよという人が多いはずなので、すこし趣向を凝らした内容にしたいと画策中です。(やるとは言っていない)

オブジェクトの3分シリアライズ(XmlSerializer)

アプリケーションを作成する際に設定ファイルなんかをXMLファイルに定義する場合、みなさんはどのようにXMLシリアライズクラスを作成しますか?

 

手順的には、

  1. XMLファイルを作成する。
  2. シリアライズクラスを作成する。
  3. 読み込み/書き込み処理を作成する。
  4. テスト

こんな順番でしょうか。

まぁ、先にXMLファイルを作るのかシリアライズクラスを作るのかケースバイケースですが、今回はこの順番で作成する場合に、手順2.を簡単に実装できますよ

というお話です。

 

シリアライズクラスを作成する

例えば、以下の様なXMLファイルがあったとします。

<?xml version="1.0"?>
<MessageConfigration>
    <MessageDatas>
        <MessageData>
            <ID>E00001</ID>
            <Type>Error</Type>
            <Message>予期せぬエラーが発生しました。</Message>
        </MessageData>
        <MessageData>
            <ID>E00002</ID>
            <Type>Error</Type>
            <Message>メモリー容量が不足しています。再度実行してください。</Message>
        </MessageData>
        <MessageData>
            <ID>E00003</ID>
            <Type>Error</Type>
            <Message>ファイルの操作に失敗しました。</Message>
        </MessageData>
    </MessageDatas>
</MessageConfigration>

 

よくあるメッセージの情報を定義したXMLファイルです。

このシリアライズクラスを作成する場合、まず思いつくのはルートのクラスを作成し、必要なプロパティを設定、それぞれのプロパティに属性を設定していく…

と言った方法でしょう。

多くのブログでもそのような記述を見かけます。

この方法だと、上記のような簡単な構造ならいいですが、更に複雑な構造に変更した際に無駄に工数を重ねることになります。

 

この問題を解決するために、MicrosoftVisual Studioに便利な機能をのせています。

この機能を使用すると、XMLファイルの内容からシリアライズ可能なクラスを自動生成してくれます。

 

VSの機能を使ってシリアライズクラスを自動生成する

(1) XMLファイルをクリップボードにコピーする。

(2) シリアライズクラスを作成する。

 ⇒今回は、"MessageConfigration.Auto.cs"を作成

(3) メニューバー[編集]-[形式を指定して貼り付け]-[XMLをクラスとして貼り付ける] を選択する。

 

はい。たったこれだけです。

それではここで手順(2)から(3)の結果を見てみましょう。

 

ここに適当に作ったクラスファイルがあるじゃろ?( ^ω^) ⊃

f:id:iyemon018:20151117230931p:plain

これをこうして( ^ω^) ⊃

f:id:iyemon018:20151117231250p:plain

こうじゃ( ^ω^) ⊃

f:id:iyemon018:20151117231406p:plain

 

できました~(^ω^)

 

あとは、コメント追加したりリファクタリングしたりしましょう。

<MessageConfigration.Auto.cs>

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Serialization;
 
namespace XmlSerializerSample.Configrations
{
    /// <summary>
    /// メッセージ設定ファイルの情報を定義します。
    /// </summary>
    [XmlTypeAttribute(AnonymousType = true)]
    [XmlRootAttribute(Namespace = "", IsNullable = false)]
    public partial class MessageConfigration
    {
        #region Fields
        
        /// <summary>
        /// メッセージデータリスト
        /// </summary>
        private MessageData[] messageDatasField;
 
        #endregion //Fields
 
        #region Properties
        
        /// <summary>
        /// メッセージデータリスト
        /// </summary>
        [XmlArrayItemAttribute("MessageData", IsNullable = false)]
        public MessageData[] MessageDatas
        {
            get
            {
                return this.messageDatasField;
            }
            set
            {
                this.messageDatasField = value;
            }
        }
 
        #endregion //Properties
    }
}

 

 

<MessageConfigration.cs>

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using XmlSerializerSample.Xml;
 
namespace XmlSerializerSample.Configrations
{
    /// <summary>
    /// メッセージ設定ファイルの情報を定義します。
    /// </summary>
    public sealed partial class MessageConfigration
    {
        #region Static Fields
        
        /// <summary>
        /// 規定のファイル名
        /// </summary>
        public static readonly string FileName = "Message-Config.xml";
 
        /// <summary>
        /// 規定のファイルパス
        /// </summary>
        public static readonly string FilePath = Path.Combine(Path.GetDirectoryName(Assembly.GetEntryAssembly().Location), FileName);
 
        /// <summary>
        /// カレントデータ
        /// </summary>
        private static MessageConfigration _current = null;
 
        #endregion //Static Fields
 
        #region Ctor
 
        /// <summary>
        /// コンストラクタ
        /// </summary>
        public MessageConfigration()
        {
 
        }
 
        #endregion //Ctor
 
        #region Static Properties
 
        /// <summary>
        /// カレントデータを取得します。
        /// </summary>
        public static MessageConfigration Current
        {
            get { return _current; }
            private set { _current = value; }
        }
 
        #endregion //Static Properties
 
        #region Static Methods
        
        /// <summary>
        /// ファイルからデータを読み込みます。
        /// </summary>
        /// <param name="filePath">ファイルパス(空文字の場合、規定のファイルパスから読み込みます。)</param>
        public static void Load(string filePath = "")
        {
            if (string.IsNullOrEmpty(filePath))
            {
                filePath = FilePath;
            }
            Current = filePath.Deserialize<MessageConfigration>();
        }
 
        /// <summary>
        /// カレントデータをファイルへ保存します。
        /// </summary>
        /// <param name="filePath">ファイルパス(空文字の場合、規定のファイルパスから読み込みます。)</param>
        public static void Save(string filePath = "")
        {
            if (string.IsNullOrEmpty(filePath))
            {
                filePath = FilePath;
            }
            Current.Serialize<MessageConfigration>(filePath);
        }
 
        #endregion //Static Methods
    }
}

 

 

<MessageData.Auto.cs>

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Serialization;
 
namespace XmlSerializerSample.Configrations
{
    /// <summary>
    /// メッセージデータの情報を定義します。
    /// </summary>
    [XmlTypeAttribute(AnonymousType = true)]
    public partial class MessageData
    {
        #region Fields
        
        /// <summary>
        /// ID
        /// </summary>
        private string _id;
 
        /// <summary>
        /// 種別名
        /// </summary>
        private string _type;
 
        /// <summary>
        /// メッセージ
        /// </summary>
        private string _message;
 
        #endregion //Fields
 
        #region Properties
 
        /// <summary>
        /// ID
        /// </summary>
        public string ID
        {
            get
            {
                return this._id;
            }
            set
            {
                this._id = value;
            }
        }
 
        /// <summary>
        /// 種別名
        /// </summary>
        public string Type
        {
            get
            {
                return this._type;
            }
            set
            {
                this._type = value;
            }
        }
 
        /// <summary>
        /// メッセージ
        /// </summary>
        public string Message
        {
            get
            {
                return this._message;
            }
            set
            {
                this._message = value;
            }
        }
 
        #endregion //Properties
    }
}

 

 

<MessageData.cs>

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
 
namespace XmlSerializerSample.Configrations
{
    /// <summary>
    /// メッセージデータの情報を定義します。
    /// </summary>
    public partial class MessageData
    {
        #region Properties
 
        /// <summary>
        /// メッセージ種別
        /// </summary>
        public MessageType MessageType
        {
            get
            {
                MessageType result;
                return Enum.TryParse<MessageType>(Type, out result) ? result : default(MessageType);
            }
        }
 
        #endregion //Properties
    }
}

 今回はシリアライズクラスから直接読み込み、書き込みを行えるようにしました。

また、自動生成ファイル(*.Auto.cs)と手動ファイル(*.cs)ファイルを分離することにより、保守性も高めます。

 

今後この設定ファイルに項目を追加した場合は、*.Auto.cs ファイルを変更するだけで読み込み/書き込み処理の変更は不要です。

 

あとは必要に応じてデータのチェック処理を追加すればそこそこ使えるものになるんじゃないでしょうか。

 

 

今回作成したサンプルはこちら↓↓↓

github.com

 

 

 

 

 

時刻の比較

時刻の比較って結構使うんですが、よく忘れるのでメモ

いつも迷って調べるんですが、だいたいMSDNのDateTime.CompareToに飛ばされます。

あのメソッドってミリ秒単位で比較するんで結局ある程度整形した形で比較しないといけないんですよね。

比較するにしてもそれが秒単位の比較なのか日単位の比較なのか実装時に簡単に選べるといいんですが、そんな便利メソッドはありません(知りません)。

 

なので時刻同士を比較する拡張メソッドを作ってみました。

/// <summary>
/// 日時種別
/// </summary>
public enum DateTimeKind
{
    /// <summary></summary>
    Year,
    /// <summary></summary>
    Month,
    /// <summary></summary>
    Day,
    /// <summary>時間</summary>
    Hour,
    /// <summary></summary>
    Minute,
    /// <summary></summary>
    Second,
    /// <summary>ミリ秒</summary>
    Milliseconds,
}
 
/// <summary>
/// DateTime型の拡張メソッドを定義する。
/// </summary>
public static class DateTimeExtensions
{
    /// <summary>
    /// 現在のインスタンスと比較します。
    /// </summary>
    /// <param name="self">自分自身</param>
    /// <param name="value">比較する値</param>
    /// <param name="compareKind">比較する時間の種別</param>
    /// <returns>比較結果(true:一致, false:不一致)</returns>
    public static bool Compare(this DateTime self, DateTime value, DateTimeKind compareKind)
    {
        var ts = self - value;
        double total = 0D;
        double sub = 1.0D;
 
        switch (compareKind)
        {
            case DateTimeKind.Year:
                total = self.Year - value.Year;
                break;
            case DateTimeKind.Month:
                sub = 30.0D;
                total = ts.TotalDays;
                break;
            case DateTimeKind.Day:
                total = ts.TotalDays;
                break;
            case DateTimeKind.Hour:
                total = ts.TotalHours;
                break;
            case DateTimeKind.Minute:
                total = ts.TotalMinutes;
                break;
            case DateTimeKind.Second:
                total = ts.TotalSeconds;
                break;
            case DateTimeKind.Milliseconds:
                total = ts.TotalMilliseconds;
                break;
        }
 
        return Math.Abs(total) < sub;
    }
 
    /// <summary>
    /// 現在のインスタンスと年を比較する。
    /// </summary>
    /// <param name="self">自分自身</param>
    /// <param name="value">比較する値</param>
    /// <returns>比較結果(true:一致, false:不一致)</returns>
    public static bool CompareYear(this DateTime self, DateTime value)
    {
        return self.Compare(value, DateTimeKind.Year);
    }
 
    /// <summary>
    /// 現在のインスタンスと月を比較する。
    /// </summary>
    /// <param name="self">自分自身</param>
    /// <param name="value">比較する値</param>
    /// <returns>比較結果(true:一致, false:不一致)</returns>
    public static bool CompareMonth(this DateTime self, DateTime value)
    {
        return self.Compare(value, DateTimeKind.Month);
    }
 
    /// <summary>
    /// 現在のインスタンスと日を比較する。
    /// </summary>
    /// <param name="self">自分自身</param>
    /// <param name="value">比較する値</param>
    /// <returns>比較結果(true:一致, false:不一致)</returns>
    public static bool CompareDay(this DateTime self, DateTime value)
    {
        return self.Compare(value, DateTimeKind.Day);
    }
 
    /// <summary>
    /// 現在のインスタンスと時間を比較する。
    /// </summary>
    /// <param name="self">自分自身</param>
    /// <param name="value">比較する値</param>
    /// <returns>比較結果(true:一致, false:不一致)</returns>
    public static bool CompareHour(this DateTime self, DateTime value)
    {
        return self.Compare(value, DateTimeKind.Hour);
    }
 
    /// <summary>
    /// 現在のインスタンスと分を比較する。
    /// </summary>
    /// <param name="self">自分自身</param>
    /// <param name="value">比較する値</param>
    /// <returns>比較結果(true:一致, false:不一致)</returns>
    public static bool CompareMinute(this DateTime self, DateTime value)
    {
        return self.Compare(value, DateTimeKind.Minute);
    }
 
    /// <summary>
    /// 現在のインスタンスと秒を比較する。
    /// </summary>
    /// <param name="self">自分自身</param>
    /// <param name="value">比較する値</param>
    /// <returns>比較結果(true:一致, false:不一致)</returns>
    public static bool CompareSecond(this DateTime self, DateTime value)
    {
        return self.Compare(value, DateTimeKind.Second);
    }
 
    /// <summary>
    /// 現在のインスタンスとミリ秒を比較する。
    /// </summary>
    /// <param name="self">自分自身</param>
    /// <param name="value">比較する値</param>
    /// <returns>比較結果(true:一致, false:不一致)</returns>
    public static bool CompareMillisecond(this DateTime self, DateTime value)
    {
        return self.Compare(value, DateTimeKind.Milliseconds);
    }
}

 

方法は単純

DateTime同士の差分TimeSpanのTotal○○を見て一致しているかどうかを判定しているだけです。(Compareメソッド

あとはそれぞれの比較メソッドを追加すれば、結構使いやすくなるんじゃないでしょうか?

 

使い方はこんな感じ。

private void Button_Click(object sender, RoutedEventArgs e)
{
    var now = DateTime.Now;
    var inputTime = DateTime.Parse(DateTimeTextBox.Text);
 
    if (!inputTime.CompareYear(now))
    {
        ConsoleLog.Write("年が異なる。");
    }
    if (!inputTime.CompareMonth(now))
    {
        ConsoleLog.Write("月が異なる。");
    }
    if (!inputTime.CompareDay(now))
    {
        ConsoleLog.Write("日が異なる。");
    }
    if (!inputTime.CompareHour(now))
    {
        ConsoleLog.Write("時間が異なる。");
    }
    if (!inputTime.CompareMinute(now))
    {
        ConsoleLog.Write("分が異なる。");
    }
    if (!inputTime.CompareSecond(now))
    {
        ConsoleLog.Write("秒が異なる。");
    }
 
}

 

今回のサンプルはこちら↓↓

github.com