UPDATE 29.7.2014
The Zip File includes a Visual Studio Project Family consisting of three Projects.
Developmentsystem: VisualStudio 2012
Framework: .Net 4.5
Language: VB.Net
Introduction
Do you like this ios (iphone/ipad) controls? If yes I took the basic idea and developed some equal controls for the use with Winform in .Net.
This is the 1st control of two the other control is:
I know that there is an article for the rotator control with a simmillar control for WPF in code project and I know too that it is possible to use WPF controls inside a Winform but I do not like this workaround and therefor I developed this nice control to extend the possibilities to present the user a selection list in a Winform.
What can ISOrotor do for you?
A) For the User.
- Cycle through a list items of Strings or Images by using the Mouse wheel or the Up/Down Keys of the keyboard.
- Choose/select on of the items by clicking on the Selector (this is the middle Area of the control).
- See the cylinder cycling if moving forward or backward.
- See a border if the control is focused.
- Use key Up/Down instead of Mouse wheel.
B) For the Developer
- Choose the item type to be used (String or Image) and preset 3-15 Strings or Images to be displayed.
- Give each Image a unique name as Identifier.
- Change the Font Forecolor and Alignment of the items.
- Change the Font and Forecolor of the Selector (the clickable selecting Area in the middle).
- Change the color of the cycling cylinder.
- Decide if the Selector area is highlighted with a semitransparent green color.
- Decide if the Selector area may have a Border or not.
For examples see pictures above.
The Basic Idea
The controls base is a usercontrol that contains 3 transparent Panels . Panel A ist docked to the top Panel C is docked to the Bottom and Panel B (the Selector) is in the middle.
Each panel contains a label and a picturebox control inside. Due to the value of the property ItemType the label or the picturebox is visible.
To imitate the cycling of the predefined items (Strings or Images) we move them up or down from one label/picturebox of a panel to the next/previous label/picturebox of the panel above/below.
At the beginning the first item of the predefined item list is shown in the middle Area (in the Selector).
The items are presented as an endless chain.
Using the code
The images you use may have an transparent Aplha Chanel and a size not less 150x99 pixel. This big size is used to become a good looking cylinder picture even if the control is resized to a bigger dimension. The standard dimension is 150 x 99 pixel.
The control must be populated with 3 items in the minimum and 15 items in the maximum this is done in the Load event of the parent form (see description below). The custom properties may be set by code or direct in the controls property grid.
Installing
Add the controls DLL to the VisualStudio Toolbox. It is represented by the icon .
- Drag the control from the toolbox onto the Form.
- Set the items (images and/or strings) - Form_load event.
- Set the properties by code or in the property grid - Form_load event.
- Create Sub SelectorClicked(..) event in the parent form.
The cycling magic
The most important work of the control is to cycle the items in the correct sequence upward or downward and to animate the cycling cylinder.
To manage the cycling I build a special set of classes. Thank you very much for my friend Christoph Knorr who is a student of informatics and who did this excellent work for me.
Class clsListitem
This class creates a new object the Listelement. The Listelement holds the value moving from one label/picturebox to the next/previous. The Listelement is used by the class DoubleChainedList. The set functions are used to transfer the value to the labels/pictureboxes.
Public Class Listelement
Public value As Object
Public nextElem, prevElem As Listelement
Public Sub New(ByVal value As Object) Me.value = value
nextElem = Nothing
prevElem = Nothing
End Sub
Public Sub setNextElem(ByVal nextElem As Listelement)
Me.nextElem = nextElem
End Sub
Public Sub setPrevElem(ByVal prevElem As Listelement)
Me.prevElem = prevElem
End Sub
Public Function getNextElem() As Listelement
Return nextElem
End Function
Public Function getPrevElem() As Listelement
Return Me.prevElem
End Function
Public Function getValue() As Object Return value
End Function
End Class
Class DoubleChainedList
This class manages the correct forward or backward movement. It moves the Listelements and gives the possibility to add new Listelements into the internal element stack. The class provides the functions scrollUp and scrollDown to be used in the movement event of the control (Mous wheel scrolls/ keyDown event).
Public Class DoubleChainedList
Private startElem As New Listelement(0)
Private secondElem As New Listelement(1)
Private thirdElem As New Listelement(2)
Public Sub New(ByVal firstvalue As Object, ByVal secondvalue As Object, ByVal thirdvalue As Object)
startElem.setNextElem(secondElem)
startElem.setPrevElem(thirdElem)
startElem.value = firstvalue
secondElem.setNextElem(thirdElem)
secondElem.setPrevElem(startElem)
secondElem.value = secondvalue
thirdElem.setPrevElem(secondElem)
thirdElem.setNextElem(startElem)
thirdElem.value = thirdvalue
End Sub
Public Sub add(ByVal value As Object) Try
Cursor.Current = Cursors.WaitCursor
Dim newElem, pointerElem As Listelement
newElem = New Listelement(value)
pointerElem = startElem.getPrevElem()
pointerElem.setNextElem(newElem)
newElem.setNextElem(startElem)
newElem.setPrevElem(pointerElem)
startElem.setPrevElem(newElem)
Catch ex As Exception
Cursor.Current = Cursors.Default
MsgBox(ex.Message, MessageBoxIcon.Exclamation, Application.ProductName)
Finally
Cursor.Current = Cursors.Default
End Try
End Sub
Public Function getFirstElem() As Listelement
Return startElem
End Function
Public Function getSecondElem() As Listelement
Return secondElem
End Function
Public Function getThirdElem() As Listelement
Return thirdElem
End Function
Public Sub scrollUp()
startElem = secondElem
secondElem = thirdElem
thirdElem = thirdElem.nextElem
End Sub
Public Sub scrollDown()
thirdElem = secondElem
secondElem = startElem
startElem = startElem.prevElem
End Sub
End Class
Class IOSrotor
This is the class of the usercontrol. For better reading it is devided by regions. Let's go through the regions now.
Class Header Privat Variable and Custom Event Area:
Imports System.Windows.Forms
Imports System.ComponentModel
Imports System.Threading.Thread
Imports System.Drawing.Drawing2D
<ToolboxBitmap(GetType(IOSrotor))> Public Class IOSrotor
#Region " Declaration"
Public Event SelectorClicked(ByVal Result As Object)
Public Enum enuItemType As Integer
Text
Picture
End Enum
Public Enum enuAlignment As Integer
Left
Center
Right
End Enum
Public Enum enuSkin As Integer
Silver
Red
Yellow
Green
Blue
End Enum
Private _ItemType As Integer = enuItemType.Text
Private _ItemTextAlign As Integer = enuAlignment.Center
Private _Skin As Integer = enuSkin.Silver
Private _SelectorBorder As Boolean = False
Private _SelectorFont As Font
Private _SelectorForeColor As Color
Private _INIReg As Boolean = False
Public ItemText(14) As String
Public Image(14) As Image
Public ImageName(14) As String
Private imageA, imageB, imageC As Image
Public Register As DoubleChainedList
Private RegisterItem As Listelement
Private intBorderThickness As Integer = 1
Private colBorderColor As Color = Color.Transparent
#End Region
Class Region of custom properties:
Notice the difference between the property Name and the name shown in the property grid (DisplayName).
#Region "custom Properties"
<Category("Items"), DisplayName("Initialize"), _
Description("First populate Images/ItemText Arrays!"),
Browsable(False)> Public Property IniRegister As Boolean
Get
Return _INIReg
End Get
Set(value As Boolean)
If value Then
If ItemType = enuItemType.Picture Then
Dim temp As Integer = 0
For y0 As Integer = 0 To UBound(Image)
If Not IsNothing(Image(y0)) Then
temp += 1
Else
Exit For
End If
Next
If temp >= 3 Then Register = Nothing Register = New DoubleChainedList(Image(0), Image(1), _
Image(2))
For y1 As Integer = 3 To temp - 1
If Not IsNothing(Image(y1)) Then Register.add(Image(y1))
Next
End If
_INIReg = False
ElseIf ItemType = enuItemType.Text Then
Dim temp As Integer = 0
For x As Integer = 0 To UBound(ItemText)
If ItemText(x) <> "" Then
temp += 1
Else
Exit For
End If
Next
If temp >= 3 Then Register = Nothing Register = New DoubleChainedList(ItemText(0), _
ItemText(1), ItemText(2))
For y2 As Integer = 3 To temp - 1
If ItemText(y2) <> "" Then _
Register.add(ItemText(y2))
Next
End If
_INIReg = False
End If
moveForward()
End If
End Set
End Property
<Category("Items"), DisplayName("Item Type"), _
Description("Type of Items.")> Public Property ItemType As enuItemType
Get
Return _ItemType
End Get
Set(value As enuItemType)
_ItemType = value
If value = enuItemType.Text Then
picA.Dock = DockStyle.None : picB.Dock = DockStyle.None
picA.Dock = DockStyle.None
picB.Visible = False : picA.Visible = False
picC.Visible = False
lblA.Dock = DockStyle.Fill : lblB.Dock = DockStyle.Fill
lblC.Dock = DockStyle.Fill
lblB.Visible = True : lblA.Visible = True
lblC.Visible = True
Else
lblA.Dock = DockStyle.None : lblB.Dock = DockStyle.None
lblC.Dock = DockStyle.None
lblB.Visible = False : lblA.Visible = False
lblC.Visible = False
picA.Dock = DockStyle.Fill : picB.Dock = DockStyle.Fill
picA.Dock = DockStyle.Fill
picB.Visible = True : picA.Visible = True
picC.Visible = True
End If
End Set
End Property
<Category("Items"), DisplayName("Item Alignment"), _
Description("Aligment for Items type of Text.")> Public Property _
ItemTextAlign As enuAlignment
Get
Return _ItemTextAlign
End Get
Set(value As enuAlignment)
If ItemType = enuItemType.Text Then
_ItemTextAlign = value
Select Case value
Case enuAlignment.Center
lblB.TextAlign = ContentAlignment.MiddleCenter
lblA.TextAlign = ContentAlignment.MiddleCenter
lblC.TextAlign = ContentAlignment.MiddleCenter
Case enuAlignment.Left
lblB.TextAlign = ContentAlignment.MiddleLeft
lblA.TextAlign = ContentAlignment.MiddleLeft
lblC.TextAlign = ContentAlignment.MiddleLeft
Case enuAlignment.Right
lblB.TextAlign = ContentAlignment.MiddleRight
lblA.TextAlign = ContentAlignment.MiddleRight
lblC.TextAlign = ContentAlignment.MiddleRight
End Select
End If
End Set
End Property
<Category("Selector"), DisplayName("Selector Highlighted"), _
Description("Show/hide transparent green Selector")> Public Property _
SelectorGreen As Boolean
Get
Return _SelectorBorder
End Get
Set(value As Boolean)
_SelectorBorder = value
If value Then
PanelB.BackgroundImage = My.Resources.SelectorBack
Else
PanelB.BackgroundImage = Nothing
End If
End Set
End Property
<Category("Selector"), DisplayName("Selector Font"), _
Description("The Font of the Selector.")> Public Property SelectorFont _
As Font
Get
Return _SelectorFont
End Get
Set(value As Font)
_SelectorFont = value
lblB.Font = value
End Set
End Property
<Category("Selector"), DisplayName("Selector Textcolor"), _
Description("Textcolor of the Selector.")> Public Property _
SelectorForeColor As Color
Get
Return _SelectorForeColor
End Get
Set(value As Color)
_SelectorForeColor = value
lblB.ForeColor = value
End Set
End Property
<Category("Skin"), DisplayName("Skin"), _
Description("The controls skin.")> Public Property Skin As enuSkin
Get
Return _Skin
End Get
Set(value As enuSkin)
_Skin = value
My.Settings.Skin = value
My.Settings.Save()
Select Case value
Case enuSkin.Blue
Me.BackgroundImage = My.Resources.blueB
imageA = My.Resources.bluebA
imageB = My.Resources.bluebA
imageC = My.Resources.blueB
Case enuSkin.Green
Me.BackgroundImage = My.Resources.greenB
imageA = My.Resources.greenA
imageB = My.Resources.greenB
imageC = My.Resources.greenC
Case enuSkin.Red
Me.BackgroundImage = My.Resources.redB
imageA = My.Resources.redA
imageB = My.Resources.redB
imageC = My.Resources.redC
Case enuSkin.Yellow
Me.BackgroundImage = My.Resources.yellowB
imageA = My.Resources.yellowA
imageB = My.Resources.yellowB
imageC = My.Resources.yellowC
Case enuSkin.Silver
Me.BackgroundImage = My.Resources.silverB
imageA = My.Resources.silverA
imageB = My.Resources.silverB
imageC = My.Resources.silverC
End Select
End Set
End Property
<Category("Info"), DisplayName("Version"), _
Description("The controls version information")> Public ReadOnly _
Property Version As Object
Get
Return My.Application.Info.Version
End Get
End Property
#End Region
Class region of usercontrol events:
First we use the standard control properties Font changed and ForeColor changed to transfer the given property values to the same values of the child items.
There is one big problem to solve. The panels and its child controls are covering the complete area of the usercontrol. Therefor there is a problem to become the correct focus this leads to the circumstance that only one control is working if multiple instances of the control are on the same form.
The first part of the solution is done by drawing a boarder around the control if it has the focus and hiding the border if not. Due to this the user is able to recognize which rotor control has the current focus. We call the border drawing in the Got Fous and Lost Focus events. Notice command Invalidate to force a complete redrawing using this command does a better job than using the refresh command. The drawing of the border is done in the Paint event.
#Region "control events"
Private Sub tumbler_FontChanged(sender As Object, e As EventArgs) _
Handles Me.FontChanged
lblB.Font = Me.Font : lblC.Font = Me.Font
End Sub
Private Sub tumbler_ForeColorChanged(sender As Object, e As EventArgs) _
Handles Me.ForeColorChanged
lblB.ForeColor = Me.ForeColor : lblC.ForeColor = Me.ForeColor
End Sub
Private Sub IOSrotor_GotFocus(sender As Object, e As EventArgs) Handles Me.GotFocus
colBorderColor = Color.Black
intBorderThickness = 2
Invalidate()
End Sub
Private Sub IOSrotor_LostFocus(sender As Object, e As EventArgs) Handles Me.LostFocus
colBorderColor = Color.Transparent
intBorderThickness = 1
Invalidate()
End Sub
Private Sub tumbler_KeyDown(sender As Object, e As KeyEventArgs) Handles Me.KeyDown
If e.KeyCode = Keys.Oemplus Then
moveForward()
ElseIf e.KeyCode = Keys.OemMinus Then
moveBackward()
End If
End Sub
Private Sub tumbler_Load(sender As Object, e As EventArgs) Handles _
Me.Load
lblB.Font = Me.Font : lblC.Font = Me.Font
lblB.ForeColor = Me.ForeColor : lblC.ForeColor = Me.ForeColor
SelectorForeColor = Me.ForeColor : SelectorFont = Me.Font
Me.BackColor = Me.ParentForm.BackColor
Me.BorderStyle = Windows.Forms.BorderStyle.None
Invalidate()
End Sub
Private Sub tumbler_MouseWheel(sender As Object, e As MouseEventArgs) Handles Me.MouseWheel
Try
Cursor.Current = Cursors.WaitCursor
If e.Delta < 0 Then
moveForward()
Else moveBackward()
End If
Catch ex As Exception
Cursor.Current = Cursors.Default
MsgBox(ex.Message, MessageBoxIcon.Exclamation, _
Application.ProductName)
Finally
Cursor.Current = Cursors.Default
End Try
End Sub
Private Sub IOSrotor_Paint(sender As Object, e As PaintEventArgs) Handles Me.Paint
ControlPaint.DrawBorder(e.Graphics, Me.ClientRectangle, _
colBorderColor, intBorderThickness, ButtonBorderStyle.Solid, _
colBorderColor, intBorderThickness, ButtonBorderStyle.Solid, _
colBorderColor, intBorderThickness, ButtonBorderStyle.Solid, _
colBorderColor, intBorderThickness, ButtonBorderStyle.Solid)
End Sub
Private Sub tumbler_Resize(sender As Object, e As EventArgs) Handles _
Me.Resize
Dim intHeight As Integer = Me.Height / 3
PanelA.Height = intHeight
PanelB.Height = Me.Height - PanelA.Height - PanelC.Height
PanelC.Height = intHeight
Invalidate()
End Sub
#End Region
The scond part of the solution is realized by making sure the control has the focus. We have to activate its focus if the user clicks on one of the child controls.
#Region "other control events"
Private Sub lblA_Click(sender As Object, e As EventArgs) Handles lblA.Click, lblC.Click, picA.Click, picB.Click
Me.Focus()
End Sub
#End Region
To transfer the information of the selected item we use the click event of the child control in the middle Panel B and the custom event SelectorClicked.
Public Event SelectorClicked(ByVal Result As Object)
- Result contains the selected string or the name of the selected image.
#Region "selector events"
Protected Sub Selector_Click(sender As Object, e As EventArgs) Handles _
picB.Click, lblB.Click
Try
Me.Focus()
If ItemType = enuItemType.Picture Then
For x = 0 To Image.Length
If Image(x) Is picB.Image Then
RaiseEvent SelectorClicked(ImageName(x))
Exit Sub
End If
Next
ElseIf ItemType = enuItemType.Text Then
RaiseEvent SelectorClicked(lblB.Text)
End If
Catch ex As Exception
Cursor.Current = Cursors.Default
MsgBox(ex.Message, MessageBoxIcon.Exclamation, _
Application.ProductName)
End Try
End Sub
We want that the standard cursor changes to the Hand cursor if the Mouse pointer is hovering above the middle Panel (Selector).
Private Sub lblB_MouseHover(sender As Object, e As EventArgs) Handles _
lblB.MouseHover, picB.MouseHover
lblB.Cursor = Cursors.Hand
picB.Cursor = Cursors.Hand
End Sub
At least we need two private functions to move forward or backward.
Except the different movement directions this functions ar equal. We start with switching the background image to animate the cylinder cycling. To see each image we hold on the process 250ms until loading the next one. Due to there is no direct sleep or wait command in Vb.Net we substitute this command by using the appropriated Thread command. To show the correct contents of the labels/pictureboxes we use the functions get.Elem of our class DoubleChainedList.
#Region "private Functions"
Sub moveForward()
Try
Me.BackgroundImage = imageC : Me.Refresh()
Threading.Thread.Sleep(250) Me.BackgroundImage = imageB : Me.Refresh()
Register.scrollDown()
If ItemType = enuItemType.Picture Then
picA.Image = Register.getFirstElem.value
picB.Image = Register.getSecondElem.value
picC.Image = Register.getThirdElem.value
ElseIf ItemType = enuItemType.Text Then
lblA.Text = Register.getFirstElem.value
lblB.Text = Register.getSecondElem.value
lblC.Text = Register.getThirdElem.value
End If
Catch ex As Exception
Cursor.Current = Cursors.Default
MsgBox(ex.Message, MessageBoxIcon.Exclamation, _
Application.ProductName)
Finally
Me.Refresh()
End Try
End Sub
Sub moveBackward()
Try
Me.BackgroundImage = imageA : Me.Refresh()
Threading.Thread.Sleep(250) Me.BackgroundImage = imageB : Me.Refresh()
Register.scrollUp()
If ItemType = enuItemType.Picture Then
picA.Image = Register.getFirstElem.value
picB.Image = Register.getSecondElem.value
picC.Image = Register.getThirdElem.value
ElseIf ItemType = enuItemType.Text Then
lblA.Text = Register.getFirstElem.value
lblB.Text = Register.getSecondElem.value
lblC.Text = Register.getThirdElem.value
End If
Catch ex As Exception
Cursor.Current = Cursors.Default
MsgBox(ex.Message, MessageBoxIcon.Exclamation, _
Application.ProductName)
Finally
Me.Refresh()
End Try
End Sub
#End Region
Setting up the parent Form
to initialize the control we use the load event.
- We set the item strings and or the images to the internal item arrays ItemText() / Image().
- We choose the ItemType to be displayed.
- We set the property value of IniReg to true to force the adding of the items from the arrays to the internal stack used and build by the class DoubleChainedList.
Important! Do not use this property before you setup the item strings/images arrays. If you like to clear the stack first change the string/image Array values and than use this property again .
- We set the rest of the custom properties (remember you can do this direct in the property grid as well).
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Try
Cursor.Current = Cursors.WaitCursor
With IoSrotor1
.ItemText(0) = "Speed"
.ItemText(1) = "Length"
.ItemText(2) = "Weight"
.ItemText(3) = "Volume"
.ItemText(4) = "Temperature"
.ItemType = TechDOCiosRotor.IOSrotor.enuItemType.Text
.IniRegister = True
.Skin = TechDOCiosRotor.IOSrotor.enuSkin.Silver
.ItemTextAlign = TechDOCiosRotor.IOSrotor.enuAlignment.Center
.SelectorForeColor = Color.Black
.SelectorGreen = True
.SelectorFont = New Font(IoSrotor1.Font.FontFamily, IoSrotor1.Font.Size, FontStyle.Bold)
End With
With IoSrotor3
.Image(0) = My.Resources.apple : .ImageName(0) = "Apple"
.Image(1) = My.Resources.Banana : .ImageName(0) = "Banana"
.Image(2) = My.Resources.pear : .ImageName(0) = "Pear"
.ItemType = TechDOCiosRotor.IOSrotor.enuItemType.Picture
.IniRegister = True
.Skin = TechDOCiosRotor.IOSrotor.enuSkin.Silver
.ItemTextAlign = TechDOCiosRotor.IOSrotor.enuAlignment.Center
.SelectorForeColor = Color.Black
.SelectorGreen = True
.SelectorFont = New Font(IoSrotor1.Font.FontFamily, IoSrotor1.Font.Size, FontStyle.Bold)
End With
Catch ex As Exception
Cursor.Current = Cursors.Default
MsgBox(ex.Message, MessageBoxIcon.Exclamation, Application.ProductName)
Finally
Cursor.Current = Cursors.Default
End Try
End Sub
If the user clicks on the middle item (Selector) the custom event SelectorClicked is fired. We can use this event in our parent forms code.
Private Sub IOSrotor1_SelectorClicked(Result As Object) Handles IoSrotor1.SelectorClicked, IOSrotor3.SelectorClicked
Try
Cursor.Current = Cursors.WaitCursor
If IoSrotor1.ItemType = TechDOCiosRotor.IOSrotor.enuItemType.Picture Then
Select Case Name
Case "Apple"
End Select
ElseIf IoSrotor1.ItemType = TechDOCiosRotor.IOSrotor.enuItemType.Text Then
Select Case Result
Case "Speed"
End Select
End If
Catch ex As Exception
Cursor.Current = Cursors.Default
MsgBox(ex.Message, MessageBoxIcon.Exclamation, Application.ProductName)
Finally
Cursor.Current = Cursors.Default
End Try
End Sub
Points of Interest
Belive it or not to develop the correct movement procedures cost me at least one week. May be I am to stupid and not remembering my old Knowledge of Register Shifting (Assembler) I give up and asked my friend. He got the solution in 3 Days - well done!
Next problem is the animation of the cylinder image in the background. In my first solution I use a animated GIF. This was not possible because I got in trouble with the transparent backgrounds of the overlaying panels and theire child controls, Therefor I decided to manage the animation by switching between the pictures for the up or down movement.
History
Update 29.7.2014
- Fixed some bugs.
- Added function of border shown/hidden if focused/lost focus.
- Changed Selector_Clicked Event now only one Parameter Result.
- Changed the animation sequence.
- Changed missing handles command in the Form of the test project.
- Solved Focus problem if multiple instances on the same form.
- Added the other IOS like Control IOSswitch to the Project familiy.