对于上位机开发,我们有时候有这样的需求:如何显示所有的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>
{
}