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
.
First, the code for it:
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
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
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
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
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
using System;
using System.ComponentModel;
public class SelectionOnOffConverter : BooleanConverter
{
public override bool CanConvertTo_
(ITypeDescriptorContext context, Type destinationType)
{
if (destinationType == typeof(string))
return true;
return base.CanConvertTo(context, destinationType);
}
public override object ConvertTo(ITypeDescriptorContext context,
System.Globalization.CultureInfo culture, object value, Type destinationType)
{
if (destinationType == typeof(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;
return base.CanConvertFrom(context, sourceType);
}
public override object ConvertFrom(ITypeDescriptorContext context,
System.Globalization.CultureInfo culture, object value)
{
if ((value is 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 TypeConverter
s - 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:
Public Class Symbol2Property
Inherits System.Drawing.Design.UITypeEditor
Public Overloads Overrides Function GetPaintValueSupported_
(ByVal context As System.ComponentModel.ITypeDescriptorContext) As Boolean
Return True
End Function
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
public partial class Symbol2Property : System.Drawing.Design.UITypeEditor
{
public override bool GetPaintValueSupported_
(System.ComponentModel.ITypeDescriptorContext context)
{ return true; }
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 = 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:
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:
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
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
myPD = TypeDescriptor.CreateProperty(myPD.ComponentType, myPD, New Attribute() _
{New BrowsableAttribute(False), _
New EditorBrowsableAttribute(EditorBrowsableState.Never)})
End If
If noSerialize Then
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
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;
}
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 Enum
s 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 ENum
s 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...