在WinForm中使用ReactiveUI框架,网上可以搜到一部分,但是大多是和基础操作有关的,与Collection类数据的绑定有关的文章少之又少,能搜到的基本上都是关于WPF的,这里整理了一些关于Collection类型数据绑定的注意事项。
这是官方关于ReactiveUI中Collection类型数据的使用手册https://reactiveui.net/docs/handbook/collections/;官方给出的例程中使用的Collection是ReadOnlyObservableCollection,该Collection实现了INotifyPropertyChanged接口,按理说,在绑定到控件的DataSource之后应该能够自动接收集合更改的通知,自动刷新UI才对,但是经过反反复复,两天的尝试无论我怎么更改绑定的方式,无论是使用ReadOnlyObservableCollection还是ObservableCollection,WinForm的UI都无法自动刷新,但是在WPF中可以刷新。
于是尝试使用其他的Collection,List肯定不行,因为基本上没有实现和UI刷新有关的接口,选择使用BindingList,更换完之后一切都顺畅了,查看BindingList的源码发现,这个Collection并没有实现INotifyPropertyChanged接口,但是实现了IRaiseItemChangedEvents接口,猜测是WinForm和WPF的界面刷新机制不同,WPF是INotifyPropertyChanged触发刷新,WinForm是IRaiseItemChangedEvents触发刷新,但是还有待测试。在使用了BindingList之后往其中增删成员界面会自动刷新,在成员实现了INotifyPropertyChanged接口之后,成员的属性发生变化之后,也会触发界面刷新。
BindingList的文档: public class BindingList : Collection, IBindingList, IList, ICollection, IEnumerable, ICancelAddNew, IRaiseItemChangedEvents
基于ComboBox进行测试的几段代码:
ViewModel:
public class ComboBoxViewModel : ReactiveObject
{
// private readonly SourceList<string> _NameList;
// public IObservable<IChangeSet<string>> NameList => _NameList.Connect();
// private readonly ReadOnlyObservableCollection<Person> _items;
//public ReadOnlyObservableCollection<Person> Items => _items;
// public ObservableCollectionExtended<Person> itemExtend;
private readonly BindingList<Person> _Names;
public BindingList<Person> Names => _Names;
/*
public ObservableAsPropertyHelper<List<string>> _ItemList;
public List<string> ItemList => _ItemList.Value;
*/
private string _text;
public string Text
{
get => _text;
set => this.RaiseAndSetIfChanged(ref _text, value);
}
private int _Age;
public int Age
{
get => _Age;
set => this.RaiseAndSetIfChanged(ref _Age,value);
}
private int _Index;
public int Index
{
get => _Index;
set => this.RaiseAndSetIfChanged(ref _Index,value);//当前选定的Item序列
}
public ReactiveCommand<Unit, Unit> AddCommand;
public ReactiveCommand<Unit, Unit> DeleteCommand;
// private readonly ObservableAsPropertyHelper<int> _Num;
public int Num => _Num.Value;
public Interaction<string, bool> DelInteraction;
public ComboBoxViewModel()
{
DelInteraction = new Interaction<string, bool>();
_Names = new BindingList<Person>();
itemExtend = new ObservableCollectionExtended<Person>();
itemExtend.Add(new Person { Name="徐群",Age=23});
itemExtend.Add(new Person { Name="刘婷",Age=24});
itemExtend.Add(new Person { Name="陈世美",Age=45});
itemExtend.Add(new Person { Name = "刘语熙", Age = 15 });
itemExtend.ToObservableChangeSet()
//.Filter(x=>x.Age>20)//过滤器,lamda表达式返回Bool且为true的时候筛选出来
//.Sort(SortExpressionComparer<Person>.Ascending(Person=>Person.Age))
//.Bind(out _items)
//.Bind(itemExtend)
.Bind(_Names)
.Subscribe()
;
_Num = _items.ToObservableChangeSet()
.Filter(x => x.Age > 20)
.Count()
.ToProperty(this, x => x.Num);
AddCommand = ReactiveCommand.Create(()=>
{
itemExtend.Add(new Person { Name = Text, Age = _Age });
// itemExtend = itemExtend.(x => x.Age);
/* if (itemExtend.Count > 0)
{
itemExtend.First().Name="DAD";
}
*/
});
DeleteCommand = ReactiveCommand.CreateFromTask(async ()=> {
if (itemExtend.Count > 0)
{
//var res = await DelInteraction.Handle(itemExtend[Index].Name);//界面交互
//if (res)
//{
itemExtend.RemoveAt(Index);
//}
}
});
}
}
主界面cs文件的绑定:
this.WhenActivated(a=> {
this.OneWayBind(ViewModel,
x=>x.Names,
x=>x.cmbBoxCollection.DataSource
).DisposeWith(a);//绑定ComBoBox的数据源
cmbBoxCollection.DisplayMember = "Name";//不变的属性可以直接这样表示
this.Bind(ViewModel,
x=>x.Text,
x=>x.txtBoxShow.Text).DisposeWith(a);
this.Bind(ViewModel,
x=>x.Age,
x=>x.txtBoxAge.Text).DisposeWith(a);
this.BindCommand(ViewModel,
x => x.AddCommand,
x => x.btnAdd
).DisposeWith(a);//绑定添加命令
this.BindCommand(ViewModel,
x=>x.DeleteCommand,
x=>x.button2).DisposeWith(a);//绑定删除命令
this.Bind(ViewModel,
x=>x.Index,
x=>x.cmbBoxCollection.SelectedIndex,
this.cmbBoxCollection.Events().SelectedIndexChanged).DisposeWith(a);//绑定当前选中的序列号
this.OneWayBind(ViewModel,
x=>x.Num,
x=>x.label2.Text);
/*
this.ViewModel.DelInteraction.RegisterHandler(async Interaction=> {
var res = await SendRes("You want to Delete "+Interaction.Input+"?","Delete Confirm");
Interaction.SetOutput(res);
}).DisposeWith(a);//给交互对象注册事件
*/
});
用于操作的实体类:
public class Person : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));//实现通知刷新
}
}
private bool _National;
public bool National
{
get => _National;
set
{
if (value != _National)
{
_National = value;
NotifyPropertyChanged();//通知刷新
}
}
}
private string _Name;
public string Name
{
get => _Name;
set
{
if (value != _Name)
{
_Name = value;
NotifyPropertyChanged();
}
}
}
private int _Age;
public int Age
{
get => _Age;
set
{
if (value != _Age)
{
_Age = value;
NotifyPropertyChanged();
}
}
}
private Province _Pr;//枚举类型,可自定义
public Province Pr
{
get => _Pr;
set {
if (value != _Pr)
{
_Pr = value;
NotifyPropertyChanged();
}
}
}
private Club _Fc;//枚举类型
public Club Fc
{
get => _Fc;
set
{
if (value != _Fc)
{
_Fc = value;
NotifyPropertyChanged();
}
}
}
// public static object obj = new object();
public Person()
{
Random rd = new Random();
Age = rd.Next(18, 40);
Name = GetRandomChinese(rd.Next(2, 4));
Province[] Iprovinces = Enum.GetValues(typeof(Province)) as Province[];
_Pr = Iprovinces[rd.Next(0, Iprovinces.Length)];
Club[] clubs = Enum.GetValues(typeof(Club)) as Club[];
_Fc = clubs[rd.Next(0, clubs.Length)];
}
public string GetRandomChinese(int strlength)
{
// 获取GB2312编码页(表)
Encoding gb = Encoding.GetEncoding("gb2312");
object[] bytes = this.CreateRegionCode(strlength);
StringBuilder sb = new StringBuilder();
for (int i = 0; i < strlength; i++)
{
string temp = gb.GetString((byte[])Convert.ChangeType(bytes[i], typeof(byte[])));
sb.Append(temp);
}
return sb.ToString();
}
private object[] CreateRegionCode(int strlength)
{
//定义一个字符串数组储存汉字编码的组成元素
string[] rBase = new String[16] { "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f" };
Random rnd = new Random();
//定义一个object数组用来
object[] bytes = new object[strlength];
/**
每循环一次产生一个含两个元素的十六进制字节数组,并将其放入bytes数组中
每个汉字有四个区位码组成
区位码第1位和区位码第2位作为字节数组第一个元素
区位码第3位和区位码第4位作为字节数组第二个元素
**/
for (int i = 0; i < strlength; i++)
{
//区位码第1位
int r1 = rnd.Next(11, 14);
string str_r1 = rBase[r1].Trim();
//区位码第2位
rnd = new Random(r1 * unchecked((int)DateTime.Now.Ticks) + i); // 更换随机数发生器的 种子避免产生重复值
int r2;
if (r1 == 13)
{
r2 = rnd.Next(0, 7);
}
else
{
r2 = rnd.Next(0, 16);
}
string str_r2 = rBase[r2].Trim();
//区位码第3位
rnd = new Random(r2 * unchecked((int)DateTime.Now.Ticks) + i);
int r3 = rnd.Next(10, 16);
string str_r3 = rBase[r3].Trim();
//区位码第4位
rnd = new Random(r3 * unchecked((int)DateTime.Now.Ticks) + i);
int r4;
if (r3 == 10)
{
r4 = rnd.Next(1, 16);
}
else if (r3 == 15)
{
r4 = rnd.Next(0, 15);
}
else
{
r4 = rnd.Next(0, 16);
}
string str_r4 = rBase[r4].Trim();
// 定义两个字节变量存储产生的随机汉字区位码
byte byte1 = Convert.ToByte(str_r1 + str_r2, 16);
byte byte2 = Convert.ToByte(str_r3 + str_r4, 16);
// 将两个字节变量存储在字节数组中
byte[] str_r = new byte[] { byte1, byte2 };
// 将产生的一个汉字的字节数组放入object数组中
bytes.SetValue(str_r, i);
}
return bytes;
}
public static IEnumerable<Person> CreatePerson(int Num)
{
List<Person> people = new List<Person>();
for (int i = 0; i < Num; i++)
{
people.Add(new Person());
System.Threading.Thread.Sleep(20);//不加这行会生成很多重复的
}
return people;
}
}
通过以上的绑定,在ViewModel中对数据源进行操作之后,UI将会自动刷新。
下一篇写DataGridView的各种控件和数据之间的绑定。