Welcome to RenEvo Sign in | Join | Help

Your first WPF Ribbon Application

Well, in the spirit of those “great” first programs, I thought that I would post this one up as a follow up to my last news post about how I found this little gem in the WPF Futures site.

So, without further ado, lets get started.

First things first, you need to head over to codeplex and download the WPF Ribbon Preview, this will require you to fill out an Office 2007 licensing agreement, it is free, and basically says that you aren’t going to compete with Microsoft if you use this UI, and that you will adhere to the Ribbon Standards, which the control does a really good job of enforcing for you.

In the download, the file you are going to be most worried about is the “RibbonControlsLibrary.dll”, this is the assembly that contains all the Ribbon goodness. Extract that to a location where you can easily find it, and startup Visual Studio 2008.

We want to create a new WPF Application, the language you choose at this point is totally irrelevant, as most of this article is going to be in XAML. I named mine “RibbonSample”.

image

First things first, add a reference to the RibbonControlsLibrary.dll. You will need to browse to the file, the place you extracted it above. Also be sure that the “Copy Local” property for the reference is set to True.

Now lets dig into some XAML, I prefer in Visual Studio to just remove the preview pane all together when starting my XAML work.

We are going to have to add a few new schema references to the XAML, specifically for the Ribbon Control.  Lets prefix this with an “r” to keep our XAML sane. If you have never added any XAML references, it is done by simply declaring a new CLR schema reference. The code below has the added code italic and bolded.

<Window x:Class="Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:r="clr-namespace:Microsoft.Windows.Controls.Ribbon;assembly=RibbonControlsLibrary"
    Title="Window1" Height="300" Width="300">
    <Grid>
        
    </Grid>
</Window>

Now some major changes, we are going to change the main window class to a ribbon window, modify the size, startup location, resize mode, min height, and min width. For easier layout, I am going to also change the Grid to a DockPanel. Inside the DockPanel, we also need to add an actual Ribbon, or else our app will just show up as a big black mass. This is done by simply placing a Ribbon control inside of the DockPanel. And finally, to fill up the area that we don’t need with the ribbon, lets just dump in a RichTextBox control.

After doing all of the above, the XAML to get a Ribbon on a Form up and running is only this amount of XAML.

<r:RibbonWindow x:Class="Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:r="clr-namespace:Microsoft.Windows.Controls.Ribbon;assembly=RibbonControlsLibrary"
    Title="My First Ribbon Form" ResizeMode="CanResizeWithGrip" WindowStartupLocation="CenterScreen"
    Height="600" Width="800" MinHeight="300" MinWidth="400">

    <DockPanel>
        <r:Ribbon DockPanel.Dock="Top" Title="My First Ribbon Form" x:Name="mainRibbon">
            
        </r:Ribbon>
        <RichTextBox Height="auto" Width="auto"></RichTextBox>
    </DockPanel>
</r:RibbonWindow>

image

Pretty simple eh?

It doesn’t really do much at this point, as we haven’t added anything to it, but that will come in the next step.

Adding some images
Ribbon controls are very graphical, so in order to fill this thing up, lets add some images to our projects, create an images directory and find some nice png files, or use the ones provided in the download below. I used 48x48 PNG files with transparency.

Starting at the top, we are going to want to start placing some images on our form. Just like regular forms, you can add the form icon in the root window declaration.

Next we want to add a button to the QAT (Quick Access Toolbar) that is located on the form’s title bar. This is done by simply adding to the QuickAccessToolBar element for the Ribbon. For this one, we will simply add a command button that has an icon and is clickable. This part is a bit new to me, but we need to create some static resources in the form (or you can link them) for the Ribbon Commands, this makes them very re-usable, and easier to work with.  Below is the XAML for the new QAT button and its placement. You will however have to implement the “CanExecute” event in order for the button to be clickable, simply return True for this function.

<r:RibbonWindow x:Class="Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:r="clr-namespace:Microsoft.Windows.Controls.Ribbon;assembly=RibbonControlsLibrary"
    Title="My First Ribbon Form" ResizeMode="CanResizeWithGrip" WindowStartupLocation="CenterScreen"
    Icon="Images\app.png"
    Height="600" Width="800" MinHeight="300" MinWidth="400">

    <r:RibbonWindow.Resources>
        <ResourceDictionary>
            <r:RibbonCommand x:Key="QATButton" CanExecute="RibbonCommand_CanExecute" LabelTitle="QAT Button" LabelDescription="This is a sample QAT Button" ToolTipTitle="QAT Button" ToolTipDescription="This is a sample QAT Button, it doesn't do anything" SmallImageSource="Images\save.png" LargeImageSource="Images\save.png" />
        </ResourceDictionary>
    </r:RibbonWindow.Resources>
    
    <DockPanel>
        <r:Ribbon DockPanel.Dock="Top" Title="My First Ribbon Form" x:Name="mainRibbon">
            <r:Ribbon.QuickAccessToolBar>
                <r:RibbonQuickAccessToolBar>
                    <r:RibbonButton Command="{StaticResource QATButton}" />
                </r:RibbonQuickAccessToolBar>
            </r:Ribbon.QuickAccessToolBar>
        </r:Ribbon>
        <RichTextBox Height="auto" Width="auto"></RichTextBox>
    </DockPanel>
</r:RibbonWindow>

image

Now that we have implemented a QAT button, lets add an image to our “Start Button” for the Ribbon, this is pretty straightforward. For this image I used a 24x24 as it doesn’t streatch into the area.

<r:RibbonWindow x:Class="Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:r="clr-namespace:Microsoft.Windows.Controls.Ribbon;assembly=RibbonControlsLibrary"
    Title="My First Ribbon Form" ResizeMode="CanResizeWithGrip" WindowStartupLocation="CenterScreen"
    Icon="Images\app.png"
    Height="600" Width="800" MinHeight="300" MinWidth="400">

    <r:RibbonWindow.Resources>
        <ResourceDictionary>
            <r:RibbonCommand x:Key="QATButton" CanExecute="RibbonCommand_CanExecute" LabelTitle="QAT Button" LabelDescription="This is a sample QAT Button" ToolTipTitle="QAT Button" ToolTipDescription="This is a sample QAT Button, it doesn't do anything" SmallImageSource="Images\save.png" LargeImageSource="Images\save.png" />
        </ResourceDictionary>
    </r:RibbonWindow.Resources>

    <DockPanel>
        <r:Ribbon DockPanel.Dock="Top" Title="My First Ribbon Form" x:Name="mainRibbon">
            <r:Ribbon.QuickAccessToolBar>
                <r:RibbonQuickAccessToolBar>
                    <r:RibbonButton Command="{StaticResource QATButton}" />
                </r:RibbonQuickAccessToolBar>
            </r:Ribbon.QuickAccessToolBar>
            <r:Ribbon.ApplicationMenu>
                <r:RibbonApplicationMenu>
                    <r:RibbonApplicationMenu.Command>
                        <r:RibbonCommand SmallImageSource="Images\box.png" LargeImageSource="Images\box.png" />
                    </r:RibbonApplicationMenu.Command>
                </r:RibbonApplicationMenu>
            </r:Ribbon.ApplicationMenu>
        </r:Ribbon>
        <RichTextBox Height="auto" Width="auto"></RichTextBox>
    </DockPanel>
</r:RibbonWindow>

The next step will be to add a few menu item commands, and the first one we will add a few sub-items to so you can see how the rollout menus work. As part of a work around, we will need to also add a sized rectangle to the RecentItemList of the application menu so that it will draw large enough for us to have sub items without scrolling (similar to how office works).

<r:RibbonWindow x:Class="Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:r="clr-namespace:Microsoft.Windows.Controls.Ribbon;assembly=RibbonControlsLibrary"
    Title="My First Ribbon Form" ResizeMode="CanResizeWithGrip" WindowStartupLocation="CenterScreen"
    Icon="Images\app.png"
    Height="600" Width="800" MinHeight="300" MinWidth="400">

    <r:RibbonWindow.Resources>
        <ResourceDictionary>
            <r:RibbonCommand x:Key="QATButton" CanExecute="RibbonCommand_CanExecute" LabelTitle="QAT Button" LabelDescription="This is a sample QAT Button" ToolTipTitle="QAT Button" ToolTipDescription="This is a sample QAT Button, it doesn't do anything" SmallImageSource="Images\save.png" LargeImageSource="Images\save.png" />
            <r:RibbonCommand x:Key="MenuItem1" CanExecute="RibbonCommand_CanExecute" LabelTitle="Menu Item 1" LabelDescription="This is a sample menu item" ToolTipTitle="Menu Item 1" ToolTipDescription="This is a sample menu item" SmallImageSource="Images\files.png" LargeImageSource="Images\files.png" />
            <r:RibbonCommand x:Key="MenuItem2" CanExecute="RibbonCommand_CanExecute" LabelTitle="Menu Item 2" LabelDescription="This is a sample menu item" ToolTipTitle="Menu Item 2" ToolTipDescription="This is a sample menu item" SmallImageSource="Images\save.png" LargeImageSource="Images\save.png" />
            <r:RibbonCommand x:Key="MenuItem3" CanExecute="RibbonCommand_CanExecute" LabelTitle="Menu Item 3" LabelDescription="This is a sample menu item" ToolTipTitle="Menu Item 3" ToolTipDescription="This is a sample menu item" SmallImageSource="Images\print.png" LargeImageSource="Images\print.png" />
            <r:RibbonCommand x:Key="MenuItem4" CanExecute="RibbonCommand_CanExecute" LabelTitle="Menu Item 4" LabelDescription="This is a sample menu item" ToolTipTitle="Menu Item 4" ToolTipDescription="This is a sample menu item" SmallImageSource="Images\diagnostic.png" LargeImageSource="Images\diagnostic.png" />
            
        </ResourceDictionary>
    </r:RibbonWindow.Resources>

    <DockPanel>
        <r:Ribbon DockPanel.Dock="Top" Title="My First Ribbon Form" x:Name="mainRibbon">
            <r:Ribbon.QuickAccessToolBar>
                <r:RibbonQuickAccessToolBar>
                    <r:RibbonButton Command="{StaticResource QATButton}" />
                </r:RibbonQuickAccessToolBar>
            </r:Ribbon.QuickAccessToolBar>
            <r:Ribbon.ApplicationMenu>
                <r:RibbonApplicationMenu>
                    <r:RibbonApplicationMenu.Command>
                        <r:RibbonCommand SmallImageSource="Images\box.png" LargeImageSource="Images\box.png" />
                    </r:RibbonApplicationMenu.Command>
                    <r:RibbonApplicationMenuItem Command="{StaticResource MenuItem1}">
                        <TextBlock Text="Item 1 in the list" />
                        <TextBlock Text="Item 2 in the list" />
                        <TextBlock Text="Item 3 in the list" />
                        <TextBlock Text="Item 4 in the list" />
                    </r:RibbonApplicationMenuItem>
                    <r:RibbonApplicationMenuItem Command="{StaticResource MenuItem2}" />
                    <r:RibbonApplicationMenuItem Command="{StaticResource MenuItem3}" />
                    <r:RibbonApplicationMenuItem Command="{StaticResource MenuItem4}" />
                    <r:RibbonApplicationMenu.RecentItemList>
                        <Rectangle Height="300" />
                    </r:RibbonApplicationMenu.RecentItemList>
                </r:RibbonApplicationMenu>
            </r:Ribbon.ApplicationMenu>
        </r:Ribbon>
        <RichTextBox Height="auto" Width="auto"></RichTextBox>
    </DockPanel>
</r:RibbonWindow>

image

Now we can move along to the fun parts, and add a few ribbon tabs, and a few groups of buttons on those tabs.

<r:RibbonWindow x:Class="Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:r="clr-namespace:Microsoft.Windows.Controls.Ribbon;assembly=RibbonControlsLibrary"
    Title="My First Ribbon Form" ResizeMode="CanResizeWithGrip" WindowStartupLocation="CenterScreen"
    Icon="Images\app.png"
    Height="600" Width="800" MinHeight="300" MinWidth="400">

    <r:RibbonWindow.Resources>
        <ResourceDictionary>
            <r:RibbonCommand x:Key="QATButton" CanExecute="RibbonCommand_CanExecute" LabelTitle="QAT Button" LabelDescription="This is a sample QAT Button" ToolTipTitle="QAT Button" ToolTipDescription="This is a sample QAT Button, it doesn't do anything" SmallImageSource="Images\save.png" LargeImageSource="Images\save.png" />
            <r:RibbonCommand x:Key="MenuItem1" CanExecute="RibbonCommand_CanExecute" LabelTitle="Menu Item 1" LabelDescription="This is a sample menu item" ToolTipTitle="Menu Item 1" ToolTipDescription="This is a sample menu item" SmallImageSource="Images\files.png" LargeImageSource="Images\files.png" />
            <r:RibbonCommand x:Key="MenuItem2" CanExecute="RibbonCommand_CanExecute" LabelTitle="Menu Item 2" LabelDescription="This is a sample menu item" ToolTipTitle="Menu Item 2" ToolTipDescription="This is a sample menu item" SmallImageSource="Images\save.png" LargeImageSource="Images\save.png" />
            <r:RibbonCommand x:Key="MenuItem3" CanExecute="RibbonCommand_CanExecute" LabelTitle="Menu Item 3" LabelDescription="This is a sample menu item" ToolTipTitle="Menu Item 3" ToolTipDescription="This is a sample menu item" SmallImageSource="Images\print.png" LargeImageSource="Images\print.png" />
            <r:RibbonCommand x:Key="MenuItem4" CanExecute="RibbonCommand_CanExecute" LabelTitle="Menu Item 4" LabelDescription="This is a sample menu item" ToolTipTitle="Menu Item 4" ToolTipDescription="This is a sample menu item" SmallImageSource="Images\diagnostic.png" LargeImageSource="Images\diagnostic.png" />
            
            <r:RibbonCommand x:Key="HomeButton1" CanExecute="RibbonCommand_CanExecute" LabelTitle="Calculator" LabelDescription="Calc This!" ToolTipTitle="Calculator" ToolTipDescription="Used to do math and stuff" SmallImageSource="Images\calculator.png" LargeImageSource="Images\calculator.png" />
            <r:RibbonCommand x:Key="HomeButton2" CanExecute="RibbonCommand_CanExecute" LabelTitle="Calendar" LabelDescription="Schedule This!" ToolTipTitle="Calendar" ToolTipDescription="Schedule and remind yourself of stuff" SmallImageSource="Images\calendar.png" LargeImageSource="Images\calendar.png" />
            <r:RibbonCommand x:Key="HomeButton3" CanExecute="RibbonCommand_CanExecute" LabelTitle="Computer" LabelDescription="Format This!" ToolTipTitle="Computer" ToolTipDescription="Where you store your naked pictures" SmallImageSource="Images\computer.png" LargeImageSource="Images\computer.png" />
            
            <r:RibbonCommand x:Key="MediaEject" CanExecute="RibbonCommand_CanExecute" LabelTitle="Eject" LabelDescription="Eject" ToolTipTitle="Eject" ToolTipDescription="Open the cup holder" SmallImageSource="Images\bt_eject.png" LargeImageSource="Images\bt_eject.png" />
            <r:RibbonCommand x:Key="MediaBackward" CanExecute="RibbonCommand_CanExecute" LabelTitle="Previous" LabelDescription="Previous" ToolTipTitle="Previous" ToolTipDescription="Previous Tune" SmallImageSource="Images\bt_skip_backward.png" LargeImageSource="Images\bt_skip_backward.png" />
            <r:RibbonCommand x:Key="MediaPlay" CanExecute="RibbonCommand_CanExecute" LabelTitle="Play" LabelDescription="Play" ToolTipTitle="Play" ToolTipDescription="Play Tune" SmallImageSource="Images\bt_play.png" LargeImageSource="Images\bt_play.png" />
            <r:RibbonCommand x:Key="MediaStop" CanExecute="RibbonCommand_CanExecute" LabelTitle="Stop" LabelDescription="Stop" ToolTipTitle="Stop" ToolTipDescription="Stop the music" SmallImageSource="Images\bt_stop.png" LargeImageSource="Images\bt_stop.png" />
            <r:RibbonCommand x:Key="MediaForward" CanExecute="RibbonCommand_CanExecute" LabelTitle="Next" LabelDescription="Next" ToolTipTitle="Next" ToolTipDescription="Next Tune" SmallImageSource="Images\bt_skip_forward.png" LargeImageSource="Images\bt_skip_forward.png" />
        </ResourceDictionary>
    </r:RibbonWindow.Resources>

    <DockPanel>
        <r:Ribbon DockPanel.Dock="Top" Title="My First Ribbon Form" x:Name="mainRibbon">
            <r:Ribbon.QuickAccessToolBar>
                <r:RibbonQuickAccessToolBar>
                    <r:RibbonButton Command="{StaticResource QATButton}" />
                </r:RibbonQuickAccessToolBar>
            </r:Ribbon.QuickAccessToolBar>
            <r:Ribbon.ApplicationMenu>
                <r:RibbonApplicationMenu>
                    <r:RibbonApplicationMenu.Command>
                        <r:RibbonCommand SmallImageSource="Images\box.png" LargeImageSource="Images\box.png" />
                    </r:RibbonApplicationMenu.Command>
                    <r:RibbonApplicationMenuItem Command="{StaticResource MenuItem1}">
                        <TextBlock Text="Item 1 in the list" />
                        <TextBlock Text="Item 2 in the list" />
                        <TextBlock Text="Item 3 in the list" />
                        <TextBlock Text="Item 4 in the list" />
                    </r:RibbonApplicationMenuItem>
                    <r:RibbonApplicationMenuItem Command="{StaticResource MenuItem2}" />
                    <r:RibbonApplicationMenuItem Command="{StaticResource MenuItem3}" />
                    <r:RibbonApplicationMenuItem Command="{StaticResource MenuItem4}" />
                    <r:RibbonApplicationMenu.RecentItemList>
                        <Rectangle Height="300" />
                    </r:RibbonApplicationMenu.RecentItemList>
                </r:RibbonApplicationMenu>
            </r:Ribbon.ApplicationMenu>
            <r:RibbonTab Label="Home">
                <r:RibbonGroup>
                    <r:RibbonButton Command="{StaticResource HomeButton1}" />
                    <r:RibbonButton Command="{StaticResource HomeButton2}" />
                    <r:RibbonButton Command="{StaticResource HomeButton3}" />
                </r:RibbonGroup>
            </r:RibbonTab>
            <r:RibbonTab Label="Media">
                <r:RibbonGroup>
                    <r:RibbonButton Command="{StaticResource MediaEject}" />
                    <r:RibbonButton Command="{StaticResource MediaBackward}" />
                    <r:RibbonButton Command="{StaticResource MediaPlay}" />
                    <r:RibbonButton Command="{StaticResource MediaStop}" />
                    <r:RibbonButton Command="{StaticResource MediaForward}" />
                </r:RibbonGroup>
            </r:RibbonTab>
        </r:Ribbon>
        <RichTextBox Height="auto" Width="auto">
            <FlowDocument>
                <Paragraph>
                    <Hyperlink NavigateUri="http://www.renevo.com">RenEvo Software &amp; Designs</Hyperlink>
                </Paragraph>
            </FlowDocument>
        </RichTextBox>
    </DockPanel>
</r:RibbonWindow>

And the Preview:

image image

Now, lets work on those groups a bit, lets add a title to both groups, and make the icons for the media in a group.

<r:RibbonWindow x:Class="Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:r="clr-namespace:Microsoft.Windows.Controls.Ribbon;assembly=RibbonControlsLibrary"
    Title="My First Ribbon Form" ResizeMode="CanResizeWithGrip" WindowStartupLocation="CenterScreen"
    Icon="Images\app.png"
    Height="600" Width="800" MinHeight="300" MinWidth="400">

    <r:RibbonWindow.Resources>
        <ResourceDictionary>
            <r:RibbonCommand x:Key="QATButton" CanExecute="RibbonCommand_CanExecute" LabelTitle="QAT Button" LabelDescription="This is a sample QAT Button" ToolTipTitle="QAT Button" ToolTipDescription="This is a sample QAT Button, it doesn't do anything" SmallImageSource="Images\save.png" LargeImageSource="Images\save.png" />
            <r:RibbonCommand x:Key="MenuItem1" CanExecute="RibbonCommand_CanExecute" LabelTitle="Menu Item 1" LabelDescription="This is a sample menu item" ToolTipTitle="Menu Item 1" ToolTipDescription="This is a sample menu item" SmallImageSource="Images\files.png" LargeImageSource="Images\files.png" />
            <r:RibbonCommand x:Key="MenuItem2" CanExecute="RibbonCommand_CanExecute" LabelTitle="Menu Item 2" LabelDescription="This is a sample menu item" ToolTipTitle="Menu Item 2" ToolTipDescription="This is a sample menu item" SmallImageSource="Images\save.png" LargeImageSource="Images\save.png" />
            <r:RibbonCommand x:Key="MenuItem3" CanExecute="RibbonCommand_CanExecute" LabelTitle="Menu Item 3" LabelDescription="This is a sample menu item" ToolTipTitle="Menu Item 3" ToolTipDescription="This is a sample menu item" SmallImageSource="Images\print.png" LargeImageSource="Images\print.png" />
            <r:RibbonCommand x:Key="MenuItem4" CanExecute="RibbonCommand_CanExecute" LabelTitle="Menu Item 4" LabelDescription="This is a sample menu item" ToolTipTitle="Menu Item 4" ToolTipDescription="This is a sample menu item" SmallImageSource="Images\diagnostic.png" LargeImageSource="Images\diagnostic.png" />

            <r:RibbonCommand x:Key="HomeButton1" CanExecute="RibbonCommand_CanExecute" LabelTitle="Calculator" LabelDescription="Calc This!" ToolTipTitle="Calculator" ToolTipDescription="Used to do math and stuff" SmallImageSource="Images\calculator.png" LargeImageSource="Images\calculator.png" />
            <r:RibbonCommand x:Key="HomeButton2" CanExecute="RibbonCommand_CanExecute" LabelTitle="Calendar" LabelDescription="Schedule This!" ToolTipTitle="Calendar" ToolTipDescription="Schedule and remind yourself of stuff" SmallImageSource="Images\calendar.png" LargeImageSource="Images\calendar.png" />
            <r:RibbonCommand x:Key="HomeButton3" CanExecute="RibbonCommand_CanExecute" LabelTitle="Computer" LabelDescription="Format This!" ToolTipTitle="Computer" ToolTipDescription="Where you store your naked pictures" SmallImageSource="Images\computer.png" LargeImageSource="Images\computer.png" />

            <r:RibbonCommand x:Key="MediaEject" CanExecute="RibbonCommand_CanExecute" LabelTitle="Eject" LabelDescription="Eject" ToolTipTitle="Eject" ToolTipDescription="Open the cup holder" SmallImageSource="Images\bt_eject.png" LargeImageSource="Images\bt_eject.png" />
            <r:RibbonCommand x:Key="MediaBackward" CanExecute="RibbonCommand_CanExecute" LabelTitle="Previous" LabelDescription="Previous" ToolTipTitle="Previous" ToolTipDescription="Previous Tune" SmallImageSource="Images\bt_skip_backward.png" LargeImageSource="Images\bt_skip_backward.png" />
            <r:RibbonCommand x:Key="MediaPlay" CanExecute="RibbonCommand_CanExecute" LabelTitle="Play" LabelDescription="Play" ToolTipTitle="Play" ToolTipDescription="Play Tune" SmallImageSource="Images\bt_play.png" LargeImageSource="Images\bt_play.png" />
            <r:RibbonCommand x:Key="MediaStop" CanExecute="RibbonCommand_CanExecute" LabelTitle="Stop" LabelDescription="Stop" ToolTipTitle="Stop" ToolTipDescription="Stop the music" SmallImageSource="Images\bt_stop.png" LargeImageSource="Images\bt_stop.png" />
            <r:RibbonCommand x:Key="MediaForward" CanExecute="RibbonCommand_CanExecute" LabelTitle="Next" LabelDescription="Next" ToolTipTitle="Next" ToolTipDescription="Next Tune" SmallImageSource="Images\bt_skip_forward.png" LargeImageSource="Images\bt_skip_forward.png" />
        </ResourceDictionary>
    </r:RibbonWindow.Resources>

    <DockPanel>
        <r:Ribbon DockPanel.Dock="Top" Title="My First Ribbon Form" x:Name="mainRibbon">
            <r:Ribbon.QuickAccessToolBar>
                <r:RibbonQuickAccessToolBar>
                    <r:RibbonButton Command="{StaticResource QATButton}" />
                </r:RibbonQuickAccessToolBar>
            </r:Ribbon.QuickAccessToolBar>
            <r:Ribbon.ApplicationMenu>
                <r:RibbonApplicationMenu>
                    <r:RibbonApplicationMenu.Command>
                        <r:RibbonCommand SmallImageSource="Images\box.png" LargeImageSource="Images\box.png" />
                    </r:RibbonApplicationMenu.Command>
                    <r:RibbonApplicationMenuItem Command="{StaticResource MenuItem1}">
                        <TextBlock Text="Item 1 in the list" />
                        <TextBlock Text="Item 2 in the list" />
                        <TextBlock Text="Item 3 in the list" />
                        <TextBlock Text="Item 4 in the list" />
                    </r:RibbonApplicationMenuItem>
                    <r:RibbonApplicationMenuItem Command="{StaticResource MenuItem2}" />
                    <r:RibbonApplicationMenuItem Command="{StaticResource MenuItem3}" />
                    <r:RibbonApplicationMenuItem Command="{StaticResource MenuItem4}" />
                    <r:RibbonApplicationMenu.RecentItemList>
                        <Rectangle Height="300" />
                    </r:RibbonApplicationMenu.RecentItemList>
                </r:RibbonApplicationMenu>
            </r:Ribbon.ApplicationMenu>
            <r:RibbonTab Label="Home">
                <r:RibbonGroup>
                    <r:RibbonGroup.Command>
                        <r:RibbonCommand LabelTitle="Programs" />
                    </r:RibbonGroup.Command>
                    <r:RibbonGroup.GroupSizeDefinitions>
                        <r:RibbonGroupSizeDefinitionCollection>
                            <r:RibbonGroupSizeDefinition>
                                <r:RibbonControlSizeDefinition ImageSize="Large" IsLabelVisible="True" />
                                <r:RibbonControlSizeDefinition ImageSize="Large" IsLabelVisible="True" />
                                <r:RibbonControlSizeDefinition ImageSize="Large" IsLabelVisible="True" />
                            </r:RibbonGroupSizeDefinition>
                        </r:RibbonGroupSizeDefinitionCollection>
                    </r:RibbonGroup.GroupSizeDefinitions>

                    <r:RibbonButton Command="{StaticResource HomeButton1}" />
                    <r:RibbonButton Command="{StaticResource HomeButton2}" />
                    <r:RibbonButton Command="{StaticResource HomeButton3}" />
                </r:RibbonGroup>
            </r:RibbonTab>
            <r:RibbonTab Label="Media">
                <r:RibbonGroup>
                    <r:RibbonGroup.Command>
                        <r:RibbonCommand LabelTitle="Media Controls" />
                    </r:RibbonGroup.Command>
                    <r:RibbonControlGroup>
                        <r:RibbonButton Command="{StaticResource MediaEject}" />
                        <r:RibbonButton Command="{StaticResource MediaBackward}" />
                        <r:RibbonButton Command="{StaticResource MediaPlay}" />
                        <r:RibbonButton Command="{StaticResource MediaStop}" />
                        <r:RibbonButton Command="{StaticResource MediaForward}" />
                    </r:RibbonControlGroup>
                </r:RibbonGroup>
            </r:RibbonTab>
        </r:Ribbon>
        <RichTextBox Height="auto" Width="auto">
            <FlowDocument>
                <Paragraph>
                    <Hyperlink NavigateUri="http://www.renevo.com">RenEvo Software &amp; Designs</Hyperlink>
                </Paragraph>
            </FlowDocument>
        </RichTextBox>
    </DockPanel>
</r:RibbonWindow>

image image

And now lets add a single extra item to the QAT Drop down.

image

<r:RibbonWindow x:Class="Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:r="clr-namespace:Microsoft.Windows.Controls.Ribbon;assembly=RibbonControlsLibrary"
    Title="My First Ribbon Form" ResizeMode="CanResizeWithGrip" WindowStartupLocation="CenterScreen"
    Icon="Images\app.png"
    Height="600" Width="800" MinHeight="300" MinWidth="400">

    <r:RibbonWindow.Resources>
        <ResourceDictionary>
            <r:RibbonCommand x:Key="QATButton" CanExecute="RibbonCommand_CanExecute" LabelTitle="QAT Button" LabelDescription="This is a sample QAT Button" ToolTipTitle="QAT Button" ToolTipDescription="This is a sample QAT Button, it doesn't do anything" SmallImageSource="Images\save.png" LargeImageSource="Images\save.png" />
            <r:RibbonCommand x:Key="MenuItem1" CanExecute="RibbonCommand_CanExecute" LabelTitle="Menu Item 1" LabelDescription="This is a sample menu item" ToolTipTitle="Menu Item 1" ToolTipDescription="This is a sample menu item" SmallImageSource="Images\files.png" LargeImageSource="Images\files.png" />
            <r:RibbonCommand x:Key="MenuItem2" CanExecute="RibbonCommand_CanExecute" LabelTitle="Menu Item 2" LabelDescription="This is a sample menu item" ToolTipTitle="Menu Item 2" ToolTipDescription="This is a sample menu item" SmallImageSource="Images\save.png" LargeImageSource="Images\save.png" />
            <r:RibbonCommand x:Key="MenuItem3" CanExecute="RibbonCommand_CanExecute" LabelTitle="Menu Item 3" LabelDescription="This is a sample menu item" ToolTipTitle="Menu Item 3" ToolTipDescription="This is a sample menu item" SmallImageSource="Images\print.png" LargeImageSource="Images\print.png" />
            <r:RibbonCommand x:Key="MenuItem4" CanExecute="RibbonCommand_CanExecute" LabelTitle="Menu Item 4" LabelDescription="This is a sample menu item" ToolTipTitle="Menu Item 4" ToolTipDescription="This is a sample menu item" SmallImageSource="Images\diagnostic.png" LargeImageSource="Images\diagnostic.png" />

            <r:RibbonCommand x:Key="HomeButton1" CanExecute="RibbonCommand_CanExecute" LabelTitle="Calculator" LabelDescription="Calc This!" ToolTipTitle="Calculator" ToolTipDescription="Used to do math and stuff" SmallImageSource="Images\calculator.png" LargeImageSource="Images\calculator.png" />
            <r:RibbonCommand x:Key="HomeButton2" CanExecute="RibbonCommand_CanExecute" LabelTitle="Calendar" LabelDescription="Schedule This!" ToolTipTitle="Calendar" ToolTipDescription="Schedule and remind yourself of stuff" SmallImageSource="Images\calendar.png" LargeImageSource="Images\calendar.png" />
            <r:RibbonCommand x:Key="HomeButton3" CanExecute="RibbonCommand_CanExecute" LabelTitle="Computer" LabelDescription="Format This!" ToolTipTitle="Computer" ToolTipDescription="Where you store your naked pictures" SmallImageSource="Images\computer.png" LargeImageSource="Images\computer.png" />

            <r:RibbonCommand x:Key="MediaEject" CanExecute="RibbonCommand_CanExecute" LabelTitle="Eject" LabelDescription="Eject" ToolTipTitle="Eject" ToolTipDescription="Open the cup holder" SmallImageSource="Images\bt_eject.png" LargeImageSource="Images\bt_eject.png" />
            <r:RibbonCommand x:Key="MediaBackward" CanExecute="RibbonCommand_CanExecute" LabelTitle="Previous" LabelDescription="Previous" ToolTipTitle="Previous" ToolTipDescription="Previous Tune" SmallImageSource="Images\bt_skip_backward.png" LargeImageSource="Images\bt_skip_backward.png" />
            <r:RibbonCommand x:Key="MediaPlay" CanExecute="RibbonCommand_CanExecute" LabelTitle="Play" LabelDescription="Play" ToolTipTitle="Play" ToolTipDescription="Play Tune" SmallImageSource="Images\bt_play.png" LargeImageSource="Images\bt_play.png" />
            <r:RibbonCommand x:Key="MediaStop" CanExecute="RibbonCommand_CanExecute" LabelTitle="Stop" LabelDescription="Stop" ToolTipTitle="Stop" ToolTipDescription="Stop the music" SmallImageSource="Images\bt_stop.png" LargeImageSource="Images\bt_stop.png" />
            <r:RibbonCommand x:Key="MediaForward" CanExecute="RibbonCommand_CanExecute" LabelTitle="Next" LabelDescription="Next" ToolTipTitle="Next" ToolTipDescription="Next Tune" SmallImageSource="Images\bt_skip_forward.png" LargeImageSource="Images\bt_skip_forward.png" />
        </ResourceDictionary>
    </r:RibbonWindow.Resources>

    <DockPanel>
        <r:Ribbon DockPanel.Dock="Top" Title="My First Ribbon Form" x:Name="mainRibbon">
            <r:Ribbon.QuickAccessToolBar>
                <r:RibbonQuickAccessToolBar>
                    <r:RibbonButton Command="{StaticResource QATButton}" />
                    <r:RibbonButton Command="{StaticResource MediaEject}" r:RibbonQuickAccessToolBar.Placement="InCustomizeMenu" />
                </r:RibbonQuickAccessToolBar>
            </r:Ribbon.QuickAccessToolBar>
            <r:Ribbon.ApplicationMenu>
                <r:RibbonApplicationMenu>
                    <r:RibbonApplicationMenu.Command>
                        <r:RibbonCommand SmallImageSource="Images\box.png" LargeImageSource="Images\box.png" />
                    </r:RibbonApplicationMenu.Command>
                    <r:RibbonApplicationMenuItem Command="{StaticResource MenuItem1}">
                        <TextBlock Text="Item 1 in the list" />
                        <TextBlock Text="Item 2 in the list" />
                        <TextBlock Text="Item 3 in the list" />
                        <TextBlock Text="Item 4 in the list" />
                    </r:RibbonApplicationMenuItem>
                    <r:RibbonApplicationMenuItem Command="{StaticResource MenuItem2}" />
                    <r:RibbonApplicationMenuItem Command="{StaticResource MenuItem3}" />
                    <r:RibbonApplicationMenuItem Command="{StaticResource MenuItem4}" />
                    <r:RibbonApplicationMenu.RecentItemList>
                        <Rectangle Height="300" />
                    </r:RibbonApplicationMenu.RecentItemList>
                </r:RibbonApplicationMenu>
            </r:Ribbon.ApplicationMenu>
            <r:RibbonTab Label="Home">
                <r:RibbonGroup>
                    <r:RibbonGroup.Command>
                        <r:RibbonCommand LabelTitle="Programs" />
                    </r:RibbonGroup.Command>
                    <r:RibbonGroup.GroupSizeDefinitions>
                        <r:RibbonGroupSizeDefinitionCollection>
                            <r:RibbonGroupSizeDefinition>
                                <r:RibbonControlSizeDefinition ImageSize="Large" IsLabelVisible="True" />
                                <r:RibbonControlSizeDefinition ImageSize="Large" IsLabelVisible="True" />
                                <r:RibbonControlSizeDefinition ImageSize="Large" IsLabelVisible="True" />
                            </r:RibbonGroupSizeDefinition>
                        </r:RibbonGroupSizeDefinitionCollection>
                    </r:RibbonGroup.GroupSizeDefinitions>

                    <r:RibbonButton Command="{StaticResource HomeButton1}" />
                    <r:RibbonButton Command="{StaticResource HomeButton2}" />
                    <r:RibbonButton Command="{StaticResource HomeButton3}" />
                </r:RibbonGroup>
            </r:RibbonTab>
            <r:RibbonTab Label="Media">
                <r:RibbonGroup>
                    <r:RibbonGroup.Command>
                        <r:RibbonCommand LabelTitle="Media Controls" />
                    </r:RibbonGroup.Command>
                    <r:RibbonControlGroup>
                        <r:RibbonButton Command="{StaticResource MediaEject}" />
                        <r:RibbonButton Command="{StaticResource MediaBackward}" />
                        <r:RibbonButton Command="{StaticResource MediaPlay}" />
                        <r:RibbonButton Command="{StaticResource MediaStop}" />
                        <r:RibbonButton Command="{StaticResource MediaForward}" />
                    </r:RibbonControlGroup>
                </r:RibbonGroup>
            </r:RibbonTab>
        </r:Ribbon>
        <RichTextBox Height="auto" Width="auto">
            <FlowDocument>
                <Paragraph>
                    <Hyperlink NavigateUri="http://www.renevo.com">RenEvo Software &amp; Designs</Hyperlink>
                </Paragraph>
            </FlowDocument>
        </RichTextBox>
    </DockPanel>
</r:RibbonWindow>

Finally, if you want to switch to the Office 2007 look & feel, instead of the Windows 7 look & feel, simply change the Resource Dictionary (you can also create customized ones). you will want to remove the form’s icon if you use the office 2007 theme though.

<!-- Remove the Icon Property if you are going to use the Office 2007 themes-->
<r:RibbonWindow x:Class="Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:r="clr-namespace:Microsoft.Windows.Controls.Ribbon;assembly=RibbonControlsLibrary"
    Title="My First Ribbon Form" ResizeMode="CanResizeWithGrip" WindowStartupLocation="CenterScreen"
    Icon="Images\app.png"
    Height="600" Width="800" MinHeight="300" MinWidth="400">

    <r:RibbonWindow.Resources>
        <ResourceDictionary>
            <r:RibbonCommand x:Key="QATButton" CanExecute="RibbonCommand_CanExecute" LabelTitle="QAT Button" LabelDescription="This is a sample QAT Button" ToolTipTitle="QAT Button" ToolTipDescription="This is a sample QAT Button, it doesn't do anything" SmallImageSource="Images\save.png" LargeImageSource="Images\save.png" />
            <r:RibbonCommand x:Key="MenuItem1" CanExecute="RibbonCommand_CanExecute" LabelTitle="Menu Item 1" LabelDescription="This is a sample menu item" ToolTipTitle="Menu Item 1" ToolTipDescription="This is a sample menu item" SmallImageSource="Images\files.png" LargeImageSource="Images\files.png" />
            <r:RibbonCommand x:Key="MenuItem2" CanExecute="RibbonCommand_CanExecute" LabelTitle="Menu Item 2" LabelDescription="This is a sample menu item" ToolTipTitle="Menu Item 2" ToolTipDescription="This is a sample menu item" SmallImageSource="Images\save.png" LargeImageSource="Images\save.png" />
            <r:RibbonCommand x:Key="MenuItem3" CanExecute="RibbonCommand_CanExecute" LabelTitle="Menu Item 3" LabelDescription="This is a sample menu item" ToolTipTitle="Menu Item 3" ToolTipDescription="This is a sample menu item" SmallImageSource="Images\print.png" LargeImageSource="Images\print.png" />
            <r:RibbonCommand x:Key="MenuItem4" CanExecute="RibbonCommand_CanExecute" LabelTitle="Menu Item 4" LabelDescription="This is a sample menu item" ToolTipTitle="Menu Item 4" ToolTipDescription="This is a sample menu item" SmallImageSource="Images\diagnostic.png" LargeImageSource="Images\diagnostic.png" />

            <r:RibbonCommand x:Key="HomeButton1" CanExecute="RibbonCommand_CanExecute" LabelTitle="Calculator" LabelDescription="Calc This!" ToolTipTitle="Calculator" ToolTipDescription="Used to do math and stuff" SmallImageSource="Images\calculator.png" LargeImageSource="Images\calculator.png" />
            <r:RibbonCommand x:Key="HomeButton2" CanExecute="RibbonCommand_CanExecute" LabelTitle="Calendar" LabelDescription="Schedule This!" ToolTipTitle="Calendar" ToolTipDescription="Schedule and remind yourself of stuff" SmallImageSource="Images\calendar.png" LargeImageSource="Images\calendar.png" />
            <r:RibbonCommand x:Key="HomeButton3" CanExecute="RibbonCommand_CanExecute" LabelTitle="Computer" LabelDescription="Format This!" ToolTipTitle="Computer" ToolTipDescription="Where you store your naked pictures" SmallImageSource="Images\computer.png" LargeImageSource="Images\computer.png" />

            <r:RibbonCommand x:Key="MediaEject" CanExecute="RibbonCommand_CanExecute" LabelTitle="Eject" LabelDescription="Eject" ToolTipTitle="Eject" ToolTipDescription="Open the cup holder" SmallImageSource="Images\bt_eject.png" LargeImageSource="Images\bt_eject.png" />
            <r:RibbonCommand x:Key="MediaBackward" CanExecute="RibbonCommand_CanExecute" LabelTitle="Previous" LabelDescription="Previous" ToolTipTitle="Previous" ToolTipDescription="Previous Tune" SmallImageSource="Images\bt_skip_backward.png" LargeImageSource="Images\bt_skip_backward.png" />
            <r:RibbonCommand x:Key="MediaPlay" CanExecute="RibbonCommand_CanExecute" LabelTitle="Play" LabelDescription="Play" ToolTipTitle="Play" ToolTipDescription="Play Tune" SmallImageSource="Images\bt_play.png" LargeImageSource="Images\bt_play.png" />
            <r:RibbonCommand x:Key="MediaStop" CanExecute="RibbonCommand_CanExecute" LabelTitle="Stop" LabelDescription="Stop" ToolTipTitle="Stop" ToolTipDescription="Stop the music" SmallImageSource="Images\bt_stop.png" LargeImageSource="Images\bt_stop.png" />
            <r:RibbonCommand x:Key="MediaForward" CanExecute="RibbonCommand_CanExecute" LabelTitle="Next" LabelDescription="Next" ToolTipTitle="Next" ToolTipDescription="Next Tune" SmallImageSource="Images\bt_skip_forward.png" LargeImageSource="Images\bt_skip_forward.png" />

            <!-- Uncomment below for Office 2007 Blue -->
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="/RibbonControlsLibrary;component/Themes/Office2007Blue.xaml" />
            </ResourceDictionary.MergedDictionaries>
            <!-- Uncomment below for Office 2007 Silver -->
            <!--<ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="/RibbonControlsLibrary;component/Themes/Office2007Silver.xaml" />
            </ResourceDictionary.MergedDictionaries>-->
            <!-- Uncomment below for Office 2007 Black -->
            <!--<ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="/RibbonControlsLibrary;component/Themes/Office2007Black.xaml" />
            </ResourceDictionary.MergedDictionaries>-->
        </ResourceDictionary>
    </r:RibbonWindow.Resources>

    <DockPanel>
        <r:Ribbon DockPanel.Dock="Top" Title="My First Ribbon Form" x:Name="mainRibbon">
            <r:Ribbon.QuickAccessToolBar>
                <r:RibbonQuickAccessToolBar>
                    <r:RibbonButton Command="{StaticResource QATButton}" />
                    <r:RibbonButton Command="{StaticResource MediaEject}" r:RibbonQuickAccessToolBar.Placement="InCustomizeMenu" />
                </r:RibbonQuickAccessToolBar>
            </r:Ribbon.QuickAccessToolBar>
            <r:Ribbon.ApplicationMenu>
                <r:RibbonApplicationMenu>
                    <r:RibbonApplicationMenu.Command>
                        <r:RibbonCommand SmallImageSource="Images\box.png" LargeImageSource="Images\box.png" />
                    </r:RibbonApplicationMenu.Command>
                    <r:RibbonApplicationMenuItem Command="{StaticResource MenuItem1}">
                        <TextBlock Text="Item 1 in the list" />
                        <TextBlock Text="Item 2 in the list" />
                        <TextBlock Text="Item 3 in the list" />
                        <TextBlock Text="Item 4 in the list" />
                    </r:RibbonApplicationMenuItem>
                    <r:RibbonApplicationMenuItem Command="{StaticResource MenuItem2}" />
                    <r:RibbonApplicationMenuItem Command="{StaticResource MenuItem3}" />
                    <r:RibbonApplicationMenuItem Command="{StaticResource MenuItem4}" />
                    <r:RibbonApplicationMenu.RecentItemList>
                        <Rectangle Height="300" />
                    </r:RibbonApplicationMenu.RecentItemList>
                </r:RibbonApplicationMenu>
            </r:Ribbon.ApplicationMenu>
            <r:RibbonTab Label="Home">
                <r:RibbonGroup>
                    <r:RibbonGroup.Command>
                        <r:RibbonCommand LabelTitle="Programs" />
                    </r:RibbonGroup.Command>
                    <r:RibbonGroup.GroupSizeDefinitions>
                        <r:RibbonGroupSizeDefinitionCollection>
                            <r:RibbonGroupSizeDefinition>
                                <r:RibbonControlSizeDefinition ImageSize="Large" IsLabelVisible="True" />
                                <r:RibbonControlSizeDefinition ImageSize="Large" IsLabelVisible="True" />
                                <r:RibbonControlSizeDefinition ImageSize="Large" IsLabelVisible="True" />
                            </r:RibbonGroupSizeDefinition>
                        </r:RibbonGroupSizeDefinitionCollection>
                    </r:RibbonGroup.GroupSizeDefinitions>

                    <r:RibbonButton Command="{StaticResource HomeButton1}" />
                    <r:RibbonButton Command="{StaticResource HomeButton2}" />
                    <r:RibbonButton Command="{StaticResource HomeButton3}" />
                </r:RibbonGroup>
            </r:RibbonTab>
            <r:RibbonTab Label="Media">
                <r:RibbonGroup>
                    <r:RibbonGroup.Command>
                        <r:RibbonCommand LabelTitle="Media Controls" />
                    </r:RibbonGroup.Command>
                    <r:RibbonControlGroup>
                        <r:RibbonButton Command="{StaticResource MediaEject}" />
                        <r:RibbonButton Command="{StaticResource MediaBackward}" />
                        <r:RibbonButton Command="{StaticResource MediaPlay}" />
                        <r:RibbonButton Command="{StaticResource MediaStop}" />
                        <r:RibbonButton Command="{StaticResource MediaForward}" />
                    </r:RibbonControlGroup>
                </r:RibbonGroup>
            </r:RibbonTab>
        </r:Ribbon>
        <RichTextBox Height="auto" Width="auto">
            <FlowDocument>
                <Paragraph>
                    <Hyperlink NavigateUri="http://www.renevo.com">RenEvo Software &amp; Designs</Hyperlink>
                </Paragraph>
            </FlowDocument>
        </RichTextBox>
    </DockPanel>
</r:RibbonWindow>

image

And that is it, a very simple look into creating and using the new WPF Ribbon Control.  There are a lot more features, but this is just a taste.

You can download the code used in this article if you like which includes a whole host of images to play with.  You will however need to get the Ribbon Control and re-adjust the reference to your location before compiling (licensing and all).

Also, Microsoft has posted an introduction to this which I kind of learned from on windowsclient.net

kick it on DotNetKicks.com

Posted by Tom Anderson | 3 Comments

Removing My Namespace from VB.Net

Recently a question arose on Stackoverflow that asked if you could remove the My namespace from vb.net.

So, before I get going with this article, I want to state that the My Namespace does have a few uses, it provides instant access to resources, settings, and quick environment settings.

I also want to state that THIS IS NOT A REQUIRED FEATURE IN VB.NET. Did I stress that yet? It is however a default feature in VB.Net.

Anyway, lets get on with it.

First things first, backup your project directory, I don’t want to be responsible for you deleting any of your settings or resources because you want to remove something you are using.

Now that you did that (right?) lets move to the solution explorer and click on the option to “Show all files”.

image

Expand the “My Project” Node and select the “Application.myapp”, “Resources.resx”, and “Settings.settings” nodes. When I say nodes, that means items below “My Project”, this is a treeview.

image

Now, hit delete. This will remove any of the “My” code that has automatically already been added to your project. Go ahead and click on the “Show all files” button again to get back to a clean view.

Next, double click on “My Project” and navigate to the Compile tab, and click on the “Advanced Compile Options”. This dialog has all kinds of fun stuff in it, but we are only worried about one particular setting. Go ahead and click on “Enable Optimizations”.

image

Click “OK” and we are about 50% done.

Now, in the solution explorer, right click on your project and select “Unload Project”.

image

This will unload your project from the IDE, but retain the reference to it, another great thing about it is it allows us to edit the .vbproj file directly instead of through the UI, which is what is required for us to do the next step.

image

Look for the <MyType> xml tag, we need to set this to “Empty”, not Empty, but with the value of “Empty”. You may also need to change the <StartupObject> tag to reflect your main form if it is currently set to “My.Application”.

Below is the XML from the first PropertyGroup after modifying it.

   1:    <PropertyGroup>
   2:      <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
   3:      <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
   4:      <ProductVersion>9.0.30729</ProductVersion>
   5:      <SchemaVersion>2.0</SchemaVersion>
   6:      <ProjectGuid>{99D23E3F-D6D5-467F-AB1D-A594E40F4378}</ProjectGuid>
   7:      <OutputType>WinExe</OutputType>
   8:      <StartupObject>RemoveMyNamespace.Form1</StartupObject>
   9:      <RootNamespace>RemoveMyNamespace</RootNamespace>
  10:      <AssemblyName>RemoveMyNamespace</AssemblyName>
  11:      <FileAlignment>512</FileAlignment>
  12:      <MyType>Empty</MyType>
  13:      <TargetFrameworkVersion>v3.5</TargetFrameworkVersion>
  14:      <OptionExplicit>On</OptionExplicit>
  15:      <OptionCompare>Binary</OptionCompare>
  16:      <OptionStrict>Off</OptionStrict>
  17:      <OptionInfer>On</OptionInfer>
  18:    </PropertyGroup>

I bolded the change.

Save the file and close it, now right click on the project in the solution explorer and reload the project.

image

Compile it, and you can now view it in Reflector to see that the “My” namespace is completely removed.

image

We have now successfully remove the “My” namespace.

image

The keyword still exists, but it is now removed completely from your project.

kick it on DotNetKicks.com
Posted by Tom Anderson | 1 Comments

Custom Configuration Sections in Application Config files

A colleague of mine approached me the other day on how to build custom configuration section handlers in .Net.  As I have said in previous articles, I have been using “real” .config files now instead of pseudo .config, .cfg, or .xml files for complex application settings, and this has proven very portable for the configurations.

When creating custom configuration sections, there are essentially three steps.

  1. Provide a way to load the configuration section
  2. Define properties for the section
  3. Read/Write to the base object’s collection

A bit of knowledge here.  ConfigurationSection and ConfigurationElement are essentially property bags, you will be directing all of your properties in your classes to read and write to the Item collection where your property name is the key, and the value is, you guessed it, the value.

So lets start off easy, lets create the configuration section first and lets make it something simple, like a configuration section for our application windows.

   1:  Imports System.Configuration
   2:   
   3:  Public Class WindowSettings
   4:      Inherits ConfigurationSection
   5:   
   6:  End Class

Basic class, yes, now we want to define some primary properties for the section, lets add “applicationKey” so this is re-usable.

Above I mentioned that we are simply creating wrappers for the internal property bag, we also need to add an attribute to the property specifying the name of the attribute in the actual config file.

   1:      <ConfigurationProperty("applicationKey")> _
   2:      Public Property ApplicationKey() As String
   3:          Get
   4:              Return MyBase.Item("applicationKey")
   5:          End Get
   6:          Set(ByVal value As String)
   7:              MyBase.Item("applicationKey") = value
   8:          End Set
   9:      End Property

Pretty simply so far?

Now is an optional step that I like to use for testing, it really helps me work with these sections, below are some helper methods (both static and instanced) that I build for any custom section handlers, a base class is also recommended if you are going to be doing a lot of these.

   1:  #Region " Base Implementation For Load/Save "
   2:   
   3:      'only initialized internally
   4:      Protected Sub New()
   5:   
   6:      End Sub
   7:   
   8:      ''' <summary>
   9:      ''' Internal placeholder for configuration file
  10:      ''' </summary>
  11:      ''' <remarks></remarks>
  12:      Protected m_BaseConfiguration As Configuration = Nothing
  13:   
  14:      ''' <summary>
  15:      ''' Gets the custom configuration by path specifying a custom section name
  16:      ''' </summary>
  17:      ''' <param name="path">Path to the configuration file</param>
  18:      ''' <param name="section">Section in configuration file to load</param>
  19:      Public Shared Function LoadConfigSection(ByVal path As String, _
  20:                                               ByVal section As String) As WindowSettings
  21:          Dim retVal As New WindowSettings(), config As Configuration = Nothing
  22:   
  23:          Dim configFileMap As New ExeConfigurationFileMap()
  24:   
  25:          configFileMap.ExeConfigFilename = path
  26:          config = ConfigurationManager.OpenMappedExeConfiguration( _
  27:                                                              configFileMap, _
  28:                                                              ConfigurationUserLevel.None)
  29:   
  30:          If config.Sections(section) Is Nothing Then
  31:              config.Sections.Add(section, New WindowSettings)
  32:          End If
  33:   
  34:          retVal = TryCast(config.GetSection(section), WindowSettings)
  35:          retVal.m_BaseConfiguration = config
  36:   
  37:          Return retVal
  38:      End Function
  39:   
  40:      ''' <summary>
  41:      ''' Gets the custom configuration by path
  42:      ''' </summary>
  43:      ''' <param name="path"></param>
  44:      ''' <returns></returns>
  45:      ''' <remarks></remarks>
  46:      Public Shared Function LoadConfigSection(ByVal path As String) As WindowSettings
  47:          Return LoadConfigSection(path, "WindowSettings")
  48:      End Function
  49:   
  50:      ''' <summary>
  51:      ''' Gets the custom configuration
  52:      ''' </summary>
  53:      Public Shared Function LoadConfigSection() As WindowSettings
  54:          Return LoadConfigSection(Application.ExecutablePath & ".config")
  55:      End Function
  56:   
  57:      ''' <summary>
  58:      ''' Saves the current configuration
  59:      ''' </summary>
  60:      ''' <remarks></remarks>
  61:      Public Sub Save()
  62:          m_BaseConfiguration.Save(ConfigurationSaveMode.Minimal)
  63:      End Sub
  64:   
  65:  #End Region

This is the big bread and butter code that makes these easy to work with. First it makes the class construtor protected which prevents the class from accidentally being created, adds three overloaded methods for loading the config section, and then provides a save routine.

I am sure somewhere down the road I will be creating a generic implementation of this class so you can simply inherit from ConfigurationSection(of T).

So currently we have implemented all three steps required to create a basic configuration section in a standard .config file.

To load, set, and save simply do the following line of code.

   1:          Dim config As WindowSettings= WindowSettings.LoadConfigSection()
   2:          config.ApplicationKey = Application.ProductName
   3:          config.Save()

To retrieve the value, simply use the following one liner.

   1:          MessageBox.Show(WindowSettings.LoadConfigSection().ApplicationKey)

Well, all that is fine and dandy, but what if we need to do some collections in our settings, like saving each forms top, left, width, and height values?

Then we will create a ConfigurationElement and ConfigurationElementCollection.  First with the ConfigurationElement, it is almost identical to the ConfigurationSection as far as wrapping the property bags.  Although since we are setting form settings, lets add some default values to these properties so our forms are 0,0 location and 0,0 sizes.

   1:      Public Class WindowSetting
   2:          Inherits ConfigurationElement
   3:   
   4:          <ConfigurationProperty("id")> _
   5:          Public Property ID() As String
   6:              Get
   7:                  Return MyBase.Item("id")
   8:              End Get
   9:              Set(ByVal value As String)
  10:                  MyBase.Item("id") = value
  11:              End Set
  12:          End Property
  13:   
  14:          <ConfigurationProperty("top", DefaultValue:=100)> _
  15:          Public Property Top() As Integer
  16:              Get
  17:                  Return MyBase.Item("top")
  18:              End Get
  19:              Set(ByVal value As Integer)
  20:                  MyBase.Item("top") = value
  21:              End Set
  22:          End Property
  23:   
  24:          <ConfigurationProperty("left", DefaultValue:=100)> _
  25:          Public Property Left() As Integer
  26:              Get
  27:                  Return MyBase.Item("left")
  28:              End Get
  29:              Set(ByVal value As Integer)
  30:                  MyBase.Item("left") = value
  31:              End Set
  32:          End Property
  33:   
  34:          <ConfigurationProperty("height", DefaultValue:=600)> _
  35:          Public Property Height() As Integer
  36:              Get
  37:                  Return MyBase.Item("height")
  38:              End Get
  39:              Set(ByVal value As Integer)
  40:                  MyBase.Item("height") = value
  41:              End Set
  42:          End Property
  43:   
  44:          <ConfigurationProperty("width", DefaultValue:=800)> _
  45:          Public Property Width() As Integer
  46:              Get
  47:                  Return MyBase.Item("width")
  48:              End Get
  49:              Set(ByVal value As Integer)
  50:                  MyBase.Item("width") = value
  51:              End Set
  52:          End Property
  53:      End Class

 

This class is about as straight forward as they come, define properties, route to property bag, define attribute name, set default value.  We also added an ID property which will act as our key for our collection.

Which brings us to our Collection. ConfigurationElementCollections have two must overrides, CreateNewElement() and GetElementKey()  both of these methods are pretty easy to implement, and below is the most basic ConfigurationElementCollection implementation you can get by with. You must also specify an attribute to the class in order to name the element in the config file.

   1:      <ConfigurationCollection(GetType(WindowSetting), AddItemName:="windows")> _
   2:      Public Class WindowSettingCollection
   3:          Inherits ConfigurationElementCollection
   4:   
   5:          Protected Overloads Overrides Function CreateNewElement() _
   6:                                  As System.Configuration.ConfigurationElement
   7:              Return New WindowSetting
   8:          End Function
   9:   
  10:          Protected Overrides Function GetElementKey( _
  11:                                  ByVal element As System.Configuration.ConfigurationElement) _
  12:                                  As Object
  13:              Return DirectCast(element, WindowSetting).ID
  14:          End Function
  15:      End Class

That is it, but how much fun would it be if I just showed you that bit of code, and said deal with figuring out the rest on your own?  Personally, I like to add lots of helper methods to this class, again, this is another huge candidate for a generic.

   1:  <ConfigurationCollection(GetType(WindowSetting), AddItemName:="windows")> _
   2:      Public Class WindowSettingCollection
   3:          Inherits ConfigurationElementCollection
   4:   
   5:          ''' <summary>
   6:          ''' Creates a new element with default properties
   7:          ''' </summary>
   8:          Protected Overloads Overrides Function CreateNewElement() _
   9:                                          As System.Configuration.ConfigurationElement
  10:              Return New WindowSetting()
  11:          End Function
  12:   
  13:          ''' <summary>
  14:          ''' Creates a new element with the id specified
  15:          ''' </summary>
  16:          ''' <param name="id">id of the new element</param>
  17:          Protected Overloads Function CreateNewElement(ByVal id As String) _
  18:                                          As System.Configuration.ConfigurationElement
  19:              Return New WindowSetting() With {.ID = id}
  20:          End Function
  21:   
  22:          ''' <summary>
  23:          ''' Gets an element's key
  24:          ''' </summary>
  25:          ''' <param name="element">element to test</param>
  26:          Protected Overrides Function GetElementKey( _
  27:                                          ByVal element As System.Configuration.ConfigurationElement) _
  28:                                          As Object
  29:              Return DirectCast(element, WindowSetting).ID
  30:          End Function
  31:   
  32:          ''' <summary>
  33:          ''' Adds a new element to the collection
  34:          ''' </summary>
  35:          ''' <param name="element"></param>
  36:          ''' <remarks></remarks>
  37:          Public Sub Add(ByVal element As WindowSetting)
  38:              MyBase.BaseAdd(element)
  39:          End Sub
  40:   
  41:          ''' <summary>
  42:          ''' Adds a new element to the collection by id
  43:          ''' </summary>
  44:          ''' <param name="id">id of the new element</param>
  45:          Public Function AddNew(ByVal id As String) As WindowSetting
  46:              Dim newElement As WindowSetting = Me.CreateNewElement(id)
  47:              Add(newElement)
  48:              Return newElement
  49:          End Function
  50:   
  51:          ''' <summary>
  52:          ''' Adds a new element to the collection by form
  53:          ''' </summary>
  54:          ''' <param name="form">form for the new element</param>
  55:          Public Function AddNew(ByVal form As System.Windows.Forms.Form) As WindowSetting
  56:              Dim newElement As WindowSetting = Me.CreateNewElement(form.Name)
  57:              newElement.Top = form.Top
  58:              newElement.Left = form.Left
  59:              newElement.Height = form.Height
  60:              newElement.Width = form.Width
  61:              Add(newElement)
  62:              Return newElement
  63:          End Function
  64:   
  65:          ''' <summary>
  66:          ''' Removes an element by id
  67:          ''' </summary>
  68:          ''' <param name="id">id of the element to remove</param>
  69:          Public Sub Remove(ByVal id As String)
  70:              MyBase.BaseRemove(id)
  71:          End Sub
  72:   
  73:          ''' <summary>
  74:          ''' Clears all elements from the collection
  75:          ''' </summary>
  76:          Public Sub Clear()
  77:              MyBase.BaseClear()
  78:          End Sub
  79:   
  80:          ''' <summary>
  81:          ''' Retrieves an item from the collection by index
  82:          ''' </summary>
  83:          ''' <param name="index">index of the element</param>
  84:          Default Public Overloads ReadOnly Property Item(ByVal index As Integer) As WindowSetting
  85:              Get
  86:                  Return MyBase.BaseGet(index)
  87:              End Get
  88:          End Property
  89:   
  90:          ''' <summary>
  91:          ''' Retrieves an item from the collection by id
  92:          ''' </summary>
  93:          ''' <param name="id">id of the element</param>
  94:          Default Public Overloads ReadOnly Property Item(ByVal id As String) As WindowSetting
  95:              Get
  96:                  'auto-add if not exists
  97:                  If MyBase.BaseGet(id) Is Nothing Then
  98:                      Me.AddNew(id)
  99:                  End If
 100:   
 101:                  Return Me.BaseGet(id)
 102:              End Get
 103:          End Property
 104:   
 105:      End Class

All of those additional methods will give you all the tools you need to create a CRUD interface to the collection. I also added in an AddNew that simply takes a form object.

Finally to add the collection to the section, simply add a property for it.

   1:      <ConfigurationProperty("windows")> _
   2:      Public ReadOnly Property Windows() As WindowSettingCollection
   3:          Get
   4:              Return Me.Item("windows")
   5:          End Get
   6:      End Property

I also added IDisposable support to the WindowSettings class for ease of use as well as two helper methods for loading and saving form settings.

   1:      ''' <summary>
   2:      ''' Helper method to load form settings
   3:      ''' </summary>
   4:      ''' <param name="form">form to load</param>
   5:      Public Sub LoadFormSettings(ByVal form As System.Windows.Forms.Form)
   6:          With form
   7:              .Top = Me.Windows(form.Name).Top
   8:              .Left = Me.Windows(form.Name).Left
   9:              .Height = Me.Windows(form.Name).Height
  10:              .Width = Me.Windows(form.Name).Width
  11:          End With
  12:      End Sub
  13:   
  14:      ''' <summary>
  15:      ''' Helper method to save form settings
  16:      ''' </summary>
  17:      ''' <param name="form"></param>
  18:      ''' <param name="saveConfig">Optional parameter, when true will save the configuration file</param>
  19:      ''' <remarks></remarks>
  20:      Public Sub SaveFormSettings(ByVal form As System.Windows.Forms.Form, Optional ByVal saveConfig As Boolean = False)
  21:          With form
  22:              Me.Windows(form.Name).Top = .Top
  23:              Me.Windows(form.Name).Left = .Left
  24:              Me.Windows(form.Name).Height = .Height
  25:              Me.Windows(form.Name).Width = .Width
  26:          End With
  27:   
  28:          If saveConfig Then
  29:              Me.Save()
  30:          End If
  31:      End Sub

Now to use it!  In our forms Load event handler, add the following code:

   1:          Using config As WindowSettings = WindowSettings.LoadConfigSection()
   2:              config.ApplicationKey = Application.ProductName
   3:              config.LoadFormSettings(Me)
   4:          End Using

In our forms closing event handler, add the following code:

   1:          Using config As WindowSettings = WindowSettings.LoadConfigSection()
   2:              config.ApplicationKey = Application.ProductName
   3:              config.SaveFormSettings(Me, True)
   4:          End Using

 

And for the curious, here is the resulting app.config.

   1:  <configuration>
   2:      <configSections>
   3:          <section name="WindowSettings" type="SandBoxVB.WindowSettings, SandBoxVB, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
   4:      </configSections>
   5:      <WindowSettings applicationKey="SandBoxVB">
   6:          <windows>
   7:              <windows id="Form1" top="215" left="313" height="334" width="479" />
   8:          </windows>
   9:      </WindowSettings>
  10:  </configuration>

 

Below is the full code for the WindowSettings Section Handler.

   1:  Imports System.Configuration
   2:   
   3:  Public Class WindowSettings
   4:      Inherits ConfigurationSection
   5:      Implements IDisposable
   6:   
   7:  #Region " Base Implementation For Load/Save "
   8:   
   9:      'only initialized internally
  10:      Protected Sub New()
  11:   
  12:      End Sub
  13:   
  14:      ''' <summary>
  15:      ''' Internal placeholder for configuration file
  16:      ''' </summary>
  17:      ''' <remarks></remarks>
  18:      Protected m_BaseConfiguration As Configuration = Nothing
  19:   
  20:      ''' <summary>
  21:      ''' Gets the custom configuration by path specifying a custom section name
  22:      ''' </summary>
  23:      ''' <param name="path">Path to the configuration file</param>
  24:      ''' <param name="section">Section in configuration file to load</param>
  25:      Public Shared Function LoadConfigSection(ByVal path As String, _
  26:                                               ByVal section As String) As WindowSettings
  27:          Dim retVal As New WindowSettings(), config As Configuration = Nothing
  28:   
  29:          Dim configFileMap As New ExeConfigurationFileMap()
  30:   
  31:          configFileMap.ExeConfigFilename = path
  32:          config = ConfigurationManager.OpenMappedExeConfiguration( _
  33:                                                              configFileMap, _
  34:                                                              ConfigurationUserLevel.None)
  35:   
  36:          If config.Sections(section) Is Nothing Then
  37:              config.Sections.Add(section, New WindowSettings)
  38:          End If
  39:   
  40:          retVal = TryCast(config.GetSection(section), WindowSettings)
  41:          retVal.m_BaseConfiguration = config
  42:   
  43:          Return retVal
  44:      End Function
  45:   
  46:      ''' <summary>
  47:      ''' Gets the custom configuration by path
  48:      ''' </summary>
  49:      ''' <param name="path"></param>
  50:      ''' <returns></returns>
  51:      ''' <remarks></remarks>
  52:      Public Shared Function LoadConfigSection(ByVal path As String) As WindowSettings
  53:          Return LoadConfigSection(path, "WindowSettings")
  54:      End Function
  55:   
  56:      ''' <summary>
  57:      ''' Gets the custom configuration
  58:      ''' </summary>
  59:      Public Shared Function LoadConfigSection() As WindowSettings
  60:          Return LoadConfigSection(Application.ExecutablePath & ".config")
  61:      End Function
  62:   
  63:      ''' <summary>
  64:      ''' Saves the current configuration
  65:      ''' </summary>
  66:      ''' <remarks></remarks>
  67:      Public Sub Save()
  68:          m_BaseConfiguration.Save(ConfigurationSaveMode.Minimal)
  69:      End Sub
  70:   
  71:  #End Region
  72:   
  73:      ''' <summary>
  74:      ''' Application key for the project, 
  75:      ''' Useful when loading multiples from the save file.
  76:      ''' </summary>
  77:      <ConfigurationProperty("applicationKey")> _
  78:      Public Property ApplicationKey() As String
  79:          Get
  80:              Return MyBase.Item("applicationKey")
  81:          End Get
  82:          Set(ByVal value As String)
  83:              MyBase.Item("applicationKey") = value
  84:          End Set
  85:      End Property
  86:   
  87:      ''' <summary>
  88:      ''' Retrieves a collection of windows in the application
  89:      ''' </summary>
  90:      <ConfigurationProperty("windows")> _
  91:      Public ReadOnly Property Windows() As WindowSettingCollection
  92:          Get
  93:              Return Me.Item("windows")
  94:          End Get
  95:      End Property
  96:   
  97:      ''' <summary>
  98:      ''' Helper method to load form settings
  99:      ''' </summary>
 100:      ''' <param name="form">form to load</param>
 101:      Public Sub LoadFormSettings(ByVal form As System.Windows.Forms.Form)
 102:          With form
 103:              .Top = Me.Windows(form.Name).Top
 104:              .Left = Me.Windows(form.Name).Left
 105:              .Height = Me.Windows(form.Name).Height
 106:              .Width = Me.Windows(form.Name).Width
 107:          End With
 108:      End Sub
 109:   
 110:      ''' <summary>
 111:      ''' Helper method to save form settings
 112:      ''' </summary>
 113:      ''' <param name="form"></param>
 114:      ''' <param name="saveConfig">
 115:      ''' Optional parameter, when true will save the configuration file
 116:      ''' </param>
 117:      ''' <remarks></remarks>
 118:      Public Sub SaveFormSettings(ByVal form As System.Windows.Forms.Form, _
 119:                                      Optional ByVal saveConfig As Boolean = False)
 120:          With form
 121:              Me.Windows(form.Name).Top = .Top
 122:              Me.Windows(form.Name).Left = .Left
 123:              Me.Windows(form.Name).Height = .Height
 124:              Me.Windows(form.Name).Width = .Width
 125:          End With
 126:   
 127:          If saveConfig Then
 128:              Me.Save()
 129:          End If
 130:      End Sub
 131:   
 132:      Public Class WindowSetting
 133:          Inherits ConfigurationElement
 134:   
 135:          <ConfigurationProperty("id")> _
 136:          Public Property ID() As String
 137:              Get
 138:                  Return MyBase.Item("id")
 139:              End Get
 140:              Set(ByVal value As String)
 141:                  MyBase.Item("id") = value
 142:              End Set
 143:          End Property
 144:   
 145:          <ConfigurationProperty("top", DefaultValue:=100)> _
 146:          Public Property Top() As Integer
 147:              Get
 148:                  Return MyBase.Item("top")
 149:              End Get
 150:              Set(ByVal value As Integer)
 151:                  MyBase.Item("top") = value
 152:              End Set
 153:          End Property
 154:   
 155:          <ConfigurationProperty("left", DefaultValue:=100)> _
 156:          Public Property Left() As Integer
 157:              Get
 158:                  Return MyBase.Item("left")
 159:              End Get
 160:              Set(ByVal value As Integer)
 161:                  MyBase.Item("left") = value
 162:              End Set
 163:          End Property
 164:   
 165:          <ConfigurationProperty("height", DefaultValue:=600)> _
 166:          Public Property Height() As Integer
 167:              Get
 168:                  Return MyBase.Item("height")
 169:              End Get
 170:              Set(ByVal value As Integer)
 171:                  MyBase.Item("height") = value
 172:              End Set
 173:          End Property
 174:   
 175:          <ConfigurationProperty("width", DefaultValue:=800)> _
 176:          Public Property Width() As Integer
 177:              Get
 178:                  Return MyBase.Item("width")
 179:              End Get
 180:              Set(ByVal value As Integer)
 181:                  MyBase.Item("width") = value
 182:              End Set
 183:          End Property
 184:      End Class
 185:   
 186:      <ConfigurationCollection(GetType(WindowSetting), AddItemName:="windows")> _
 187:      Public Class WindowSettingCollection
 188:          Inherits ConfigurationElementCollection
 189:   
 190:          ''' <summary>
 191:          ''' Creates a new element with default properties
 192:          ''' </summary>
 193:          Protected Overloads Overrides Function CreateNewElement() _
 194:                                          As System.Configuration.ConfigurationElement
 195:              Return New WindowSetting()
 196:          End Function
 197:   
 198:          ''' <summary>
 199:          ''' Creates a new element with the id specified
 200:          ''' </summary>
 201:          ''' <param name="id">id of the new element</param>
 202:          Protected Overloads Function CreateNewElement(ByVal id As String) _
 203:                                          As System.Configuration.ConfigurationElement
 204:              Return New WindowSetting() With {.ID = id}
 205:          End Function
 206:   
 207:          ''' <summary>
 208:          ''' Gets an element's key
 209:          ''' </summary>
 210:          ''' <param name="element">element to test</param>
 211:          Protected Overrides Function GetElementKey( _
 212:                                  ByVal element As System.Configuration.ConfigurationElement) _
 213:                                  As Object
 214:              Return DirectCast(element, WindowSetting).ID
 215:          End Function
 216:   
 217:          ''' <summary>
 218:          ''' Adds a new element to the collection
 219:          ''' </summary>
 220:          ''' <param name="element"></param>
 221:          ''' <remarks></remarks>
 222:          Public Sub Add(ByVal element As WindowSetting)
 223:              MyBase.BaseAdd(element)
 224:          End Sub
 225:   
 226:          ''' <summary>
 227:          ''' Adds a new element to the collection by id
 228:          ''' </summary>
 229:          ''' <param name="id">id of the new element</param>
 230:          Public Function AddNew(ByVal id As String) As WindowSetting
 231:              Dim newElement As WindowSetting = Me.CreateNewElement(id)
 232:              Add(newElement)
 233:              Return newElement
 234:          End Function
 235:   
 236:          ''' <summary>
 237:          ''' Adds a new element to the collection by form
 238:          ''' </summary>
 239:          ''' <param name="form">form for the new element</param>
 240:          Public Function AddNew(ByVal form As System.Windows.Forms.Form) _
 241:                                                                  As WindowSetting
 242:              Dim newElement As WindowSetting = Me.CreateNewElement(form.Name)
 243:              newElement.Top = form.Top
 244:              newElement.Left = form.Left
 245:              newElement.Height = form.Height
 246:              newElement.Width = form.Width
 247:              Add(newElement)
 248:              Return newElement
 249:          End Function
 250:   
 251:          ''' <summary>
 252:          ''' Removes an element by id
 253:          ''' </summary>
 254:          ''' <param name="id">id of the element to remove</param>
 255:          Public Sub Remove(ByVal id As String)
 256:              MyBase.BaseRemove(id)
 257:          End Sub
 258:   
 259:          ''' <summary>
 260:          ''' Clears all elements from the collection
 261:          ''' </summary>
 262:          Public Sub Clear()
 263:              MyBase.BaseClear()
 264:          End Sub
 265:   
 266:          ''' <summary>
 267:          ''' Retrieves an item from the collection by index
 268:          ''' </summary>
 269:          ''' <param name="index">index of the element</param>
 270:          Default Public Overloads ReadOnly Property Item(ByVal index As Integer) _
 271:                                                                      As WindowSetting
 272:              Get
 273:                  Return MyBase.BaseGet(index)
 274:              End Get
 275:          End Property
 276:   
 277:          ''' <summary>
 278:          ''' Retrieves an item from the collection by id
 279:          ''' </summary>
 280:          ''' <param name="id">id of the element</param>
 281:          Default Public Overloads ReadOnly Property Item(ByVal id As String) _
 282:                                                                      As WindowSetting
 283:              Get
 284:                  'auto-add if not exists
 285:                  If MyBase.BaseGet(id) Is Nothing Then
 286:                      Me.AddNew(id)
 287:                  End If
 288:   
 289:                  Return Me.BaseGet(id)
 290:              End Get
 291:          End Property
 292:   
 293:      End Class
 294:   
 295:  #Region " IDisposable Support "
 296:   
 297:      Private disposedValue As Boolean = False        ' To detect redundant calls
 298:   
 299:      ' IDisposable
 300:      Protected Overridable Sub Dispose(ByVal disposing As Boolean)
 301:          If Not Me.disposedValue Then
 302:              If disposing Then
 303:   
 304:              End If
 305:   
 306:          End If
 307:          Me.disposedValue = True
 308:      End Sub
 309:   
 310:      ' This code added by Visual Basic to correctly implement the disposable pattern.
 311:      Public Sub Dispose() Implements IDisposable.Dispose
 312:          ' Do not change this code.
 313:          Dispose(True)
 314:          GC.SuppressFinalize(Me)
 315:      End Sub
 316:   
 317:  #End Region
 318:   
 319:  End Class
 320:   
 321:   

 

C# Version provided by request.

 

*Sorry about formatting, I don’t normally use underscores to break lines other than for attributes, but the page is skinny and I didn’t want any overrun.

Posted by Tom Anderson | 0 Comments

Creating Compilable User Files in Visual Studio 2008

Back in my c++ days when working on projects with other people in source control etc… I would do all kinds of weird things, things like include files for coding only for me.

Example:

#ifdef (TOM)
include “Tom.h”;
#endif

This allowed me to call code that I didn’t quite want out of my grasp yet, but I wanted in source control.  Naturally the “Tom.h” and its associated files where not in source control, or even included in the project. I kind of missed that feature, and investigated today how to get the same functionality.

Behold the *.proj.user files in Visual Studio 2008 (might work in 2005, who knows, I didn’t try).

Normal source control will keep the .user files out of source control, as these are generally used to store debug information, local configurations, etc… There is nothing in the rulebook that stated I couldn’t use this file to also compile “user” code. You can create a .user file simply by adding it in the same directory as your *.csproj file with the <Project> tags.  The *.csproj files (and *.vbproj files) are simply msbuild file format, so you can check up on the MSDN documentation on how to work with it.

My “Sandbox.csproj.user” file:

   1:  <Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
   2:    <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
   3:      <UserConfig>Tom</UserConfig>
   4:      <DefineConstants>DEBUG;TRACE;TOM</DefineConstants>
   5:    </PropertyGroup>
   6:    <ItemGroup Condition=" '$(UserConfig)' == 'Tom'">
   7:      <Compile Include="Tom.cs" />
   8:    </ItemGroup>
   9:  </Project>

So, basically what is going on here is that I added a new property in the Debug configuration for “UserConfig”, this is used later for a conditional, and thats about it.  I then added “TOM” to the defined constants.

I then added an ItemGroup with the condition of the UserConfig being “Tom”, clever eh?  Inside this ItemGroup I simply added a Compile for Tom.cs.  This file is pretty barebones, and does nothing, but here it is anyway.

   1:  using System;
   2:  using System.Collections.Generic;
   3:  using System.Linq;
   4:  using System.Text;
   5:   
   6:  namespace Sandbox
   7:  {
   8:      class Tom
   9:      {
  10:      }
  11:  }

Then in my main application, I simply do an #if on the TOM constant and insert my code in that block.

   1:  #if (TOM)
   2:              Tom t = new Tom();
   3:  #endif

Pretty nifty eh?

What is even better, if I switch to release mode Line 2 above grays out (letting me know it isn’t compiled) and my “Tom” class isn’t included in the compile either (verified with Reflector).

The only issue is that it doesn’t show up in the solution explorer, but neither did the c++ counter parts, this also has the nice beauty of not adding the file to source control, so now I can do all that “tom machine” specific coding I need to to bang out that gong feature.

image

So, if you want to do some code and keep it checked in without screwing everyone else up, or have your own set of tools you want to be stingy with, this method might work great for you!

 

*Warning: Code in the #if (TOM) blocks will be visible in source control, no one will have the compiled version of it, and unless they declare the constant, their code will be safe as code in conditional blocks that don’t meet the condition don’t get compiled.

Posted by Tom Anderson | 0 Comments

Proper Thread Work with WF

Most inexperienced developers tend to put workload heavy operations on the same thread that the UI runs on. While you can call ‘Application.DoEvents()’ to keep the windows messages flowing, this is not the proper way to keep your UI painted. In this article I will be explaining how to put your workload heavy operations on a separate thread, and how to properly update your form with information you want the user to see.

Let’s start with a simple snippet.

FileInfo[] files = new DirectoryInfo(@"C:\Windows").GetFiles();

Depending on the the size of the directory, this operation could potentially take several minutes or more. During this period, your UI cannot paint itself or receive windows messages. This means, your user thinks the application has locked up, or crashed. This is where threads come into play.

        private void MainForm_Shown(object sender, EventArgs e) {
            Thread thread = new Thread(new ThreadStart(ThreadProc));
            thread.IsBackground = true;
            thread.Start();
        }

The code above simply starts a new thread that runs on the ‘ThreadProc’ method. This will allow our form to run its own code, while the thread is running. Essentially with the result that we can run our workload heavy operations, without hindering the form. Below is the ‘ThreadProc’ method, which contains our workload heavy operation to simply read a directory and its files.       

        /// <summary>
        /// Method to process our workload
        /// </summary>
        private void ThreadProc() {
            FileInfo[] files = new DirectoryInfo(@"C:\Windows").GetFiles();
            UpdateActionDelegate uxad = new UpdateActionDelegate(UpdateAction);

            for (int i = 0; i < files.Length; i++) {
                DateTime time = DateTime.Now;

                if ((time - m_Time).Milliseconds >= 50) {
                    m_Time = time;

                    // Now we can update the thread since
                    // we waited the 50ms
                    uxAction.Invoke(uxad, files[ i ].Name);

                    // We could also invoke the mainform directly
                    // and have access to all of the controls
                    //this.Invoke(uxad, item.Name);

                    // Just an example since we are not really processing
                    // anything, so we want to see whats happening
                    Thread.Sleep(250);
                }
            }
        }

There are two key points to this method body.

1. We use a delegate called UpdateActionDelegate, which we use to properly invoke a label named ‘uxAction’ on the form, to safely update its text.


2. We use a private member named ‘m_DateTime’ of type DateTime, which we use to check if 50ms of time has passed between each iteration in the for-loop.

The 50ms is simply a delay. If you have no delay, the systems CPU will show it cycling at 100%, and depending on the clients system, they may have intense flickering on the form. This occurs because the operation would normally try to update the text of the label faster than the form can paint itself. This is why we implemented a 50ms delay before updating the label again. Below is the full code of the MainForm class, which demonstrates this in action. I have also attached the project files in a ZIP.

// *********************************************************************
// [RenEvo Software & Designs]
// [RenEvo], [Proper thread work with WF]
//
//   THIS FILE IS PROVIDED "AS-IS" WITHOUT ANY WARRANTY OF ANY KIND. ANY
//   MODIFICATIONS TO THIS FILE IN ANY WAY ARE YOUR SOLE RESPONSIBILITY.
//
// [Copyright (C) RenEvo Software & Designs  All rights reserved.]
// *********************************************************************

namespace ThreadUIExample {
    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Data;
    using System.Drawing;
    using System.Linq;
    using System.Text;
    using System.Windows.Forms;
    using System.Collections;
    using System.IO;
    using System.Threading;

    public partial class MainForm : Form {
        public MainForm() {
            InitializeComponent();
        }

        private void MainForm_Shown(object sender, EventArgs e) {
            Thread thread = new Thread(new ThreadStart(ThreadProc));
            thread.IsBackground = true;
            thread.Start();
        }

        /// <summary>
        /// Used to check if 50ms of time has passed since the last file was read
        /// </summary>
        private DateTime m_Time = DateTime.Now;

        /// <summary>
        /// Delegate used to safely invoke the action label
        /// </summary>
        /// <param name="action"></param>
        private delegate void UpdateActionDelegate(string action);

        /// <summary>
        /// Callback used to safely update the text of the action label
        /// when the label is properly invoked
        /// </summary>
        public void UpdateAction(string action) {
            uxAction.Text = string.Format("Action: {0}", action);
        }

        /// <summary>
        /// Method to process our workload
        /// </summary>
        private void ThreadProc() {
            FileInfo[] files = new DirectoryInfo(@"C:\Windows").GetFiles();
            UpdateActionDelegate uxad = new UpdateActionDelegate(UpdateAction);

            for (int i = 0; i < files.Length; i++) {
                DateTime time = DateTime.Now;

                if ((time - m_Time).Milliseconds >= 50) {
                    m_Time = time;

                    // Now we can update the thread since
                    // we waited the 50ms
                    uxAction.Invoke(uxad, files[ i ].Name);

                    // We could also invoke the mainform directly
                    // and have access to all of the controls
                    //this.Invoke(uxad, item.Name);

                    // Just an example since we are not really processing
                    // anything, so we want to see whats happening
                    Thread.Sleep(250);
                }
            }
        }
    }
}
Posted by Dave Anderson | 1 Comments

Attachment(s): ThreadUIExample.zip

New code snippet plugin for Live Writer

I am trying out a new code snippet plug-in for Live Writer.

Public Class CommitDB
    Public Function GetCommitStatusAll() As DataSet
        ' Create Instance of Connection and Command Object
        Dim myConnection As New SqlConnection(ConfigurationManager.AppSettings("NorthstarConnectionString"))
        Dim myCommand As New SqlDataAdapter("GetCommitStatusAll", myConnection)

        ' Mark the Command as a SPROC
        myCommand.SelectCommand.CommandType = CommandType.StoredProcedure

        ' Create and Fill the DataSet
        Dim myDataSet As New DataSet
        myCommand.Fill(myDataSet)

        ' Return the DataSet
        Return myDataSet
    End Function
End Class
Lets see how it looks on the blogs!
Posted by Tom Anderson | 0 Comments

Sharing configuration files

Derik Whittaker has posted a blog post that I found recently via stackoverflow.com that demonstrates a very unknown feature in the Add Existing Dialog in visual studio. Adding as a link.

This is… super useful.

Posted by Tom Anderson | 2 Comments

CAB – Part 5 Simple Module

In the previous articles on CAB, we have gone over some of the basics of getting our shell application ready to load up some modules.

In this article, we will actually create a simplistic Module to display a “SmartPart” in our primary workspace.

The first thing we need to do, is to add a new “Class Library” project to our current solution. Name the new project “RenEvo.Blogs.Cab.SimpleModule” and hit ok. Rename the “Class1.vb” to “SimpleModuleInit.vb”.  This class will be our entry point into the actual module.

At this point, we are going to make a few modifications to our current projects, as the build directories are kind of all over the place, and since we have a defined structure we need to adhere to (example: ./Modules/ for any modules loaded into the Shell).

In the RenEvo.Blogs.Cab project, set the Build output Path in the Compile tab of the project properties to “..\bin\Debug\” for Debug configuration, and “..\bin\Release\” for the Release configuration.  Do the same thing to the RenEvo.Blogs.Cab.Interfaces project.

For the new RenEvo.Blogs.Cab.SimpleModule, set the output paths to “..\bin\Debug\Modules\” and “..\bin\Release\Modules\” respectively.

Build the entire solution, and verify that there is a bin directory in the Solution folder that contains all of the projects output assemblies in the per-configuration folder.

Back to our Simple Module project, we need to add some key references. Add the following references, and be sure to set the “Copy Local” property to False for each reference.

  • Microsoft.Practices.CompositeUI
  • Microsoft.Practices.CompositeUI.WinForms
  • Microsoft.Practices.ObjectBuilder
  • System.Windows.Forms
  • System.Drawing

And then finally add a project reference to the RenEvo.Blogs.Cab.Interfaces, also settings the “Copy Local” property to false.

image

Next, create a few new folders inside of the Simple Module project.

  • Services
  • SmartParts
  • WorkItems

The first thing we need to do is to setup the SimpleModuleInit class so that the shell will load it up. The first thing we will do is to import a few namespaces, then add an assembly attribute “Module” which will be the attribute that the Shell will look for when loading modules via reflection. Next we will inherit ModuleInit in our class.  At this point, your code will look like this:

   1:  Imports Microsoft.Practices.CompositeUI
   2:  Imports Microsoft.Practices.CompositeUI.Services
   3:   
   4:  <Assembly: [Module]("Simple Module")> 
   5:   
   6:  Public Class SimpleModuleInit
   7:      Inherits ModuleInit
   8:   
   9:   
  10:  End Class

We will have to setup our Dependency Injection property in order to integrate into the Shell Application. If you are not familiar with Dependency Injection, I suggest you read up on it before continuing.  Essentially for this implementation of Injection, we will use Property Attributes to notify the base framework that we have a service dependency, and then the property declared type will provide the service type we are accessing, in this case “WorkItem” type.

   1:  #Region " Root Work Item Dependency Injection "
   2:   
   3:      Private m_WorkItem As WorkItem = Nothing
   4:      <ServiceDependency()> _
   5:      Public Property RootWorkItem() As WorkItem
   6:          Get
   7:              Return m_WorkItem
   8:          End Get
   9:          Set(ByVal value As WorkItem)
  10:              m_WorkItem = value
  11:          End Set
  12:      End Property
  13:   
  14:  #End Region

This is basically setting this property by doing the following line of code (not used in our code, but deep behind the scenes).

   1:  SimpleModuleInit.RootWorkItem = Services.Get(Of WorkItem)()

By using Dependency Injection, we don’t need to know what is going on behind the scenes, and in some later examples of implementing our own services, it is quite useful to just have what we need automatically populated, rather than trying to find it manually.

Moving along, the next step we will need to do, is to override the ModuleInit.Load() sub in our SimpleModuleInit. Within this Method, lets just simple do the MessageBox.Show(“SimpleModule Loaded”) to verify that our module is being loaded.

Be sure that the RenEvo.Blogs.Cab project is set as the Startup Project, and run the application.  While the Splash Screen is displayed, you should see a message box stating that your SimpleModule Loaded.

Ok, moving along quickly here, lets remove that message box line. Now in the SmartParts folder, add a new UserControl named "SimpleSmartPart.vb” and in the WorkItems folder, add a new Class named “SimpleWorkItem.vb”

Code for SimpleSmartPart.vb

   1:  Imports Microsoft.Practices.CompositeUI.SmartParts
   2:   
   3:  <SmartPart()> _
   4:  Public Class SimpleSmartPart
   5:   
   6:  End Class

What we are doing in the class above is just tagging this user control as a SmartPart, this allows the WorkSpace objects to communicate with them properly, which we will go over in a later article.

For the sake of being able to see the smart part easier, drop a label control in the design view on the SimpleSmartPart, name it “uxSmartPartName” and set its Text property to “Simple Smart Part Loaded”. Where you put the label is irrelevant, mine looks like this:

image 

Now, the code for the SimpleWorkItem.vb is as follows:

   1:  Imports Microsoft.Practices.CompositeUI
   2:  Imports Microsoft.Practices.CompositeUI.Commands
   3:  Imports RenEvo.Blogs.Cab.Interfaces.Common
   4:   
   5:  Imports System.Windows.Forms
   6:   
   7:  Public Class SimpleWorkItem
   8:      Inherits WorkItem
   9:   
  10:      Protected Overrides Sub OnRunStarted()
  11:          MyBase.OnRunStarted()
  12:   
  13:          Me.RootWorkItem.Workspaces(Constants.WorkSpaces.PrimaryWorkSpace).Show(New SimpleSmartPart)
  14:   
  15:      End Sub
  16:   
  17:  End Class

The code above inherits from WorkItem and overrides the OnRunStarted method, we then Show a new SimpleSmartPart in our PrimaryWorkSpace.

Finally, to get the work item to actually “run” we need to modify the SimpleModuleInit.vb by adding a workitem to the RootWorkItem property, and calling the “Run” method.  The code below goes in the Load() method in SimpleModuleInit.vb

   1:  Me.RootWorkItem.WorkItems.AddNew(Of SimpleWorkItem).Run()

In the above code we are using the “AddNew” method with a generic typing of SimpleWorkitem and then calling the method “Run” on the newly created WorkItem.

Save everything, and run the application.  When the Shell interface displays after the splash screen, you will see the label with the text “Simple Smart Part Loaded” in our interface.

image

And there you have it, we have loaded up a module into the Shell, and displayed our First Smart Part.

In the next article, we will be adding a Controller to the SmartPart for the MVC (Model View Controller) design pattern, and implementing a few commands within that Controller.

Download the Solution

Posted by Tom Anderson | 0 Comments
Filed under:

SQL Server 2008 Express Now Available

SQL Server Express 2008, which released on Monday, is now available from Microsoft.

New Features:

  • Support for LINQ, Entity Data Model and ADO.NET Entity Framework make it easy to create next generation data-enabled applications
  • New Date and Time data types with time zone support and .NET compatibility provides full control over temporal data
  • New T-SQL IntelliSense support in SQL Server Management Studio makes it easy to write accurate T-SQL code
  • Access a vast community of other SQL Server enthusiasts from beginners to experts via the SQL Server Express forum

Overview:

  • Tight integration with Visual Studio 2008 with SP1
  • SQL Server 2008 Express provides high-end database features
  • Support for new data types and features like spatial data, HierarchyID, and FileStream makes it easier to model complex data
  • Support for MERGE, GROUPING SETS, sparse columns and table-valued parameters makes it easier to write T-SQL code
  • New Import/Export wizard makes it easy to migrate data
Posted by Tom Anderson | 0 Comments

CAB - Part 4 Adding a WorkSpace

In the previous articles on CAB we have gone over creating the basics with a CAB, right now our application really has no "Composite" to it, just "Application Block".  In this article we will go over creating our first WorkSpace for modules to dock into, which will lead us to our next article on creating a basic module with a single workspace.

The first thing we need to do, in order to work with the CAB WinForms controls, is to create a new Tab on our Forms Toolbox, and load up the Microsoft.Practices.CompositeUI.WinForms.dll controls to it.  If you need help on this, you can see many articles on the web on how to add items to your toolbox, like this one.

You should now have 7 new controls, the one we are going to concentrate on for this article is the DeckWorkspace.  Simply drag it onto our form, set the Dock to Fill, name it uxPrimaryWorkspace, then clear the Text property.

Your form in designer should look like this:

image

The size of the form is un-important, later we will go about saving the forms settings, etc... but for now, lets leave it as is.

Now that we have our form setup, we need to add some ways to access this workspace, as well as register the WorkSpace with the CAB framework.  This is usually done via id strings, but since we want our users to properly access this workspace, we will want to create constants for access.  Instead of the modules accessing and referencing the main executable, we will add another project with the name ".Interfaces" that will contain all the constants for extension sites, workspaces, as well as internal services.

Add a Class Library project to the solution named "RenEvo.Blogs.Cab.Interfaces" with the default directory specified.  Next delete the Class1.vb that is automaticaly created.

Create a new folder called Common, then a new class file inside of that folder called WorkSpaces.  In the class file you need to implement a namespace, as well as a few constants inside of the class.  The namespace is only for better organization, and separation of objects.

   1:  Namespace Common.Constants
   2:   
   3:      ''' <summary>
   4:      ''' Class to store all of the workspace names
   5:      ''' </summary>
   6:      Public Class WorkSpaces
   7:          ''' <summary>
   8:          ''' The Primary Workspace for the CAB application
   9:          ''' </summary>
  10:          Public Const PrimaryWorkSpace As String = "PrimaryWorkSpace"
  11:   
  12:      End Class
  13:   
  14:  End Namespace

 

For now our class really doesn't cover a lot of constants, but in a larger application this could get quite huge.  Plus we no longer will have casing or mis-spelling issues with the other developers.

Now add a reference to the new project to the main Shell project, setting the copy to True.

Next, back in our form, we need to access the Constructor, and after the InitializeComponent, we want to reset the name of the uxPrimaryWorkspace in runtime to the name of the string in the preceding class.

   1:      Public Sub New()
   2:   
   3:          ' This call is required by the Windows Form Designer.
   4:          InitializeComponent()
   5:   
   6:          ' Add any initialization after the InitializeComponent() call.
   7:          Me.uxPrimaryWorkspace.Name = Interfaces.Common.Constants.WorkSpaces.PrimaryWorkSpace
   8:   
   9:      End Sub

 

This is a really easy implementation, probably the easiest in the entire application.  To test it, we can do the following in our Startup.vb class.

In the ShellShown event handlers, add the following lines of code to the bottom of the method.

   1:      Protected Sub ShellShown(ByVal sender As Object, ByVal e As EventArgs)
   2:          'remove the handler
   3:          RemoveHandler Shell.Shown, AddressOf ShellShown
   4:   
   5:          'set the cursor back to normal
   6:          m_Splash.Cursor = Cursors.Default
   7:   
   8:          'hide, dispose, and kill the splash 
   9:          m_Splash.Hide()
  10:          m_Splash.Dispose()
  11:          m_Splash = Nothing
  12:   
  13:          'test to see if we have a workspace
  14:          MessageBox.Show("Workspace Initialized: " & Boolean.Parse(Me.RootWorkItem.Workspaces(Interfaces.Common.Constants.WorkSpaces.PrimaryWorkSpace) IsNot Nothing))
  15:      End Sub

When ran, the application will display "Workspace Initialized: True" in a message box just after the splash screen closes.

Now remove that line of code, as we don't want that message box to continue displaying.

As stated before, in the next article I will be covering the creation of a very simplistic module that will load into this newly created workspace.

Download the Solution

Posted by Tom Anderson | 0 Comments
Filed under:

CAB - Part 3 Splash Screen

In the previous articles for CAB we have gone over creating our base form, as well as implementing a new way to load our modules (once we create some), in this article we are going to go over another simple implementation (that is specifically not covered in the documentation) to make our application much more user friendly.

Splash Screens.  As you know, in Visual Studio 2005 and 2008 you have the ability to enable the Application Framework, and simply select a Splash Screen from the project properties, in a CAB shell, you must run through the Sub Main() instead of simply using a Form for startup.  We will add one real fast that displays while the CAB is loading its assemblies and modules.

First, add a new form to our project (under Forms->Dialogs in our directory structure), select the Splash Screen form template, and name it CabSplashScreen.  For now we aren't going to modify the Splash Screen, just use the default template.

Just close the Splash Screen, and forget about it for now.

In our Startup class, we need to create a new private field for the splash screen, add creation to it in the sub new (non-shared instance), override the AfterShellCreated, as well as create an event handler for ShellShown.

Below is the code to do just that:

   1:      Private m_Splash As CabSplashScreen = Nothing
   2:   
   3:      Public Sub New()
   4:          'create the splash
   5:          m_Splash = New CabSplashScreen
   6:   
   7:          'show and update the splash
   8:          m_Splash.Show()
   9:          m_Splash.Update()
  10:   
  11:          'set the cursor to the hourglass
  12:          m_Splash.Cursor = Cursors.WaitCursor
  13:   
  14:          'let splash screen events process
  15:          Application.DoEvents()
  16:      End Sub
  17:   
  18:      Protected Overrides Sub AfterShellCreated()
  19:          MyBase.AfterShellCreated()
  20:   
  21:          'add an event handler for the ShellForm.Show event (this is when we will kill our splash)
  22:          AddHandler Shell.Shown, AddressOf ShellShown
  23:   
  24:      End Sub
  25:   
  26:      Protected Sub ShellShown(ByVal sender As Object, ByVal e As EventArgs)
  27:          'remove the handler
  28:          RemoveHandler Shell.Shown, AddressOf ShellShown
  29:   
  30:          'set the cursor back to normal
  31:          m_Splash.Cursor = Cursors.Default
  32:   
  33:          'hide, dispose, and kill the splash 
  34:          m_Splash.Hide()
  35:          m_Splash.Dispose()
  36:          m_Splash = Nothing
  37:      End Sub

The code is pretty straight forward, and I will leave you to the Comments to figure out what it is doing (I am sure that if you are venturing into creating a CAB shell then this is nothing new to you)

In our next article, we will go over creating our first WorkSpace.

Download the Solution

Posted by Tom Anderson | 0 Comments
Filed under:

CAB - Part 2 Loading Modules from a directory

In the first part of the CAB articles, I explained a bit about getting the CAB working in a generic shell, and simply initiating the framework, in this article I will explain a little known ability with the CAB to dynamically load all modules in a directory, instead of the "ProductCatalog.xml" that is used in the CAB samples.  This is something that I do personally in all 3 of our CAB shells, and have found it to be much easier to deploy new modules to customers.

In our Startup class, we simply need to override one of the base WorkItem methods for adding services "AddServices", remove teh IModuleEnumerator that is currently loaded (that productcatalog.xml loader) and replace it with a new ReflectionModuleEnumerator instead.

   1:      Protected Overrides Sub AddServices()
   2:          'setup base services
   3:          MyBase.AddServices()
   4:   
   5:          'remove the FileModule for ProductCatalog.xml
   6:          MyBase.RootWorkItem.Services.Remove(Of IModuleEnumerator)()
   7:   
   8:          'create a new reflection enumerator
   9:          Dim reflectionEnumerator As New ReflectionModuleEnumerator()
  10:   
  11:          'make sure our path exists for modules
  12:          Directory.CreateDirectory(Path.GetDirectoryName(Application.ExecutablePath) & "\Modules\")
  13:   
  14:          'set the working path to ./Modules/
  15:          reflectionEnumerator.BasePath = Path.GetDirectoryName(Application.ExecutablePath) & "\Modules\"
  16:   
  17:          'add it back to the services
  18:          MyBase.RootWorkItem.Services.Add(GetType(IModuleEnumerator), reflectionEnumerator)
  19:   
  20:      End Sub

In the next article, I will go over how to add a Splash Screen to the main form, since we have not enabled the "Application Framework".

Download Solution

Posted by Tom Anderson | 0 Comments
Filed under:

Combo Box Enumerations with Titles

Have you ever just wanted to populate a combo box with values from an enumeration, but hated the fact that it dealt with the name of the item in the enumeration, instead of some snazzy string? Take the following Enum as an example:

   1:  Public Enum TitledValues
   2:      FirstName
   3:      LastName
   4:      Address1
   5:      Address2
   6:      City
   7:      State
   8:      ZipCode
   9:      PhoneNumber
  10:      EmailAddress
  11:  End Enum

For some of those properties displaying "City" would be fine, but who wants to show a user "FirstName" ?

I have figured out a little technique using a custom type converter and attributes to be able at code time set the titles from the Enum instead of having to do huge select cases when handling the drop down events.

The first thing to do is to create a new attribute called "EnumTitleAttribute", this attribute is pretty straight forward, it simply contains a Title property.

   1:  <AttributeUsage(AttributeTargets.Field)> _
   2:  Public Class EnumTitleAttribute
   3:      Inherits Attribute
   4:   
   5:      Private m_Title As String = String.Empty
   6:      Public Property Title() As String
   7:          Get
   8:              Return m_Title
   9:          End Get
  10:          Set(ByVal value As String)
  11:              m_Title = value
  12:          End Set
  13:      End Property
  14:   
  15:  End Class

The AttributeUsage attribute is restricting usage of this attribute to fields, which is what enum values are stored as in the object.

The next step was to create a type converter to work with these new custom attributes, since we want this to be a global type of enum, we are going to implement it as a generic.

   1:  Public Class EnumTitleTypeConverter(Of T)
   2:      Inherits TypeConverter
   3:   
   4:      Public Overrides Function ConvertTo(ByVal context As ITypeDescriptorContext, ByVal culture As CultureInfo, ByVal value As Object, ByVal destinationType As System.Type) As Object
   5:          If value.GetType Is GetType(T) AndAlso destinationType Is GetType(String) Then
   6:              If value.GetType.GetField(value.ToString).GetCustomAttributes(GetType(EnumTitleAttribute), True).Length > 0 Then
   7:                  Return DirectCast(value.GetType.GetField(value.ToString).GetCustomAttributes(GetType(EnumTitleAttribute), True)(0), EnumTitleAttribute).Title
   8:              Else
   9:                  Return value.ToString
  10:              End If
  11:          Else
  12:              Return MyBase.ConvertTo(context, culture, value, destinationType)
  13:          End If
  14:      End Function
  15:  End Class

The only functionality we really care about in this class is the "ConvertTo" method.  We want to check to see if the value being converted is the type of our generic (of T) and that the destination type is string.

The basics of this method are check the input parameters for the proper conversion types, if they match, see if the field in the type has the EnumTitleAttribute assigned to it, and if so, we are going to get the first one only and return the Title property, otherwise, we are simply going to return the default ToString on the value object.  If none of these match, we will simply let the standard type converter try to deal with it, where it generally just returns the ToString of the object.

Now, to apply the title's to an enumeration, we simply add the attributes to the enum fields and set the TypeConverter.

   1:  <TypeConverter(GetType(EnumTitleTypeConverter(Of TitledValues)))> _
   2:  Public Enum TitledValues
   3:      <EnumTitle(Title:="First Name")> _
   4:      FirstName
   5:      <EnumTitle(Title:="Last Name")> _
   6:      LastName
   7:      <EnumTitle(Title:="Address 1")> _
   8:      Address1
   9:      <EnumTitle(Title:="Address 2")> _
  10:      Address2
  11:      City
  12:      State
  13:      <EnumTitle(Title:="Zip Code")> _
  14:      ZipCode
  15:      <EnumTitle(Title:="Phone Number")> _
  16:      PhoneNumber
  17:      <EnumTitle(Title:="Email Address")> _
  18:      EmailAddress
  19:  End Enum

So the first blaring question is, how do you use it?  Create a form (or use an existing one), add a combo box (for this example, name it uxFieldNames) and set the DropDownStyle to DropDownList, then in the form load, do the following code.

   1:  Me.uxFieldNames.DataSource = [Enum].GetValues(GetType(TitledValues))

And then in the SelectedIndexChanged event for the ComboBox, you can find out the selected item with the following code

   1:          If Me.uxFieldNames.SelectedIndex > -1 Then
   2:              Select Case DirectCast(uxFieldNames.SelectedItem, TitledValues)
   3:                  Case TitledValues.FirstName
   4:                      'do something with the first name
   5:                  Case TitledValues.LastName
   6:                      'do something with the last name
   7:              End Select
   8:          End If

Without hardly any additional effort, especially from your UI coding, you end up with this result:

image

instead of this:

image

Good luck, and happy coding!

 

Full Source Code:

   1:  Imports System.ComponentModel
   2:  Imports System.Globalization
   3:   
   4:  <TypeConverter(GetType(EnumTitleTypeConverter(Of TitledValues)))> _
   5:  Public Enum TitledValues
   6:      <EnumTitle(Title:="First Name")> _
   7:      FirstName
   8:      <EnumTitle(Title:="Last Name")> _
   9:      LastName
  10:      <EnumTitle(Title:="Address 1")> _
  11:      Address1
  12:      <EnumTitle(Title:="Address 2")> _
  13:      Address2
  14:      City
  15:      State
  16:      <EnumTitle(Title:="Zip Code")> _
  17:      ZipCode
  18:      <EnumTitle(Title:="Phone Number")> _
  19:      PhoneNumber
  20:      <EnumTitle(Title:="Email Address")> _
  21:      EmailAddress
  22:  End Enum
  23:   
  24:  <AttributeUsage(AttributeTargets.Field)> _
  25:  Public Class EnumTitleAttribute
  26:      Inherits Attribute
  27:   
  28:      Private m_Title As String = String.Empty
  29:      Public Property Title() As String
  30:          Get
  31:              Return m_Title
  32:          End Get
  33:          Set(ByVal value As String)
  34:              m_Title = value
  35:          End Set
  36:      End Property
  37:   
  38:  End Class
  39:   
  40:  Public Class EnumTitleTypeConverter(Of T)
  41:      Inherits TypeConverter
  42:   
  43:      Public Overrides Function ConvertTo(ByVal context As ITypeDescriptorContext, ByVal culture As CultureInfo, ByVal value As Object, ByVal destinationType As System.Type) As Object
  44:          If value.GetType Is GetType(T) AndAlso destinationType Is GetType(String) Then
  45:              If value.GetType.GetField(value.ToString).GetCustomAttributes(GetType(EnumTitleAttribute), True).Length > 0 Then
  46:                  Return DirectCast(value.GetType.GetField(value.ToString).GetCustomAttributes(GetType(EnumTitleAttribute), True)(0), EnumTitleAttribute).Title
  47:              Else
  48:                  Return value.ToString
  49:              End If
  50:          Else
  51:              Return MyBase.ConvertTo(context, culture, value, destinationType)
  52:          End If
  53:      End Function
  54:  End Class

 

 

*Edit: Added C# Code below

   1:  using System;
   2:  using System.Collections.Generic;
   3:  using System.Text;
   4:  using System.ComponentModel;
   5:  using System.Globalization;
   6:   
   7:  [TypeConverter(typeof(EnumTitleTypeConverter<TitledValues>))]
   8:  enum TitledValues
   9:  {
  10:      [EnumTitle(Title="First Name")]
  11:      FirstName,
  12:      [EnumTitle(Title = "Last Name")]
  13:      LastName,
  14:      [EnumTitle(Title = "Address 1")]
  15:      Address1,
  16:      [EnumTitle(Title = "Address 2")]
  17:      Address2,
  18:      City,
  19:      State,
  20:      [EnumTitle(Title = "Zip Code")]
  21:      ZipCode,
  22:      [EnumTitle(Title = "Phone Number")]
  23:      PhoneNumber,
  24:      [EnumTitle(Title = "Email Address")]
  25:      EmailAddress
  26:  }
  27:   
  28:  [AttributeUsage(AttributeTargets.Field)]
  29:  public class EnumTitleAttribute : Attribute
  30:  {
  31:      private string m_Title = string.Empty;
  32:      public string Title
  33:      {
  34:          get { return m_Title; }
  35:          set { m_Title = value; }
  36:      }
  37:  }
  38:   
  39:  public class EnumTitleTypeConverter<T> : TypeConverter
  40:  {
  41:      public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
  42:      {
  43:          if ((value.GetType() == typeof(T)) && (destinationType == typeof(String)))
  44:          {
  45:              if (value.GetType().GetField(value.ToString()).GetCustomAttributes(typeof(EnumTitleAttribute), true).Length > 0)
  46:              {
  47:                  return (value.GetType().GetField(value.ToString()).GetCustomAttributes(typeof(EnumTitleAttribute),true)[0] as EnumTitleAttribute).Title;
  48:              }
  49:              else
  50:              {
  51:                  return value.ToString();
  52:              }
  53:          } 
  54:          else 
  55:          {
  56:              return base.ConvertTo(context, culture, value, destinationType);
  57:          }
  58:      }
  59:  }
Posted by Tom Anderson | 1 Comments

Correction for Vertigo Software post about debugging

Recently I read the article by Chris Idzerda over at Vertigo software that described how to Detect Visual Studio Debugging.  While the method he used was a bit of a "hacky workaround", below is a more sound way to detect it using built in .Net libraries, rather than a string lookup on the running executable.

VB.Net Version

   1:          If System.Diagnostics.Debugger.IsAttached Then
   2:              ' do code here for debugging with visual studio
   3:   
   4:          End If
 

C# Version

   1:              if (System.Diagnostics.Debugger.IsAttached)
   2:              {
   3:                  // do code here for debugging with visual studio
   4:              }

I hope this helps clear up any confusion.

Posted by Tom Anderson | 2 Comments

SWGEmu - .Net Stuffz

So a while back some of you may know that I was writing a pure .Net emulator.  That went "ok" and I had made zone in both pre-cu and nge based clients.  But after a while, the time constraints just couldn't be dealt with, and the project was, how do I say, huge?

One of the big issues that I had when writing the emulator in .Net was the encryption, decryption, and the crc calculations.  Originally I had just used a c++ api to pull it off.  This bothered me literally for some time as I just wanted a pure .Net solution.

So today, I spent a bit of time looking at the great documentation on the encryption/decryption algorigthm that the SWGEmu team has put up (if you recall, I used to be a part of that team for a very short lived time).

Below is the test app that I created and played around with to get these things working in pure (you got it) VB.Net.  I know the C# port would have been WAY easier using unsafe or even almost just duplicating the code, but I wanted a direct port that was spot on.

 

   1:  Public Class Program
   2:   
   3:      Public Shared Sub Main(ByVal args() As String)
   4:          Dim rawArray() As Byte = New Byte() {0, 9, 0, 1, 0, 2, &HAB, &H43, &HE3, &HD5, 0, &HFF, 0, &H11, &H45, &H32, &H76, &H43, &HD4, &HF1, 0, &HAB, &HCD}
   5:          Dim encryptedArray() As Byte
   6:          Dim decryptedArray() As Byte
   7:          Dim crcSeed As Integer = &H1D4E3287
   8:   
   9:          Console.WriteLine("Raw Bytes:")
  10:          For Each bt As Byte In rawArray
  11:              Console.Write("{0:X2} ", bt)
  12:          Next
  13:          Console.WriteLine()
  14:          Console.WriteLine()
  15:   
  16:   
  17:          encryptedArray = Encrypt(Strip(rawArray), crcSeed)
  18:          Console.WriteLine("Encrypted Bytes:")
  19:          For Each bt As Byte In encryptedArray
  20:              Console.Write("{0:X2} ", bt)
  21:          Next
  22:          Console.WriteLine()
  23:          Console.WriteLine()
  24:   
  25:          decryptedArray = Decrypt(encryptedArray, crcSeed)
  26:          Console.WriteLine("Decrypted Bytes:")
  27:          For Each bt As Byte In decryptedArray
  28:              Console.Write("{0:X2} ", bt)
  29:          Next
  30:          Console.WriteLine()
  31:          Console.WriteLine()
  32:   
  33:          Console.WriteLine("CRC Test")
  34:          Dim crc As Integer = CRC32(New Byte() {&H0, &H5, &HAA, &HBB, &HCC, &HDD, &H0, &H6, &H0}, 9, crcSeed)
  35:          Console.WriteLine("My CRC32:  {0:X4}", crc)
  36:          crc = API.GenerateCrc(New Byte() {&H0, &H5, &HAA, &HBB, &HCC, &HDD, &H0, &H6, &H0}, crcSeed)
  37:          Console.WriteLine("C++ CRC32: {0:X4}", crc)
  38:   
  39:          Console.ReadLine()
  40:      End Sub
  41:   
  42:      Private Shared Function Strip(ByVal inData() As Byte) As Byte()
  43:          Dim retVal As New List(Of Byte)
  44:          retVal.AddRange(inData)
  45:   
  46:          'strip channel and op code
  47:          If retVal(0) = 0 Then
  48:              retVal.RemoveAt(0)
  49:              retVal.RemoveAt(0)
  50:          End If
  51:   
  52:          'strip crc
  53:          retVal.RemoveAt(retVal.Count - 1)
  54:          retVal.RemoveAt(retVal.Count - 1)
  55:   
  56:          Return retVal.ToArray
  57:      End Function
  58:   
  59:      Public Shared Function Encrypt(ByVal inData() As Byte, ByVal seed As Integer) As Byte()
  60:          Dim count As Integer = Math.Floor(Decimal.Parse(inData.Length) / 4D)
  61:          Dim remainder As Integer = inData.Length - (count * 4)
  62:          Dim seedBytes() As Byte = BitConverter.GetBytes(seed)
  63:   
  64:          Array.Reverse(seedBytes)
  65:   
  66:          For i As Integer = 0 To count - 1
  67:              For b As Integer = 0 To 3
  68:                  inData((i * 4) + b) = inData((i * 4) + b) Xor seedBytes(b)
  69:              Next
  70:              For b As Integer = 0 To 3
  71:                  seedBytes(b) = inData((i * 4) + b)
  72:              Next
  73:          Next
  74:   
  75:          For i As Integer = 0 To remainder - 1
  76:              inData((count * 4) + i) = inData((count * 4) + i) Xor seedBytes(0)
  77:          Next
  78:   
  79:          Return inData
  80:      End Function
  81:   
  82:      Public Shared Function Decrypt(ByVal inData() As Byte, ByVal seed As Integer) As Byte()
  83:          Dim count As Integer = Math.Floor(Decimal.Parse(inData.Length) / 4D)
  84:          Dim remainder As Integer = inData.Length - (count * 4)
  85:          Dim seedBytes() As Byte = BitConverter.GetBytes(seed)
  86:   
  87:          Array.Reverse(seedBytes)
  88:   
  89:          For i As Integer = 0 To count - 1
  90:              Dim newSeed(3) As Byte
  91:   
  92:              For b As Integer = 0 To 3
  93:                  newSeed(b) = inData((i * 4) + b)
  94:              Next
  95:              For b As Integer = 0 To 3
  96:                  inData((i * 4) + b) = inData((i * 4) + b) Xor seedBytes(b)
  97:              Next
  98:              For b As Integer = 0 To 3
  99:                  seedBytes(b) = newSeed(b)
 100:              Next
 101:          Next
 102:   
 103:          For i As Integer = 0 To remainder - 1
 104:              inData((count * 4) + i) = inData((count * 4) + i) Xor seedBytes(0)
 105:          Next
 106:   
 107:          Return inData
 108:      End Function
 109:   
 110:      Public Shared Function CRC32(ByVal inData() As Byte, ByVal length As Integer, ByVal nCrcSeed As Integer) As Integer
 111:          'unsigned int nCrc = g_nCrcTable[(~nCrcSeed) & 0xFF];
 112:          Dim nCrc As UInt32 = m_CRCTable(Not nCrcSeed And &HFF)
 113:   
 114:          'nCrc ^= 0x00FFFFFF;
 115:          nCrc = nCrc Xor &HFFFFFF
 116:   
 117:          'unsigned int nIndex = (nCrcSeed >> 8) ^ nCrc;
 118:          Dim nIndex As UInt32 = (nCrcSeed >> 8) Xor nCrc
 119:   
 120:          'nCrc = (nCrc >> 8) & 0x00FFFFFF;
 121:          nCrc = (nCrc >> 8) And &HFFFFFF
 122:   
 123:          'nCrc ^= g_nCrcTable[nIndex & 0xFF];
 124:          nCrc = nCrc Xor m_CRCTable(nIndex And &HFF)
 125:   
 126:          'nIndex = (nCrcSeed >> 16) ^ nCrc;
 127:          nIndex = (nCrcSeed >> 16) Xor nCrc
 128:   
 129:          'nCrc = (nCrc >> 8) & 0x00FFFFFF;
 130:          nCrc = (nCrc >> 8) And &HFFFFFF
 131:   
 132:          'nCrc ^= g_nCrcTable[nIndex & 0xFF];
 133:          nCrc = nCrc Xor m_CRCTable(nIndex And &HFF)
 134:   
 135:          'nIndex = (nCrcSeed >> 24) ^ nCrc;
 136:          nIndex = (nCrcSeed >> 24) Xor nCrc
 137:   
 138:          'nCrc = (nCrc >> 8) &0x00FFFFFF;
 139:          nCrc = (nCrc >> 8) And &HFFFFFF
 140:   
 141:          'nCrc ^= g_nCrcTable[nIndex & 0xFF];
 142:          nCrc = nCrc Xor m_CRCTable(nIndex And &HFF)
 143:   
 144:          'for( short i = 0; i < nLength; i++ ) {
 145:          For i As Integer = 0 To length - 1
 146:              'nIndex = (pData[ i]) ^ nCrc;
 147:              nIndex = inData(i) Xor nCrc
 148:              'nCrc = (nCrc >> 8) & 0x00FFFFFF;
 149:              nCrc = (nCrc >> 8) And &HFFFFFF
 150:              'nCrc ^= g_nCrcTable[nIndex & 0xFF];
 151:              nCrc = nCrc Xor m_CRCTable(nIndex And &HFF)
 152:   
 153:          Next
 154:          '}
 155:   
 156:          'return ~nCrc;
 157:          Return Not nCrc
 158:      End Function
 159:   
 160:      '   void CRC_(unsigned char *data, int size, unsigned long &crc){
 161:      '        if (!size) return;
 162:      '        for (int i = 0; i < size; i++)
 163:      '            crc = (crc >> 8) ^ crc_table[(crc & 0xff) ^ data[ i]];
 164:      '   }
 165:   
 166:      Private Shared m_CRCTable() As UInt32 = New UInt32() { _
 167:                                                      &H0UL, &H77073096UL, &HEE0E612CUL, &H990951BAUL, &H76DC419UL, &H706AF48FUL, _
 168:                                                      &HE963A535UL, &H9E6495A3UL, &HEDB8832UL, &H79DCB8A4UL, &HE0D5E91EUL, &H97D2D988UL, _
 169:                                                      &H9B64C2BUL, &H7EB17CBDUL, &HE7B82D07UL, &H90BF1D91UL, &H1DB71064UL, &H6AB020F2UL, _
 170:                                                      &HF3B97148UL, &H84BE41DEUL, &H1ADAD47DUL, &H6DDDE4EBUL, &HF4D4B551UL, &H83D385C7UL, _
 171:                                                      &H136C9856UL, &H646BA8C0UL, &HFD62F97AUL, &H8A65C9ECUL, &H14015C4FUL, &H63066CD9UL, _
 172:                                                      &HFA0F3D63UL, &H8D080DF5UL, &H3B6E20C8UL, &H4C69105EUL, &HD56041E4UL, &HA2677172UL, _
 173:                                                      &H3C03E4D1UL, &H4B04D447UL, &HD20D85FDUL, &HA50AB56BUL, &H35B5A8FAUL, &H42B2986CUL, _
 174:                                                      &HDBBBC9D6UL, &HACBCF940UL, &H32D86CE3UL, &H45DF5C75UL, &HDCD60DCFUL, &HABD13D59UL, _
 175:                                                      &H26D930ACUL, &H51DE003AUL, &HC8D75180UL, &HBFD06116UL, &H21B4F4B5UL, &H56B3C423UL, _
 176:                                                      &HCFBA9599UL, &HB8BDA50FUL, &H2802B89EUL, &H5F058808UL, &HC60CD9B2UL, &HB10BE924UL, _
 177:                                                      &H2F6F7C87UL, &H58684C11UL, &HC1611DABUL, &HB6662D3DUL, &H76DC4190UL, &H1DB7106UL, _
 178:                                                      &H98D220BCUL, &HEFD5102AUL, &H71B18589UL, &H6B6B51FUL, &H9FBFE4A5UL, &HE8B8D433UL, _
 179:                                                      &H7807C9A2UL, &HF00F934UL, &H9609A88EUL, &HE10E9818UL, &H7F6A0DBBUL, &H86D3D2DUL, _
 180:                                                      &H91646C97UL, &HE6635C01UL, &H6B6B51F4UL, &H1C6C6162UL, &H856530D8UL, &HF262004EUL, _
 181:                                                      &H6C0695EDUL, &H1B01A57BUL, &H8208F4C1UL, &HF50FC457UL, &H65B0D9C6UL, &H12B7E950UL, _
 182:                                                      &H8BBEB8EAUL, &HFCB9887CUL, &H62DD1DDFUL, &H15DA2D49UL, &H8CD37CF3UL, &HFBD44C65UL, _
 183:                                                      &H4DB26158UL, &H3AB551CEUL, &HA3BC0074UL, &HD4BB30E2UL, &H4ADFA541UL, &H3DD895D7UL, _
 184:                                                      &HA4D1C46DUL, &HD3D6F4FBUL, &H4369E96AUL, &H346ED9FCUL, &HAD678846UL, &HDA60B8D0UL, _
 185:                                                      &H44042D73UL, &H33031DE5UL, &HAA0A4C5FUL, &HDD0D7CC9UL, &H5005713CUL, &H270241AAUL, _
 186:                                                      &HBE0B1010UL, &HC90C2086UL, &H5768B525UL, &H206F85B3UL, &HB966D409UL, &HCE61E49FUL, _
 187:                                                      &H5EDEF90EUL, &H29D9C998UL, &HB0D09822UL, &HC7D7A8B4UL, &H59B33D17UL, &H2EB40D81UL, _
 188:                                                      &HB7BD5C3BUL, &HC0BA6CADUL, &HEDB88320UL, &H9ABFB3B6UL, &H3B6E20CUL, &H74B1D29AUL, _
 189:                                                      &HEAD54739UL, &H9DD277AFUL, &H4DB2615UL, &H73DC1683UL, &HE3630B12UL, &H94643B84UL, _
 190:                                                      &HD6D6A3EUL, &H7A6A5AA8UL, &HE40ECF0BUL, &H9309FF9DUL, &HA00AE27UL, &H7D079EB1UL, _
 191:                                                      &HF00F9344UL, &H8708A3D2UL, &H1E01F268UL, &H6906C2FEUL, &HF762575DUL, &H806567CBUL, _
 192:                                                      &H196C3671UL, &H6E6B06E7UL, &HFED41B76UL, &H89D32BE0UL, &H10DA7A5AUL, &H67DD4ACCUL, _
 193:                                                      &HF9B9DF6FUL, &H8EBEEFF9UL, &H17B7BE43UL, &H60B08ED5UL, &HD6D6A3E8UL, &HA1D1937EUL, _
 194:                                                      &H38D8C2C4UL, &H4FDFF252UL, &HD1BB67F1UL, &HA6BC5767UL, &H3FB506DDUL, &H48B2364BUL, _
 195:                                                      &HD80D2BDAUL, &HAF0A1B4CUL, &H36034AF6UL, &H41047A60UL, &HDF60EFC3UL, &HA867DF55UL, _
 196:                                                      &H316E8EEFUL, &H4669BE79UL, &HCB61B38CUL, &HBC66831AUL, &H256FD2A0UL, &H5268E236UL, _
 197:                                                      &HCC0C7795UL, &HBB0B4703UL, &H220216B9UL, &H5505262FUL, &HC5BA3BBEUL, &HB2BD0B28UL, _
 198:                                                      &H2BB45A92UL, &H5CB36A04UL, &HC2D7FFA7UL, &HB5D0CF31UL, &H2CD99E8BUL, &H5BDEAE1DUL, _
 199:                                                      &H9B64C2B0UL, &HEC63F226UL, &H756AA39CUL, &H26D930AUL, &H9C0906A9UL, &HEB0E363FUL, _
 200:                                                      &H72076785UL, &H5005713UL, &H95BF4A82UL, &HE2B87A14UL, &H7BB12BAEUL, &HCB61B38UL, _
 201:                                                      &H92D28E9BUL, &HE5D5BE0DUL, &H7CDCEFB7UL, &HBDBDF21UL, &H86D3D2D4UL, &HF1D4E242UL, _
 202:                                                      &H68DDB3F8UL, &H1FDA836EUL, &H81BE16CDUL, &HF6B9265BUL, &H6FB077E1UL, &H18B74777UL, _
 203:                                                      &H88085AE6UL, &HFF0F6A70UL, &H66063BCAUL, &H11010B5CUL, &H8F659EFFUL, &HF862AE69UL, _
 204:                                                      &H616BFFD3UL, &H166CCF45UL, &HA00AE278UL, &HD70DD2EEUL, &H4E048354UL, &H3903B3C2UL, _
 205:                                                      &HA7672661UL, &HD06016F7UL, &H4969474DUL, &H3E6E77DBUL, &HAED16A4AUL, &HD9D65ADCUL, _
 206:                                                      &H40DF0B66UL, &H37D83BF0UL, &HA9BCAE53UL, &HDEBB9EC5UL, &H47B2CF7FUL, &H30B5FFE9UL, _
 207:                                                      &HBDBDF21CUL, &HCABAC28AUL, &H53B39330UL, &H24B4A3A6UL, &HBAD03605UL, &HCDD70693UL, _
 208:                                                      &H54DE5729UL, &H23D967BFUL, &HB3667A2EUL, &HC4614AB8UL, &H5D681B02UL, &H2A6F2B94UL, _
 209:                                                      &HB40BBE37UL, &HC30C8EA1UL, &H5A05DF1BUL, &H2D02EF8DUL _
 210:                                                  }
 211:   
 212:  End Class

 

There it is, in all the unfriendly nastyness.  The API class I am using is calling the previous c++ implementation that I had before, just to verify that they match.

Just thought I would gloat a bit, it was a tedious process, and a lot of learning about .Net binary actions that I wasn't aware of, sometimes you just look at the code, but don't really understand it, I finally have a really good understanding of how this all works.

*EDIT For Credit
SWGEmu.com
CRC Explained
Encryption Explained

Posted by Tom Anderson | 1 Comments

Renamed Web Blog - .Net Articles

I just renamed this web blog from vb.net articles to .net articles.  I have decided to start blogging all .net now, instead of just vb.net.

More to come later!

 

*Edit: I have just realised that in the RSS the images are borked, this will be fixed in the next aggregate, which should occur on next startup, if not, I may have to force a re-aggregation.

Posted by Tom Anderson | 0 Comments

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 Tom Anderson | 1 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 Tom Anderson | 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 Tom Anderson | 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 Tom Anderson | 0 Comments

CAB - Part 1 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 Tom Anderson | 2 Comments
Filed under:

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 Tom Anderson | 0 Comments
Filed under:

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 Tom Anderson | 1 Comments