MVVM最早是在WPF開發中了解到的,現在流行的web前端框架Vue也借鑒了這種思路:Viewmodel Binder View,我個人的話說就是:視圖就是數據,數據就是視圖。
在web端使用Vue.js能很方便使數據與element進行雙向綁定,使得前后端的耦合度大大的降低;
重新回到winform,使用MVVM來做項目,順便記錄下,注意:只是viewModel與view的雙向綁定,viewmodel與Model轉換自己想方法。
先來2個Viewmodel,操作重點是Student,繼承INotifyPropertyChanged,當ViewModel的屬性發生改變,即執行了set訪問器時,觸發PropertyChanged事件,通知前端控件即時更新綁定的屬性值;
public class Room
{
public int Id { get; set; }
string _number;
string _building;
int _max;
public string Number { get; set; }
public string Building { get; set; }
public string Address
{
get
{
if (Number != null && Building != null)
{
return Building + Number;
}
return "";
}
}
public int Max { get; set; }
}
public class Student : INotifyPropertyChanged
{
private string _name = string.Empty;
private DateTime _brithday = DateTime.Parse("1753-01-01 00:00:00.000");
private double _high;
private decimal _money;
private bool _enable = true;
private int? _roomId;
private Room _room;
private double _qty;
private decimal _price;
public int Id { get; set; }
public string Name
{
get
{
return _name;
}
set
{
_name = value;
OnPropertyChanged("Name");
}
}
public DateTime Brithday
{
get
{
return _brithday;
}
set
{
_brithday = value;
OnPropertyChanged("Brithday");
}
}
public double High
{
get
{
return _high;
}
set
{
_high = value;
OnPropertyChanged("High");
}
}
public decimal Money
{
get
{
return _money;
}
set
{
_money = value;
OnPropertyChanged("Money");
}
}
public bool Enable
{
get
{
return _enable;
}
set
{
_enable = value;
OnPropertyChanged("Enable");
}
}
public int? RoomId
{
get
{
if (Room != null)
{
_roomId = Room.Id;
}
return _roomId;
}
set { }
}
public Room Room
{
get
{
return _room;
}
set
{
_room = value;
OnPropertyChanged("Room");
}
}
public double Qty
{
get
{
return _qty;
}
set
{
_qty = value;
OnPropertyChanged("Qty");
OnPropertyChanged("Account");
}
}
public decimal Price
{
get
{
return _price;
}
set
{
_price = value;
OnPropertyChanged("Price");
OnPropertyChanged("Account");
}
}
public decimal Account
{
get
{
if (_qty != 0 && _price != 0)
{
var s = decimal.Parse(_qty.ToString()) * _price;
return s;
}
return 0;
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string proName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(proName));
}
}
然后是winform界面,要對student對象操作,把這些textbox,checkbox,combobox的tag設置為對應student對象的屬性名

思路:通過找到窗體內所有控件,如果tag屬性與studnet的屬性相同,則把控件與student對象屬性綁定;
先上后台代碼:
1 public partial class MVVM : Form 2 { 3 private Student VModel; 4 private List<Room> Rooms = new List<Room>() { 5 new Room {Id=0, Building="",Number="請選擇..." } 6 }; 7 public MVVM() 8 { 9 InitializeComponent(); 10 #region 模擬數據 11 VModel = new Student 12 { 13 //屬性賦值表示以修改模式打開這個頁面,如果是新增模式,把屬性都注釋掉 14 //已測試,修改、新增模式都可通過 15 Id = 1, 16 Name = "Jack", 17 Brithday = DateTime.Now.AddYears(-22), 18 Enable = true, 19 High = 175.4, 20 Money = 10000000000.5m, 21 Room = new Room { Id = 2, Building = "A棟", Number = "102", Max = 8 }, 22 RoomId = 2 23 }; 24 if (VModel.Room != null) Rooms.Add(VModel.Room); 25 #endregion 26 } 27 /// <summary> 28 /// 遞歸獲取所有控件 29 /// </summary> 30 /// <param name="ctl"></param> 31 /// <returns></returns> 32 private List<Control> GetAllControl(Control ctl) 33 { 34 List<Control> result = new List<Control>(); 35 if (ctl.HasChildren) 36 { 37 if (!(ctl is Form)) result.Add(ctl); 38 foreach (Control item in ctl.Controls) 39 { 40 result.AddRange(GetAllControl(item)); 41 } 42 } 43 else 44 { 45 result.Add(ctl); 46 } 47 48 return result; 49 } 50 protected override void OnLoad(EventArgs e) 51 { 52 base.OnLoad(e); 53 54 this.cbxRoom.DisplayMember = "Address"; 55 this.cbxRoom.ValueMember = "Id"; 56 57 this.cbxRoom.DataSource = Rooms; 58 59 List<Control> list = GetAllControl(this); 60 var propes = VModel.GetType().GetProperties(); 61 try 62 { 63 foreach (Control item in list) 64 { 65 var prop = propes.FirstOrDefault(a => item.Tag != null && a.Name == item.Tag.ToString()); 66 if (prop != null) 67 { 68 if (item is TextBox) item.DataBindings.Add(new Binding("Text", VModel, prop.Name)); 69 if (item is CheckBox) item.DataBindings.Add(new Binding("Checked", VModel, prop.Name)); 70 if (item is ComboBox) item.DataBindings.Add(new Binding("SelectedItem", VModel, prop.Name)); 71 72 } 73 } 74 } 75 catch (Exception) 76 { 77 78 throw; 79 } 80 } 81 82 /// <summary> 83 /// 查看 button 84 /// </summary> 85 /// <param name="sender"></param> 86 /// <param name="e"></param> 87 private void button1_Click(object sender, EventArgs e) 88 { 89 MessageBox.Show(Newtonsoft.Json.JsonConvert.SerializeObject(VModel)); 90 } 91 /// <summary> 92 /// 更改屬性 button 93 /// </summary> 94 /// <param name="sender"></param> 95 /// <param name="e"></param> 96 private void button2_Click(object sender, EventArgs e) 97 { 98 #region 測試更改viewmodel屬性,視圖是否會更新 99 VModel.Name = "Bob"; 100 Room r6 = Rooms.FirstOrDefault(a => a.Id == 6); 101 if (r6 != null) VModel.Room = r6; 102 VModel.Qty = 120; 103 VModel.Price = 12; 104 #endregion 105 } 106 /// <summary> 107 /// 標識是否已從服務器獲取到數據 108 /// </summary> 109 private bool IsLoadRooms = false; 110 /// <summary> 111 /// room combobox DropDown事件處理,第一次從服務器獲取數據 112 /// </summary> 113 /// <param name="sender"></param> 114 /// <param name="e"></param> 115 private void cbxRoom_DropDown(object sender, EventArgs e) 116 { 117 if (!IsLoadRooms) 118 { 119 #region 模擬請求服務器得到數據 120 for (int i = 0; i < 10; i++) 121 { 122 Room r = new Room { Building = "A棟", Max = 8, Number = "10" + (i + 1), Id = i + 1 }; 123 if (!Rooms.Exists(a => a.Id == r.Id)) 124 { 125 Rooms.Add(r); 126 } 127 } 128 #endregion 129 //重新綁定數據源 130 this.cbxRoom.DataSource = null; 131 this.cbxRoom.DisplayMember = "Address"; 132 this.cbxRoom.ValueMember = "Id"; 133 this.cbxRoom.DataSource = Rooms; 134 IsLoadRooms = true; 135 136 } 137 } 138 }
說明:
1、GetAllControl 方法獲取所有控件,toolstrip里 的toolstripButton是不能獲取到的,還有其他的一些控件,這里不談論;
2、當前頁面是進行對象進行新增、修改、參看等操作的頁面
3、VModel的Room屬性類型為其他對象,需要對其進行操作時,第一次從服務器端請求數據源,保存到當前的Rooms List里;
4、不同控件綁定的屬性是不同的,如name屬性類型為string,可用放到textbox里綁定,即textbox.text=name,同理,checkbox綁定checked 對象類型為bool,combobox是數據源選擇,默認給一個提示選擇item,然后讓他綁定數據源rooms,新增操作時,vmodel.Room是null,所有當前combobox.selecteditem還是默認第一個;以修改模式打開時vmodel.Room有值時,經過binding后,selecteditem就是當前vmodel.Room,當然前提是把Room屬性值放到rooms中;
現在運行試試

效果還不錯,對了,binding還有一個好處就是可用驗證輸入數據合理性
( ̄▽ ̄)"
