WPF使用ItemsControl显示Object的所有属性值

发布于:2024-05-16 ⋅ 阅读:(60) ⋅ 点赞:(0)

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

    }

我们在WorkLed1WorkLed2这两个属性上打了两个标签,界面显示的时候,我们希望显示我们打上的标签的值。
现在我们把转换器修改下:

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>
{
    
}