げんさん日記

プログラミングで気付いた事等を書きます。

ViewModelのバインディングプロパティを簡単に定義する。

2019年09月12日 16時39分11秒 | WPF
ViewModelのバインディングプロパティを定義する場合にViewへの通知処理が存在する為、プロパティの定義がめんどくさくなります。
それを簡単に書く方法を記します。
また下記方法によりプロパティの解放を一括で行う為、メモリリークするコマンド等も解放されます。

BindingModel (画面通知するモデル)に下記内容を追加します。

プロパティを格納する箱を用意します。
public abstract class BindingModel : BaseModel
{
  // プロパティを管理する箱
  private Dictionary Propertys { get; set; } = new Dictionary();
}

次にGetプロパティを作成します。
protected T Get<T>([CallerMemberName] string name = null)
{
  // プロパティ名が無い場合はデフォルト値を返します。
  if (string.IsNullOrEmpty(name) == true)
  {
   return default(T);
  }

  // プロパティリストが無い場合はデフォルト値を返します。※Dispose時に発生する場合あり
  if (this.PropertieItems == null)
  {
   return default(T);
  }

  // プロパティリストから名前が同一の情報を取得し返します。
  object value = null;
  if (this.PropertieItems.TryGetValue(name, out value) == true)
  {
   return value == null ? default(T) : (T)value;
  }

  // プロパティリストに名前が同一の情報が無い時、デフォルト値を返します。
  return default(T);
}

次にSetプロパティを作成します。
protected void Set<T>(T value, [CallerMemberName] string name = null)
{
  // プロパティ名が無い場合は処理を終了します。
  if (string.IsNullOrEmpty(name) == true)
  {
   return;
  }

  // プロパティリストが無い場合は処理を終了します。※Dispose時に発生する場合あり
  if (this.PropertieItems == null)
  {
   return;
  }
  // プロパティが存在するかチェックします。
  if (this.Propertys.ContainsKey(name) == false)
  {
    // キーが存在しない時、プロパティリストに追加します。
    this.Propertys.Add(name, value);

    // プロパティ変更通知を発生させます。
    this.NotifyPropertyChanged(name);
  }
  else
  {
    // キーが存在する時、値を変更します。
    if (Equals(value, this.Get(name)))
    {
      // 同一の値の場合は処理を終了します。
      return;
    }

    // プロパティの値を置き換えます。
    this.Propertys[name] = value;
    // プロパティ変更通知を発生させます。
    this.NotifyPropertyChanged(name);
  }
}

最後にまとめてプロパティを解放する処理を記述します。
protected override void Dispose(bool disposing)
{
  // 終了処理中の時は処理を行わない。
  if (base.Disposed == false)
  {
    // プロパティを全て解放する。
    this.Propertys?.RemoveAll();※
    this.Propertys = null;

    // ベースのDisposeを呼び出す。
    base.Dispose(disposing);
  }
}
※RemoveAllは拡張メソッドでIDisposable属性のものを探しDisposeを実行する処理を自作したものです。

使用例
public void TestViewModel : BindingModel
{
  // バインディングプロパティ
  public int Code
  {
    get { base.Get<int>(); }
    set { base.Set(value); }
  }
}
上記のようにバインディングプロパティがすっきりします。

メモリリーク対策

2019年09月12日 14時43分01秒 | C#
C#のプログラミングで気を付けないといけないのがメモリリークです。通常のシステム開発ではそれ程気にしなくても良いのですが画像処理等メモリを多く使う場合やWPFにおいてメモリリークは問題になってきます。
その対策方法としてモデルのベースに解放処理を組み込み、PGではUsingを使用し確実なメモリの解放を行うこととする。
下記にその内容を記述します。

Dispose中かを判断するフラグを用意する。
public abstract class BaseModel : IDisposable
{
  protected bool IsDisposed { get; set; } = false;
}

継承するDisposeメソッドを定義します。
protected virtual void Dispose(bool disposing)
{
  // 終了処理中でない時、下記処理を行う。
  if (this.IsDisposed == false)
  {
    if (disposing == true)
    {
      // ガーベージコレクションを解放します。
      // ※自分のみを解放します。(全てを解放すると処理がすごく重くなります。)
      GC.SuppressFinalize(this);
    }
  this.Disposed = true;
}

パブリックのDisposeメソッドを定義します。
public void Dispose()
{
  // Dispose(引数:true)を呼び出します。
  this.Dispose(true);
}

ファイナライザを定義します。
~BaseModel()
{
  // Dispose(引数:false)を呼び出します。
  this.Dispose(false);
}

継承モデル例
public class Test : BaseModel
{
  protected override void Dispose(bool disposing)
  {
    // 終了処理でない時に処理を行う。
    if (this.IsDisposed == false)
    {
      // 管理外のオブジェクトを解放する。
      ・・・
      // ベースのDisposeを呼び出す。
      base.Dispose(disposing);
    }
  }
}

使用例
public class Main
{
  public Main()
  {
    // Usingで処理することで確実にメモリの解放を行える。
    using (var model = new Test()
    {
      ・・・
    }
  }
}

ViewModelによるユーザーコントロールの切り替え方法

2019年09月03日 16時21分58秒 | WPF
Viewmodel内でパネルの内容を動的に切り替える方法を記します。
方法としてはDataTemplateを定義して切り替えます。
手順は下記の通りです。

App.XamlにViewとViewModelのマッチングをApplicationのリソースに定義します。
<Application.Resources>
  <DataTemplate DataType="{x:Type local:Panel1ViewModel}">
    <local:Panel1View/>
  </DataTemplate>
  <DataTemplate DataType="{x:Type local:Panel2ViewModel}">
    <local:Panel2View/>
  </DataTemplate>
</Application.Resources>

XamlにContentControlを配置する。
<ContentControl Content="{Binding ViewModel}"/>

ViewModelで表示するViewModelを設定する。
public class MainViewModel : BaseViewModel
{
  // 表示させる画面のViewModel
  public BaseViewModel ViewModel
  {
    get { return base.Get<BaseViewModel >(); }
    set { base.Set(value);
  }

  public MainViewModel(int no)
  {
    if (no == 0)
    {
      this.ViewModel = new Panel1ViewModel();
    }
    else
    {
      this.ViewModel = new Panel2ViewModel();
    }
  }
}


ViewModelからViewを表示する

2019年09月03日 13時27分34秒 | WPF
Livet及びPrism等のフレームワークを使用した場合、メッセンジャー形式の為、ViewModelからViewを表示した場合の戻り値がイベントとなり、ソースが見にくくなります。ViewModelのみで完結する方法として下記ViewとViewModelのマッチングする方法を記述します。
作成意図としてはViewModelからViewを表示し戻り値を次の行で受け取る仕組みにすることでPGの可読性を高くする。

ViewとViewModelのマッチングDictionaryをAppに作成する。
public partial class App : Application
{
  private Dictionary ViewModelMatchings { get; set; } = new Dictionary<Type,Type>();
}

コンストラクタでViewとViewModelのマッチングを定義する。
public partial class App : Application
{
  public App()
  {
    ViewModelMatchings.Add(typeof(MainView),typeof(MainViewModel));
    ViewModelMatchings.Add(typeof(SubView),typeof(SubviewModel));
  }
}

ViewModelBaseで画面表示処理を行う。
public class ViewModelBase
{
  // ViewModelからViewを生成する
  public IVewResult ShowDialog(T viewModel)
  {
    // ViewModelに対応するViewを検索する
    var ret = ViewModelMatchings.ContainsKey(viewModel.GetType());
    if (ret = true)
    {
      // Viewを生成し、DataContextにViewModelを設定する
      var viewType = ViewModelMatchings[typeof(viewModel)];
      var view = Activator.CreateInstance(viewType) as Window;
      view.DataContext = viewModel;
      view.ShowDialog();
      return view.DataContext.Result as IViewResult;
    }
  }
}

使用例
public class MainViewModel : ViewModelBase
{
  // サブ画面表示
  public void ShowSubWindow()
  {
    using (var vm = new SubViewModel())
    {
      var ret = base.ShowDialog(vm);
      // retの情報により処理を分けられる。
    }
  }
}
上記のメリットは画面表示後の処理をViewModelで連続して処理を行うことができます。

Windowがアクティブな時のみ枠が表示される。

2019年09月03日 10時06分40秒 | WPF
Xamlのリソースにボーダーのスタイルを作成する。
 <Window.Resources>
    <Style x:Key="ActiveBorderStyle" TargetType="{x:Type Border}"> 
    </Style>
</Window.Resources>   

アクティブ時のボーダー色をBlueVioletで表示する。
 <Setter Property="BorderBrush" Value="BlueViolet" />
 <Setter Property="BorderThickness" Value="1" />  

非アクティブ時のボーダー色をWindowのBackgroundと同一にする。
 <Style.Triggers>
    <DataTrigger Binding="{Binding ElementName=BaseWindow, Path=IsActive}" Value="false">
        <Setter Property="BorderBrush" Value="{Binding ElementName=BaseWindow, Path=Background}" />
    </DataTrigger>
 </Style.Triggers> 

最終的に下記のようになります。
<Window.Resources>
  <Style x:Key="ActiveBorderStyle" TargetType="{x:Type Border}">
    <Setter Property="BorderBrush" Value="BlueViolet" />
    <Setter Property="BorderThickness" Value="1" />
    <Style.Triggers>
      <DataTrigger Binding="{Binding ElementName=BaseWindow, Path=IsActive}" Value="false">
        <Setter Property="BorderBrush" Value="{Binding ElementName=BaseWindow, Path=Background}" />
      </DataTrigger>
    </Style.Triggers> 
  </Style>
</Window.Resources>
<Border Style="{StaticResource ActiveBorderStyle}"/>
  <Grid>
    ・・・・
  </Grid>
</Border>

実行例