日常的工作當中,還是難免遇到需要維護 Windows Forms 的程式,基本上 Windows Forms 控制項的互動能夠用 Binding 的方式去處理,我就儘量用 Binding 的方式去處理,讓我自己能夠有更多的精神放在商業邏輯上,這篇文章想跟大家分享 Windows Forms 的三種 Binding 的連動方式。
TwoWayBinding
TwoWayBinding(雙向綁定)是 Windows Forms 預設的綁定方式,控制項跟資料源綁定上之後,不管是控制項上面的值異動了,還是資料源的值異動了,都會彼此連動,當我們有需要對使用者的輸入,在同一個控制項上去做出回應的時候,就可以用 TwoWayBinding。
假定我的畫面有一個 TextBox 及一個 Button,然後我想 TextBox 的 Text 屬性跟 MyViewModel 的 Id 屬性做雙向綁定。


OneWayBinding
OneWayBinding(單向綁定)我把它定義為資料源的值異動了,會連動控制項的值,反過來則不會,這個 Binding 需要將 DataSourceUpdateMode
設定為 DataSourceUpdateMode.Never
,意思就是說「不要更新 DataSource」,OneWayBinding 通常用在對於使用者來說,這個控制項所代表的資料源是唯讀的時候。


OneWayToSourceBinding
最後是 OneWayToSourceBinding(單向來源綁定),它是與 OneWayBinding 相反的連動方式,控制項的值異動了,會連動資料源的值,反過來則不會,通常用在不需要去驗證輸入格式的控制項上,或是說之後的運算會去異動資料源,但不希望這個異動反應到控制項上,要做到 OneWayToSourceBinding 我們需要將 ControlUpdateMode
設定為 ControlUpdateMode.Never
。


最後跟大家分享一個小技巧,從剛剛的範例就可以看到,觸發資料源更新是在焦點從 TextBox 移開之後,但是有時候我們想所見即所得,隨著使用者一邊輸入,一邊就更新資料源,這時候我們只要將 DataSourceUpdateMode 設定為 DataSourceUpdateMode.OnPropertyChanged
就可以了。


完整原始碼
public partial class Form1 : Form
{
private readonly MyViewModel myViewModel;
public Form1()
{
this.InitializeComponent();
this.myViewModel = new MyViewModel();
// TwoWayBinding
//this.textBox1.DataBindings.Add("Text", this.myViewModel, nameof(MyViewModel.Id));
// OneWayBinding
//var binding = new Binding("Text", this.myViewModel, nameof(MyViewModel.Id))
// {
// DataSourceUpdateMode = DataSourceUpdateMode.Never
// };
//this.textBox1.DataBindings.Add(binding);
// OneWayToSourceBinding
//var binding = new Binding("Text", this.myViewModel, nameof(MyViewModel.Id))
// {
// ControlUpdateMode = ControlUpdateMode.Never
// };
//this.textBox1.DataBindings.Add(binding);
// 所見即所得
var binding = new Binding("Text", this.myViewModel, nameof(MyViewModel.Id))
{
ControlUpdateMode = ControlUpdateMode.Never, DataSourceUpdateMode = DataSourceUpdateMode.OnPropertyChanged
};
this.textBox1.DataBindings.Add(binding);
}
private void button1_Click(object sender, EventArgs e)
{
this.myViewModel.Id = new Random(Guid.NewGuid().GetHashCode()).Next(100);
}
}
public class MyViewModel : INotifyPropertyChanged
{
private int id;
public event PropertyChangedEventHandler PropertyChanged;
public int Id
{
get => this.id;
set
{
if (value == this.id) return;
this.id = value;
this.OnPropertyChanged();
Console.WriteLine($"{nameof(MyViewModel.Id)} change to {value}.");
}
}
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}