Friday, 27 January 2012

SQL SERVER BACKUP RESTORE

Introduction

I'm going to tell you something that's funny. As you know, you could install required prerequisites with Click Once. Framework, Windows Installer or SQL Server Express 2008. But, Visual Studio 2010 does not support restoring DB over SQL Server 2005 or 2008.
You could not find a complete reference guide about this issue on internet. There are many more articles regarding Click Once, Bootstrapper Manifest Generator or Setup Project. Here we go, I tell you step by step every part of this project in a single solution.
You will find the download link of this project at the top of the page. You can open it with VS 2010 to analyze the project. Setup.rar contains sample Database, Bootstrapper Manifest Generator setup and source code of this project.
OK. This is our roadmap:
  1. Backup SQL Database
  2. Create Class Library for connect to DB
  3. Create MSI Package that uses previously created Class Library
  4. Using Bootstrapper for prerequisites of VS 2010
  5. And distribution with Click Once

Backup SQL Database

  1. Open SQL Server Management Studio. Right click on Database and select Tasks - Back Up
    Backup_01.jpg
  2. In opened window, select Add - ... (three dots) and point backup directory. Write database name at File Name section. Example: HaliYikama.bak.
    Backup_02.jpg

Attention

I want to make a reminder for you. You will encounter a problem when you restore your database if you named it differently than the original database name. Please name your backup file as your database name.
Normally, you could choose any name for backup file (example: contains datetime) Because, you restore it on the same hard drive and location. But, in this project, remote machine location may be different than your backup location.

Bonus of the Day

These sections are not related to this project. I want to give you a Tip'n Trick!
What if you restore your DB to another location than backup file's original location?
Open SQL Query Analyzer and run this command:
restore FILELISTONLY from disk = 
'C:\Program Files\Microsoft SQL Server\MSSQL10.MSSQLSERVER\MSSQL\
Backup\VeritabaniBackup.bak'
It gives you LogicalName of database in the first column. And replace your LogicalName with bold section in the below statement:
restore database Veritabanim
from disk = 'C:\Program Files\Microsoft SQL Server\
MSSQL10.MSSQLSERVER\MSSQL\Backup\HaliOtomasyonu.bak'
with move 'LogicalName' to 'C:\Program Files\
Microsoft SQL Server\MSSQL10.MSSQLSERVER\MSSQL\Backup\Test\Veritabanim.mdf',
move 'LogicalName_log' to 'C:\Program Files\
Microsoft SQL Server\MSSQL10.MSSQLSERVER\MSSQL\Backup\Test\Veritabanim_log.ldf'

Create Class Library for Connect to DB

This project aims to connect to DB and restore it.
  1. Firstly, we should an empty solution for all projects. Open Visual Studio 2010, select New Project. From left side, select Other Project Types - Visual Studio Solutions. Write ClickOnceSQL at Name section.
    Class_01.jpg
  2. Now, we are creating Class Library. Right click on the solution that we just created and select Add - New Project.
    Class_02.jpg
    In opened window, select Visual C# - Class Library and name it as CreatingDB.
    Class_03.jpg
  3. Delete Class1.cs file that is automatically created by the project. And add installer class like this: Right click on project, select Add - New Item. Afterwards, select Visual C# Items - Installer Class, and name itInstaller1.cs. Finally press Add button to complete this section.
    Class_04.jpg
  4. Click to "click here to switch to code view" statement to go codebehind.
OK. Now, I want to tell something about SMO object.The Microsoft.SqlServer.Management.Smonamespace contains classes that represent the core SQL Server Database Engine objects. These include instances, databases, tables, stored procedures, and views.
Most of the classes Microsoft.SqlServer.Management.Smo namespace resides in theMicrosoft.SqlServer.Smo.dll and Microsoft.SqlServer.SmoExtended.dll files. Additionally, some of the enumeration classes are in the Microsoft.SqlServer.SqlEnum.dll and Microsoft.SqlServer.SmoEnum.dll assembly files. You will have to import all four files to access all of the classes in the Microsoft.SqlServer.Management.Smonamespace.
By using the Microsoft.SqlServer.Management.Smo namespace, you can do the following:
  • Connect to an instance of the SQL Server Database Engine.
  • View and modify instance settings and configuration options.
  • View and modify database objects.
  • Perform DDL (data definition language) tasks on an instance of the SQL Server Database Engine.
  • Script databases dependencies.
  • Perform database maintenance tasks, such as backup and restore operations.
  • Transfer database schemas and data.
Add these assemblies to your project as reference. You could find these objects at C:\Program Files\Microsoft SQL Server\100\SDK\Assemblies location.
  • Microsoft.SqlServer.ConnectionInfo
  • Microsoft.SqlServer.Management.Sdk.Sfc
  • Microsoft.SqlServer.Smo
  • Microsoft.SqlServer.SmoExtended
In addition, you should add System.Windows.Forms as reference (in the .NET section).
Here we go:
using System.Data.SqlClient;
using System.IO;
using System.Security.AccessControl;
using System.Windows.Forms;
using Microsoft.SqlServer.Management.Common;
using Microsoft.SqlServer.Management.Smo;

namespace CreatingDB
{
[RunInstaller(true)]
public partial class Installer1 : System.Configuration.Install.Installer
{
public Installer1()
{
InitializeComponent();
}

public void RestoreDatabase(String databaseName, String filePath, String serverName,
    String userName, String password, String dataFilePath, String logFilePath)
{
// Create Restore instance
Restore sqlRestore = new Restore();

// Point to database
BackupDeviceItem deviceItem = new BackupDeviceItem(filePath, DeviceType.File);
sqlRestore.Devices.Add(deviceItem);
sqlRestore.Database = databaseName;

// Connect to DB Server
ServerConnection connection;

if (userName == "") // for Windows Authentication
{
SqlConnection sqlCon = new SqlConnection(@"Data Source=" + serverName + @"; 
Integrated Security=True;");
connection = new ServerConnection(sqlCon);
}
else // for Server Authentication
connection = new ServerConnection(serverName, userName, password);

// Restoring
Server sqlServer = new Server(connection);
Database db = sqlServer.Databases[databaseName];
sqlRestore.Action = RestoreActionType.Database;
String dataFileLocation = dataFilePath + databaseName + ".mdf";
String logFileLocation = logFilePath + databaseName + "_Log.ldf";
db = sqlServer.Databases[databaseName];
RelocateFile rf = new RelocateFile(databaseName, dataFileLocation);
sqlRestore.RelocateFiles.Add(new RelocateFile(databaseName, dataFileLocation));
sqlRestore.RelocateFiles.Add(new RelocateFile(databaseName + "_log", logFileLocation));
sqlRestore.ReplaceDatabase = true;
sqlRestore.PercentCompleteNotification = 10;

try
{
sqlRestore.SqlRestore(sqlServer);
}
catch (Exception ex)
{
MessageBox.Show(ex.InnerException.ToString());
}

db = sqlServer.Databases[databaseName];
db.SetOnline();
sqlServer.Refresh();
}

public override void Commit(System.Collections.IDictionary savedState)
{
// Required permission
try
{
DirectorySecurity dirSec = Directory.GetAccessControl(Context.Parameters["TargetDir"]);
FileSystemAccessRule fsar = new FileSystemAccessRule
(@"NT AUTHORITY\NETWORK SERVICE"
, FileSystemRights.FullControl
, InheritanceFlags.ContainerInherit | InheritanceFlags.ObjectInherit
, PropagationFlags.None
, AccessControlType.Allow);
dirSec.AddAccessRule(fsar);
Directory.SetAccessControl(Context.Parameters["TargetDir"], dirSec);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}

// Parameters that comes from setup project (CreatingDBSetup) 
RestoreDatabase(Context.Parameters["databaseName"].ToString(), 
    Context.Parameters["filePath"].ToString(), Context.Parameters
["serverName"].ToString(), Context.Parameters["userName"].ToString(), 
    Context.Parameters["password"].ToString(), Context.Parameters
["dataFilePath"].ToString(), Context.Parameters["logFilePath"].ToString());

base.Commit(savedState);
}
}
}

Create MSI Package that Uses Previously Created Class Library

We completed the hard section above. Now, we package it as MSI to distribute it.
Right click on solution (ClickOnceSQL), select Add - New Project. In opened window, select Other Project Types - Setup and Deployment - Visual Studio Installer - Setup Project'. Name it CreatingDBSetup and click OK to complete it..
MSI-Setup-01.jpg
You will see File System (CreatingDBSetup) tab when you newly opened page. If not, select File System Editor icon at top of Solution Explorer.
MSI-Setup-00.jpg
Right click on Application Folder and select Add - Project Output.
MSI-Setup-01.jpg
You will see the CreatingDB and Primary Output already selected. Don't touch that. Click OK to apply settings.
MSI-Setup-02.jpg
Required dependencies are loaded automatically as you see in the below picture. Now, we should add database backup file. Right click on empty area in panel, select Add - File.
MSI-Setup-03.jpg
Point to database backup file (HaliYikama.bak).
MSI-Setup-04.jpg
Next, we create a folder for restored DBs .mdf and .ldf. Right click on Application Folder, select Add - Folder and name it Database. Afterwards, click Database folder, open properties window (F4), and set AlwaysCreateproperty to True.
MSI-Setup-05.jpg
Next, it's time to pass parameters to Installer class (Installer1.cs) in the previous project named CreatingDB. These parameters go to method name public override void Commit section as Context.Parameters. These parameters contains server name, dbname and so on.
OK. When you one click CreatingDBSetup in Solution Explorer, you will see Custom Actions Editor at top of Solution Explorer. Click on it. You will see Custom Actions (CreatingDBSetup) pane at the left side of page.
MSI-Setup-06.jpg
As you will see, there are four folders named Install, Commit, Rollback, Uninstall. We repeat these actions below for each folder. Right click on Install folder, select Add Custom Action. Click Application Folder to get in. Select Primary output from CreatingDB (Active) and click OK to apply settings.
MSI-Setup-07.jpg
You will see Primary output from CreatingDB (Active) section under the Install folder. Click once to it and press F4 to view its Properties. There is CustomActionData property in the third row. Write the followingstring:
/TargetDir="[TARGETDIR]\" /databaseName="HaliYikama" 
/filePath="[TARGETDIR]HaliYikama.bak" /serverName=".\SQLEXPRESS" 
/userName="" /password="" /dataFilePath="[TARGETDIR]Database\\" 
/logFilePath="[TARGETDIR]Database\\"
MSI-Setup-08.jpg
OK. Now, the first section is completed. Repeat these steps again for the remaining 3 folders. (Commit, Rollback,Uninstall)

Attention

Installation order of prerequisites is a very important issue for this project. Because, if you install Restore DB setup before installation of SQL Server 2008, you will fail. Therefore, I will tell you some critical configuration at the end of the fourth section below under "Attention" tag.

Using Bootstrapper for Prerequisites of VS 2010

We use BMG for extending Visual Studio 2010 prerequisites section capacity. The Setup program is a generic installer that can be configured to detect and install redistributable components such as Windows Installer (.msi) files and executable programs. The installer is also known as a bootstrapper. It is programmed through a set of XML manifests that specify the metadata to manage the installation of the component. The bootstrapper first detects whether any of the prerequisites are already installed. If prerequisites are not installed, first the bootstrapper shows the license agreements. Second, after the end-user accepts the license agreements, the installation begins for the prerequisites. Otherwise, if all the prerequisites are detected, the bootstrapper just starts the application installer.
You can download BMG from http://code.msdn.microsoft.com/bmg/Release/ProjectReleases.aspx?ReleaseId=1567. Click BMG2008Setup.msi to start download. Install it after download. You can see the program's shortcut on desktop. Open the program. Select File-New-Package Manifest and click OK to close window.
Package-01.jpg
Set Project Name to Restore DB in opened window Also Product Code automatically filled. Don't touch.
Click an icon named Add Install File that stays top left of screen. Select Browse and point to Release version of your MSI package (example: D:\My Projects\Visual Studio 2010\ClickOnceSQL\CreatingDBSetup\Release). Click OK to complete this section.
Package-02.jpg
Write Restore DB to Display Name section. Afterwards, click Build icon that stays as second icon on top of screen. Build process completed in newly opened window.
Package-03.jpg
You can see built package location at the top of newly opened window. C:\Users\Ferhat\Documents\Restore DBis my location for this sample project. Open this location, go one level up, and copy entire (Restore DB) folder. Then paste into C:\Program Files (x86)\Microsoft SDKs\Windows\v7.0A\Bootstrapper\Packages folder to let VS 2010 to recognize new prerequisite. Well, we complete Bootstrapper operation. Restart VS 2010 to work with new prerequisite.

Attention

Now, we are in the middle of the most critical configuration phase. Please read carefully and implement it as I said.
The installation order of prerequisites is a very important issue for this deployment project. But also VS 2010 does not support installation order with its user interface. We make it with some XML changes.
Firstly, we should understand the XML form of product.xml. Open SqlExpress2008 folder under C:\Program Files (x86)\Microsoft SDKs\Windows\v7.0A\Bootstrapper\Packages\. You will see product.xml file. Open it and review carefully:
<?xml version="1.0" encoding="utf-8"?>
<!-- SQL Express 2008 VS Bootstrapper : product.xml : Language neutral information -->
<Product xmlns=http://schemas.microsoft.com/developer/2004/01/bootstrapper 
 ProductCode="Microsoft.Sql.Server.Express.10.0">
<RelatedProducts>
<EitherProducts>
<DependsOnProduct Code=".NETFramework,Version=v4.0" />
<DependsOnProduct Code="Microsoft.Net.Framework.3.5.SP1" />
</EitherProducts>
<DependsOnProduct Code="Microsoft.Windows.Installer.4.5" />
<IncludesProduct Code="Microsoft.Sql.Server.Express.1.0" />
<IncludesProduct Code="Microsoft.Sql.Server.Express.9.2" />
</RelatedProducts>
<PackageFiles>
<PackageFile Name="SqlExpressChk.exe" />
</PackageFiles>
</Product>
There is DependsOnProduct node that defines the required installation. Code property points toProductCode which resides in Product node at top of XML file. Example:
<Product xmlns=http://schemas.microsoft.com/developer/2004/01/bootstrapper 
 ProductCode="Microsoft.Sql.Server.Express.10.0">
OK. Open your product.xml (C:\Program Files (x86)\Microsoft SDKs\Windows\v7.0A\Bootstrapper\Packages\Restore DB) and add the following nodes:
<RelatedProducts>
<DependsOnProduct Code="Microsoft.Sql.Server.Express.10.0" />
</RelatedProducts>
</Product>
Finally, the product.xml file should be:
<?xml version="1.0" encoding="utf-8"?>
<Product ProductCode="Restore.DB" 
xmlns="http://schemas.microsoft.com/developer/2004/01/bootstrapper">
<PackageFiles CopyAllPackageFiles="false">
<PackageFile Name="creatingdbsetup.msi" Hash="31E40D0C5D7BCA642E412EABF16E4C381EB7ADEB" />
</PackageFiles>
<Commands Reboot="Defer">
<Command PackageFile="creatingdbsetup.msi">
<ExitCodes>
<DefaultExitCode Result="Success" String="Anunexpectedexitcodewasr" 
FormatMessageFromSystem="true" />
</ExitCodes>
</Command>
</Commands>
<RelatedProducts>
<DependsOnProduct Code="Microsoft.Sql.Server.Express.10.0" />
</RelatedProducts>
</Product>
Please be careful !
Your installation has failed if your product.xml is not the same as above. If you don't change product.xml as above or leave it as original, your published "Click Once" screen become like this:
Prerequisites-Order-01.jpg
As you see Restore DB stays before than SQL Server 2008 Express installation. After product.xml configuration, our ClickOnce publish screen becomes like this:
Prerequisites-Order-02.jpg
Yeap! This is what we wait for. :)

And Distribution with Click Once

I will mention about ADO.NET Entity Data Model and WPF Windows Application in this section. But these concepts do not belong to this article. Briefly, I want to make a windows application that connects local database with data layer. You may choose Windows Forms rather than WPF, and use LINQ to SQL Classes rather than ADO.NET Entity Framework.
I add new project named CreatingDB.Data as Class Library to solution. This is my data layer for WPF application. Afterwards, I add ADO.NET Entity Data Model to connect local DB with Windows Authentication.
Now, there just remains one project to add. I add another project named CreatingDB.WPF as WPF Application to solution. This project uses CreatingDB.Data to connect DB. Thereby I add CreatingDB.Data as reference using Add Reference-Project selection.
Also, I should add System.Data.Entity namespace with Add Reference - .NET to work with Entity Framework. Finally, I add Application Configuration File (App.config) to WPF project for overridingCreatingDB.Data connection string. And copy CreatingDB.Data App.Config content toCreatingDB.WPF App.Config.
OK. It's time to design the user interface.
Here is my WPF form design section:
<DataGrid AutoGenerateColumns="False" Height="80" 
 HorizontalAlignment="Left" Margin="35,70,0,0" Name="dataGrid1" 
VerticalAlignment="Top" Width="323" ItemsSource="{Binding}" >

<DataGrid.Columns>
<DataGridTextColumn Header="Adi" Width="135" Binding="{Binding Name}" IsReadOnly="True"/>
<DataGridTextColumn Header="Adresi" Width="135" 
 Binding="{Binding Address}" IsReadOnly="True"/>
</DataGrid.Columns>
</DataGrid>
This is codebehind section:
private void button1_Click(object sender, RoutedEventArgs e)
{
try
{
CreatingDB.Data.HaliYikamaEntities context = new Data.HaliYikamaEntities();
dataGrid1.DataContext = context.CustomerSet;
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
Right click on WPF project and select "Set as StartUp Project", then compile it. Check it out for errors. If there is no error, you will see records when you click on the button.

Attention

In your development machine SQL Server Enterprise or Developer edition your connection string should be changed because client machine always uses SQL Server Express edition in this "Click Once" project. Look at your connection string at App.config file that resides under WPF project.
Replace Data Source=.\; statement with Data Source=.\SQLEXPRESS;
Finally, your WPF (Windows App) App.config file should be:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<connectionStrings>
<add name="HaliYikamaEntities" connectionString= 
"metadata=res://*/Model1.csdl|res://*/Model1.ssdl|res://*/Model1.msl;
provider=System.Data.SqlClient;provider connection string="Data Source=.\SQLEXPRESS;
Initial Catalog=HaliYikama;Integrated Security=True;
MultipleActiveResultSets=True"" providerName="System.Data.EntityClient" />
</connectionStrings>
</configuration>
Next, we will publish our project. Please reset your Visual Studio to recognize new prerequisite (Restore DB), right click on WPF project. Select Properties, switch to Publish tab, click to Prerequisites button. You will see Restore DB, select it, and also SQL Server 2008 Express. There is another checked box selected by system: Microsoft. .NET Framework 4 Client Profile(x86 and x64) and Windows Installer. Don't touch. As a result, there are 4 prerequisites selected. Click OK to apply settings.
Prerequisites-01.jpg
Click Updates button and select "The application should check for updates" check box to inform user when you update your application. Click OK to apply your choice.
Prerequisites-02.jpg
Next, click Publish Wizard that stays end of page to publish your project. Select Browse and point your newly created empty directory (example: publish):
Publish-01.jpg
Click Next, your question is "How will users install the application" We prefer a website (that provides us easy updatable application):
Publish-02.jpg
Click Next, the question is "Will the application be available offline?" We choose "Yes".
Publish-03.jpg
Click FINISH to complete publish process. Upload your "publish" folder to your web site via FTP. Now, you can install your published project like this: http://www.fkaratas.com/app/publish.htm.
You will see Install button when you open up the URL. Please click it to let it begin. Also select Run when message box appears.
Prerequisites-Order-02.jpg
Here is the installation order. First screen is Microsoft .NET Framework 4 Client Profile (x86 and x64), Click "Accept" to continue. (If you have .NET Framework, you will not see this screen):
Installation-01.jpg
My test machine is XP Service Pack 3. Thereby Windows Installer 3.1 installation screens does not appear for me. As you know, it is Click Once deployment concept.
Second screen is SQL Server 2008 Express, please click "Accept" to continue.
Installation-02.jpg
Last screen is "Restore DB". Select Install to continue.
Installation-03.jpg
Now, downloading is beginning...
Installation-04.jpg
Installation is automatically started when downloading finishes. After NET Framework installation is complete, machine prompts you to restart your PC. Please select Yes to continue installing.
Installation-05.jpg
After restart SQL Server 2008 Express and Restore DB installation begins.
Installation-06.jpg
When all prerequisites are completed, our windows application starts up.
I hope this article will be useful for developers who need it.

vb.net theme - CODE

                                                                     
                                                                     
                                                                     
                                             
'Creator: Aeonhack
'Date: 3/18/2011
'Site: www.elitevs.net
'Version: 1.0
'This is a recreation of the ModernTheme.

Imports System.Drawing.Drawing2D

MustInherit Class Theme
    Inherits ContainerControl

#Region " Initialization "

    Protected B As Bitmap, G As Graphics
    Sub New()
        SetStyle(DirectCast(8198, ControlStyles), True)
        B = New Bitmap(1, 1)
        G = Graphics.FromImage(B)
    End Sub

    Private ParentIsForm As Boolean
    Protected Overrides Sub OnHandleCreated(ByVal e As EventArgs)
        Dock = DockStyle.Fill
        ParentIsForm = TypeOf Parent Is Form
        If ParentIsForm Then ParentForm.FormBorderStyle = FormBorderStyle.None
        MyBase.OnHandleCreated(e)
    End Sub

#End Region

#Region " Sizing and Movement "

    Private _Resizable As Boolean = True
    Property Resizable() As Boolean
        Get
            Return _Resizable
        End Get
        Set(ByVal value As Boolean)
            _Resizable = value
        End Set
    End Property

    Private _MoveHeight As Integer = 24
    Property MoveHeight() As Integer
        Get
            Return _MoveHeight
        End Get
        Set(ByVal v As Integer)
            _MoveHeight = v
            Header = New Rectangle(7, 7, Width - 14, _MoveHeight)
        End Set
    End Property

    Private Flag As IntPtr
    Protected Overrides Sub OnMouseDown(ByVal e As MouseEventArgs)
        If Not e.Button = MouseButtons.Left Then Return
        If ParentIsForm Then If ParentForm.WindowState = FormWindowState.Maximized Then Return

        If Header.Contains(e.Location) Then
            Flag = New IntPtr(2)
        ElseIf Current.Position = 0 Or Not _Resizable Then
            Return
        Else
            Flag = New IntPtr(Current.Position)
        End If

        Capture = False
        DefWndProc(Message.Create(Parent.Handle, 161, Flag, Nothing))

        MyBase.OnMouseDown(e)
    End Sub

    Private Structure Pointer
        ReadOnly Cursor As Cursor, Position As Byte
        Sub New(ByVal c As Cursor, ByVal p As Byte)
            Cursor = c
            Position = p
        End Sub
    End Structure

    Private F1, F2, F3, F4 As Boolean, PTC As Point
    Private Function GetPointer() As Pointer
        PTC = PointToClient(MousePosition)
        F1 = PTC.X < 7
        F2 = PTC.X > Width - 7
        F3 = PTC.Y < 7
        F4 = PTC.Y > Height - 7

        If F1 And F3 Then Return New Pointer(Cursors.SizeNWSE, 13)
        If F1 And F4 Then Return New Pointer(Cursors.SizeNESW, 16)
        If F2 And F3 Then Return New Pointer(Cursors.SizeNESW, 14)
        If F2 And F4 Then Return New Pointer(Cursors.SizeNWSE, 17)
        If F1 Then Return New Pointer(Cursors.SizeWE, 10)
        If F2 Then Return New Pointer(Cursors.SizeWE, 11)
        If F3 Then Return New Pointer(Cursors.SizeNS, 12)
        If F4 Then Return New Pointer(Cursors.SizeNS, 15)
        Return New Pointer(Cursors.Default, 0)
    End Function

    Private Current, Pending As Pointer
    Private Sub SetCurrent()
        Pending = GetPointer()
        If Current.Position = Pending.Position Then Return
        Current = GetPointer()
        Cursor = Current.Cursor
    End Sub

    Protected Overrides Sub OnMouseMove(ByVal e As MouseEventArgs)
        If _Resizable Then SetCurrent()
        MyBase.OnMouseMove(e)
    End Sub

    Protected Header As Rectangle
    Protected Overrides Sub OnSizeChanged(ByVal e As EventArgs)
        Header = New Rectangle(7, 7, Width - 14, _MoveHeight)
        G.Dispose()
        B.Dispose()
        B = New Bitmap(Width, Height)
        G = Graphics.FromImage(B)
        Invalidate()
        MyBase.OnSizeChanged(e)
    End Sub

#End Region

#Region " Convienence "

    Sub SetTransparent(ByVal c As Color)
        If ParentIsForm Then ParentForm.TransparencyKey = c
    End Sub

    Protected MustOverride Overrides Sub OnPaint(ByVal e As PaintEventArgs)

    Sub DrawCorners(ByVal c As Color, ByVal rect As Rectangle)
        B.SetPixel(rect.X, rect.Y, c)
        B.SetPixel(rect.X + (rect.Width - 1), rect.Y, c)
        B.SetPixel(rect.X, rect.Y + (rect.Height - 1), c)
        B.SetPixel(rect.X + (rect.Width - 1), rect.Y + (rect.Height - 1), c)
    End Sub

    Sub DrawBorders(ByVal p1 As Pen, ByVal p2 As Pen, ByVal rect As Rectangle)
        G.DrawRectangle(p1, rect.X, rect.Y, rect.Width - 1, rect.Height - 1)
        G.DrawRectangle(p2, rect.X + 1, rect.Y + 1, rect.Width - 3, rect.Height - 3)
    End Sub

    Private TextSize As Size
    Sub DrawText(ByVal a As HorizontalAlignment, ByVal b As Brush, Optional ByVal offset As Integer = 0)
        If String.IsNullOrEmpty(Text) Then Return
        TextSize = G.MeasureString(Text, Font).ToSize

        Select Case a
            Case HorizontalAlignment.Left
                G.DrawString(Text, Font, b, 5 + offset, _MoveHeight \ 2 - TextSize.Height \ 2 + 7)
            Case HorizontalAlignment.Right
                G.DrawString(Text, Font, b, Width - 5 - TextSize.Width - offset, _MoveHeight \ 2 - TextSize.Height \ 2 + 7)
            Case HorizontalAlignment.Center
                G.DrawString(Text, Font, b, Width \ 2 - TextSize.Width \ 2, _MoveHeight \ 2 - TextSize.Height \ 2 + 7)
        End Select
    End Sub

    ReadOnly Property ImageWidth As Integer
        Get
            If _Image Is Nothing Then Return 0
            Return _Image.Width
        End Get
    End Property

    Private _Image As Image
    Public Property Image() As Image
        Get
            Return _Image
        End Get
        Set(ByVal value As Image)
            _Image = value
            Invalidate()
        End Set
    End Property

    Sub DrawIcon(ByVal a As HorizontalAlignment, Optional ByVal offset As Integer = 0)
        If _Image Is Nothing Then Return
        Select Case a
            Case HorizontalAlignment.Left
                G.DrawImage(_Image, 5 + offset, _MoveHeight \ 2 - _Image.Height \ 2 + 7)
            Case HorizontalAlignment.Right
                G.DrawImage(_Image, Width - 5 - TextSize.Width - offset, _MoveHeight \ 2 - TextSize.Height \ 2 + 7)
            Case HorizontalAlignment.Center
                G.DrawImage(_Image, Width \ 2 - TextSize.Width \ 2, _MoveHeight \ 2 - TextSize.Height \ 2 + 7)
        End Select
    End Sub
#End Region

End Class
MustInherit Class ThemeControl
    Inherits Control

#Region " Initialization "

    Protected B As Bitmap, G As Graphics
    Sub New()
        SetStyle(DirectCast(8198, ControlStyles), True)
        B = New Bitmap(1, 1)
        G = Graphics.FromImage(B)
    End Sub

    Sub AllowTransparent()
        SetStyle(ControlStyles.Opaque, False)
        SetStyle(DirectCast(141314, ControlStyles), True)
    End Sub

#End Region

#Region " Mouse Handling "

    Enum State As Byte
        MouseNone = 0
        MouseOver = 1
        MouseDown = 2
    End Enum

    Protected MouseState As State
    Protected Overrides Sub OnMouseLeave(ByVal e As EventArgs)
        ChangeMouseState(State.MouseNone)
        MyBase.OnMouseLeave(e)
    End Sub
    Protected Overrides Sub OnMouseEnter(ByVal e As EventArgs)
        ChangeMouseState(State.MouseOver)
        MyBase.OnMouseEnter(e)
    End Sub
    Protected Overrides Sub OnMouseUp(ByVal e As MouseEventArgs)
        ChangeMouseState(State.MouseOver)
        MyBase.OnMouseUp(e)
    End Sub
    Protected Overrides Sub OnMouseDown(ByVal e As MouseEventArgs)
        If e.Button = MouseButtons.Left Then ChangeMouseState(State.MouseDown)
        MyBase.OnMouseDown(e)
    End Sub

    Private Sub ChangeMouseState(ByVal e As State)
        MouseState = e
        Invalidate()
    End Sub

#End Region

#Region " Sizing "

    Protected Overrides Sub OnSizeChanged(ByVal e As EventArgs)
        G.Dispose()
        B.Dispose()
        B = New Bitmap(Width, Height)
        G = Graphics.FromImage(B)
        Invalidate()
        MyBase.OnSizeChanged(e)
    End Sub

#End Region

#Region " Convienence "

    Protected MustOverride Overrides Sub OnPaint(ByVal e As PaintEventArgs)

    Sub DrawCorners(ByVal c As Color, ByVal rect As Rectangle)
        B.SetPixel(rect.X, rect.Y, c)
        B.SetPixel(rect.X + (rect.Width - 1), rect.Y, c)
        B.SetPixel(rect.X, rect.Y + (rect.Height - 1), c)
        B.SetPixel(rect.X + (rect.Width - 1), rect.Y + (rect.Height - 1), c)
    End Sub

    Sub DrawBorders(ByVal p1 As Pen, ByVal p2 As Pen, ByVal rect As Rectangle)
        G.DrawRectangle(p1, rect.X, rect.Y, rect.Width - 1, rect.Height - 1)
        G.DrawRectangle(p2, rect.X + 1, rect.Y + 1, rect.Width - 3, rect.Height - 3)
    End Sub

    Private TextSize As Size
    Sub DrawText(ByVal a As HorizontalAlignment, ByVal b As Brush, Optional ByVal offset As Integer = 0)
        If String.IsNullOrEmpty(Text) Then Return
        TextSize = G.MeasureString(Text, Font).ToSize

        Select Case a
            Case HorizontalAlignment.Left
                G.DrawString(Text, Font, b, 5 + offset, Height \ 2 - TextSize.Height \ 2)
            Case HorizontalAlignment.Right
                G.DrawString(Text, Font, b, Width - 5 - TextSize.Width - offset, Height \ 2 - TextSize.Height \ 2)
            Case HorizontalAlignment.Center
                G.DrawString(Text, Font, b, Width \ 2 - TextSize.Width \ 2, Height \ 2 - TextSize.Height \ 2)
        End Select
    End Sub

    ReadOnly Property ImageWidth As Integer
        Get
            If _Image Is Nothing Then Return 0
            Return _Image.Width
        End Get
    End Property

    Private _Image As Image
    Public Property Image() As Image
        Get
            Return _Image
        End Get
        Set(ByVal value As Image)
            _Image = value
            Invalidate()
        End Set
    End Property

    Sub DrawIcon(ByVal a As HorizontalAlignment, Optional ByVal offset As Integer = 0)
        If _Image Is Nothing Then Return
        Select Case a
            Case HorizontalAlignment.Left
                G.DrawImage(_Image, Width \ 10 + offset, Height \ 2 - _Image.Height \ 2)
            Case HorizontalAlignment.Right
                G.DrawImage(_Image, Width - (Width \ 10) - TextSize.Width - offset, Height \ 2 - TextSize.Height \ 2)
            Case HorizontalAlignment.Center
                G.DrawImage(_Image, Width \ 2 - TextSize.Width \ 2, Height \ 2 - TextSize.Height \ 2)
        End Select
    End Sub

#End Region

End Class

Class GTheme
    Inherits Theme

    Sub New()
        MoveHeight = 28
        ForeColor = Color.FromArgb(100, 100, 100)
        SetTransparent(Color.Fuchsia)

        C1 = Color.FromArgb(41, 41, 41)
        C2 = Color.FromArgb(25, 25, 25)

        P1 = New Pen(Color.FromArgb(58, 58, 58))
        P2 = New Pen(C2)
    End Sub

    Private C1, C2 As Color
    Private P1, P2 As Pen
    Private B1 As LinearGradientBrush
    Private R1 As Rectangle

    Protected Overrides Sub OnPaint(ByVal e As PaintEventArgs)
        G.Clear(C1)

        R1 = New Rectangle(0, 0, Width, 28)
        B1 = New LinearGradientBrush(R1, C2, C1, LinearGradientMode.Vertical)
        G.FillRectangle(B1, R1)

        G.DrawLine(P2, 0, 28, Width, 28)
        G.DrawLine(P1, 0, 29, Width, 29)

        DrawText(HorizontalAlignment.Left, New SolidBrush(ForeColor), ImageWidth)
        DrawIcon(HorizontalAlignment.Left)

        DrawBorders(Pens.Black, P1, ClientRectangle)
        DrawCorners(Color.Fuchsia, ClientRectangle)

        e.Graphics.DrawImage(B, 0, 0)
    End Sub
End Class

Class GButton
    Inherits ThemeControl

    Private P1, P2 As Pen
    Private B1 As LinearGradientBrush
    Private C1, C2 As Color
    Private R1 As Rectangle

    Sub New()
        AllowTransparent()
        BackColor = Color.FromArgb(41, 41, 41)
        ForeColor = Color.FromArgb(100, 100, 100)

        P1 = New Pen(Color.FromArgb(25, 25, 25))
        P2 = New Pen(Color.FromArgb(11, Color.White))

        C1 = Color.FromArgb(41, 41, 41)
        C2 = Color.FromArgb(51, 51, 51)
    End Sub

    Protected Overrides Sub OnPaint(ByVal e As System.Windows.Forms.PaintEventArgs)

        If MouseState = State.MouseDown Then
            B1 = New LinearGradientBrush(ClientRectangle, C1, C2, LinearGradientMode.Vertical)
        Else
            B1 = New LinearGradientBrush(ClientRectangle, C2, C1, LinearGradientMode.Vertical)
        End If

        G.FillRectangle(B1, ClientRectangle)

        DrawText(HorizontalAlignment.Center, New SolidBrush(ForeColor))
        DrawIcon(HorizontalAlignment.Left)

        DrawBorders(P1, P2, ClientRectangle)
        DrawCorners(BackColor, ClientRectangle)

        e.Graphics.DrawImage(B, 0, 0)
    End Sub
End Class

Class Seperator
    Inherits ThemeControl

    Sub New()
        AllowTransparent()
        BackColor = Color.Transparent
    End Sub

    Private _Direction As Orientation
    Property Direction() As Orientation
        Get
            Return _Direction
        End Get
        Set(ByVal value As Orientation)
            _Direction = value
            Invalidate()
        End Set
    End Property

    Private _Color1 As Color = Color.FromArgb(90, Color.Black)
    Public Property Color1() As Color
        Get
            Return _Color1
        End Get
        Set(ByVal value As Color)
            _Color1 = value
            Invalidate()
        End Set
    End Property

    Private _Color2 As Color = Color.FromArgb(14, Color.White)
    Public Property Color2() As Color
        Get
            Return _Color2
        End Get
        Set(ByVal value As Color)
            _Color2 = value
            Invalidate()
        End Set
    End Property

    Private R1 As Rectangle
    Private B1 As LinearGradientBrush
    Private Rotation As Integer

    Protected Overrides Sub OnPaint(ByVal e As PaintEventArgs)
        G.Clear(BackColor)

        If _Direction = Orientation.Horizontal Then
            G.DrawLine(New Pen(_Color1), 0, Height \ 2, Width, Height \ 2)
            G.DrawLine(New Pen(_Color2), 0, Height \ 2 + 1, Width, Height \ 2 + 1)
        Else
            G.DrawLine(New Pen(_Color1), Width \ 2, 0, Width \ 2, Height)
            G.DrawLine(New Pen(_Color2), Width \ 2 + 1, 0, Width \ 2 + 1, Height)
        End If

        e.Graphics.DrawImage(B, 0, 0)
    End Sub
End Class