65.9K
CodeProject 正在变化。 阅读更多。
Home

智能家居 – 控制 Shelly® 设备(第三部分)

starIconstarIconstarIconstarIconstarIcon

5.00/5 (1投票)

2023 年 12 月 13 日

CPOL

4分钟阅读

viewsIcon

4022

downloadIcon

200

本文将介绍 ShellySceneComponent 和 ShellyScenesComponent,它们允许用户为 Shelly 设备定义包含多个操作的场景,这些操作可分配给一个或多个控件。

引言

本文的基础是我上一篇文章的描述:控制 Shelly 设备 - 第 1 部分

这是一系列文章 - 其中还包括:控制 Shelly 设备 - 第 2 部分

在本文中,我将介绍另外两个组件,一些我认为有用的功能,然后,遵循 DRY(Don't Repeat Yourself,不要重复自己)原则,我稍微修改了前两篇文章中的基本例程。因此,如果您感兴趣,下载本文的存档并替换之前的组件是有意义的。出于兼容性考虑,我在此点上保持先前文章不变。

新组件是

  • ShellySceneComponent – 在这里您可以定义一个场景,该场景可以包含许多操作,但只分配给一个控件(按钮)。
  • ShellyScenesComponent – 在这里您可以定义许多场景,每个场景可以包含许多操作,并分配给相应的控件(按钮)。

这两个组件都可以与标准控件配合使用,无需调整。

作为一项功能,我添加了一个 TypeConverter,它允许您使用列表选择而不是输入来设置 IpAdress 属性,该属性访问项目中已知且预定义的地址。

我还修改了 ShellyCom 例程,并添加了另一个修改版本的例程,称为 ShellyCom2。我个人认为 ShellyCom2 版本更优雅。

ShellySceneComponent

此组件允许您将任意数量的操作分配给一个控件,然后可以通过单击触发这些操作。此组件的基本功能与上一篇文章《控制 Shelly 设备 - 第 2 部分》中介绍的 ShellyActionComponent 没有显著区别。只需选择一个控件,然后为其分配任意数量的操作(参见截图)。

此组件不会导致关联控件的动画。

ShellyScenesComponent

此组件允许您将任意数量的操作分配给多个控件,然后可以通过单击相应的控件来触发这些操作。此组件的基本功能与之前提到的组件没有显著区别 - 唯一的区别是在一个集合中调用另一个集合(参见截图)。

我在这里展示了这个组合的结构代码

    <TypeConverter(GetType(ExpandableObjectConverter))>
     Partial Public Class SceneAssignmentDefinition

        'if the code-line with the TypeConverter is activated this Property works with already defined IP addresses
        'provided inside the File Definitions. The Property "myIpAdresses" is part of this behaviour.
        'This makes the application (if you want) simpler and less error-prone
        <TypeConverter(GetType(DropDownConverter)), DropDownConverterData("myIpAdresses")>
        <Category("Shelly"), Description("IpAdress of the Shelly-Device to work with")>
        <RefreshProperties(RefreshProperties.All)>
        Property IpAdress As String
            Get
                Return myIPAdress
            End Get
            Set(value As String)
                If System.Net.IPAddress.TryParse(value, myIP) Then
                    myShellyType = ShellyCom.Shelly_GetType(value)
                    If myShellyType <> Shelly.ShellyType.None Then myIPAdress = value
                    If myShellyType = Shelly.ShellyType.Shelly_Dimmer2 Then myOutput = 0
                End If
            End Set
        End Property
        <Browsable(False), EditorBrowsable(EditorBrowsableState.Never)>
        <DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)>
        ReadOnly Property myIpAdresses As String()
            Get
                Return Shelly.My.IpAdresses ' availible adresses are defined in File: Definitions
            End Get
        End Property

        Private myIPAdress As String = ""
        Private myIP As System.Net.IPAddress

        <Category("Shelly"), Description("shows the Type of the connected Shelly-Device")>
        ReadOnly Property ShellyType As String
            Get
                Return myShellyType.ToString
            End Get
        End Property
        Private myShellyType As Shelly.ShellyType

        <Category("Shelly"), Description("Output-Number of the Shelly-Device to work with")>
        <DefaultValue(0)>
        Property OutputNr As Integer
            Get
                Return myOutput
            End Get
            Set(value As Integer)
                If (value >= 0) And (value <= 1) Then
                    myOutput = value
                End If
            End Set
        End Property
        Private myOutput As Integer = 0

        <Category("Shelly"), Description("the Value which is assigned to the Shelly-Device")>
        <DefaultValue(0)>
        Property Value As Integer
            Get
                Return myValue
            End Get
            Set(value As Integer)
                If (value >= 0) And (value <= 100) Then
                    myValue = value
                End If

            End Set
        End Property
        Private myValue As Integer = 0

        <Category("Shelly"), Description("the Action which happens with a Control-Click Event")>
        <DefaultValue(GetType(Shelly.ActionDefinition), "none")>
        Property Action As Shelly.ActionDefinition
            Get
                Return myAction
            End Get
            Set(value As Shelly.ActionDefinition)
                myAction = value
            End Set
        End Property
        Private myAction As Shelly.ActionDefinition = Shelly.ActionDefinition.none

        Public Sub New()
        End Sub

        Public Overrides Function toString() As String
            Return myIPAdress + ", " + myAction.ToString + ", O:" + myOutput.ToString.Trim + ", Val:" + myValue.ToString.Trim
        End Function

    End Class

    <TypeConverter(GetType(ExpandableObjectConverter))>
    Partial Public Class ControlAssignmentDefinition

        <Category("Control"), Description("the Control which should do the Action(s)")>
        Property SelectedControl As Control
            Get
                Return mySelectedControl
            End Get
            Set(value As Control)
                mySelectedControl = value
            End Set
        End Property
        Private mySelectedControl As Control = Nothing

        Public savedBackColor As Color
        Public savedForeColor As Color

        <Category("Control"), Description("says that this Scene was last activated")>
        <DefaultValue(False)>
        <DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)>
        Property isActivated As Boolean
            Get
                Return myActivated
            End Get
            Set(value As Boolean)
                myActivated = value
            End Set
        End Property
        Private myActivated As Boolean = False

        <Category("Control-Settings"), Description("Enables the Animation of this Control")>
        <DefaultValue(True)>
        Property EnableAnimation As Boolean
            Get
                Return my_EnableAnimation
            End Get
            Set(value As Boolean)
                my_EnableAnimation = value
            End Set
        End Property
        Private my_EnableAnimation As Boolean = True

        <Category("Control"), Description("Assignment to the Shelly-Devices")>
        <DesignerSerializationVisibility(DesignerSerializationVisibility.Content)>
        ReadOnly Property ShellyActions As ScenesCollection
            Get
                Return myShellyActions
            End Get
        End Property
        Private myShellyActions As New ScenesCollection

        Public Overrides Function toString() As String
            If mySelectedControl IsNot Nothing Then Return mySelectedControl.Name
            Return "[-]"
        End Function

    End Class
    [TypeConverter(typeof(ExpandableObjectConverter))]
    public partial class SceneAssignmentDefinition
    {
        // if the code-line with the TypeConverter is activated this Property works with already defined IP addresses
        // provided inside the File Definitions. The Property "myIpAdresses" is part of this behaviour.
        // This makes the application (if you want) simpler and less error-prone
        [TypeConverter(typeof(DropDownConverter))]
        [DropDownConverterData("myIpAdresses")]
        [Category("Shelly")]
        [Description("IpAdress of the Shelly-Device to work with")]
        [RefreshProperties(RefreshProperties.All)]
        public string IpAdress
        {
            get { return myIPAdress; }
            set
            {
                if (System.Net.IPAddress.TryParse(value, out myIP))
                {
                    myShellyType = ShellyCom.Shelly_GetType(value);
                    if (myShellyType != Shelly.ShellyType.None)
                        myIPAdress = value;
                    if (myShellyType == Shelly.ShellyType.Shelly_Dimmer2)
                        myOutput = 0;
                }
            }
        }
        [Browsable(false)]
        [EditorBrowsable(EditorBrowsableState.Never)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public string[] myIpAdresses
        {
            get { return Shelly.My.IpAdresses; } // availible adresses are defined in File: Definitions
        }        
        
        private string myIPAdress = "";
        private System.Net.IPAddress myIP;

        [Category("Shelly")]
        [Description("shows the Type of the connected Shelly-Device")]
        public string ShellyType
        {
            get { return Convert.ToString(myShellyType); }
        }
        private Shelly.ShellyType myShellyType;

        [Category("Shelly")]
        [Description("Output-Number of the Shelly-Device to work with")]
        [DefaultValue(0)]
        public int OutputNr
        {
            get { return myOutput; }
            set
            {
                if ((value >= 0) & (value <= 1))
                    myOutput = value;
            }
        }
        private int myOutput = 0;

        [Category("Shelly")]
        [Description("the Value which is assigned to the Shelly-Device")]
        [DefaultValue(0)]
        public int Value
        {
            get { return myValue; }
            set
            {
                if ((value >= 0) & (value <= 100))
                    myValue = value;
            }
        }
        private int myValue = 0;

        [Category("Shelly")]
        [Description("the Action which happens with a Control-Click Event")]
        [DefaultValue(typeof(Shelly.ActionDefinition), "none")]
        public Shelly.ActionDefinition Action
        {
            get { return myAction; }
            set { myAction = value; }
        }
        private Shelly.ActionDefinition myAction = Shelly.ActionDefinition.none;

        public SceneAssignmentDefinition()
        {
        }

        public override string ToString()
        {
            return myIPAdress + ", " + Convert.ToString(myAction) + ", O:" + myOutput.ToString().Trim() + ", Val:" + myValue.ToString().Trim();
        }
    }

    [TypeConverter(typeof(ExpandableObjectConverter))]
    public partial class ControlAssignmentDefinition
    {
        [Category("Control")]
        [Description("the Control which should do the Action(s)")]
        public Control SelectedControl
        {
            get { return mySelectedControl; }
            set { mySelectedControl = value; }
        }
        private Control mySelectedControl = null/* TODO Change to default(_) if this is not a reference type */;

        public Color savedBackColor;
        public Color savedForeColor;

        [Category("Control")]
        [Description("says that this Scene was last activated")]
        [DefaultValue(false)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public bool isActivated
        {
            get { return myActivated; }
            set { myActivated = value; }
        }
        private bool myActivated = false;

        [Category("Control-Settings")]
        [Description("Enables the Animation of this Control")]
        [DefaultValue(true)]
        public bool EnableAnimation
        {
            get { return my_EnableAnimation; }
            set { my_EnableAnimation = value; }
        }
        private bool my_EnableAnimation = true;

        [Category("Control")]
        [Description("Assignment to the Shelly-Devices")]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
        public ScenesCollection ShellyActions
        {
            get { return myShellyActions; }
        }
        private ScenesCollection myShellyActions = new ScenesCollection();

        public override string ToString()
        {
            if (mySelectedControl != null)
                return mySelectedControl.Name;
            return "[-]";
        }
    }

最初,这是两个集合的两个基类。

如您所见,ScenesCollection 已集成到 ControlAssignmentDefinition 类中。

这些是关联的集合

    Partial Public Class ActionCollection
        Inherits CollectionBase

        Public Sub Add(ByVal item As ControlAssignmentDefinition)
            Dim myControl As Control = item.SelectedControl
            If myControl IsNot Nothing Then AddHandler item.SelectedControl.Click, AddressOf ControlClickHandler
            List.Add(item)
        End Sub

        Public Sub Insert(ByVal item As ControlAssignmentDefinition)
            Dim myControl As Control = item.SelectedControl
            If myControl IsNot Nothing Then AddHandler item.SelectedControl.Click, AddressOf ControlClickHandler
            List.Add(item)
        End Sub

        Public Sub Remove(ByVal index As Integer)
            If (index > -1) Then
                Dim item As ControlAssignmentDefinition = List.Item(index)
                Dim myControl As Control = item.SelectedControl
                If myControl IsNot Nothing Then RemoveHandler item.SelectedControl.Click, AddressOf ControlClickHandler
                List.RemoveAt(index)
            End If
        End Sub

        Public Property Item(ByVal index As Integer) As ControlAssignmentDefinition
            Get
                Return List(index)
            End Get
            Set(ByVal value As ControlAssignmentDefinition)
                List(index) = value
            End Set
        End Property

        Public ReadOnly Property Item(ByVal ControlName As String) As ControlAssignmentDefinition
            Get
                For i As Integer = 0 To List.Count - 1
                    If Item(i).SelectedControl.Name = ControlName Then Return Item(i)
                Next

                Return Nothing
            End Get
        End Property


        Public Shadows Sub Clear()
            For i As Integer = 0 To List.Count - 1
                RemoveHandler Item(i).SelectedControl.Click, AddressOf ControlClickHandler
            Next
            List.Clear()
        End Sub

        Overrides Function ToString() As String
            Return "[...]"
        End Function

        Public Sub Dispose()
            Me.Clear()
        End Sub



        Property Enabled As Boolean
            Get
                Return my_Enabled
            End Get
            Set(value As Boolean)
                my_Enabled = value
            End Set
        End Property
        Private my_Enabled As Boolean = True



        Private Sub ControlClickHandler(sender As System.Object, e As System.EventArgs)
            If Not my_Enabled Then Exit Sub

            Dim myControl As Control = sender
            Dim myItem As ControlAssignmentDefinition = Item(myControl.Name)

            If myItem IsNot Nothing Then
                For i As Integer = 0 To List.Count - 1
                    Item(i).isActivated = False
                Next
                myItem.isActivated = True

                For i As Integer = 0 To myItem.ShellyActions.Count - 1
                    Dim myAction As SceneAssignmentDefinition = myItem.ShellyActions.Item(i)

                    If myAction IsNot Nothing Then
                        If myAction.Action <> Shelly.ActionDefinition.none Then
                            Select Case myAction.Action
                                Case Shelly.ActionDefinition.SetOut
                                    Dim myState As Boolean = myAction.Value > 0
                                    ShellyCom.Shelly_SetOutput(myAction.IpAdress, myAction.OutputNr, myState)
                                Case Shelly.ActionDefinition.ToggleOut
                                    ShellyCom.Shelly_ToggleOutput(myAction.IpAdress, myAction.OutputNr)
                                Case Shelly.ActionDefinition.SetDimmer
                                    ShellyCom.Shelly_SetDimmer(myAction.IpAdress, myAction.Value)
                                Case Shelly.ActionDefinition.SetRoller
                                    ShellyCom.Shelly_SetRoller(myAction.IpAdress, myAction.Value)
                                Case Shelly.ActionDefinition.ToggleRoller
                                    ShellyCom.Shelly_ToggleRoller(myAction.IpAdress)
                                Case Shelly.ActionDefinition.none
                                    ' do nothing - only for display Values
                            End Select
                        End If
                    End If

                Next
            End If
        End Sub

    End Class

    Partial Public Class ScenesCollection
        Inherits CollectionBase

        Public Sub Add(ByVal item As SceneAssignmentDefinition)
            List.Add(item)
        End Sub

        Public Sub Insert(ByVal item As SceneAssignmentDefinition)
            List.Add(item)
        End Sub

        Public Sub Remove(ByVal index As Integer)
            If (index > -1) Then
                Dim item As SceneAssignmentDefinition = List.Item(index)
                List.RemoveAt(index)
            End If
        End Sub

        Public Property Item(ByVal index As Integer) As SceneAssignmentDefinition
            Get
                Return List(index)
            End Get
            Set(ByVal value As SceneAssignmentDefinition)
                List(index) = value
            End Set
        End Property


        Public Shadows Sub Clear()
            List.Clear()
        End Sub

        Overrides Function ToString() As String
            Return "[...]"
        End Function

        Public Sub Dispose()
            List.Clear()
        End Sub

    End Class
   public class ActionCollection : System.Collections.Generic.List<ControlAssignmentDefinition>
    {
        public void Add(ControlAssignmentDefinition item)
        {
            Control myControl = item.SelectedControl;
            if (myControl != null)
                item.SelectedControl.Click += ControlClickHandler;
            base.Add(item);
        }

        public void Insert(ControlAssignmentDefinition item)
        {
            Control myControl = item.SelectedControl;
            if (myControl != null)
                item.SelectedControl.Click += ControlClickHandler;
            base.Add(item);
        }

        public void Remove(int index)
        {
            if ((index > -1))
            {
                ControlAssignmentDefinition item = (ControlAssignmentDefinition)this[index];
                Control myControl = item.SelectedControl;
                if (myControl != null)
                    item.SelectedControl.Click -= ControlClickHandler;
                base.RemoveAt(index);
            }
        }

        public ControlAssignmentDefinition Item(int index)
        { return (ControlAssignmentDefinition)this[index]; }

        public ControlAssignmentDefinition Item(String ControlName)
        {
            for (int i = 0; i <= Count - 1; i++)
            {
                ControlAssignmentDefinition myItem = (ControlAssignmentDefinition)this[i];
                if (myItem.SelectedControl.Name == ControlName)
                    return myItem;
            }
            return null;
        }


        public new void Clear()
        {
            for (int i = 0; i <=  Count - 1; i++)
                Item(i).SelectedControl.Click -= ControlClickHandler;
            base.Clear();
        }

        public override string ToString()
        { return "[...]"; }

        public void Dispose()
        { this.Clear(); }



        public bool Enabled
        {
            get { return my_Enabled; }
            set { my_Enabled = value; }
        }
        private bool my_Enabled = true;



        private void ControlClickHandler(System.Object sender, System.EventArgs e)
        {
            if (!my_Enabled)
                return;

            Control myControl = (Control)sender;
            ControlAssignmentDefinition myItem = Item(myControl.Name);

            if (myItem != null)
            {
                for (int i = 0; i <= Count - 1; i++)
                    Item(i).isActivated = false;
                myItem.isActivated = true;

                for (int i = 0; i <= myItem.ShellyActions.Count - 1; i++)
                {
                    SceneAssignmentDefinition myAction = myItem.ShellyActions.Item(i);

                    if (myAction != null)
                    {
                        if (myAction.Action != Shelly.ActionDefinition.none)
                        {
                            switch (myAction.Action)
                            {
                                case ActionDefinition.SetOut:
                                    {
                                        bool myState = myAction.Value > 0;
                                        ShellyCom.Shelly_SetOutput(myAction.IpAdress, myAction.OutputNr, myState);
                                        break;
                                    }

                                case ActionDefinition.ToggleOut:
                                    {
                                        ShellyCom.Shelly_ToggleOutput(myAction.IpAdress, myAction.OutputNr);
                                        break;
                                    }

                                case ActionDefinition.SetDimmer:
                                    {
                                        ShellyCom.Shelly_SetDimmer(myAction.IpAdress, myAction.Value);
                                        break;
                                    }

                                case ActionDefinition.SetRoller:
                                    {
                                        ShellyCom.Shelly_SetRoller(myAction.IpAdress, myAction.Value);
                                        break;
                                    }

                                case ActionDefinition.ToggleRoller:
                                    {
                                        ShellyCom.Shelly_ToggleRoller(myAction.IpAdress);
                                        break;
                                    }

                                case ActionDefinition.none:
                                    { break; }
                            }
                        }
                    }
                }
            }
        }
    }

    public partial class ScenesCollection : System.Collections.Generic.List<SceneAssignmentDefinition>
    {
        public void Add(SceneAssignmentDefinition item)
        { base.Add(item); }

        public void Insert(SceneAssignmentDefinition item)
        { base.Add(item); }

        public void Remove(int index)
        {
            if ((index > -1))
            { base.RemoveAt(index); }
        }

        public SceneAssignmentDefinition Item(int index)
        { return (SceneAssignmentDefinition)this[index]; }



        public new void Clear()
        { base.Clear(); }

        public override string ToString()
        { return "[...]"; }

        public void Dispose()
        { base.Clear(); }
    }

ActionCollection 内部,会评估相应控件的单击事件(ControlClickHandler),并执行从属集合中分配的操作。

但是,这里再次提供了使触发控件动起来的可能性。这仍然是通过组件本身的计时器完成的。

TypeConverter (DropDownConverter)

在此我展示了如何在属性中使用转换器

        <TypeConverter(GetType(DropDownConverter)), DropDownConverterData("myIpAdresses")>
        <Category("Shelly"), Description("IpAdress of the Shelly-Device to work with")>
        <RefreshProperties(RefreshProperties.All)>
        Property IpAdress As String
            Get
                Return myIPAdress
            End Get
            Set(value As String)
                If System.Net.IPAddress.TryParse(value, myIP) Then
                    myShellyType = ShellyCom.Shelly_GetType(value)
                    If myShellyType <> Shelly.ShellyType.None Then myIPAdress = value
                    If myShellyType = Shelly.ShellyType.Shelly_Dimmer2 Then myOutput = 0
                End If
            End Set
        End Property
        <Browsable(False), EditorBrowsable(EditorBrowsableState.Never)>
        <DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)>
        ReadOnly Property myIpAdresses As String()
            Get
                Return Shelly.My.IpAdresses ' availible adresses are defined in File: Definitions
            End Get
        End Property

        Private myIPAdress As String = ""
        Private myIP As System.Net.IPAddress
        [TypeConverter(typeof(DropDownConverter, DropDownConverterData("myIpAdresses")]
        [Category("Shelly")]
        [Description("IpAdress of the Shelly-Device to work with")]
        [RefreshProperties(RefreshProperties.All)]
        public string IpAdress
        {
            get { return myIPAdress; }
            set
            {
                if (System.Net.IPAddress.TryParse(value, out myIP))
                {
                    myShellyType = ShellyCom.Shelly_GetType(value);
                    if (myShellyType != Shelly.ShellyType.None)
                        myIPAdress = value;
                    if (myShellyType == Shelly.ShellyType.Shelly_Dimmer2)
                        myOutput = 0;
                }
            }
        }
        [Browsable(false)]
        [EditorBrowsable(EditorBrowsableState.Never)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public string[] myIpAdresses
        {
            get { return Shelly.My.IpAdresses; } // availible adresses are defined in File: Definitions
        }        
        
        private string myIPAdress = "";
        private System.Net.IPAddress myIP;

带有 TypeConverter 赋值的行决定了在选择属性时是否激活组合框功能(其中提供了属性 myIpAddresses 传递的字符串数组供选择),或者是否注释掉了该行,执行属性的默认功能。

我想显示的 IP 地址在 Definitions 文件中指定。

我之前在 CodeProject 的问答部分已经发布过 DropDownConverter 一两次了。

Imports System.ComponentModel

Public Class DropDownConverter
    Inherits StringConverter

    Public Overloads Overrides Function GetStandardValuesSupported(ByVal context As ITypeDescriptorContext) As Boolean
        Return True 'True - tells the PropertyGrid, that a Combobox shall be shown
    End Function

    Public Overloads Overrides Function GetStandardValuesExclusive(ByVal context As ITypeDescriptorContext) As Boolean
        Return True
        ' True  - allows only selection out of the Combobox
        ' False - allows free inputs also
    End Function

    Private EntriesArray(-1) As String  'saves the selectable entries for the DropDown

    Public Overrides Function CanConvertFrom(context As System.ComponentModel.ITypeDescriptorContext, sourceType As System.Type) As Boolean
        If sourceType Is GetType(String) Then Return True 'allows the converting from String 
        Return MyBase.CanConvertFrom(context, sourceType)
    End Function

    Public Overloads Overrides Function GetStandardValues(ByVal context As ITypeDescriptorContext) As StandardValuesCollection
         Dim myPD As PropertyDescriptor = Nothing

        Dim myAttribute As DropDownConverterDataAttribute = context.PropertyDescriptor.Attributes(GetType(DropDownConverterDataAttribute))
        If myAttribute IsNot Nothing Then  ' is the name of the array to be used passed as an Attribute ...?
            myPD = TypeDescriptor.GetProperties(context.Instance)(myAttribute.DataArray)

        Else    ' The name of the array to be used is derived from the name of the Property ...?
            ' identify Host-Property and related options 
            Dim HostPropertyName As String = context.PropertyDescriptor.Name
            Dim HostPropertyArrayName As String = HostPropertyName + "Array"
            myPD = TypeDescriptor.GetProperties(context.Instance)(HostPropertyArrayName)

        End If

        If myPD IsNot Nothing Then
            If myPD.PropertyType Is GetType(String()) Then
                EntriesArray = myPD.GetValue(context.Instance)
            ElseIf myPD.PropertyType Is GetType(List(Of String)) Then
                Dim myList As List(Of String) = myPD.GetValue(context.Instance)
                ReDim EntriesArray(myList.Count - 1)
                For i As Integer = 0 To myList.Count - 1
                    EntriesArray(i) = myList(i)
                Next
            ElseIf myPD.PropertyType Is GetType(Collection) Then
                Dim myCollection As Collection = myPD.GetValue(context.Instance)
                ReDim EntriesArray(myCollection.Count - 1)
                For i As Integer = 0 To myCollection.Count - 1
                    EntriesArray(i) = myCollection.Item(i + 1)
                Next
            End If
        End If

        Return New StandardValuesCollection(EntriesArray) ' exports our specified options
    End Function

End Class

<AttributeUsage(AttributeTargets.[Property] Or AttributeTargets.Method)> _
Public Class DropDownConverterDataAttribute
    Inherits Attribute

    Public Sub New(DataArray_PropertyName As String)
        my_DataArray = DataArray_PropertyName
    End Sub

    Public ReadOnly Property DataArray() As String
        Get
            Return my_DataArray
        End Get
    End Property
    Private my_DataArray As String

End Class
using System;
using System.Collections.Generic;
using System.ComponentModel;

public class DropDownConverter : StringConverter
{
    public override bool GetStandardValuesSupported(ITypeDescriptorContext context)
    {
        return true; // True - tells the PropertyGrid, that a Combobox shall be shown
    }

    public override bool GetStandardValuesExclusive(ITypeDescriptorContext context)
    { return true; }

    private string[] EntriesArray = new string[0];  // saves the selectable entries for the DropDown

    public override bool CanConvertFrom(System.ComponentModel.ITypeDescriptorContext context, System.Type sourceType)
    {
        if (sourceType == typeof(string))
            return true; // allows the converting from String 
        return base.CanConvertFrom(context, sourceType);
    }

    public override TypeConverter.StandardValuesCollection GetStandardValues(ITypeDescriptorContext context)
    {
        PropertyDescriptor myPD = null;

        DropDownConverterDataAttribute myAttribute = (DropDownConverterDataAttribute)context.PropertyDescriptor.Attributes[typeof(DropDownConverterDataAttribute)];
        if (myAttribute != null)
            myPD = TypeDescriptor.GetProperties(context.Instance)[myAttribute.DataArray];
        else
        {
            // identify Host-Property and related options 
            string HostPropertyName = context.PropertyDescriptor.Name;
            string HostPropertyArrayName = HostPropertyName + "Array";
            myPD = TypeDescriptor.GetProperties(context.Instance)[HostPropertyArrayName];
        }

        if (myPD != null)
        {
            if (myPD.PropertyType == typeof(string[]))
                EntriesArray = (string[]) myPD.GetValue(context.Instance );
            else if (myPD.PropertyType == typeof(List<string>))
            {
                List<string> myList = (List<string>) myPD.GetValue(context.Instance);
                EntriesArray = new string[myList.Count - 1 + 1];
                for (int i = 0; i <= myList.Count - 1; i++)
                    EntriesArray[i] = myList[i];
            }
         }

        return new TypeConverter.StandardValuesCollection(EntriesArray); // exports our specified options
    }
}

[AttributeUsage(AttributeTargets.Property | AttributeTargets.Method)]
public class DropDownConverterDataAttribute : Attribute
{
    public DropDownConverterDataAttribute(string DataArray_PropertyName)
    { my_DataArray = DataArray_PropertyName; }

    public string DataArray
    {
        get { return my_DataArray; }
    }
    private string my_DataArray;
}

尽管是我自己创建的转换器,但我只能简单介绍一下内部功能。

在下半部分,我创建了自己的 Attribute,它允许转换器指定数据源。转换器本身现在访问此属性的内容,并通过 PropertyDescriptor 将其转发到属性。同时,转换器会告知 PropertyGrid 应为输出选择一个组合框。

我可以将 StringArray 和 List(of String) 类型传递给转换器本身,在 VB.NET 中,还可以将集合类型作为数据源。

ShellyCom 和 ShellyCom2

这两个库在基本功能上没有区别。区别在于状态输出。

对于 ShellyCom,有用于输入和输出状态的独立变量。对于 ShellyCom2,我定义了与实际输入和输出大小相同的数组。

DRY – Don’t Repeat Yourself(不要重复自己)

由于组件的功能不同,会重复使用各种 ENums

我现在已将它们移至 Shelly 命名空间中的 Definitions 文件。这也会改变在组件中使用时的寻址方式。

最后 - 结束语

这便是我的迷你系列暂时告一段落。但是,我乐于接受建议或启发,也许最终还会有第 4 部分。

如果我使用额外的 Shelly 设备或获得它们的访问权限,我会相应地扩展/调整库。

我想再次感谢 @sean-ewington 在创作本文及前几篇文章中的帮助。

© . All rights reserved.