extending Windows and Internet Explorer with Band Objects
Introduction
A lot has been already said about extending Windows and Internet Explorer with Band Objects, Browser Bands, Toolbar Bands and Communication Bands. So if you are familiar with COM and ATL you even might have implemented one yourself. Well, in case your were waiting for an easy way to impress your friends with something like Google Toolbar here it is - the .NET way (or should I say Windows Forms and COM Interop way?). In this tutorial I am going to show how to create any of the mentioned above Band Object types with the help of the BandObject
control. Later I will also talk about some implementation details of the BandObject
.
Hello World Bar step by step
1.
Build a Release version of BandObjectLib and register it in the Global Assembly Cache. The easiest way to do this is to open BandObjectLib.sln in Visual Studio, set the active configuration to Release and select 'Rebuild Solution' from the 'Build' menu. The second project in the solution - RegisterLib - is a C++ utility project that performs the 'gacutil /if BandObjectLib.dll' command that puts assembly into GAC.
As you probably already know, Band Objects are COM components. And for the .NET framework to find an assembly that implements a COM component it must be either be registered in the GAC or located in the directory of the client application. There are two possible client applications for Band Objects - explorer.exe and iexplorer.exe. Explorer is located in the windows directory and IE somewhere inside 'Program Files'. So GAC is actually the only one option in this case. Thus .NET assemblies that implement Band Objects should be registered in GAC and all libraries they depend on - like BandObjectLib.dll - should also be there.
Assemblies in the GAC must have strong names and thus key pairs are required. I have provided the BandObjects.snk file with a key pair but I encourage you to replace it with your own. See the sn.exe tool for more details.
2.
Create a new Windows Control Library project and call it SampleBars. We are going to rely on the base functionality of BandObjectLib so we have to add a reference to BandObjectLib\Relase\bin\BandObjectLib.dll. As we are developing a 'Hello World Bar', rename UserControl1.cs and the UserControl1
class inside it appropriately - into HelloWolrdBar.cs and HelloWorldBar
. Also put the following lines at the beginning of HelloWorldBar.cs:
using BandObjectLib;using System.Runtime.InteropServices;
3.
Make HelloWorldBar
class inherit BandObject
instead of System.Windows.Forms.UserControl
. As I mentioned earlier, Band Objects are COM components so we should use the Guid
attribute. Use guidgen.exe to generate your unique GUID or you can use the one I have generated for you:
[Guid("AE07101B-46D4-4a98-AF68-0333EA26E113")]
We also have to sign our assembly with a strong name. You can do this by putting the following line into AssemblyInfo.cs file:
[assembly: AssemblyKeyFile(@"..\..\..\BandObjects.snk")]
4.
Now its time to decide what kind of Band Object we want to develop. Lets make it an Explorer Toolbar as well as a Horizontal Explorer Bar (also known as a Browser Communication Band). All we need to do to implement this decision is to add custom BandObject
attribute to our HelloWorldBar
class:
[Guid("AE07101B-46D4-4a98-AF68-0333EA26E113")][BandObject("Hello World Bar",BandObjectStyle.Horizontal | BandObjectStyle.ExplorerToolbar,HelpText = "Shows bar that says hello.")]public class HelloWorldBar : BandObject{ ...
That's enough to make our control available through 'View->Explorer Bars' and 'View->Toolbars' explorer menus. It also takes care of menu item text - "Hello World Bar", and hen the menu item is highlighted status bar displays "Shows bar that says hello.". Don't you like declarative programming and custom attributes?
5.
Now it is time to open HelloWorldBar.cs in the Visual Studio Designer and put some controls on it. Although in my version of HelloWorldBar I decided to put a single button with 'Say Hello' caption on it you are free to do something more personalized. I made the size of the button equal to the size of the control's client area and also set its Anchor
property to the combination of all possible styles - 'Top, Bottom, Left, Right'. The background color is 'HotTrack' and ForeColor
is 'Info'.
The BandObject
control has several properties specific to the Band Objects (and so classes derived from it) - Title
, MinSize
, MaxSize
and IntegralSize
. I set Title
for HelloWorldBar
to "Hello Bar" and both MinSize
and Size
to '150, 24'. Oh, and in button's On Click event handler I put code that displays a message box. This is what my final code looks like (and most of it was generated by VS.Net):
using System;using System.ComponentModel;using System.Windows.Forms;using BandObjectLib;using System.Runtime.InteropServices;namespace SampleBars{ [Guid("AE07101B-46D4-4a98-AF68-0333EA26E113")] [BandObject("Hello World Bar", BandObjectStyle.Horizontal | BandObjectStyle.ExplorerToolbar, HelpText = "Shows bar that says hello.")] public class HelloWorldBar : BandObject { private System.Windows.Forms.Button button1; private System.ComponentModel.Container components = null; public HelloWorldBar() { InitializeComponent(); } protected override void Dispose( bool disposing ) { if( disposing ) { if( components != null ) components.Dispose(); } base.Dispose( disposing ); } #region Component Designer generated code private void InitializeComponent() { this.button1 = new System.Windows.Forms.Button(); this.SuspendLayout(); // // button1 // this.button1.Anchor = (((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) | System.Windows.Forms.AnchorStyles.Left) | System.Windows.Forms.AnchorStyles.Right); this.button1.BackColor = System.Drawing.SystemColors.HotTrack; this.button1.ForeColor = System.Drawing.SystemColors.Info; this.button1.Name = "button1"; this.button1.Size = new System.Drawing.Size(150, 24); this.button1.TabIndex = 0; this.button1.Text = "Say Hello"; this.button1.Click += new System.EventHandler(this.button1_Click); // // HelloWorldBar // this.Controls.AddRange(new System.Windows.Forms.Control[] { this.button1 }); this.MinSize = new System.Drawing.Size(150, 24); this.Name = "HelloWorldBar"; this.Size = new System.Drawing.Size(150, 24); this.Title = "Hello Bar"; this.ResumeLayout(false); } #endregion private void button1_Click(object sender, System.EventArgs e) { MessageBox.Show("Hello, World!"); } }}
6.
Ok, now we are ready to build SampleBars.dll but its not enough to see it in explorer yet. We have to put our assembly into the GAC as well as register it as a COM server. There are tools - gacutil.exe and regasm.exe that do just this. The C++ utility project named Register in my version of the SampleBars solution liberates me from using these tools manually. It has no files in it, just the following post-build command (debug version):
cd $(ProjectDir)..\bin\Debug gacutil /if SampleBars.dll regasm SampleBars.dll
Of cause you have to make sure that Register project is the last one to be built in the solution using Project Dependencies / Build Order.
After building the solution, and executing the gacutil and regasm commands, we are finally ready to start Explorer and see our toolbar and explorer bar. And if you did everything right you should be able to see something like the picture at the top of the article. On this picture you can also see how HelloWorldBar
looks in the Windows Taskbar. To achieve this all you need to do is to modify BandObject
attribute adding the BandObjectStyle.TaskbarToolBar
flag.
Note
There are several issues that you might face developing a band object. First of all, every time you rebuild your project Visual Studio tends to generate new version of the assembly. It does this because of the following line in AssemblyInfo.cs:
[assembly: AssemblyVersion("1.0.*")]
I recommend using something like "1.0.0.0" or you'll end up with multiple versions of your assembly in GAC. As a matter of fact, according to Jeffrey Richter, the auto increment feature of assembly version is a bug; the original intent was to increment the version of a file, not the assembly.
Another issue, more specific to BandObjects, is that explorer caches COM components. This means that even after you deployed a new version of your assembly into GAC, Explorer will not use it until restarted. What may help is setting the 'Launch folder windows in separate process' Explorer setting on. Also if you are getting "Unexpected error creating debug information file..." it is also due to the fact that previous version of your assembly is loaded into Explorer's process space.
Inside the BandObject
Ok, now we can look inside the BandObject
class implementation. Lets start with ComComInterop.cs file. Explorer requires band objects to implement a set of COM interfaces - IObjectWithSite
, IDeskBand
and others. Unfortunately these interfaces are not available in the required form of type library so you cannot start using them just by adding a new reference to your project. These interfaces declarations are available in the form of C++ classes and MIDL interfaces. So before using them in .NET you have to convert these declarations into one of the .Net languages. This file also contains several structs and enums used by these interfaces. The whole process of converting is quite straightforward - you see HWND
make it IntPtr
, IUnknown
- Object
etc. Probably the most difficult for me was dealing with the DESKBANDINFO
structure. You see, in the C++ version one of its parameters is declared as
WCHAR wszTitle[256];
It took quite a lot of debugging until I figured out what the C# version should look like. Note the use of CharSet.Unicode
and that SizeConst
is set to 255 not to 256.
[StructLayout(LayoutKind.Sequential,CharSet=CharSet.Unicode)]public struct DESKBANDINFO{ public UInt32 dwMask; public Point ptMinSize; public Point ptMaxSize; public Point ptIntegral; public Point ptActual; [MarshalAs(UnmanagedType.ByValTStr, SizeConst=255)] public String wszTitle; public DBIM dwModeFlags; public Int32 crBkgnd;};
And so the BandObject
class implements required interfaces. The entry point is the IDeskBand.GetBandInfo
method. Its implementation simply gets the values of the Size
, MinSize
, IntegralSize
and Title
properties, and populates the DESKBANDINFO
structure. That gives Explorer hints how to display and resize band object.
The implementation of other methods of IDeskBand
interface was easy: ShowDW()
delegates to Control.Show()
or Hide()
, CloseDW()
calls Dispose()
and GetWindow()
simply returns the Handle
property of the control.
IObjectWithSite
is equired to establish communication with the hosting Explorer process. Inside the SetSite()
method of the BandObject
control tries to get a reference to an IWebBrowser
interface - an interface implemented by the top-level object of Explorer. It will be available through the BandObject.Explorer
property. In the case of a taskbar toolbar there is no IWebBrowser
interface so it handles this situation gracefully. As soon as a pointer to IWebBrowser
is retrived, BandObject
fires the ExplorerAttached
event. Handling this event is useful when you want to add extra initialization code - subscribing to Web Browser events etc.
The IInputObject
implementation is more interesting. This interface is required if you want your band object to participate in processing of keyboard input. The user can navigate from one explorer interface object (address bar, folder view) to another by pressing 'Tab' or 'Shift+Tab'. When it is your band object's turn to be activated or deactivated, Explorer calls the UIActivateIO()
method. BandObject
's implementation of it simply calls Select()
on one of its child controls that acquires focus. Which control to select depends on the tab order of controls and whether user navigates forward or backward (with Shift key). BandObject
also makes sure that focus can leave it (in case last control is selected and user presses Tab). This logic is implemented inside the TranslateAcceleratorIO()
method. It first checks if the 'Tab' or 'F6' keys were pressed. Then depending on the state of 'Shift' key it tries to move focus from one child control to another using the SelectNextControl()
method. The last parameter of SelectNextControl
is false
as we don't want to cycle through controls forever (from last to first and vice versa). If there is no next control we return zero which signals to Explorer that we don't know how to process this command. So Explorer itself processes the command, moving focus to an appropriate user interface object.
And finally my favorite part: the Register()
and Unregister()
methods. These methods are adorned with Com{Un}registerFunction
attributes so the regasm.exe tool knows to call them when assembly is registered as a COM server. Register
function checks its input parameter for presence of BandObjectAttribute
. If it is present then its Name
, Style
and HelpText
properties are used to create apropriate registry settings. For instance, to make your COM component to be considered as a 'Browser Communication Band' you have to mark it as implementing the 'Browser Communication Band' COM category. To make it an Explorer toolbar you have to register its CLSID
under SOFTWARE\Microsoft\Internet Explorer\Toolbar. But you don't have to worry about these details. All you need to know is what BandObjectStyle
flag to use and Register()
takes care of the rest. Similarly, the Unregister()
method takes care of what have to be removed from the registry when assembly is unregistered.
Download source files - 118 Kb
Posted using illegal copy of BlogJet.
Please purchase a license here.
0 Comments:
Post a Comment
<< Home