Skip navigation

Category Archives: CAB

Author: Jeff Noble

Purpose

This article shows how to use the awesome menu controls from Devcomponents within the CAB environment. It uses the extension points provided by CAB and is one way to implement such a system.

File Download Instructions

You must rename the file back to a .zip extension and unzip the file. This blog site does not allow code samples to be saved in zip format.

Source: download here
(rename to cabmenuhosting.zip)

Background

If you have never checked out the controls from Devcomponents you are in for a real treat. These controls are about the best I have come across for rich client applications (and they aren’t overpriced like some of the other 3rd party controls). In the DotNet Bar Suite of controls lies the Ribbon Control. This is a drop-in control which gives you an Office 2007 look/feel with minimal effort. If you want a full cracked version click here. Just kidding, but you can check out a demo from their site at www.devcomponents.com.

A ribbon control is a replacement for an applications menu. The age old File, Edit, View … Help system, is replaced with tabs and the buttons on the tabs are the old menu items. Gone is the old toolbar, because every menu item, is now a toolbar button. It looks like this:

I am not going to go into the RibbonControl’s features, I’ll leave that to DevComponents. Instead, our goal is to integrate this cool control into CAB which is by default not very cool looking.

Assumptions

I am going to assume that you already know how to build a CAB application. I will go step by step, but won’t dwell on the steps. You should already have CAB installed and the Smart Client Software Factory packages and Guidance Automation Toolkit and Extensions stuff installed to follow along.

A note about 3rd party controls

The information in this article can be used to extend just about any 3rd party control, but I am only going to be covering DotNetBar.RibbonControl. I may not be able to answer specific questions about other controls because I may have never used them.

Create a new CAB project

Then uncheck what we dont need for this example:

Once all of that’s done, we can start changing the defaults so we can use a RibbonControl instead of the standard controls.

Making our changes

The RibbonControl works best when in conjunction with the Office2007RibbonForm. To use this we need to add a reference to our Shell, to the Devcomponents dll. There is only one which is really nice!

First, lets remove the nasty default menu and tool bar in the ShellForm. Just select them and delete em’.

I don’t use the default ShellForm layouts normally, so we are going to remove them. It will make illustrating easier for me and you can always add the workspaces back in later. So just select the SplitContainer in the form properties and delete it as well (click the control in the ide, then delete).

Now we need to comment out the lines of code that reference the controls we just deleted. So if we compile the solution, we will see the following errors:

To fix this, we just comment out the _leftWorkspace, _rightWorkspace and the whole _mainToolStrip Property as we won’t be using it.

We also need to comment out lines 79 & 81 in ShellApplication.cs. Those two lines are also irrelevent because they reference controls we have already deleted.

The project should once again build.

The start of coolness

Now lets change the form to a cool Office 2007 form. In the code-behind of ShellForm.cs change the inherits to the following:

public partial class ShellForm : DevComponents.DotNetBar.Office2007RibbonForm

Now flip back to design mode in the IDE. You should be left with a nice looking empty shell (with no menu system or workspaces).

Now drag a ribboncontrol onto the form. It should now look like this:

Change the name of the RibbonControl to MainMenu. Change the modifier property on the RibbonControl to Public. That way we can access it from the outside. If you like it would probably be a good idea to create an interface property for it and access it that way, but for the purposes of this simple example, I am going to make it public. Now go ahead and delete the RibbonTabItem tabs. Those will get created on the fly as we need them. Just click the tab, and hit the delete key (for both of them).

Now you should see this:

Next grab a deck workspace from the Microsoft.Practices.CompositeUI.WinForms.dll and add it to the ShellForm. Name it MainWorkSpace.

Drag it onto the form and Dock-Fill it. If you get a reference error, just remove the Microsoft.Practices.CompositeUI.Winforms reference and let the deck add it for you. Sometimes it just doesnt like that it exists already.

We also need to add the Workspace name to the constants to keep our code readable. Edit the file Infrastructure.Interface.Constants.WorkSpaceNames (in the Infrastructure.Interface project) and add the following line there:

public const string MainWorkSpace = “MainWorkSpace”;

Our shell is ready for now. I don’t want to overload anyone, so lets just add a default view to our application and make sure we can run. Add a SplashView using the Guidance Package.

Now, right click the Views folder in your new SplashView Project (in VS) and Add a SplashView.

Next, open the SplashView.cs and put a nice picturebox or text on it so we know it’s loaded. I used a picture of a clock.

In the SplashView project edit the ModuleController.cs and add the following code to the AddView method:

SplashView view = WorkItem.SmartParts.Get<SplashView>(“SplashView”);

if (view == null)
{
view = WorkItem.SmartParts.AddNew<SplashView>();
}

view.Dock = DockStyle.Fill;
WorkItem.Workspaces[WorkspaceNames.MainWorkSpace].Show(view);

This code simply creates our splash view, adds it as a new smartpart, sets the dock to fill and shows the view in our workspace.

Run the application and it should look something like mine. Notice my click in the application, that shows that my smartpart is being loaded!

Ok, great, what about that fancy menu system?

Now that we have a working CAB application, we can concentrate on the menu system. I had a number of different requrements for the menu so my implementation may be different from yours. I also have not finished the implementations for the big round button in the top left, but you will still be able to access it from code, there just won’t be any fancy wrappers for it yet. I should also note that it is working and can be fully implemented in it’s current form.

To use menus and toolbars in CAB, they created extension points so that you can extend the built-in functionality with your own. To do this, you will need to get the code at the beginning of this article and reference the UIExtensionAdapters.dll and the CommandAdapters.dll in the shell project and reference the menuBuilder.dll in each view that needs to interact with the shell’s menu. Since Views are loosly coupled in CAB, the chore is getting a View to “see” the shell (or more realistically the menu in the shell).

To get started with the menu, we have to register two parts of the puzzle. The first part is the menu itself, and the second part are the click commands. Commands are like events that get broadcasted through the CAB infrastructure. But because CAB has to broadcast them, it needs to understand what they contain. Unfortunatly, CAB has no idea what a BaseItem is (a commonly used base class in the Devcomponents object model). Because of this, we need to build (or I needed to build) both of these parts. You get the easy part, just register them in code.

Open up the ShellApplication.cs file in the Shell project. add the following code to the AfterShellCreated() method (note that I am overwriting the hooks for the status bar, but I only do this because I use the Devcomponents Status bar and don’t use the built in one):

base.AfterShellCreated();

// Add custom UI Extension Sites to the Adapter Factory Catalog
RegisterUIExtensionServices();

// Register the Command event services with the mapService
RegisterCommandServices();

RootWorkItem.UIExtensionSites.RegisterSite(“MainMenu”, Shell.MainMenu);

Then add the two methods that register the Extension and the command.

private void RegisterUIExtensionServices()
{
IUIElementAdapterFactoryCatalog catalog =
RootWorkItem.Services.Get<IUIElementAdapterFactoryCatalog>();

catalog.RegisterFactory(new dnbRibbonAdapterFactory());

}

private void RegisterCommandServices()
{
ICommandAdapterMapService mapService = RootWorkItem.Services.Get<ICommandAdapterMapService>();
mapService.Register(typeof(BaseItem), typeof(dnbEventCommandAdapter));

}

I use Resharper, so it adds the using statements at the top of the ShellApplication.cs for me. Here is a list of the using’s I am using <grin>.

using System;
using System.Threading;
using System.Windows.Forms;
using CABMenuHosting.Infrastructure.Library;
using CommandAdapters.DotNetBarCommand;
using DevComponents.DotNetBar;
using Microsoft.Practices.CompositeUI;
using Microsoft.Practices.CompositeUI.Commands;
using Microsoft.Practices.CompositeUI.UIElements;
using Microsoft.Practices.EnterpriseLibrary.ExceptionHandling;
using UIExtensionAdapters.RibbonBar;

Why was that so hard?

Now that we are all registered up we need to create and inject our menu. Although it seemed long to get to this point, just getting a CAB project to load a smartpart was half of the work. Once we got there all we really did was add a couple references, and add the three methods worth of code to the shell. The second time through, it should only take a couple minutes!!

Making menus via designers

I have always tried to use designers when creating menus. I think it’s much more intuitive than creating menu’s entirely in code. Some object models are quite deep and don’t always redraw correctly if you don’t know exactly what to call, when to call and what order to call the redraws in (yes Denis, I am talking to you <grin>). So lets add a folder to our Splash project and call it Menus. In the Menus folder add a usercontrol called SplashViewMenu. Set the modifier proeprty to ‘public’ on the RibbonControl. Set the size of the SplashViewMenu to 699, 150. Add a RibbonControl to the SplashViewMenu. Remove RibbonTabItem2 (click it and delete it). Make sure to click on the remaining tab for focus reasons. Set the name of the tab to ‘_splashTab’ Set the text of the remaining tab to ‘Splash Menu’.

Right Click and add a button in the RibbonBar1. Name the button _btnViola. I am going to put an image in mine. Just set the ButtonStyle to ImageAndText. Set the Image to an image you like. Set the Text to ‘Viola’ and set the ImagePosition = Top. Set the RibbonWordWrap = false.

You can add as many buttons or groups as you like. The menu you create will be copied and injected into the shell’s menu. Do not add any event handlers to the buttons on the menu. We need those to be handled as Commands via CAB. So, on to that!

Open the ModuleController.cs file and add the following line to the top of the class:

private RibbonControl _mainMenu = null;

Next, add the following method:

public void SetupViews()
{

// Create the user control that has the menus for this view.
SplashViewMenu menu = new SplashViewMenu();

// Add the menu (ribbon control) from the user control into the wrapper along with the
// action of CONTROLS so it adds tabs and controls.
// You also supply the module name which can be used in the appsettings to set the order.

MenuWrapper SplashViewWrapper =
new MenuWrapper(menu.ribbonControl1,
MenuWrapper.INSERT_ACTION.CONTROLS, “SplashView”);

// Add the UIExtensionSite and capture the MenuWrapper return.
SplashViewWrapper = WorkItem.UIExtensionSites["MainMenu"].Add(SplashViewWrapper);

// Inside the MenuWrapper is a populated ribbon bar for you to use as you wish.
_mainMenu = SplashViewWrapper.MainMenu;

_mainMenu.SelectFirstVisibleRibbonTab();
}

This method will appear in every view that needs a menu. It’s a great candidate for a custom recipe, or maybe a snippet. Here is how it works:

First it creates a new menu (the usercontrol we created in the Menus folder). Next it adds the menu from in the control to a wrapper class. The wrapper class takes a menu, an action of Tab or Controls and the module name. The enum is incase you want to load a tab but not the buttons on the tab. Upon clicking the tab, you can run the same code with the Controls enum and load the controls. It’s just for performance and lazy loading the menu. The module name is used to set the order of the tabs. I have found that ordering in the profilecatalog.xml is easier and I have been using that. There are bugs in the ordering mechanism so I would not rely on it right now. You can add appsetting key/value pairs to set the order like this:

<add key=”ModuleName:TabName” value=”1″/>
Where the names are replaced and the value is the numerical order of the tabs. Again, I don’t think this is working properly at this time.

The next line adds the menu (in the wrapper) to the MainMenu extension site (copies the menu into the shell).

Then we store a pointer to the menu for use later in case we need it.

The last line just selected the First Visible Ribbon. This would just be for the splash view and no others.

Now we just have to add a call to SetupViews(). In the Run() method, add the following line of code:

SetupViews();

Hooking up events

To use the buttons, just add the following code to the bottom of the SetupViews() method (after the _mainMenu lines):

BaseItem violaButton = SplashViewWrapper.FindControl(“_splashTab”, “_btnViola”);

WorkItem.Commands["btnViola_Click"].AddInvoker(violaButton, “Click”);

The first line does a find control  (pass the tab’s name and the buttonName) and returns the button from the menu in the shell.

The next line is standard CAB notation to hook up a command to that button.

Then just add a Command Handler to handle the command.

[CommandHandler("btnViola_Click")]
public void btnViola_Click(object sender, EventArgs e)
{
int i = 10;

}

The int i=10 is just so you can add a break point to see the button click getting fired, you would put code there normally to handle the click of the button.

Thats it! Just run the program to see your cool-ass menu’s and click the button and watch it stop on your debug line in the command handler code (if you set one).

The complete code for this project and the code to extend the CAB functions are available at the link at the top of this article.

If people ask for it, I will write another article about how the extenders work and also how I am using this menu system in a production application. You will notice some interfaces in the code that I do not explain, but I am using them in my application….

-Enjoy!

Savij

Author: Jeff Noble

I was so excited that the P&P team released Enterprise Library 3.0. I first want to congratulate them on a fine product. Nice Job! Ok, so I installed the 3.0, Enterprise Library 3 and then the new Guidance Automation Extensions (which had errors, see this post if you get them) and the New Guidance Automation Toolkit both from May 2007. Then I installed the new Smart Client Software Factory. AWSOME!

I opened my CAB project and BOOM! 44 errors to fix. It took a LOT of time to diagnose what the problem is, but I will skip telling you how great of a programmer I am and how well I can diagnose a problem (ok, maybe not).

Missed Version Tick OOPS

The Object Builder has been moved into it’s own project space on Codeplex. You can find it here (but don’t find it there now): http://www.Codeplex.com/ObjectBuilder. If you were to d/l the project and build it, it builds version 1.0.51206.0

CAB has not been updated to a new Production release at this time and so it was built with an older version of the Microsoft.Practices.ObjectBuilder.dll. CAB was built with 1.0.51205.0. I assumed that I could just rebuild the CAB solution with a reference to the latest ObjectBuilder and I would be fine. THAT DOES NOT WORK AS EXPLAINED BELOW.

Remember in the title of this article I also included Enterprise Library. Like the SCSF, and new Guidance extensions and toolkit, they are all compiled against the 1.0.5.2106.0 ObjectBuilder.

Here is the problem. Somewhere along the line, the Microsoft.Practices.ObjectBuilder.dll was changed and the version was not ticked to the next minor. Here is a picture of the Microsoft.Practices.ObjectBuilder.dll as it sits in explorer after being built from the Codeplex download from the ObjectBuilder project space:

 objectbuilder1.png

Here is an image of the SAME dll as it sits in the Enterprise Library 3.0 bin folder, see if you can see the difference:
 objectbuilder2.png

In the statusbar (footer) of the explorer window, you can see that both Microsoft.Practices.ObjectBuilder.dll files are the SAME version, but the size of the files in the properties window is quite DIFFERENT. O-O-P-S!

Fortunately the fix is quite easy. The one in the Enterprise Library 3.0′s bin folder is the correct file. I opened the CAB project and removed the ObjectBuilder project from the CAB Solution, and referenced instead the Microsoft.Practices.ObjectBuilder.dll in the Enterprise Library folder.

Ok, CAB Builds, But My Project Is Still Broken

First, make sure you remove ALL of the references to CAB and replace them with the new one we just built. Then remove any ObjectBuilder References and use the ones from the Enterprise Library 3.0. You can also replace the Enterprise Library references while you are at it.

The next thing to do is to change your app.config to reflect the changes to your Enterprise Library Data, Logging and Exception Handling.

Normally you just change the lines which name the versions in your app.config (this file may be in the Constants folder in your shell project if you used the Guidance package).

<section name=”loggingConfiguration” type=”Microsoft.Practices.EnterpriseLibrary.Logging.Configuration.LoggingSettings, Microsoft.Practices.EnterpriseLibrary.Logging, Version=2.0.0.0, Culture=neutral, PublicKeyToken=null” />

In our case, the Enterprise Library is now strong named so we need to change the PublicKeyToken to the actual strong name key. I changed the above line to the following:

<section name=”loggingConfiguration” type=”Microsoft.Practices.EnterpriseLibrary.Logging.Configuration.LoggingSettings, Microsoft.Practices.EnterpriseLibrary.Logging, Version=3.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a” />

You will need to find EVERY mention of ANY type from the Enterprise Library and change these two values. They all use the same PublicKeyToken and are all version 3.0.0.0, so you can just blanket change them. Just make sure what you change IS part of the Enterprise Library. It is possible that you have other referenced dll’s in your app.config.

Your project with the new (fixed) CAB and the latest Guidance Packages and Toolkits installed, using the correct Object Builder are all set. You should be able to build your project once again all upgraded.

Painless! – NOT <grin>

Follow

Get every new post delivered to your Inbox.