Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / IoT

Smart Home – Controlling Shelly® Devices  (Part 4) - Property-Manipulation with the PropertyDescriptor

5.00/5 (2 votes)
17 Jan 2024CPOL10 min read 3.7K   109  
This article revolutionizes Shelly device management, providing an updated, efficient, and feature-rich solution for both beginners and advanced users. You will discover new ShellyCom2 features, such as property manipulation using PropertyDescriptor, and a set of valuable helpers and commands.

Image 1

Introduction

The basis for this article is the description in my previous articles:

In my last article, I wrote that this series would be over... things sometimes turn out differently than you think...

On the one hand, I got access to additional devices (Shelly Button, Shelly Plug, Shelly 4pm, Shelly Bulb Vintage, Shelly Bulb Duo and Shelly Bulb Duo RGBW) and added them. For the Shelly Bulbs, the documentation for controlling them wasn't particularly good. That's why I'm dedicating a small separate section in this article to this point.

After this, I implemented additional functions in the existing toolbox or revised old functions - also with regard to the DRY concept (don’t repeat yourself).

In addition, following my instinct to play, I changed the property behavior of my actions so that only the properties that are applicable in the context are visible. In this article, I particularly present these things, which do not have anything directly to do with the Shelly devices but may also be of interest to others - using the example of the application in my toolbox. This article could also be called Property-Manipulation with the PropertyDescriptor.

The download includes the complete toolbox with all current components (in VB.NET and C#) and basically completely replaces the downloads of the previous articles.

New Functions inside ShellyCom2

Some new functions and helpers have been added here:

Helper
Shelly_CheckIp checks whether the IP address passed is legal, actually exists, leads to a Shelly device and this device can also be used for the task. Since the input of a property is checked here, all violations are output as message box messages.
JsonPrettify converts a passed JSON string into a more readable format
ConvertUnixTimestamp converts the Unix timestamp used by Shelly into the Microsoft DateTime format
Shelly_GetStatusString returns the status string for any Shelly request to be passed
Shelly_ShowDeviceInfo provides the DeviceInfo of the Shelly device at the passed IP address for display purposes
CheckColorIsKnown checks whether a Color.ARGB value passed from the Shelly device corresponds to a known color assignment

 

Requests
Shelly_GetType Determines the Shelly type at the passed IP address. There are now more types stored here than I could actually check. But the identifiers passed are correct...
Shelly_GetInfo Delivers the basic information of the Shelly device at the passed IP address
Shelly_GetBaseType Returns the base type of the Shelly device at the passed IP address (Input, Out single-channel, Out multi-channel, dimmer, lamp)
Shelly_GetNrOutputs Returns the number of available outputs of the Shelly device at the passed IP address
   
Shelly_DimmerRGBW_GetStatus Provides the status of an RGBW device.

 

Commands
AssignAction According to the transfer, selects the associated function routine for executing the command. Since this method was basically used in each of the components, I removed it from the components and stored it here as a collection routine.
Shelly_SetRoller Prepared for Shelly Pro Dual Cover, because 2 blinds can be configured and used here
Shelly_ToggleRoller
Shelly_SetLamp + 1 Overlay Assigns the corresponding data to the lamp at the transferred IP address
Shelly_ToggleLamp + 1 Overlay Toggles the status of the lamp at the passed IP address with the corresponding data

Controlling the Shelly-Bulbs

The different Shelly bulbs have very different behavior and therefore possibilities:

  • The Shelly-Bulb Vintage basically has the same behavior as the Shelly Dimmer - so it can be treated the same way.
  • The Shelly-Bulb Duo also offers the option of changing the color of white.
  • Finally the Shelly-Bulb RBGW has 2 operating modes. In Mode=White, it behaves identically to the Bulb Duo, except that a different property (temp) with a different value range is used for the color temperature (3000 ... 6500). There is also the Mode=Color in which the lamp can be controlled using the Windows RGB color table and its brightness can be manipulated. Effects can also be selected. I have taken these aspects into account in the associated method Shelly_SetLamp or Shelly_ToggleLamp.

The Item-Definition(s) for the Different Components

Originally, each component had its own item definition - I have now summarized this for all components. So in the Definitions file, there is only the SceneActionDefinition and the ControlActionDefinition, which derives from the SceneActionDefinition.

The basic structure of these definitions has remained the same - only a few additionally required properties have been added... AND... the interface ICustomTypeDescriptor, which allows individual properties in the PropertyGrid to be made visible or invisible according to a preselection. I will describe how this works below.

In addition, as a kind of gimmick, the UITypeEditor Symbol2Property has been added, which displays image symbols in front of 2 of the properties. In order for this to work, the included graphics must be imported into the project as a resource. I will also describe this in more detail below.

The final treat is the TypeConverter SelectionOnOffConverter, which now represents ON and OFF instead of TRUE and FALSE for the State property. I will also come to this below.

The SelectionOnOffConverter

This converter changes the standard output for properties for bool variables from True/False to On/Off.

Image 2

First, the code for it:

VB.NET
Imports System
Imports System.ComponentModel

Public Class SelectionOnOffConverter
    Inherits BooleanConverter

    Public Overloads Overrides Function CanConvertTo_
    (ByVal context As ITypeDescriptorContext, ByVal destinationType As Type) As Boolean
        If destinationType Is GetType(String) Then Return True

        'allow converting to String
        Return MyBase.CanConvertTo(context, destinationType)
    End Function

    Public Overloads Overrides Function ConvertTo_
           (ByVal context As ITypeDescriptorContext, _
            ByVal culture As System.Globalization.CultureInfo, _
            ByVal value As Object, ByVal destinationType As Type) As Object
        If destinationType Is GetType(String) Then
            'converting to String
            Dim result As Boolean
            Try
                result = CBool(value)
            Catch ex As Exception
                result = False
            End Try

            If Not result Then
                Return "Off"
            Else
                Return "On"
            End If
        End If

        Return MyBase.ConvertTo(context, culture, value, destinationType)
    End Function

    Public Overloads Overrides Function CanConvertFrom_
           (ByVal context As ITypeDescriptorContext, _
            ByVal sourceType As Type) As Boolean
        If sourceType Is GetType(String) Then Return True

        'allow converting from String
        Return MyBase.CanConvertFrom(context, sourceType)
    End Function

    Public Overloads Overrides Function ConvertFrom_
           (ByVal context As ITypeDescriptorContext, _
            ByVal culture As System.Globalization.CultureInfo, _
            ByVal value As Object) As Object
        If (TypeOf value Is String) Then
            'converting from String
            If value.ToString.ToLower.Trim = "off" Then
                Return False
            Else
                Return True
            End If
        End If

        Return MyBase.ConvertFrom(context, culture, value)
    End Function

End Class
C#
using System;
using System.ComponentModel;

public class SelectionOnOffConverter : BooleanConverter
{
    public override bool CanConvertTo_
           (ITypeDescriptorContext context, Type destinationType)
    {
        if (destinationType == typeof(string))
            return true;

        // allow converting to String
        return base.CanConvertTo(context, destinationType);
    }

    public override object ConvertTo(ITypeDescriptorContext context,
           System.Globalization.CultureInfo culture, object value, Type destinationType)
    {
        if (destinationType == typeof(string))
        {
            // converting to String
            bool result;
            try
            { result = System.Convert.ToBoolean(value); }
            catch (Exception)
            { result = false; }

            if (!result)
                return "Off";
            else
                return "On";
        }

        return base.ConvertTo(context, culture, value, destinationType);
    }

    public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
    {
        if (sourceType == typeof(string))
            return true;

        // allow converting from String
        return base.CanConvertFrom(context, sourceType);
    }

    public override object ConvertFrom(ITypeDescriptorContext context,
                    System.Globalization.CultureInfo culture, object value)
    {
        if ((value is string))
        {
            // converting from String
            if (value.ToString().ToLower().Trim() == "off")
                return false;
            else
                return true;
        }

        return base.ConvertFrom(context, culture, value);
    }
}

The PropertyGrid itself basically represents every variable displayed there, regardless of what type it is, as text (i.e., string). Accordingly, converters generate a string from each property. For all variables known to the system, there is a converter that is used accordingly. But... you can of course also specify that a completely different converter or even a converter you created yourself should be used for the property. In my last article in this series, I already introduced the DropDownConverter as a tool.

CodeProject has already written a number of articles on the topic of TypeConverters - so if you're interested in creating something like this yourself, I recommend simply entering it in the search.

Basically, the procedure is like this:

  • The CanConvertTo method determines which target format can be created – in this case, String.
  • The ConvertTo method now determines what content the string to be output should have. Here, TRUE and FALSE are then made ON and OFF (for the output).
  • The CanConvertFrom method determines which source format can be converted from – in this case, String.
  • The ConvertFrom method now converts the passed value, here ON and OFF, back into the boolean values ​​TRUE and FALSE.

I admit that this is really a gimmick - but I thought it was nice to do it that way in the context … 😉

The Symbol2Property UITypeEditor

For some properties, a symbol is displayed in front of the property itself in the PropertyGrid - for example, for a Color, a rectangle with the selected color in it.

Now I thought: why not display a WiFi symbol in front of the IP address and a small image of the selected device in front of the ShellyType. This task is carried out by the UITypeEditor Symbol2Property.

There are also a lot of articles on CodeProject that cover this topic, i.e., creating a custom UITypeEditor by yourself, and here too, I recommend that if you are interested in creating something like this yourself, simply enter it into the search.

Here's the code for it:

VB.NET
Public Class Symbol2Property
    Inherits System.Drawing.Design.UITypeEditor

    ' paint a Symbol in front of the Property
    Public Overloads Overrides Function GetPaintValueSupported_
           (ByVal context As System.ComponentModel.ITypeDescriptorContext) As Boolean
        Return True
    End Function

    ' this method paints the Symbol
    Public Overloads Overrides Sub PaintValue_
           (ByVal e As System.Drawing.Design.PaintValueEventArgs)

        Dim PropertyName As String = e.Context.PropertyDescriptor.Name

        Dim myImage As System.Drawing.Image
        Dim myRect As New Rectangle()
        myRect = e.Bounds
        myRect.Inflate(-2, -2)

        If PropertyName = "IpAdress" Then
            myImage = My.Resources.Resources.Wifi
            e.Graphics.DrawImage(myImage, myRect)

        ElseIf PropertyName = "ShellyType" Then
            Dim myShellyType As Shelly.ShellyType = _
                [Enum].Parse(GetType(Shelly.ShellyType), e.Value.ToString)

            Select Case myShellyType
                Case Shelly.ShellyType.Shelly_Plus1, Shelly.ShellyType.Shelly_1, _
                ShellyType.Shelly_1_g3, ShellyType.Shelly_1L
                    myImage = My.Resources.Resources.Shelly_1
                Case Shelly.ShellyType.Shelly_Plus2, _
                     ShellyType.Shelly_2, ShellyType.Shelly_25
                    myImage = My.Resources.Resources.Shelly_2
                Case Shelly.ShellyType.Shelly_Dimmer12
                    myImage = My.Resources.Resources.Shelly_Dimmer
                Case Shelly.ShellyType.Shelly_Bulb, ShellyType.Shelly_Bulb_Duo, _
                ShellyType.Shelly_Bulb_RGBW, ShellyType.Shelly_Bulb_Vintage
                    myImage = My.Resources.Resources.Shelly_Bulb
                Case Shelly.ShellyType.Shelly_Plug
                    myImage = My.Resources.Resources.Shelly_Plug
                Case Shelly.ShellyType.Shelly_i4Plus, ShellyType.Shelly_i3
                    myImage = My.Resources.Resources.Shelly_I4
                Case Else
                    myImage = My.Resources.Resources.unknown
            End Select

            e.Graphics.DrawImage(myImage, myRect)
        End If

    End Sub

End Class
C#
public partial class Symbol2Property : System.Drawing.Design.UITypeEditor
{
    // paint a Symbol in front of the Property
    public override bool GetPaintValueSupported_
           (System.ComponentModel.ITypeDescriptorContext context)
    { return true; }

    // this method paints the Symbol
    public override void PaintValue(System.Drawing.Design.PaintValueEventArgs e)
    {
        string PropertyName = e.Context.PropertyDescriptor.Name;

        System.Drawing.Image myImage;
        Rectangle myRect = new Rectangle();
        myRect = e.Bounds;
        myRect.Inflate(-2, -2);

        if (PropertyName == "IpAdress")
        {
            //myImage = My.Resources.Resources.Wifi;
            myImage = global::ShellyConnect.Properties.Resources.Wifi;
            e.Graphics.DrawImage(myImage, myRect);
        }
        else if (PropertyName == "ShellyType")
        {
            Shelly.ShellyType myShellyType = (Shelly.ShellyType)System.Enum.Parse_
                                             (typeof(Shelly.ShellyType),
                                              System.Convert.ToString(e.Value), true);

            switch (myShellyType)
            {
                case Shelly.ShellyType.Shelly_Plus1:
                case Shelly.ShellyType.Shelly_1:
                case ShellyType.Shelly_1_g3:
                case ShellyType.Shelly_1L:
                    {
                        myImage = global::ShellyConnect.Properties.Resources.Shelly_1;
                        break;
                    }

                case Shelly.ShellyType.Shelly_Plus2:
                case ShellyType.Shelly_2:
                case ShellyType.Shelly_25:
                    {
                        myImage = global::ShellyConnect.Properties.Resources.Shelly_2;
                        break;
                    }

                case Shelly.ShellyType.Shelly_Dimmer12:
                    {
                        myImage = global::ShellyConnect.Properties.Resources.Shelly_Dimmer;
                        break;
                    }

                case Shelly.ShellyType.Shelly_Bulb:
                case ShellyType.Shelly_Bulb_Duo:
                case ShellyType.Shelly_Bulb_RGBW:
                case ShellyType.Shelly_Bulb_Vintage:
                    {
                        myImage = global::ShellyConnect.Properties.Resources.Shelly_Bulb;
                        break;
                    }

                case Shelly.ShellyType.Shelly_Plug:
                    {
                        myImage = global::ShellyConnect.Properties.Resources.Shelly_Plug;
                        break;
                    }

                case Shelly.ShellyType.Shelly_i4Plus:
                case ShellyType.Shelly_i3:
                    {
                        myImage = global::ShellyConnect.Properties.Resources.Shelly_I4;
                        break;
                    }

                default:
                    {
                        myImage = global::ShellyConnect.Properties.Resources.unknown;
                        break;
                    }
            }

            e.Graphics.DrawImage(myImage, myRect);
        }
    }
}

First, the images included in my ZIP archive must be added to the project resources so that they can be accessed by the editor and integrated into the later project. In the case of the C# example, the access path may need to be adjusted within the editor (in my case, the solution itself is called ShellyConnect - the name is certainly different for you. VB is a little friendlier in this regard 😉).

To my editor:

First, the “GetPaintValueSupported” method tells the editor that a symbol should be drawn - so a TRUE is simply passed as a return.

The PaintValue method is now responsible for the drawing itself. All graphics drawing methods are available in this method (so you can also draw completely freely) - I use the DrawImage method here.

Now a few words about the PaintValueEventArgs passed to the method, because they are essentially the key.

This is used to pass the name of the currently active property using e.Context.PropertyDescriptor.Name.

Furthermore, use e.Value to pass the content of the property and use e.Bounds to pass the size of the available drawing area (I reduced this by 2 pixels in width and height so that the image is not drawn right up to the edge will – that didn’t look nice).

Now I simply output the corresponding graphic as a symbol based on the property name and its content.

The Interface ICustomTypeDescriptor

The implementation of the ICustomTypeDescriptor interface allows access to the property behavior at design time. I took advantage of this to, depending on the selected Shelly device and the selected action, only make visible (and in some cases also serializable) those properties that make sense and can be used according to the selected device.

Here are some of the different representations:

Image 3 Image 4 Image 5

Image 6 Image 7 Image 8

The interface comes with a lot of methods that I have to create but don't really use - that can't be avoided... For me, only the two GetProperties methods from the interface are relevant - here, I call my own method FilterProperties, which I first present as code:

VB.NET
Private Function FilterProperties(ByVal origProperties As PropertyDescriptorCollection) _
        As PropertyDescriptorCollection

    Dim myPD As PropertyDescriptor
    Dim myList As New List(Of PropertyDescriptor)
    Dim set_Hidden, noSerialize As Boolean

    For i As Integer = 0 To origProperties.Count - 1
        myPD = origProperties.Item(i)

        set_Hidden = False
        noSerialize = False

        Select Case myPD.Name
            Case "ShellyText"
                If myShellyText = "" _
                Or ShellyDevice = Shelly.ShellyType.None Then set_Hidden = True
            Case "OutputNr"
                If ShellyDevice = Shelly.ShellyType.Shelly_1 _
                Or ShellyDevice = Shelly.ShellyType.Shelly_1L _
                Or ShellyDevice = Shelly.ShellyType.Shelly_1_g3 _
                Or ShellyDevice = Shelly.ShellyType.Shelly_Bulb _
                Or ShellyDevice = Shelly.ShellyType.Shelly_Bulb_RGBW _
                Or ShellyDevice = Shelly.ShellyType.Shelly_Bulb_Duo _
                Or ShellyDevice = Shelly.ShellyType.Shelly_Bulb_Vintage _
                Or ShellyDevice = Shelly.ShellyType.Shelly_Dimmer12 _
                Or ShellyDevice = Shelly.ShellyType.Shelly_Plus1 _
                Or ShellyDevice = Shelly.ShellyType.Shelly_Pro1 _
                Or ShellyDevice = Shelly.ShellyType.Shelly_Plug _
                Or ShellyDevice = Shelly.ShellyType.Shelly_Motion12 _
                Or ShellyDevice = Shelly.ShellyType.Shelly_i3 _
                Or ShellyDevice = Shelly.ShellyType.Shelly_i4Plus _
                Or ShellyDevice = Shelly.ShellyType.None _
                Or myAction = ActionDefinition.SetRoller _
                Or myAction = ActionDefinition.ToggleRoller Then set_Hidden = True
            Case "ActionS1"
                If Shelly_GetBaseType(ShellyDevice) <> ShellyBaseType.Outs1 _
                   Then set_Hidden = True
            Case "ActionS2"
                If Shelly_GetBaseType(ShellyDevice) <> ShellyBaseType.Outs2 _
                   Then set_Hidden = True
            Case "ActionD"
                If Shelly_GetBaseType(ShellyDevice) <> ShellyBaseType.Dimmer _
                   Then set_Hidden = True
            Case "ActionL"
                If Shelly_GetBaseType(ShellyDevice) <> ShellyBaseType.Lamp _
                   Then set_Hidden = True
            Case "State"
                If myAction <> Shelly.ActionDefinition.SetOut _
                Or myAction = Shelly.ActionDefinition.none Then set_Hidden = True
            Case "RollerAction"
                If myAction <> Shelly.ActionDefinition.SetRoller _
                Or myAction = Shelly.ActionDefinition.none Then set_Hidden = True
            Case "RollerPosition"
                If myAction <> Shelly.ActionDefinition.SetRoller _
                Or myAction = Shelly.ActionDefinition.none Then set_Hidden = True
                'Or RollerAction <> RollerMode.toPosition _
            Case "Value"
                If myAction <> Shelly.ActionDefinition.SetRoller _
                And myAction <> Shelly.ActionDefinition.ToggleRoller _
                Or myAction = Shelly.ActionDefinition.none Then set_Hidden = True
            Case "Brightness"
                    If ShellyDevice <> Shelly.ShellyType.Shelly_Dimmer12 _
                     And ShellyDevice <> Shelly.ShellyType.Shelly_Bulb _
                     And ShellyDevice <> Shelly.ShellyType.Shelly_Bulb_Vintage _
                     And ShellyDevice <> Shelly.ShellyType.Shelly_Bulb_RGBW _
                     And ShellyDevice <> Shelly.ShellyType.Shelly_Bulb_Duo _
                     And ShellyDevice <> Shelly.ShellyType.Shelly_RGBW2 _
                     Or myAction = Shelly.ActionDefinition.none Then set_Hidden = True
            Case "ColorTone"
                    If ShellyDevice <> Shelly.ShellyType.Shelly_Dimmer12 _
                    And ShellyDevice <> Shelly.ShellyType.Shelly_Bulb _
                    And ShellyDevice <> Shelly.ShellyType.Shelly_Bulb_RGBW _
                    And ShellyDevice <> Shelly.ShellyType.Shelly_Bulb_Duo _
                    And ShellyDevice <> Shelly.ShellyType.Shelly_RGBW2 _
                    Or myColor <> Drawing.Color.White _
                    Or ShellyDevice = Shelly.ShellyType.None _
                       Then set_Hidden = True : noSerialize = True
            Case "Color"
                    If ShellyDevice <> Shelly.ShellyType.Shelly_Bulb _
                    And ShellyDevice <> Shelly.ShellyType.Shelly_Bulb_RGBW _
                    And ShellyDevice <> Shelly.ShellyType.Shelly_RGBW2 _
                    Or ShellyDevice = Shelly.ShellyType.None _
                       Then set_Hidden = True : noSerialize = True
            Case "ColorEffect"
                    If ShellyDevice <> Shelly.ShellyType.Shelly_Bulb_RGBW _
                    And ShellyDevice <> Shelly.ShellyType.Shelly_RGBW2 _
                    Or myColor = Drawing.Color.White _
                    Or ShellyDevice = Shelly.ShellyType.None _
                       Then set_Hidden = True : noSerialize = True

        End Select

        If set_Hidden Then      ' make Property unvisible inside the Property-Grid
            myPD = TypeDescriptor.CreateProperty(myPD.ComponentType, myPD, New Attribute() _
            {New BrowsableAttribute(False), _
             New EditorBrowsableAttribute(EditorBrowsableState.Never)})
        End If
        If noSerialize Then     ' this Property will not be serialized
            myPD = TypeDescriptor.CreateProperty(myPD.ComponentType, myPD, New Attribute() _
            {New DesignerSerializationVisibilityAttribute_
            (DesignerSerializationVisibility.Hidden)})
        End If
        myList.Add(myPD)
    Next

    Dim myPDListe(myList.Count - 1) As PropertyDescriptor
    myList.CopyTo(myPDListe)

    Return New PropertyDescriptorCollection(myPDListe)
End Function
C#
private PropertyDescriptorCollection FilterProperties_
        (PropertyDescriptorCollection origProperties)
{
    PropertyDescriptor myPD;
    List<propertydescriptor> myList = new List<propertydescriptor>();
    bool set_Hidden, noSerialize;

    for (int i = 0; i <= origProperties.Count - 1; i++)
    {
        myPD = origProperties[i];

        set_Hidden = false;
        noSerialize = false;

        switch (myPD.Name)
        {
            case "ShellyText":
                {
                 if (myShellyText == "" | ShellyDevice == Shelly.ShellyType.None)
                    set_Hidden = true;
                 break;
                }

            case "OutputNr":
                {
                    if (ShellyDevice == Shelly.ShellyType.Shelly_1
                      | ShellyDevice == Shelly.ShellyType.Shelly_1L
                      | ShellyDevice == Shelly.ShellyType.Shelly_1_g3
                      | ShellyDevice == Shelly.ShellyType.Shelly_Bulb
                      | ShellyDevice == Shelly.ShellyType.Shelly_Bulb_RGBW
                      | ShellyDevice == Shelly.ShellyType.Shelly_Bulb_Duo
                      | ShellyDevice == Shelly.ShellyType.Shelly_Bulb_Vintage
                      | ShellyDevice == Shelly.ShellyType.Shelly_Dimmer12
                      | ShellyDevice == Shelly.ShellyType.Shelly_Plus1
                      | ShellyDevice == Shelly.ShellyType.Shelly_Pro1
                      | ShellyDevice == Shelly.ShellyType.Shelly_Plug
                      | ShellyDevice == Shelly.ShellyType.Shelly_Motion12
                      | ShellyDevice == Shelly.ShellyType.Shelly_i3
                      | ShellyDevice == Shelly.ShellyType.Shelly_i4Plus
                      | ShellyDevice == Shelly.ShellyType.None
                      | myAction == ActionDefinition.SetRoller
                      | myAction == ActionDefinition.ToggleRoller)
                        set_Hidden = true;
                    break;
                }

            case "ActionS1":
                {
                    if (ShellyCom2.Shelly_GetBaseType(ShellyDevice) != ShellyBaseType.Outs1)
                        set_Hidden = true;
                    break;
                }

            case "ActionS2":
                {
                    if (ShellyCom2.Shelly_GetBaseType(ShellyDevice) != ShellyBaseType.Outs2)
                        set_Hidden = true;
                    break;
                }

            case "ActionD":
                {
                    if (ShellyCom2.Shelly_GetBaseType(ShellyDevice) != ShellyBaseType.Dimmer)
                        set_Hidden = true;
                    break;
                }

            case "ActionL":
                {
                    if (ShellyCom2.Shelly_GetBaseType(ShellyDevice) != ShellyBaseType.Lamp)
                        set_Hidden = true;
                    break;
                }

            case "State":
                {
                    if (myAction != Shelly.ActionDefinition.SetOut | 
                        myAction == Shelly.ActionDefinition.none)
                        set_Hidden = true;
                    break;
                }

            case "RollerAction":
                {
                    if (myAction != Shelly.ActionDefinition.SetRoller | 
                        myAction == Shelly.ActionDefinition.none)
                        set_Hidden = true;
                    break;
                }

            case "RollerPosition":
                {
                    if (myAction != Shelly.ActionDefinition.SetRoller
                      | myAction == Shelly.ActionDefinition.none)
                        set_Hidden = true;
                    break;
                    //| RollerAction != RollerMode.toPosition
                }

            case "Value":
                {
                    if (myAction != Shelly.ActionDefinition.SetDimmer
                      & myAction != Shelly.ActionDefinition.SetLamp
                      & myAction != Shelly.ActionDefinition.SetRoller
                      & myAction != Shelly.ActionDefinition.ToggleRoller
                      | myAction == Shelly.ActionDefinition.none)
                        set_Hidden = true;
                    break;
                }

            case "Brightness":
                {
                    if (ShellyDevice != Shelly.ShellyType.Shelly_Dimmer12
                      & ShellyDevice != Shelly.ShellyType.Shelly_Bulb
                      & ShellyDevice != Shelly.ShellyType.Shelly_Bulb_Vintage
                      & ShellyDevice != Shelly.ShellyType.Shelly_Bulb_RGBW
                      & ShellyDevice != Shelly.ShellyType.Shelly_Bulb_Duo
                      & ShellyDevice != Shelly.ShellyType.Shelly_RGBW2
                      | myAction == Shelly.ActionDefinition.none)
                        set_Hidden = true;
                    break;
                }

            case "ColorTone":
                {
                    if (ShellyDevice != Shelly.ShellyType.Shelly_Dimmer12
                      & ShellyDevice != Shelly.ShellyType.Shelly_Bulb
                      & ShellyDevice != Shelly.ShellyType.Shelly_Bulb_RGBW
                      & ShellyDevice != Shelly.ShellyType.Shelly_Bulb_Duo
                      & ShellyDevice != Shelly.ShellyType.Shelly_RGBW2
                      | myColor != System.Drawing.Color.White
                      | ShellyDevice == Shelly.ShellyType.None)
                        { set_Hidden = true; noSerialize = true; }
                        break;
                }

            case "Color":
                {
                    if (ShellyDevice != Shelly.ShellyType.Shelly_Bulb
                      & ShellyDevice != Shelly.ShellyType.Shelly_Bulb_RGBW
                      & ShellyDevice != Shelly.ShellyType.Shelly_RGBW2
                      | ShellyDevice == Shelly.ShellyType.None)
                        { set_Hidden = true; noSerialize = true; }
                        break;
                }

            case "ColorEffect":
                {
                    if (ShellyDevice != Shelly.ShellyType.Shelly_Bulb_RGBW
                     & ShellyDevice != Shelly.ShellyType.Shelly_RGBW2
                     | myColor == System.Drawing.Color.White
                     | ShellyDevice == Shelly.ShellyType.None)
                        { set_Hidden = true; noSerialize = true; }
                        break;
                }
        }

        if (set_Hidden)
            myPD = TypeDescriptor.CreateProperty_
                   (myPD.ComponentType, myPD, new System.Attribute[]
            { new BrowsableAttribute(false), _
              new EditorBrowsableAttribute(EditorBrowsableState.Never) });
        if (noSerialize)
            myPD = TypeDescriptor.CreateProperty_
                   (myPD.ComponentType, myPD, new System.Attribute[]
            { new DesignerSerializationVisibilityAttribute_
            (DesignerSerializationVisibility.Hidden) });
        myList.Add(myPD);
    }

    PropertyDescriptor[] myPDListe = new PropertyDescriptor[myList.Count - 1 + 1];
    myList.CopyTo(myPDListe);

    return new PropertyDescriptorCollection(myPDListe);
}</propertydescriptor></propertydescriptor>

The whole thing refers to the SceneActionDefinition, to which this interface belongs.

I have identified the properties whose behavior/visibility I manipulate with a corresponding remark text (dependent property).

How Does It All Work?

It is very important that the properties that can influence the PropertyGrid have a RefreshProperties(RefreshProperties.All) as an attribute. This causes the PropertyGrid to be redrawn if the value of this property changes.

The PropertyDescriptorCollection of the class is passed to my FilterProperties method from the interface (so it contains all the properties of the class) and my method returns the PropertyDescriptorCollection that I changed. I have used this on many “custom” controls to hide properties that are passed from the inherited base class, but which I do not use in my controls. Well… basically something similar is happening here too.

For example, some Shelly devices only have one output or even none at all - in this case, I hide the “OutputNr” property.

Or with the actions: The ActionsDefinition used here is an ENum – I cannot influence its content depending. But I can work with multiple Enums and associated properties. According to the Shelly device (the ShellyBaseType), it is then switched between the properties, each of which uses a stripped-down ENum, and all of these partial ENums are converted into the actual Enum at the end (but the latter happens in the class).

So all properties of the class are gone through in a loop and checked under which conditions something should happen to them. Depending on the bool set_Hidden or noSerialize or both set in the loop, the corresponding attributes are then added to the PropertyDescriptor at the end.

I haven't found this used anywhere yet (except for my own controls), but I find it to be a useful way to optimize the usability of a control (or component), especially in this case.

Further Changes

Inside the ShellyCom2 module and also the Definitions module, I have carried out some restructuring from the DRY aspect (Don’t repeat yourself), unfortunately some renaming, but in any case extensions. The archive linked here essentially represents a completely new version of my previous work from previous articles.

But I think that everyone who has downloaded my previous work will be happy about the changes implemented here...

Finally – Last Words

I would like to thank (again) for the help in creating this and the previous articles at @sean-ewington.

Also, the Shelly support and the Shelly marketing were particularly helpful to me with the expansion of the devices.

In addition, the wealth of knowledge in this forum was a great help to me in the development of converters and editors. All of the functionalities used here have already been partially covered in other articles in one way or another. Since I have been using many of these things for a very long time and have modified them many times, I can no longer name any sources for them.

At this point, I would like to point out again that I am definitely open to suggestions and suggestions, i.e., inspiration...

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)