Creating a skinnable application

I posted this to the forum for someone asking questions who was acting very arrogant, so I decided to delete my posts from that thread’ I felt like this code still offers someone else the opportunity to learn, so I moved to this blog.

Here is an example of making a custom program skinnable, but be informed that every little detail needs to be addressed. If you need to make for the skin writer(end user) to be able to specify drag areas of the form, then you need to create that logic. Every single little detail that you wish to be customized, needs to be added to the library, and also the logic to: Serialize, deserialize, validate, and apply it.

This is an extreme amount of work to allow for skinnable applications, so I hope your application is worth skinning.

Example Configuration File(Created with the SimpleSkin.ToXMLFile method)
*Note if you would like to add more fields, you need to modify the library(listed further down)

<?xml version="1.0" encoding="utf-16"?>
<SimpleSkin xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <FormSkinPath>C:\Users\et3ishak\Desktop\myFormSkin.png</FormSkinPath>
  <Button1SkinPath>C:\Users\et3ishak\Desktop\skinnedButton.png</Button1SkinPath>
  <Button1MouseDownSkinPath>C:\Users\et3ishak\Desktop\skinnedButtonMouseDown.png</Button1MouseDownSkinPath>
  <Button1Location>
    <X>100</X>
    <Y>100</Y>
  </Button1Location>
</SimpleSkin>

Example Usage:

Option Strict On
Option Explicit On
Option Infer Off
Imports System.IO
Public Class Form1
    Private FormSkinPath As String = Nothing 'My.Computer.FileSystem.SpecialDirectories.Desktop & "\myFormSkin.png"
    Private Button1SkinPath As String = Nothing 'My.Computer.FileSystem.SpecialDirectories.Desktop & "\skinnedButton.png"
    Private Button1MouseDownSkinPath As String = Nothing 'My.Computer.FileSystem.SpecialDirectories.Desktop & "\skinnedButtonMouseDown.png"
    Private Button1Location As Point = New Point(100, 100)
    Private SimpleSkin1 As New CustomSkinningLibrary.SimpleSkin
    WithEvents skinnedForm As CustomSkinningLibrary.SkinnableForm
    Private Sub btnShowSkinnedForm_Click(sender As Object, e As EventArgs) Handles btnShowSkinnedForm.Click
        With SimpleSkin1
            .FormSkinPath = Me.FormSkinPath
            .Button1SkinPath = Me.Button1SkinPath
            .Button1MouseDownSkinPath = Me.Button1MouseDownSkinPath
            .Button1Location = Me.Button1Location
        End With
        Try
            skinnedForm = CustomSkinningLibrary.SkinnableForm.Create(SimpleSkin1)
            skinnedForm.Show()
        Catch ex As CustomSkinningLibrary.SkinIncompleteException
            For Each s As String In ex.Errors
                MsgBox(s)
            Next
        End Try
    End Sub
    Private Sub btnSetFormSkinPath_Click(sender As Object, e As EventArgs) Handles btnSetFormSkinPath.Click
        Dim ofd As New OpenFileDialog With {.Title = "Select Form Skin Image"}
        ofd.Filter = "JPEG files (*.jpg)|*.jpg|GIF files (*.gif)|*.gif|All files (*.*)|*.*"
        ofd.Multiselect = False
        If ofd.ShowDialog = Windows.Forms.DialogResult.OK Then
            Me.FormSkinPath = ofd.FileName
        End If
    End Sub
    Private Sub btnSetButton1SkinPath_Click(sender As Object, e As EventArgs) Handles btnSetButton1SkinPath.Click
        Dim ofd As New OpenFileDialog With {.Title = "Select Button Skin Image"}
        ofd.Filter = "JPEG files (*.jpg)|*.jpg|GIF files (*.gif)|*.gif|All files (*.*)|*.*"
        ofd.Multiselect = False
        If ofd.ShowDialog = Windows.Forms.DialogResult.OK Then
            Me.Button1SkinPath = ofd.FileName
        End If
    End Sub
    Private Sub btnSetButton1MouseDownSkinPath_Click(sender As Object, e As EventArgs) Handles btnSetButton1MouseDownSkinPath.Click
        Dim ofd As New OpenFileDialog With {.Title = "Select Button MouseDown Skin Image"}
        ofd.Filter = "JPEG files (*.jpg)|*.jpg|GIF files (*.gif)|*.gif|All files (*.*)|*.*"
        ofd.Multiselect = False
        If ofd.ShowDialog = Windows.Forms.DialogResult.OK Then
            Me.Button1MouseDownSkinPath = ofd.FileName
        End If
    End Sub
    Private Sub btnSaveSkinDataToXml_Click(sender As Object, e As EventArgs) Handles btnSaveSkinDataToXml.Click
        MsgBox("Make sure you set all your properties!")
        Dim sfd As New SaveFileDialog With {.Title = "Export Skin To XML"}
        sfd.Filter = "Xml files (*.xml|*.xml"
        If sfd.ShowDialog = Windows.Forms.DialogResult.OK Then
            With SimpleSkin1
                .FormSkinPath = Me.FormSkinPath
                .Button1SkinPath = Me.Button1SkinPath
                .Button1MouseDownSkinPath = Me.Button1MouseDownSkinPath
                .Button1Location = Me.Button1Location
            End With
            SimpleSkin1.ToXMLFile(sfd.FileName)
        End If
    End Sub
    Private Sub btnLoadSkinFromXml_Click(sender As Object, e As EventArgs) Handles btnLoadSkinFromXml.Click
        Dim skin As CustomSkinningLibrary.SimpleSkin = Nothing
        Dim ofd As New OpenFileDialog With {.Title = "Import Skin From XML"}
        ofd.Filter = "Xml files (*.xml|*.xml"
        If ofd.ShowDialog = Windows.Forms.DialogResult.OK Then
            skin = SimpleSkin1.FromXMLFile(ofd.FileName)
        Else
            Exit Sub
        End If
        Dim form As CustomSkinningLibrary.SkinnableForm = CustomSkinningLibrary.SkinnableForm.Create(skin)
        form.Show()
    End Sub
End Class

Example Skin Library:

Namespace CustomSkinningLibrary
    <Serializable>
    Public Class SimpleSkin
        Public Property FormSkinPath As String = Nothing
        Public Property Button1SkinPath As String = Nothing
        Public Property Button1MouseDownSkinPath As String = Nothing
        Public Property Button1Location As Point = Nothing
        Public ReadOnly Property Complete As Boolean
            Get
                If FormSkinPath Is Nothing Then Return False
                If Button1SkinPath Is Nothing Then Return False
                If Button1MouseDownSkinPath Is Nothing Then Return False
                If Button1Location = Nothing Then Return False
                If Not System.IO.File.Exists(Me.FormSkinPath) Then Return False
                If Not System.IO.File.Exists(Me.Button1SkinPath) Then Return False
                If Not System.IO.File.Exists(Me.Button1MouseDownSkinPath) Then Return False
                Return True
            End Get
        End Property
        Public Sub ToXMLFile(Path As String)
            Dim serializer As New Xml.Serialization.XmlSerializer(GetType(SimpleSkin))
            Dim sb As New System.Text.StringBuilder
            Using s As New StringWriter(sb)
                serializer.Serialize(s, Me)
            End Using
            My.Computer.FileSystem.WriteAllText(Path, sb.ToString, False)
        End Sub
        Public Function FromXMLFile(Path As String) As SimpleSkin
            Dim serializer As New Xml.Serialization.XmlSerializer(GetType(SimpleSkin))
            Dim a As New StreamReader(Path)
            Dim obj As Object
            obj = serializer.Deserialize(a)
            Return DirectCast(obj, SimpleSkin)
        End Function
    End Class
    Public Class SkinnableForm
        Inherits Form
        Public WithEvents Button1 As SkinnableButton
        Private MySkin As Bitmap
        Protected Sub New()
        End Sub
        Protected Sub SetSkin(bmp As Bitmap)
            Dim g As Graphics = Me.CreateGraphics
            g.DrawImage(bmp, Me.ClientRectangle)
        End Sub
        Protected Overrides Sub OnPaint(pevent As PaintEventArgs)
            SetSkin(MySkin)
            Exit Sub
        End Sub
        Private Sub button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
            MsgBox("hello world!")
        End Sub
        Public Shared Function Create(skin As SimpleSkin) As SkinnableForm
            If skin.Complete Then
                Dim createdForm As New SkinnableForm
                Dim formImage As Image = Image.FromFile(skin.FormSkinPath)
                createdForm.Button1 = SkinnableButton.Create(skin)
                createdForm.Button1.Parent = createdForm
                createdForm.MySkin = CType(formImage, Bitmap)
                createdForm.Size = formImage.Size
                createdForm.SetSkin(createdForm.MySkin)
                createdForm.FormBorderStyle = Windows.Forms.FormBorderStyle.None
                Return createdForm
            Else
                Dim errors As New List(Of String)
                If skin.FormSkinPath Is Nothing Then errors.Add("Property 'SimpleSkin.FormBitmapPath' Is Nothing")
                If skin.Button1SkinPath Is Nothing Then errors.Add("Property 'SimpleSkin.Button1BitmapPath' Is Nothing")
                If skin.Button1MouseDownSkinPath Is Nothing Then errors.Add("Property 'SimpleSkin.Button1MouseDownBitmapPath' Is Nothing")
                If skin.Button1Location = Nothing Then errors.Add("Property 'SimpleSkin.Button1Location' Is Nothing")
                If Not System.IO.File.Exists(skin.FormSkinPath) Then errors.Add("Property 'SimpleSkin.Button1Location' Specified File Does Not Exist")
                If Not System.IO.File.Exists(skin.Button1SkinPath) Then errors.Add("Property 'SimpleSkin.Button1Location' Specified File Does Not Exist")
                If Not System.IO.File.Exists(skin.Button1MouseDownSkinPath) Then errors.Add("Property 'SimpleSkin.Button1Location' Specified File Does Not Exist")
                Throw New SkinIncompleteException(errors)
            End If
        End Function
    End Class
    Public Class SkinIncompleteException
        Inherits Exception
        Public Property Errors As List(Of String)
        Sub New(errors As List(Of String))
            Me.Errors = errors
        End Sub
    End Class
    Public Class SkinnableButton
        Inherits Button
        Private MySkin As Bitmap
        Private MouseDownSkin As Bitmap
        Private MouseIsDown As Boolean = False
        Protected Sub New()
            Me.SetStyle(ControlStyles.UserPaint, True)
        End Sub
        Protected Overrides Sub OnPaintBackground(pevent As PaintEventArgs)
            MyBase.OnPaintBackground(pevent)
        End Sub
        Protected Sub SetSkin(bmp As Bitmap, g As Graphics)
            Dim r As New Rectangle(New Point(0, 0), New Size(Me.Width, Me.Height))
            g.DrawImage(bmp, r)
        End Sub
        Protected Overrides Sub OnPaint(pevent As PaintEventArgs)
            Dim mouserect As New Rectangle(Me.PointToClient(MousePosition), New Size(1, 1))
            If MouseIsDown Then
                SetSkin(MouseDownSkin, pevent.Graphics)
            Else
                SetSkin(MySkin, pevent.Graphics)
            End If
            Exit Sub
        End Sub
        Protected Overrides Sub OnMouseDown(mevent As MouseEventArgs)
            Me.MouseIsDown = True
            MyBase.OnMouseDown(mevent)
        End Sub
        Protected Overrides Sub OnMouseUp(mevent As MouseEventArgs)
            Me.MouseIsDown = False
            MyBase.OnMouseUp(mevent)
        End Sub
        Public Shared Function Create(skin As SimpleSkin) As SkinnableButton
            Dim skBtn As New SkinnableButton()
            skBtn.Location = skin.Button1Location
            skBtn.MySkin = CType(Image.FromFile(skin.Button1SkinPath), Bitmap)
            skBtn.Size = skBtn.MySkin.Size
            skBtn.MouseDownSkin = CType(Image.FromFile(skin.Button1MouseDownSkinPath), Bitmap)
            Return skBtn
        End Function
    End Class
End Namespace
Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s