
The basis for this article is the description in my previous article Smart Home – Controlling Shelly® Devices – Part 1
In this article I present a component that works with the standard controls without having to adapt them. Here the functions are assigned and the controls are animated via the component that I present in this article.
All or just some of the controls of the form can be selected and the desired action can be assigned to them:

In this article I assume that property handling, especially for collection objects, is known.
Basics
In contrast to a control, a component does not know which form it belongs to. This information can only be obtained using a trick. I found the basics on StackOverFlow in this post: get components parent-form
Now I know the form to which my component belongs and therefore have access to all the controls it contains:
Private Sub GetParentForm()
Dim myHost As IDesignerHost = Nothing
If MyBase.Site IsNot Nothing Then myHost = CType(MyBase.Site.GetService(GetType(IDesignerHost)), IDesignerHost)
If myHost IsNot Nothing Then
myParentForm = CType(myHost.RootComponent, Form)
Exit Sub
End If
End Sub
Public Overrides Property Site() As System.ComponentModel.ISite
Get
Return MyBase.Site
End Get
Set(ByVal value As System.ComponentModel.ISite)
MyBase.Site = value
GetParentForm()
End Set
End Property
<Category("Info-Data"), Description("the Parentform of this Component")>
Property ParentForm() As Form
Get
Return myParentForm
End Get
Set(value As Form)
myParentForm = value
End Set
End Property
Private myParentForm As Form
private void GetParentForm()
{
IDesignerHost myHost = null;
if (base.Site != null)
myHost = (IDesignerHost)base.Site.GetService(typeof(IDesignerHost));
if (myHost != null)
{
myParentForm = (System.Windows.Forms.Form)myHost.RootComponent;
return;
}
}
public override System.ComponentModel.ISite Site
{
Get { return base.Site; }
set
{
base.Site = value;
GetParentForm();
}
}
[Category("Info-Data")]
[Description("the Parentform of this Component")]
public Form ParentForm
{
get { return myParentForm; }
set { myParentForm = value; }
}
private Form myParentForm;
With my own properties I now can specify with which colors a selected control should be animated and at what time interval a possible value change should be detected.
How it works
This class ControlAssignmentDefinition
contains the basis for the function assignment to the controls:
<TypeConverter(GetType(ExpandableObjectConverter))>
Public Class ControlAssignmentDefinition
<Category("Control"), Description("the Control which should do the Action")>
Property SelectedControl As Control
Get
Return mySelectedControl
End Get
Set(value As Control)
mySelectedControl = value
savedBackColor = value.BackColor
savedForeColor = value.ForeColor
End Set
End Property
Private mySelectedControl As Control = Nothing
Public savedBackColor As Color
Public savedForeColor As Color
<Category("Shelly"), Description("IpAdress of the Shelly-Device to work with")>
<RefreshProperties(RefreshProperties.All)>
Property IpAdress As String
Get
Return my_IPAdress
End Get
Set(value As String)
my_ShellyType = ShellyCom.Shelly_GetType(value)
If my_ShellyType <> ShellyCom.ShellyType.None Then my_IPAdress = value
If my_ShellyType = ShellyCom.ShellyType.Shelly_Dimmer2 Then myOutput = 0
End Set
End Property
Private my_IPAdress As String = ""
<Category("Shelly"), Description("shows the Type of the connected Shelly-Device")>
ReadOnly Property ShellyType As String
Get
Return my_ShellyType.ToString
End Get
End Property
Private my_ShellyType As ShellyCom.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(ShellyActionComponent.ActionDefinition), "none")>
Property Action As ShellyActionComponent.ActionDefinition
Get
Return myAction
End Get
Set(value As ShellyActionComponent.ActionDefinition)
myAction = value
End Set
End Property
Private myAction As ShellyActionComponent.ActionDefinition = ShellyActionComponent.ActionDefinition.none
Public Sub New()
End Sub
Public Overrides Function toString() As String
If mySelectedControl IsNot Nothing Then Return mySelectedControl.Name + " => " + myAction.ToString
Return "[-]"
End Function
End Class
[TypeConverter(typeof(ExpandableObjectConverter))]
public class ControlAssignmentDefinition
{
[Category("Control")]
[Description("the Control which should do the Action")]
public Control SelectedControl
{
get { return mySelectedControl; }
set
{
mySelectedControl = value;
savedBackColor = value.BackColor;
savedForeColor = value.ForeColor;
}
}
private Control mySelectedControl = null;
public Color savedBackColor;
public Color savedForeColor;
[Category("Shelly")]
[Description("IpAdress of the Shelly-Device to work with")]
[RefreshProperties(RefreshProperties.All)]
public string IpAdress
{
get { return my_IPAdress; }
set
{
my_ShellyType = ShellyCom.Shelly_GetType(value);
if (my_ShellyType != ShellyCom.ShellyType.None)
my_IPAdress = value;
if (my_ShellyType == ShellyCom.ShellyType.Shelly_Dimmer2)
myOutput = 0;
}
}
private string my_IPAdress = "";
[Category("Shelly")]
[Description("shows the Type of the connected Shelly-Device")]
public string ShellyType
{
get { return Convert.ToString(my_ShellyType); }
}
private ShellyCom.ShellyType my_ShellyType;
[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(ShellyActionComponent.ActionDefinition), "none")]
public ShellyActionComponent.ActionDefinition Action
{
get { return myAction; }
set { myAction = value; }
}
private ShellyActionComponent.ActionDefinition myAction = ShellyActionComponent.ActionDefinition.none;
public ControlAssignmentDefinition()
{
}
public override string ToString()
{
if (mySelectedControl != null)
return mySelectedControl.Name + " => " + Convert.ToString(myAction);
return "[-]";
}
}
In order to be able to address several controls of the form with this class, it is embedded in the ActionCollection
as a List (of ControlAssignmentDefinition)
.
This collection provides the designer script of the form with the assignments to the controls via the ShellyActions
property:
<DesignerSerializationVisibility(DesignerSerializationVisibility.Content)>
ReadOnly Property ShellyActions As ActionCollection
Get
Return my_ShellyActions
End Get
End Property
Private my_ShellyActions As New ActionCollection
[Category("Shelly")]
[Description("Assignment to the Controls")]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
public ActionCollection ShellyActions
{
get { return my_ShellyActions; }
}
private ActionCollection my_ShellyActions = new ActionCollection();
The properties of the ControlAssignmentDefinition
class already mentioned have the following meaning:
SelectedControl
- the control that should execute the assigned function IpAdress
– the IPAdress of the Shelly device to work with ShellyType
– shows the Type of the Shelly Device of the selected IpAdress - Action – which action should happen? (
SetOut
, ToggleOut
, SetDimmer
, SetRoller
, ToggleRoller
) - Output number – the number of the output of the Shelly device to work with
- Value – the Value which is assigned to the Output – if the selected Action is Dimmer or Roller the Value is their percentage
For each control defined here, the click event is evaluated in the collection and assigned to the selected action.
For the sake of simplicity, I embedded a timer in the component to animate the controls used, the interval of which calls the ShellyRequest
method, in which the elements of the collection are then gone through and checked for status changes.
The controls are then animated accordingly - although there is different logic for this, depending on the function selected.
Private myTimer As New Timer With {.Enabled = False, .Interval = 1000}
Private Sub ShellyRequest(sender As Object, e As System.EventArgs)
If myParentForm IsNot Nothing And Not DesignMode Then
Dim ShellyStatus As ShellyCom.Shelly_IOStatus
Dim myItem As ControlAssignmentDefinition
Dim outActive1, outActive2, outActive3, notActive As Boolean
Dim ControlType As Type
For i As Integer = 0 To my_ShellyActions.Count - 1
myItem = my_ShellyActions.Item(i)
If ShellyCom.Shelly_GetType(myItem.IpAdress) <> ShellyCom.ShellyType.None Then
ShellyStatus = ShellyCom.Shelly_GetStatus(myItem.IpAdress)
notActive = ((myItem.Action = ActionDefinition.SetOut) Or (myItem.Action = ActionDefinition.SetDimmer)) And (myItem.Value = 0)
outActive1 = (myItem.Action = ActionDefinition.SetOut) And (myItem.Value <> 0)
outActive2 = (myItem.Value <> 0) And (((myItem.OutputNr = 0) And ShellyStatus.Out0) Or ((myItem.OutputNr = 1) And ShellyStatus.Out1))
outActive3 = ShellyStatus.RollerState = ShellyCom.ShellyRollerState.Opening Or ShellyStatus.RollerState = ShellyCom.ShellyRollerState.Closing
ControlType = myItem.SelectedControl.GetType
Select Case ControlType
Case GetType(Button)
If (outActive1 Or outActive2 Or outActive3) And Not notActive Then
myItem.SelectedControl.BackColor = my_AnimationBackColor
myItem.SelectedControl.ForeColor = my_AnimationForeColor
Else
myItem.SelectedControl.BackColor = myItem.savedBackColor
myItem.SelectedControl.ForeColor = myItem.savedForeColor
End If
Case GetType(Label), GetType(TextBox)
myItem.SelectedControl.Text = ShellyStatus.OutValue.ToString + " %"
If ShellyStatus.Mode = ShellyCom.ShellyMode.Roller Then myItem.SelectedControl.Text += " - " + ShellyStatus.RollerState.ToString
End Select
End If
Next
End If
End Sub
private Timer myTimer = new Timer() { Enabled = false, Interval = 1000 };
private void ShellyRequest(object sender, System.EventArgs e)
{
if (myParentForm != null & !DesignMode)
{
ShellyCom.Shelly_IOStatus ShellyStatus;
ControlAssignmentDefinition myItem;
bool outActive1, outActive2, outActive3, notActive;
Type ControlType;
for (int i = 0; i <= my_ShellyActions.Count - 1; i++)
{
myItem = my_ShellyActions.Item(i);
if (ShellyCom.Shelly_GetType(myItem.IpAdress) != ShellyCom.ShellyType.None)
{
ShellyStatus = ShellyCom.Shelly_GetStatus(myItem.IpAdress);
notActive = ((myItem.Action == ActionDefinition.SetOut) | (myItem.Action == ActionDefinition.SetDimmer)) & (myItem.Value == 0);
outActive1 = (myItem.Action == ActionDefinition.SetOut) & (myItem.Value != 0);
outActive2 = ((myItem.OutputNr == 0) & ShellyStatus.Out0) | ((myItem.OutputNr == 1) & ShellyStatus.Out1);
outActive3 = ShellyStatus.RollerState == ShellyCom.ShellyRollerState.Opening | ShellyStatus.RollerState == ShellyCom.ShellyRollerState.Closing; ControlType = myItem.SelectedControl.GetType();
if (ControlType == typeof(Button))
{
if ((outActive1 | outActive2 | outActive3) & !notActive)
{
myItem.SelectedControl.BackColor = my_AnimationBackColor;
myItem.SelectedControl.ForeColor = my_AnimationForeColor;
}
else
{
myItem.SelectedControl.BackColor = myItem.savedBackColor;
myItem.SelectedControl.ForeColor = myItem.savedForeColor;
}
}
else if(ControlType == typeof(Label) | ControlType == typeof(TextBox))
myItem.SelectedControl.Text = Convert.ToString(ShellyStatus.OutValue)+ " %";
if (ShellyStatus.Mode == ShellyCom.ShellyMode.Roller)
myItem.SelectedControl.Text += " - " + Convert.ToString(ShellyStatus.RollerState);
}
}
}
}
However, if the actual value of a blind or dimmer is to be displayed, this can only be done on a Label or a Textbox. In this case, these elements would also have to be included in the ShellyActions
.
An assignment could look like this:

Finally – last words
I have planned the Button control for executing the actions (use of the click event) and the Label and Textbox controls for displaying values (use of the text property). For other controls, a connection did not seem to make sense to me - except you would want to work with customized controls here
I would like to thank @sean-ewington for the help in creating this and the previous article.