WinFormsでReactivePropertyを使いたい
WinFormsでMVVMしたく、その手段の1つにReactivePropertyを利用した時、バインドがイマイチなので、Windows Forms で MVVM 2 (ReactiveProperty 編)からちょっとだけガチャガチャした。
コード
他のライブラリも使っていてMetNumericTextBox, AutoCompleteBoxとかTouchStripButtonとかは標準にないので、不要ならカットすればいいと思う。
必要なコントロールのプロパティ分を書けばいける!
WinFormsで開発している以上、プロジェクトごとに毎回作り上げて差が生まれるのはめっちゃ面倒なので、めっちゃコントロールとプロパティをカバーしたライブラリが欲しいなぁ(´-`).。oO
記述例
processNameLabel.Bind(() => ViewModel.Subject.ProcessName.Value);
itemCdTextBox.Bind(() => ViewModel.Subject.ItemCd.Value);
abilityButton1.Bind(ViewModel.Ability1Command);
exponentTextBox.BindEnabled(() => ViewModel.Subject.ExponentChecked.Value);
majorNameCdComboBox.BindDataSource(() => ViewModel.Subject.MajorNames, nameof(NamesNameDto.NameCd), nameof(NamesNameDto.Name));
PropertyBindHelper.cs
using FarPoint.Win.Spread;
using Metroit.Windows.Forms;
using Reactive.Bindings;
using SdEstimate.Core.Exceptions;
using System;
using System.Linq.Expressions;
using System.Windows.Forms;
namespace Hoge
{
/// <summary>
/// ReactiveProperty による値、状態のバインドを行うヘルパー。
/// </summary>
public static class PropertyBindHelper
{
/// <summary>
/// 値、状態をバインドします。
/// </summary>
/// <typeparam name="T"></typeparam>
/// <typeparam name="U"></typeparam>
/// <param name="item1">バインドするプロパティの Expression。</param>
/// <param name="item2">バインドする値のExpression。</param>
public static void Bind<T, U>(Expression<Func<T>> item1, Expression<Func<U>> item2)
{
Tuple<object, string> ResolveLambda<V>(Expression<Func<V>> expression)
{
var lambda = expression as LambdaExpression;
if (lambda == null) throw new ArgumentException();
var property = lambda.Body as MemberExpression;
if (property == null) throw new ArgumentException();
var parent = property.Expression;
return new Tuple<object, string>(Expression.Lambda(parent).Compile().DynamicInvoke(), property.Member.Name);
}
var tuple1 = ResolveLambda(item1);
var tuple2 = ResolveLambda(item2);
var control = tuple1.Item1 as Control;
var control2 = tuple1.Item1 as AutoCompleteBox;
if (control == null && control2 == null) throw new ArgumentException();
if (control != null)
{
// 既に設定済みのバインドを解除する
var binding = control.DataBindings[tuple1.Item2];
if (binding != null)
{
control.DataBindings.Remove(binding);
}
// NOTE: UIのプロパティがNullableの場合、正しくデータバインドできないため、formattingEnabled を true とする。
control.DataBindings.Add(new Binding(tuple1.Item2, tuple2.Item1, tuple2.Item2, true, DataSourceUpdateMode.OnPropertyChanged));
}
else
{
// 既に設定済みのバインドを解除する
var binding = control2.DataBindings[tuple1.Item2];
if (binding != null)
{
control2.DataBindings.Remove(binding);
}
// NOTE: UIのプロパティがNullableの場合、正しくデータバインドできないため、formattingEnabled を true とする。
control2.DataBindings.Add(new Binding(tuple1.Item2, tuple2.Item1, tuple2.Item2, true, DataSourceUpdateMode.OnPropertyChanged));
}
}
/// <summary>
/// ラベルの値バインドを行います。
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="label">ラベルオブジェクト。</param>
/// <param name="expression">値のExpression。</param>
public static void Bind<T>(this Label label, Expression<Func<T>> expression)
{
Bind(() => label.Text, expression);
}
/// <summary>
/// 数値ラベルの値バインドを行います。
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="label">ラベルオブジェクト。</param>
/// <param name="expression">値のExpression。</param>
public static void Bind<T>(this MetNumericLabel label, Expression<Func<T>> expression)
{
Bind(() => label.Value, expression);
}
/// <summary>
/// テキストボックスの値バインドを行います。
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="textBox">テキストボックスオブジェクト。</param>
/// <param name="expression">値のExpression。</param>
public static void Bind<T>(this TextBox textBox, Expression<Func<T>> expression)
{
Bind(() => textBox.Text, expression);
}
/// <summary>
/// 数値テキストボックスの値バインドを行います。
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="textBox">テキストボックスオブジェクト。</param>
/// <param name="expression">値のExpression。</param>
public static void Bind<T>(this MetNumericTextBox textBox, Expression<Func<T>> expression)
{
Bind(() => textBox.Value, expression);
}
/// <summary>
/// 日付ピッカーの値バインドを行います。
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="dateTimePicker">日付ピッカーオブジェクト。</param>
/// <param name="expression">値のExpression。</param>
public static void Bind<T>(this DateTimePicker dateTimePicker, Expression<Func<T>> expression)
{
Bind(() => dateTimePicker.Value, expression);
}
/// <summary>
/// ラジオボタンの値バインドを行います。
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="radioButton">ラジオボタンオブジェクト。</param>
/// <param name="expression">値のExpression。</param>
public static void Bind<T>(this RadioButton radioButton, Expression<Func<T>> expression)
{
Bind(() => radioButton.Checked, expression);
}
/// <summary>
/// コンボボックスの値バインドを行います。
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="comboBox">コンボボックスオブジェクト。</param>
/// <param name="expression">値のExpression。</param>
public static void Bind<T>(this ComboBox comboBox, Expression<Func<T>> expression)
{
Bind(() => comboBox.SelectedItem, expression);
}
/// <summary>
/// コンボボックスのデータソースバインドを行います。
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="comboBox">コンボボックスオブジェクト。</param>
/// <param name="expression">値のExpression。</param>
/// <param name="valueMember">値のメンバ名。</param>
/// <param name="displayMenber">表示値のメンバ名。</param>
public static void BindDataSource<T>(this ComboBox comboBox, Expression<Func<T>> expression, string valueMember, string displayMenber)
{
Bind(() => comboBox.DataSource, expression);
comboBox.ValueMember = valueMember;
comboBox.DisplayMember = displayMenber;
}
/// <summary>
/// AutoCompleteBox のデータソースバインドを行います。
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="autoCompleteBox">AutoCompleteBox オブジェクト。</param>
/// <param name="expression">値のExpression。</param>
/// <param name="valueMember">値のメンバ名。</param>
/// <param name="displayMenber">表示値のメンバ名。</param>
public static void BindDataSource<T>(this AutoCompleteBox autoCompleteBox, Expression<Func<T>> expression, string valueMember, string displayMenber)
{
Bind(() => autoCompleteBox.DataSource, expression);
autoCompleteBox.ValueMember = valueMember;
autoCompleteBox.DisplayMember = displayMenber;
}
/// <summary>
/// ボタンの実行バインドを行います。
/// </summary>
/// <param name="button">ボタンオブジェクト。</param>
/// <param name="command">コマンド。</param>
public static void Bind(this Button button, ReactiveCommand command)
{
command.CanExecuteChanged += (sender, args) => button.Enabled = command.CanExecute();
button.Enabled = command.CanExecute();
button.Click += (sender, args) => command.Execute();
}
/// <summary>
/// メニューボタンの実行バインドを行います。
/// </summary>
/// <param name="button">メニューボタンオブジェクト。</param>
/// <param name="command">コマンド。</param>
public static void Bind(this TouchStripButton button, ReactiveCommand command)
{
command.CanExecuteChanged += (sender, args) => button.Enabled = command.CanExecute();
button.Enabled = command.CanExecute();
button.Click += (sender, args) =>
{
try
{
command.Execute();
}
catch (BusinessException ex)
{
AppMessageBox.Error(button.Owner.FindForm(), ex.Message);
}
};
}
/// <summary>
/// コントロールの活性バインドを行います。
/// </summary>
/// <param name="control">コントロールオブジェクト。</param>
/// <param name="expression">値のExpression。</param>
public static void BindEnabled<T>(this Control control, Expression<Func<T>> expression)
{
Bind(() => control.Enabled, expression);
}
/// <summary>
/// ボタンの表示バインドを行います。
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="button">ボタンオブジェクト。</param>
/// <param name="expression">値のExpression。</param>
public static void BindVisible<T>(this Button button, Expression<Func<T>> expression)
{
Bind(() => button.Visible, expression);
}
/// <summary>
/// パネルの表示バインドを行います。
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="panel">パネルオブジェクト。</param>
/// <param name="expression">値のExpression。</param>
public static void BindVisible<T>(this ScrollableControl panel, Expression<Func<T>> expression)
{
Bind(() => panel.Visible, expression);
}
}
}