Welcome to RenEvo Sign in | Join | Help

Loading Config Files from non-Default Locations

I have spent a good deal of my time trying to figure out how to load a *.config file that is compatable with the System.Configuration.Configuration object. Over the years I have done several "fake" .config files, which where nothing more then glorified structured xml files that I manually load.  I have also even created fake 0 byte files so I could load the .config using the OpenExeConfiguration.

Until recently, this has been a viable solution, and since these files where mostly hidden by ClickOnce installations and storing them in the Application Data file structure. Now though, I got a hair up my butt to do it correctly.

Enter the System.Configuration.ExeConfigurationFileMap class.  This lets you specify an exe config file name, and allows you to load it up at runtime without having a valid executable to load off of.  So now, I can save logged in specific users along with windows users configuration files. 

For example, my goal was to do the following:
Allow the user to setup connections and save them to the Application Data\Product\ folder under their user account.
After authenticating with the server, I want to store per-logged in user settings.

Just short of using datasets and/or serialized objects, I was using folders to seperate between the different logged in users.

Now, to the solution:

   1:  Imports System.Configuration
   2:   
   3:  Public Class LocalConfiguration
   4:   
   5:      Public Shared Function GetConfig(ByVal Path As String) As Configuration
   6:          Dim retVal As Configuration = Nothing
   7:   
   8:          Dim configFileMap As New ExeConfigurationFileMap()
   9:          configFileMap.ExeConfigFilename = Path
  10:   
  11:          retVal = ConfigurationManager.OpenMappedExeConfiguration(configFileMap, ConfigurationUserLevel.None)
  12:   
  13:          Return retVal
  14:      End Function
  15:   
  16:  End Class

That is it, now you can simply load a valid application config file and get a System.Configuration.Configuration object back.  All it takes is the following line of code:

   1:          Dim config As Configuration = LocalConfiguration.GetConfig("./data/user.config")

The beuty is, the file doesn't even have to exist. When you save the config, it will automatically create it if it doesn't exist.

The next step, which turned out to be a bit tricky, was actually saving stuff, like appSettings, to the file, specifically if they didn't exist to begin with.

   1:      Private Sub LoadSettings()
   2:          Dim config As Configuration = LocalConfiguration.GetConfig("./data/user.config")
   3:          Dim newSection As AppSettingsSection = Nothing
   4:   
   5:          If config.Sections("MySettings") Is Nothing Then
   6:              newSection = New AppSettingsSection()
   7:              newSection.SectionInformation.AllowExeDefinition = ConfigurationAllowExeDefinition.MachineToLocalUser
   8:              config.Sections.Add("MySettings", newSection)
   9:          Else
  10:              newSection = config.Sections("MySettings")
  11:          End If
  12:   
  13:          If newSection.Settings("Text") Is Nothing Then
  14:              newSection.Settings.Add("Text", Me.Text)
  15:          End If
  16:   
  17:          Me.Text = newSection.Settings("Text").Value
  18:   
  19:          config.Save(ConfigurationSaveMode.Minimal)
  20:   
  21:      End Sub

On line 2 I call the method from the first example to get the configuration object, next I define an AppSettingsSection which is a key/value pair collection that is used in the <appSettings> in normal .config files.  Depeding on whether the section exists or not in the current file I either set the newSection to the existing reference, or create a new one, set the SectionInformation to Allow Machine to Local User, then work with the keys as normal.

Finally, I save the config file, just incase it didn't exist before I read it into the application.

Once saved, the user.config looks like this:

   1:  <?xml version="1.0" encoding="utf-8"?>
   2:  <configuration>
   3:      <configSections>
   4:          <section name="MySettings" type="System.Configuration.AppSettingsSection, System.Configuration, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" allowExeDefinition="MachineToLocalUser" />
   5:      </configSections>
   6:      <MySettings>
   7:          <add key="Text" value="My Application" />
   8:      </MySettings>
   9:  </configuration>

As you can see, it created a new config section named "MySettings" using the System.Configuration.AppSettingsSection.

Pretty simple once you get the hang of it, and I could very easily see a CAB based, or plugin based application really taking advantage of loading different config sections per module/plugin, and reading other settings from other modules/plugins.

In the next discovery, I will be trying to figure out how to merge multiple configurations into a single config file to ease the reading of multi-configs.

Posted by Dante | 0 Comments

Update to the Build Automation

Well, after a few months working on this off and on, I thought I would share the current status of our operations.

First and foremost, we got Cruise Control up and running, this made a huge step forward with the process, and it is open source, so that allowed me to create a custom UI for it to run.  This resulted in us having a custom application that runs very similarly to the Cruise Control Tray Application (CCTray), except I implemented grouping by categories, displaying the categories, as well as auto-discovery of projects, rather then having to set them up.

Additionally, because we don't need everyone in our department building every piece of software all the time, I created a pseudo permission system that is bound into an xml file, like the example below.

<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
<security>
    <add user="tanderson" viewConfig="true" projects="*" />
    <add user="jsmith" viewConfig="false" projects="RecipeEditor_VB6;Rocket;QReports;" />
</security>

As you can see, it is a very simplistic permissions system, the user is the currently logged in computer user, viewConfig is a small permission to see if they can view the servers ccnet.config, and the projects are a semi-colon separated list of the names of the projects they can see status of, or for ease of people like me, an asterisk for all projects.

Below is a screen shot of the current version of the software.

image

I have made quite a bit of headway with the system in getting more and more projects completed and into the daily build.  The "DailyBuild" is the only project that is built nightly, and it builds everyone of the other projects.

The actual process of creating all of this mayhem was a few steps.

Creating an Environment

The first thing I had to do, was get an environment going that was as clean as possible to do the builds, this is a remote connection only off the domain standalone box that only myself and the VP of software development have access to.  The computer is loaded with Server 2003 Standard Edition, SQL 2000, SQL 2005, Visual Basic 6, VS 2003, VS 2005, and RedGate toolkit.

Starting the batches

When someone told me I would do a crap load of batch files as a quick solution to get a process done, I would have laughed at them, but in this case, it seemed to be the quickest route to the finish line. In total for what you see above, I have roughly 38 batch files.

The first ones that I built, where simplistic, setup the compiling environments, and then get the latest code from source control. I created two separate batch files for this to ease the calling of them from other batch files.

SetupEnvironment.cmd

@rem Setup all variables here
@SET SSCMLOGIN=user:password
@SET SSCMSERVER=server:4900
@SET CBSSoftware="C:\Build\CBS Software"
@SET CBSLegacy="C:\Build\CBS Legacy"
@SET CBSProto="C:\Build\CBS Prototype"

@SET VB6EXE="C:\Program Files\Microsoft Visual Studio\VB98\vb6.exe"
@SET VBCEXE="C:\windows\microsoft.net\framework\v2.0.50727\vbc.exe"
@SET VS2003="c:\program files\Microsoft Visual Studio .NET 2003\common7\ide\devenv.exe"
@SET VS2005="C:\Program files\microsoft visual studio 8\common7\ide\devenv.exe"
@SET VS2008="C:\Program Files\microsoft visual studio 9.0\common7\ide\devenv.exe"

@rem Build utilities here
@call Utilities/BuildUtilities

@EXIT /B %ERRORLEVEL%

I added a utilities folder to store a lot of extra files that I didn't want in the main batch folder, such as:

  • Icons
  • Sign Keys
  • Winrar.exe
  • XCopy exclusion lists for different types of software
  • Cruise Control Log Files

I also added a BuildUtilities batch file that allows me to compile and use stand alone class files in .Net, such as one that I use very frequently, the List.exe.

Utilities/BuildUtilities.cmd

@rem add any utility compiles here
@echo Building Utilities

@rem Building List
@"%VBCEXE%" /target:exe /nologo "C:\Build\Utilities\list.vb"

@EXIT /B %ERRORLEVEL%

List.exe is a very simple executable that basically outputs a file from disc to the console.

The next batch file that I built was to pull all of the code for our products from source control.

GetSource.cmd

@rem Retrieve all source
@echo Retrieving latest source code
@mkdir %CBSSoftware%
@mkdir %CBSLegacy%
@mkdir %CBSProto%

@sscm workdir ""%CBSSoftware%"" "CBS Software" -y"%SSCMLOGIN%" -z"%SSCMSERVER%"
@sscm get *.* -d""%CBSSoftware%"" -p"CBS Software" -r -e -q -wreplace -y"%SSCMLOGIN%" -z"%SSCMSERVER%"

@sscm workdir ""%CBSLegacy%"" "CBS Legacy" -y"%SSCMLOGIN%" -z"%SSCMSERVER%"
@sscm get *.* -d""%CBSLegacy%"" -p"CBS Legacy" -r -e -q -wreplace -y"%SSCMLOGIN%" -z"%SSCMSERVER%"

@sscm workdir ""%CBSProto%"" "CBS Prototype" -y"%SSCMLOGIN%" -z"%SSCMSERVER%"
@sscm get *.* -d""%CBSProto%"" -p"CBS Prototype" -r -e -q -wreplace -y"%SSCMLOGIN%" -z"%SSCMSERVER%"

@EXIT /B %ERRORLEVEL%

We use Surround SCM from Seapine software, so your command lines may differ quite a bit.

Creating a compile project

To do anything, I needed to be able to compile the projects, this was done mostly via command line to the proper visual studio, I wanted to do the direct calls to the compilers, but there was entirely way too much legwork that would have had to be done first.

Although, in order to make my giant "DailyBuild" batch file as well as a by-product batch file, I needed to separate some of the logic from the build to only do certain things, for instance, I don't want to get the source for all projects each time I build one project.  Instead I created two files, one with the product name "Application.cmd" and another with "BuildApplication.cmd".  The Build*.cmd sets up the environment, gets the source, then calls the application.cmd batch at the end.

Iconifier.cmd

@echo Building Iconifier
@echo ------------------------------------------------------------------
@echo This build requires .Net 2.0, Visual Studio 2005
@echo ------------------------------------------------------------------

@rmdir /S /Q "./Output/Iconifier/"
@mkdir "Output/Iconifier/"
@rmdir /S /Q "./Output/Iconifier/Icon Templates"
@mkdir "Output/Iconifier/Icon Templates/"

@echo Building Iconifier

@%VS2005% "./CBS Software/Desktop/Utilities/NorthStar.Desktop.Iconifier/NorthStar.Desktop.Iconifier.sln" /rebuild Release /out Output\Iconifier\build.log
@"./Utilities/List" ./Output/Iconifier/Build.log
@del output\Iconifier\build.log
@IF NOT EXIST "./CBS Software/Desktop/Utilities/NorthStar.Desktop.Iconifier/NorthStar.Desktop.Iconifier/bin/release/northstar.Desktop.Iconifier.exe" EXIT /B 1

@rem copy over all the files
@xcopy "CBS Software\Desktop\Utilities\NorthStar.Desktop.Iconifier\NorthStar.Desktop.Iconifier\bin\release\*.exe" Output\Iconifier /S /Q /Y
@xcopy "CBS Software\Desktop\Utilities\NorthStar.Desktop.Iconifier\NorthStar.Desktop.Iconifier\Icon Templates\*.png" "Output\Iconifier\Icon Templates" /S /Q /Y

@rem Archive
@"./Utilities/winrar.exe" a -r -idcdp -sfx.\Utilities\default.sfx -ibck -ep1 -iimg.\Utilities\sfx_logo.bmp -ts -iicon".\utilities\default.ico" -z".\Utilities\SFX_default.txt" ".\Output\NorthStar.Iconifier.Daily.exe" "Output\Iconifier\*.*"
@"./Utilities/winrar.exe" a -ag-MM-DD-YYYY -r -idcdp -sfx.\Utilities\default.sfx -ibck -ep1 -iimg.\Utilities\sfx_logo.bmp -ts -iicon".\utilities\default.ico" -z".\Utilities\SFX_default.txt" ".\Output\NorthStar.Iconifier.Daily.exe" "Output\Iconifier\*.*"

@EXIT /B %ERRORLEVEL%

Quick recap, remove existing directories (clean), create them, build the solution file, output the log to the console for logging purposes, copy all the files from the build directory to the output directory, archive using winrar (giant command lines for the win) a daily file, then archive again with a date stamped file.

BuildIconifier.cmd

@cls
@rem Start Iconifier Daily Build

@echo ------------------------------------------------------------------
@echo Daily  Iconifier Script Initialized
@echo Version: 1.0
@echo Author:  Tom Anderson
@echo ------------------------------------------------------------------

@call SetupEnvironment
@IF NOT %ERRORLEVEL%  == 0 EXIT /B %ERRORLEVEL%

@call GetSource
@IF NOT %ERRORLEVEL%  == 0 EXIT /B %ERRORLEVEL%

@echo ------------------------------------------------------------------

@call Iconifier
@IF NOT %ERRORLEVEL%  == 0 EXIT /B %ERRORLEVEL%

@echo ------------------------------------------------------------------
@echo Deploying to http://servername/Downloads/NorthStar.Iconifier.Daily.exe
@copy Output\NorthStar.Iconifier.Daily.exe C:\Inetpub\wwwroot\Downloads\ /Y
@echo ------------------------------------------------------------------

@ECHO ERROR LEVEL: %ERRORLEVEL%

@EXIT /B %ERRORLEVEL%

And here is our setup and build batch file, which can be called stand alone.  Only thing unique is that I copy the daily.exe to the web server hosted on this machine for download ability within the company.

The project pasted above is actually a pretty quick and simple one, some of our projects have upwards of 12 compiles to make the final product, and quite a few supporting files.

Wrap up

It is still a work in progress, and I still have about 6 projects to get in that are the most complex, and a database to create a daily build off of, but the progress is going pretty well.

Posted by Dante | 0 Comments

Rounded Rectangle Tutorial

    After seeing a lot of samples on the web, and a lot of different methods, I have decided to write up my own little tutorial on getting Rounded Rectangles in .Net

     

    First off, lets start by opening up Visual Studio and creating a new Windows Forms Application.

     

    Next, resize your main form and add a new picture box to the form. Resize the picture box to 400,400 and adjust your form size to where it looks something like this.

     

    clip_image001

     

    Double click on the form to access the Form Load event handler, and lets start some small bits of code.

     

    Instead of just showing you how to do this, I am going to first demonstrate exactly how each part of what we are doing actually works.

     

    Start off by creating a new Bitmap object, I decreased the size a bit to allow for the borders and padding of the picture box.

     

    clip_image002

     

    Now lets setup our picture box and display the new bitmap in it at runtime.

     

    clip_image003

     

    Next, we are going to create a new Graphics object (System.Drawing.Graphics).

     

    clip_image004

     

    You may also want to set the smoothing mode of the Graphics object, this will produce much cleaner lines while drawing shapes.

     

    clip_image005

     

    With the graphics object you can draw and fill all kinds of wonderful shapes and colors.  We are going to first concern ourselves with the Graphics.DrawArc method.

     

    To get a good feel, lets first start with the obvious, draw a single arc on the image.

     

    clip_image006

     

    The method above is drawing an arc with a black pen at position 10,10 in a 10x10 size a start angle of 0 degrees, and an sweep angle of 90 degrees, which is a right triangle.

     

    If you run the application, you should see something like this.

     

    clip_image007

     

    Not very impressive, but a good start to the rounded rectangle we are going to want to create.  Working around in quarter circles, we can then display all 4 sides of the circle.

     

    clip_image008

     

    When you run the application now, you should see a complete circle like so

     

    clip_image009

     

    Neat huh? Comment out the lines one at a time to see which angle is which, just to save you some time, here are the mappings for our corners.

     

    Starting Angle 0 = Bottom Right

    Starting Angle 90 = Bottom Left

    Starting Angle 180 = Top Left

    Starting Angle 270 = Top Right

     

    That is a very basic understanding of the Arc drawing, lets make a rounded rectangle now!

     

    Stub out the following method

     

    clip_image010

     

    This method will create a GraphicsPath Object that defines the shape of our rounded rectangle.  A graphics path can best be described as a way to draw piece by piece the lines, points, etc… of a shape, and then "fill in the blanks" by connecting all the points.  A lot of samples I have seen, and used, usually draw all the internal lines, or create a bunch of boxes, etc…  But in this tutorial, we are going to do it the "easy way".

     

    Lets first take care of a small issue that I have noticed with this method, For some reason the right side and bottom always gets clipped off, so I manually add a padding of 1,1,2,2; that’s top 1, left 1, right 2, bottom 2.  This will center up the rounded rectangle quite nicely.  And as you noticed in the constructor, I added a way to put in a padding for the actual drawing so that you can grow shrink it using the padding.

     

    clip_image011

     

    That should be pretty self explanatory.

     

    Now, using the logic that we built above, we are going to create arcs at each of the four corners of the image, except using the GraphicsPath.AddArc method (same as the Graphics.DrawArc, except it doesn't contain a color in the arguments).

     

    clip_image012

     

    Following the code, you will see that we start in the upper left hand corner to create an arc, move to the upper right hand corner, bottom right hand corner, then finally the bottom left hand corner, now we will want to take a big shortcut

     

    clip_image013

     

    Here we tell the GraphicsPath to "connect the dots", basically it takes the current path and fills in the blanks to create a solid shape.

     

    With the method complete, it should look like this

     

    clip_image014

     

    Only thing left to do is make a call to this method to get a path, and then use the Graphics object to "fill the path" - back in our form load under the smoothing mode set

     

    clip_image015

     

    Big call, simple understanding.  Call the Graphics.FillPath, set the brush color, then pass in the return from our call to MakeRoundedRectanglePath that we created above.

     

    Now run the application, and look at your rounded rectangle goodness.

     

    clip_image016

     

    Now, lets dress this up a bit, to make it a bit more attractive. Use a System.Drawing.Drawing2D.LinearGradientBrush instead of a standard SolidBrush.

     

    clip_image017

     

    And now our newly applied fill path method

     

    clip_image018

     

    And, our final result:

     

    clip_image019

     

    Pretty simple!  I hope you enjoyed this tutorial, below is the full code for this tutorial.  With some practice and modifications of the function calls, you can very easily get this result

     

    clip_image020

     

    Source

     

    clip_image021

     

     

    *Yes, they are images, you should type to learn.

Posted by Dante | 0 Comments

Extending Generics

I am a huge fan of generics, but sometimes you just don't have that extra functionality that you need.  After using the CAB framework, I have found a few additions that I make to the root Generics that make using them a lot better.

First, I will inherit a System.Collections.Generic.List(of T) and create my own generic, from there I will add a few constructors, and Add with a return value, and AddNew method, and IndexOf, and an Item overload.  This will allow me to use this generic, specify an "IndexProperty" that will allow me to say, find an object in the list by the property "Name", "Text", or whatever else my heart desires.

Below is the code for the actual new class with comments.  Notice this is in the My namespace so will only be available from within your assembly, unless you change the namespace yourself.

   1:  Imports System.ComponentModel
   2:   
   3:  Namespace My.Generics
   4:   
   5:      ''' <summary>
   6:      ''' Extension class for the Generic.List Object
   7:      ''' </summary>
   8:      Public Class List(Of T)
   9:          Inherits Generic.List(Of T)
  10:   
  11:          ''' <summary>
  12:          ''' Default constructor
  13:          ''' </summary>
  14:          Public Sub New()
  15:          End Sub
  16:   
  17:          ''' <summary>
  18:          ''' Constructor to set the IndexProperty
  19:          ''' </summary>
  20:          Public Sub New(ByVal IndexProperty As String)
  21:              Me.IndexProperty = IndexProperty
  22:          End Sub
  23:   
  24:          Private m_IndexProperty As String = String.Empty
  25:          ''' <summary>
  26:          ''' Gets or Sets the property name to Index on
  27:          ''' </summary>
  28:          Public Property IndexProperty() As String
  29:              Get
  30:                  Return m_IndexProperty
  31:              End Get
  32:              Set(ByVal value As String)
  33:                  m_IndexProperty = value
  34:              End Set
  35:          End Property
  36:   
  37:          ''' <summary>
  38:          ''' Returns the newly added item
  39:          ''' </summary>
  40:          Public Shadows Function Add(ByVal Item As T) As T
  41:              MyBase.Add(Item)
  42:              Return Item
  43:          End Function
  44:   
  45:          ''' <summary>
  46:          ''' Uses the Activator Class to create a new instance of the object
  47:          ''' </summary>
  48:          Public Function AddNew() As T
  49:              Dim newItem As T = Activator.CreateInstance(GetType(T))
  50:              Return Add(newItem)
  51:          End Function
  52:   
  53:          ''' <summary>
  54:          ''' Returns the index of an object based on the IndexProperty
  55:          ''' </summary>
  56:          Public Overloads Function IndexOf(ByVal Value As Object) As Integer
  57:              Dim retVal As Integer = -1
  58:   
  59:              If m_IndexProperty.Length > 0 Then
  60:                  For Each obj As T In Me
  61:                      If obj.GetType.GetProperty(m_IndexProperty) IsNot Nothing Then
  62:                          If obj.GetType.GetProperty(m_IndexProperty).GetValue(obj, Nothing) Is Value Then
  63:                              retVal = IndexOf(obj)
  64:                              Exit For
  65:                          End If
  66:                      End If
  67:                  Next
  68:              End If
  69:   
  70:              Return retVal
  71:          End Function
  72:   
  73:          ''' <summary>
  74:          ''' Returns an object based on the IndexProperty
  75:          ''' </summary>
  76:          Default Public Overloads Property Item(ByVal PropertyValue As Object) As T
  77:              Get
  78:                  Return Item(IndexOf(PropertyValue))
  79:              End Get
  80:              Set(ByVal value As T)
  81:                  If IndexOf(value) > -1 Then
  82:                      Item(IndexOf(value)) = value
  83:                  Else
  84:                      Add(value)
  85:                  End If
  86:              End Set
  87:          End Property
  88:   
  89:          ''' <summary>
  90:          ''' Removes an object based on the IndexProperty
  91:          ''' </summary>
  92:          Public Overloads Sub Remove(ByVal Value As Object)
  93:              Remove(Item(Value))
  94:          End Sub
  95:   
  96:      End Class
  97:   
  98:  End Namespace

As you can see, there isn't really a lot of extra coding in there to get a LOT of functionality.  Below is a sample implementation class for the new List object.

   1:      Public Class NamedFormCollection
   2:          Inherits My.Generics.List(Of Form)
   3:   
   4:          Public Sub New()
   5:              MyBase.New("Name")
   6:          End Sub
   7:   
   8:      End Class

This allows me to now do the following, which i could never have done with a simple List(of Form)

   1:  Public Class MainForm
   2:   
   3:      Private Sub MainForm_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
   4:          Dim nfc As New NamedFormCollection()
   5:          With nfc.AddNew()
   6:              .Name = "Hello"
   7:              .Text = "Hello Form"
   8:          End With
   9:   
  10:          nfc.Item("Hello").ShowDialog(Me)
  11:   
  12:      End Sub
  13:   
  14:  End Class 
 

That to me, is way worth the extra effort of fleshing out some helper classes.

Posted by Dante | 0 Comments

CAB - Getting Started

Well, here goes nothing.  This will be the first part of a series of blog entries explaining how to get a Composite UI Application Block up and running from scratch.

Where do we start? First thing we will want to do is to Download the Composite UI Application Block from Microsoft.  This download requires that you login to Microsoft.

The CAB_VB.msi is about 6mb, and installs pretty easily. While installing you will go through a Custom Setup.  You should choose to install the NUnit 2.2 Unit Tests, and not install the Team System Unit Tests (Unless you actually have Visual Studio Team System). Once you complete the installation process, you are ready to go.

 

Compiling the CAB

Before we begin, we are going to have to compile the Composite Application Block.  Open up the Solution that was installed above in Visual Studio (Default Location is: C:\Program Files\Microsoft Composite UI App Block\VB\CompositeUI.sln).

For now, the only Solution Folder we are concerned about is the "Source" folder. Go ahead and close the others. Change the Build Configuration to Release, right click on the CompositeUI.WinForms project and select "Rebuild". Now navigate to the bin\Release folder for the CompositeUI.WinForms project (Default Location is: C:\Program Files\Microsoft Composite UI App Block\VB\Source\CompositeUI.WinForms\bin\Release\) and verify these assemblies are there:

  • Microsoft.Practices.ObjectBuilder.dll
  • Microsoft.Practices.CompositeUI.dll
  • Microsoft.Practices.CompositeUI.WinForms.dll

Keep this window open, as we are going to want to copy these files over to our project directory for referencing.

 

Create a new project

We are going to just jump right in, and create a new Windows Application project in Visual Studio 2005 or VB Express 2005. Lets name it "RenEvo.Blogs.CAB".

The first thing we want to do is delete the Form1.vb that is created by the project wizard, next, open up the Project Properties then uncheck the "Enable application framework" and change the Startup object: to Sub Main.  Now is also a good time to go ahead and set your Assembly Information.

Next, lets setup our References, from the bin\Release folder we have open from above (CompositeUI.WinForms), lets copy those three assemblies and their debug files to a new directory inside of our solution directory called "CAB Assemblies". Open up the Add Reference dialog and click on the Browse tab.  While using the "up arrow", go up one directory, and into the CAB Assemblies directory.  Select all 3 Assemblies in this directory. Go ahead and leave the default reference properties for now.

Next, we are going to want to add a Startup Object, Add a new Class to your project, and call it "Startup.vb". Create a Shared Sub Main which will act as our entry point for the application and we are going to add the STAThreadAttribute to this method.  Your class file should look like below.

 

Public Class Startup

    <STAThread()> _
    Public Shared Sub Main()

    End Sub

End Class

 

As you will notice, if you press F5 to run, the application will simply open and close, this is intentional for now.

Next we are going to add a new Form, this will be our Main GUI for the application, Create a new folder called "Forms", then add a new Form called "CabApplicationForm". For now, close the form and go back to the Startup class.

With the startup class, we are going to call and initialize a reference to its self, but first we need to add some inheritance and imports to the CompositeUI Application Block.  Add the following imports to the Startup.vb.

Microsoft.Practices.CompositeUI
Microsoft.Practices.CompositeUI.WinForms

Next, inherit the FormShellApplication with a type constructor of WorkItem and CabApplicationForm as well as adding an implementation of IDisposable (I prefer to do this with just about everything). And finally, lets create an instance of the Startup class in the Sub Main, and call the Run method.  Your Startup.vb should look like below now.

 

Imports Microsoft.Practices.CompositeUI
Imports Microsoft.Practices.CompositeUI.WinForms

Public Class Startup
    Inherits FormShellApplication(Of WorkItem, CabApplicationForm) : Implements IDisposable

    <STAThread()> _
    Public Shared Sub Main()
        Using appRunner As New Startup()
            appRunner.Run()
        End Using
    End Sub

#Region " IDisposable Support "

    Private disposedValue As Boolean = False        ' To detect redundant calls

    ' IDisposable
    Protected Overridable Sub Dispose(ByVal disposing As Boolean)
        If Not Me.disposedValue Then
            If disposing Then

            End If

        End If
        Me.disposedValue = True
    End Sub

    ' This code added by Visual Basic to correctly implement the disposable pattern.
    Public Sub Dispose() Implements IDisposable.Dispose
        ' Do not change this code.  Put cleanup code in Dispose(ByVal disposing As Boolean) above.
        Dispose(True)
        GC.SuppressFinalize(Me)
    End Sub

#End Region

End Class

 

Now when you press F5 to launch the application, the Form will display, and we have initialized the CAB framework. Granted at this time, it isn't really doing much.

Conclusion

This is the first step, the first step to many. In the next blogs I will go into actually adding some functionality to the CAB block, this was just a quick way to get your environment going.

Download the Solution

Posted by Dante | 1 Comments

CAB - Coming Soon!

Well, it is about that time that I start my blog series on the Composite UI Application Block from Microsoft.

What is it?  Well, I will give you the short form and ask you to go look at the wiki: CABpedia.com.

In short, let me plagarize from the wiki:

CAB stands for the Composite UI Application Block, and it's a set of .NET 2.0 classes designed to make it easier to build complex Windows Forms applications. Perhaps the most important way it helps you simplify your applications is through decoupling the pieces of code that make up a large application.

CAB also provides support for decoupling the elements in the user interface. This means you can often make major changes to how the UI is put together without having to change any of the modules that make up an application. For example, you might have an Outlook-style user interface and then decide you want to change it to a web-style interface. No problem. Just change the UI shell and the modules will load themselves into the new locations as you've defined it.

When you start a CAB application, you start by creating a UI shell. This is the main form that might include the menu, tool bars, etc. Next you add one or more Workspaces to the form. These are the locations where the different elements of the UI will appear when the application is running. Finally, you create one or more modules that contain either elements that will appear in the UI, or Services that will be available for use by other classes.

It takes a little time to learn how to use CAB, but once you get used to it, CAB definitely helps build Windows Forms applications.

What can you do with CAB?  A whole hell of a lot, and starting on Monday, I will walk you through getting CAB working, starting your own executable, and then implementing modular application development.

Posted by Dante | 0 Comments

Creating a ListView for Vista (Tiles Extended Support)

Have you ever wondered why Windows Vista's ListViews and .Net 2.0's ListViews just don't match?  Well, after a lot of hunting on the web, and finding the best implementations, I have put together this little tutorial to turn this:

ListView.NoStyle

Into this:

ListView.Style

 

Building the project

Create a new Class Library Project somewhere on your hard drive, for now, lets name it "RenEvo.Articles.Controls". As I state always, lets get rid of the Class1.vb that was created with the new project by right clicking and deleting it.

Adding the needed references

Next lets double click on "My Project" and bring up the Project Properties Editor. Click on the References tab, and add the following two references from the .Net tab.

System.Windows.Forms
System.Drawing

At this time you can do a global import on the namespaces, but for this article I am going to be assuming you didn't.

 

Creating our custom control

Now that we have our environment setup, lets add a new Class by right clicking on the project "Add New Item", select Class from the items in the list, and name it VistaListView.vb. You should now have a blank class.

Lets go ahead and add some imports to the top of the class.

Imports System.Windows.Forms
Imports System.ComponentModel
Imports System.Drawing

We are now going to add some inheritance to the class by adding "Inherits ListView" under the class declaration.

Adding a toolbox bitmap

Since we don't like the look of the default gear icon in the Design Toolbox, lets implement the Icon from the Listview to show. Add the following attribute tag above the class declaration.

<ToolboxBitmap(GetType(ListView))> _
Public Class VistaListView

As you can see, we have hadded the "ToolboxBitmap" attribute, and used the Type constructor to select the ToolboxBitmap from the ListView object. There are a few other overloads for the ToolboxBitmap attribute, but we aren't going to cover those here.

 

Declaring an API

Next we are going to quickly declare an API to update the style on the control, the method that we are looking for is in the uxtheme.dll and is called "SetWindowTheme". Below is the declaration.

<System.Runtime.InteropServices.DllImport("uxtheme.dll", Charset:=System.Runtime.InteropServices.CharSet.Unicode)> _
Private Shared Function SetWindowTheme(ByVal Handle As IntPtr, ByVal Theme As String, ByVal SubIdList As String) As Integer
End Function

We have made this method private, as we don't need to access it externally.  We will call this function with the handle of the control to set, the theme to apply, and then we don't need to worry about the third argument at this time and will pass a null value.

 

Creating a Vista Check

We are going to want to detect wether we are running on Vista, or an earlier build of Windows.  This is to check and see if we should even try to call the API, or just ignore it and let the regular styles be.  Vista is version 6.* of windows, so we will simply return if the OS version is 6 or greater.

Private Function IsVistaOrLater() As Boolean
    Return (Environment.OSVersion.Version.Major >= 6)
End Function

Again, we made this function private, since we don't need to use it anywhere else, later on you may wish to implement these two functions into a Utilities or API class.

 

Overrides

We are going to want to override one very key method, the OnHandleCreated this method is called right after the class is created by the managed code. We will call the base method, check our os version, then if it is Vista or higher, we will set the theme to "explorer".  There are several themes, but this is the one we want to achieve.

Protected Overrides Sub OnHandleCreated(ByVal e As System.EventArgs)
    MyBase.OnHandleCreated(e)

    If IsVistaOrLater() Then
        SetWindowTheme(Me.Handle, "explorer", Nothing)
    End If
End Sub

Now we could drop this control on a form and run it right now, and everything would be great, but there is one more feature we should adopt.

 

Tile View Support

We are going to add another override real fast that will force a set on the View property, I will explain this in a moment.

Protected Overrides Sub OnVisibleChanged(ByVal e As System.EventArgs)
    MyBase.OnVisibleChanged(e)
    'enforce a set of the view property
    View = View
End Sub

Next we are going to shadow the View property, check to see if we are not running on vista, if we are setting Tile, and that we are not in design mode. Then if all of these are correct, we will set the view property to LargeIcon.  This will do the check internally, instead of letting windows do it for you and cause a bit of an internal mess. Personally, I like knowing why things happen, and not let any assumptions go through. We are also going to extend the current description of the property and mention that Tile is not supported on Windows XP.

<Description("Selects one of five different views that items can be shown in. (Tile is not supported in Windows XP).")> _
Public Shadows Property View() As System.Windows.Forms.View
    Get
        Return MyBase.View
    End Get
    Set(ByVal value As System.Windows.Forms.View)
        If value = Windows.Forms.View.Tile AndAlso IsVistaOrLater() = False AndAlso Me.DesignMode = False Then
            value = Windows.Forms.View.LargeIcon
        End If
        MyBase.View = value
    End Set
End Property

 

An explanation of Shadows

Shadows is a declaration type that overrides properties and functions by completely replacing the base properties, methods, etc...  This is useful for properties and such that don't have and Overridable declaration.  One such method that I like to use is to modify collection to return the newly added object on "Collection.Add".

 

Conclusion

Now that we have contructed the class, you should be able to compile it and drop the ListView on a form.  If running on Vista you will notice that the tiles are now properly fully selected, and if running on XP you will see the items in LargeIcon view.

 

Full Class - Code 2 Html

Imports System.Windows.Forms
Imports System.ComponentModel
Imports System.Drawing

<ToolboxBitmap(GetType(ListView))> _
Public Class VistaListView
    Inherits ListView

    <System.Runtime.InteropServices.DllImport("uxtheme.dll", Charset:=System.Runtime.InteropServices.CharSet.Unicode)> _
    Private Shared Function SetWindowTheme(ByVal Handle As IntPtr, ByVal Theme As String, ByVal SubIdList As String) As Integer
    End Function

    Private Function IsVistaOrLater() As Boolean
        Return (Environment.OSVersion.Version.Major >= 6)
    End Function

    Protected Overrides Sub OnHandleCreated(ByVal e As System.EventArgs)
        MyBase.OnHandleCreated(e)

        If IsVistaOrLater() Then
            SetWindowTheme(Me.Handle, "explorer", Nothing)
        End If
    End Sub

    Protected Overrides Sub OnVisibleChanged(ByVal e As System.EventArgs)
        MyBase.OnVisibleChanged(e)
        'xp overrides of view
        View = View
    End Sub

    <Description("Selects one of five different views that items can be shown in. (Tile is not supported in Windows XP).")> _
    Public Shadows Property View() As System.Windows.Forms.View
        Get
            Return MyBase.View
        End Get
        Set(ByVal value As System.Windows.Forms.View)
            If value = Windows.Forms.View.Tile AndAlso IsVistaOrLater() = False AndAlso Me.DesignMode = False Then
                value = Windows.Forms.View.LargeIcon
            End If
            MyBase.View = value
        End Set
    End Property

End Class
Posted by Dante | 0 Comments