對于上位機開發,我們有時候有這樣的需求:如何顯示所有的IO點位?比如有10個IO點位,那我們要寫10個TextBlock去綁定這10個點位的屬性(本文暫時不考慮顯示的樣式,當然也可以考慮),當點位變成了20個,我們要再加10TextBlock去顯示。
那么有沒有一種方法,直接顯示這個Object的所有屬性的值,當值發生變化,自動更新界面呢?這個需求不就類似于PropertyGrid
嗎?但是我不想用PropertyGrid,我們使用ItemsControl
去簡單的實現一下。
定義要顯示的IO點位
public class IOInfo : BindableBase{public IOInfo(){}private bool workLed1;public bool WorkLed1{get { return workLed1; }set { workLed1 = value; this.RaisePropertyChanged(nameof(WorkLed1)); }}private bool workLed2;public bool WorkLed2{get { return workLed2; }set { workLed2 = value; this.RaisePropertyChanged(nameof(WorkLed2)); }}private bool protectOn;public bool ProtectOn{get { return protectOn; }set { protectOn = value; this.RaisePropertyChanged(nameof(ProtectOn)); }}private bool pin1Status;public bool Pin1Status{get { return pin1Status; }set { pin1Status = value; this.RaisePropertyChanged(nameof(Pin1Status)); }}private bool pin2Status;public bool Pin2Status{get { return pin2Status; }set { pin2Status = value; this.RaisePropertyChanged(nameof(Pin2Status)); }}private bool cylinder1;public bool Cylinder1{get { return cylinder1; }set { cylinder1 = value; this.RaisePropertyChanged(nameof(Cylinder1)); }}private bool cylinder2;public bool Cylinder2{get { return cylinder2; }set { cylinder2 = value; this.RaisePropertyChanged(nameof(Cylinder2)); }}private bool bj;public bool BJ{get { return bj; }set { bj = value; this.RaisePropertyChanged(nameof(BJ)); }}private bool upSensor1;public bool UpSensor1{get { return upSensor1; }set { upSensor1 = value; this.RaisePropertyChanged(nameof(UpSensor1)); }}private bool upSensor2;public bool UpSensor2{get { return upSensor2; }set { upSensor2 = value; this.RaisePropertyChanged(nameof(UpSensor2)); }}private bool airPressure;public bool AirPressure{get { return airPressure; }set { airPressure = value; this.RaisePropertyChanged(nameof(AirPressure)); }}private bool doorStatus;public bool DoorStatus{get { return doorStatus; }set { doorStatus = value; this.RaisePropertyChanged(nameof(DoorStatus)); }}private bool smokeStatus;public bool SmokeStatus{get { return smokeStatus; }set { smokeStatus = value; this.RaisePropertyChanged(nameof(SmokeStatus)); }}private bool tempStatus;public bool TempStatus{get { return tempStatus; }set { tempStatus = value; this.RaisePropertyChanged(nameof(TempStatus)); }}private bool remoteStatus;public bool RemoteStatus{get { return remoteStatus; }set { remoteStatus = value; this.RaisePropertyChanged(nameof(RemoteStatus)); }}private bool trayStatus;public bool TrayStatus{get { return trayStatus; }set { trayStatus = value; this.RaisePropertyChanged(nameof(TrayStatus)); }}private bool startStatus;public bool StartStatus{get { return startStatus; }set { startStatus = value; this.RaisePropertyChanged(nameof(StartStatus)); }}private bool powerStatus;public bool PowerStatus{get { return powerStatus; }set { powerStatus = value; this.RaisePropertyChanged(nameof(PowerStatus)); }}private bool emergencyStatus;public bool EmergencyStatus{get { return emergencyStatus; }set { emergencyStatus = value; this.RaisePropertyChanged(nameof(EmergencyStatus)); }}private bool errTrace;public bool ErrorTrace{get { return errTrace; }set { errTrace = value; this.RaisePropertyChanged(nameof(ErrorTrace)); }}}
上述的BindableBase
是引用了Prism
框架
定義轉換器
無論是ItemsControl
還是PropertyGrid
最終顯示到界面的時候,都是一個集合。我們需要把object使用轉換器轉換為集合,轉換為集合之前,首先定義ItemsControl
要顯示的Model
:
public class PropertyAndValue : BindableBase{private string propertyName;/// <summary>/// 屬性名稱/// </summary>public string PropertyName{get { return propertyName; }set { propertyName = value; this.RaisePropertyChanged(nameof(PropertyName)); }}private string propertyNameAlias;/// <summary>/// 顯示的別名,如果沒有定義,則和屬性名稱一致/// </summary>public string PropertyNameAlias{get { return propertyNameAlias; }set { propertyNameAlias = value; this.RaisePropertyChanged(nameof(PropertyNameAlias)); }}private object propertyValue;/// <summary>/// 屬性的值/// </summary>public object PropertyValue{get { return propertyValue; }set { propertyValue = value; this.RaisePropertyChanged(nameof(PropertyValue)); }}}
將轉換器轉換為Model
的集合,這里重點要思考的是,IOInfo的屬性變化,如何去更新界面,我采用簡單粗暴的方式是手動訂閱PropertyChanged
事件:
public class IOStatusConverter : IValueConverter{private List<PropertyAndValue> values = new List<PropertyAndValue>();private IOInfo ioInfo = null;public object Convert(object value, Type targetType, object parameter, CultureInfo culture){if (value == null || !(value is IOInfo ioInfo)) return value;ioInfo.PropertyChanged += IoInfo_PropertyChanged;foreach (var property in typeof(IOInfo).GetProperties()){values.Add(new PropertyAndValue() { PropertyName = property.Name, PropertyNameAlias = property.Name, PropertyValue = property.GetValue(ioInfo) }); }return values;}private void IoInfo_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e){var v = values.FirstOrDefault(x => x.PropertyName == e.PropertyName);if (v == null) return;var property = typeof(IOInfo).GetProperty(e.PropertyName);if (property == null) return;v.PropertyValue = property.GetValue(sender);}public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture){return Binding.DoNothing;}}
前臺界面綁定
xaml的代碼如下:
<ItemsControl Grid.Row="1" ItemsSource="{Binding IO,Converter={StaticResource IOStatusConverter}}"><ItemsControl.ItemTemplate><DataTemplate><StackPanel Orientation="Horizontal"><TextBlock Text="{Binding PropertyName}" /><TextBlock Text=":" /><TextBlock Text="{Binding PropertyValue}" /></StackPanel></DataTemplate></ItemsControl.ItemTemplate>
</ItemsControl>
最終實現的效果
點擊上方的修改按鈕,它能更新界面的屬性值。
改進
屬性名稱顯示別名
要顯示別名,我們要經過3個步驟。
- 獲取要顯示的別名
- 將別名賦值到
PropertyNameAlias
屬性 - 將
PropertyNameAlias
綁定到界面
我們使用DescriptionAttribute
標簽來實現別名,更改后的IOInfo
類如下:
public class IOInfo : BindableBase{public IOInfo(){}private bool workLed1;[Description("燈1")]public bool WorkLed1{get { return workLed1; }set { workLed1 = value; this.RaisePropertyChanged(nameof(WorkLed1)); }}private bool workLed2;[Description("燈2")]public bool WorkLed2{get { return workLed2; }set { workLed2 = value; this.RaisePropertyChanged(nameof(WorkLed2)); }}private bool protectOn;public bool ProtectOn{get { return protectOn; }set { protectOn = value; this.RaisePropertyChanged(nameof(ProtectOn)); }}private bool pin1Status;public bool Pin1Status{get { return pin1Status; }set { pin1Status = value; this.RaisePropertyChanged(nameof(Pin1Status)); }}private bool pin2Status;public bool Pin2Status{get { return pin2Status; }set { pin2Status = value; this.RaisePropertyChanged(nameof(Pin2Status)); }}private bool cylinder1;public bool Cylinder1{get { return cylinder1; }set { cylinder1 = value; this.RaisePropertyChanged(nameof(Cylinder1)); }}private bool cylinder2;public bool Cylinder2{get { return cylinder2; }set { cylinder2 = value; this.RaisePropertyChanged(nameof(Cylinder2)); }}private bool bj;public bool BJ{get { return bj; }set { bj = value; this.RaisePropertyChanged(nameof(BJ)); }}private bool upSensor1;public bool UpSensor1{get { return upSensor1; }set { upSensor1 = value; this.RaisePropertyChanged(nameof(UpSensor1)); }}private bool upSensor2;public bool UpSensor2{get { return upSensor2; }set { upSensor2 = value; this.RaisePropertyChanged(nameof(UpSensor2)); }}private bool airPressure;public bool AirPressure{get { return airPressure; }set { airPressure = value; this.RaisePropertyChanged(nameof(AirPressure)); }}private bool doorStatus;public bool DoorStatus{get { return doorStatus; }set { doorStatus = value; this.RaisePropertyChanged(nameof(DoorStatus)); }}private bool smokeStatus;public bool SmokeStatus{get { return smokeStatus; }set { smokeStatus = value; this.RaisePropertyChanged(nameof(SmokeStatus)); }}private bool tempStatus;public bool TempStatus{get { return tempStatus; }set { tempStatus = value; this.RaisePropertyChanged(nameof(TempStatus)); }}private bool remoteStatus;public bool RemoteStatus{get { return remoteStatus; }set { remoteStatus = value; this.RaisePropertyChanged(nameof(RemoteStatus)); }}private bool trayStatus;public bool TrayStatus{get { return trayStatus; }set { trayStatus = value; this.RaisePropertyChanged(nameof(TrayStatus)); }}private bool startStatus;public bool StartStatus{get { return startStatus; }set { startStatus = value; this.RaisePropertyChanged(nameof(StartStatus)); }}private bool powerStatus;public bool PowerStatus{get { return powerStatus; }set { powerStatus = value; this.RaisePropertyChanged(nameof(PowerStatus)); }}private bool emergencyStatus;public bool EmergencyStatus{get { return emergencyStatus; }set { emergencyStatus = value; this.RaisePropertyChanged(nameof(EmergencyStatus)); }}private bool errTrace;public bool ErrorTrace{get { return errTrace; }set { errTrace = value; this.RaisePropertyChanged(nameof(ErrorTrace)); }}}
我們在WorkLed1
和WorkLed2
這兩個屬性上打了兩個標簽,界面顯示的時候,我們希望顯示我們打上的標簽的值。
現在我們把轉換器修改下:
public class IOStatusConverter : IValueConverter{private List<PropertyAndValue> values = new List<PropertyAndValue>();private IOInfo ioInfo = null;public object Convert(object value, Type targetType, object parameter, CultureInfo culture){if (value == null || !(value is IOInfo ioInfo)) return value;ioInfo.PropertyChanged += IoInfo_PropertyChanged;foreach (var property in typeof(IOInfo).GetProperties()){var propertyName=property.Name;var propertyNameAlias = property.GetCustomAttribute<DescriptionAttribute>()?.Description ?? propertyName;values.Add(new PropertyAndValue() { PropertyName = propertyName, PropertyNameAlias = propertyNameAlias, PropertyValue = property.GetValue(ioInfo) }); }return values;}private void IoInfo_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e){var v = values.FirstOrDefault(x => x.PropertyName == e.PropertyName);if (v == null) return;var property = typeof(IOInfo).GetProperty(e.PropertyName);if (property == null) return;v.PropertyValue = property.GetValue(sender);}public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture){return Binding.DoNothing;}}
界面顯示綁定修改綁定到PropertyNameAlias
屬性:
<ItemsControl Grid.Row="1" ItemsSource="{Binding IO, Converter={StaticResource IOStatusConverter}}"><ItemsControl.ItemTemplate><DataTemplate><StackPanel Orientation="Horizontal"><TextBlock Text="{Binding PropertyNameAlias}" /><TextBlock Text=":" /><TextBlock Text="{Binding PropertyValue}" /></StackPanel></DataTemplate></ItemsControl.ItemTemplate>
</ItemsControl>
最后的顯示效果:
轉換器的改進
通用轉換器:
public class ObjectToListBaseConverter<T> : IValueConverter where T:INotifyPropertyChanged{private List<PropertyAndValue> values = new List<PropertyAndValue>();public object Convert(object value, Type targetType, object parameter, CultureInfo culture){if (value == null || !(value is T obj)) return value;//如果不是T的類型,則返回obj.PropertyChanged += Obj_PropertyChanged;foreach (var property in typeof(IOInfo).GetProperties()){var propertyName = property.Name;var propertyNameAlias = property.GetCustomAttribute<DescriptionAttribute>()?.Description ?? propertyName;values.Add(new PropertyAndValue() { PropertyName = propertyName, PropertyNameAlias = propertyNameAlias, PropertyValue = property.GetValue(obj) });}return values;}private void Obj_PropertyChanged(object sender, PropertyChangedEventArgs e){var v = values.FirstOrDefault(x => x.PropertyName == e.PropertyName);if (v == null) return;var property = typeof(T).GetProperty(e.PropertyName);if (property == null) return;v.PropertyValue = property.GetValue(sender);}public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture){return Binding.DoNothing;}}
IOStatusConverter
修改如下,做到了代碼的通用:
public class IOStatusConverter : ObjectToListBaseConverter<IOInfo>
{}