Skip navigation

Daily Archives: July 31st, 2007

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

While working on a recent project (using CAB) I found that I was able to cause the Tab key to switch focus to a control that was not within the viewable area. This can happen in several situations in windows programming as well as it being a problem in a CAB application (Composite UI Application Block). In CAB, you can use a deck workspace. Think of it as a deck of cards, where the view you are dealing with is on top. Well, now think about stacking decks within decks within decks, etc. Tabbing your view will run through the controls on that view, and then continue into the deck for some reason. This is bad mkay?

The problem is not setting the TabStop = false on the control, but rather; finding the offending control so that you can add that code to it. If the control is not on the screen, you wont see the focus rectangle around it — because it’s not on the screen. Imagine tabbing from control to control to control to nothing. If you continue hitting the tab key, you will eventually come back to the first control in the Tabindex, but how can we find which control has the focus at any given time?

I came up with two approaches. The first one sounded good, but didnt work for me.

What didnt work

I added a timer to my control, and a button and a label. The idea was that I could click the button to turn on the timer. When the timer fired, I could loop all of the controls (except labels which cant get focus). I would then test if it was the control that currently had focus. Well, I quickly figured out that controls have child controls, so I created a recursive method that would walk the tree of controls. This worked as expected … well almost. Here is the code, can you spot the problem?

private void timer1_Tick1(object sender, EventArgs e)
{
timer1.Enabled = false;
FocusedControlRecursive();
timer1.Enabled = true;
}

private void FocusedControlRecursive()
{
foreach (object control in Controls)
{
if (control is Control)
{
FocusedControlRecursive((Control)control);
}
}

}
private void FocusedControlRecursive(Control control)
{
if (control.HasChildren)
{
foreach (Control ctrl in control.Controls)
{
if(!(ctrl is Label))
{
FocusedControlRecursive(ctrl);
}
}
}

if (control.Focused)
{
label1.Text = “Ctrl: ” + control.Name;
}
}

The problem is that the controls in my case were not children of the current control, but rather they existed in another “slice” of the deck workspace.

The Solution

The solution was to create a control that I could drop onto a form that would report the currently focused control. Since the contro lmay not be within my children (or childrens children) I turned to an API call called GetFocus().

The pInvoke looks like this:

[DllImport("user32.dll")]
public static extern IntPtr GetFocus();

With the returned IntPtr, you can call the static method FromHandle() which returns a Control. The Focused Control!! Perfect.

I created a control you can drop onto your form and enable or disable. When enabled it calls the API every 2 seconds and gives you the control.Name of the focused control.

 Here is the code:

private void timer1_Tick(object sender, EventArgs e)
{
timer1.Enabled = false;

IntPtr pointer = GetFocus();
Control ctrl = this.FromHandle(pointer);
label1.Text = ctrl.Name;

timer1.Enabled = true;
}

-Savij

Follow

Get every new post delivered to your Inbox.