//
you're reading...
Articles, Technology

MenuExtenderComponent for .NET – Part 2

MenuExtenderComponent — Building an Even Better Mouse Trap

Part 2 of 4

Table of Contents

How it All Works

Now that you have seen a description of the component and should have played around with it, it’s time to see how it works.

There are three major components that make up the MenuExtenderComponent. I’ll try to discuss each separately, and then bring them all together later.

First I will cover the other major component that I made – the MdiChildManager — this is a helper component to make handling MDI children easier. Next I will cover the MenuDrawer component which is responsible for doing…all the drawing. Finally I’ll cover the MenuExtenderComponent itself, explain how it works, and walk through the creation of a new MenuDrawer (a duplicate of the Normal style minus a few features).

The MdiChildManager

In the first version of the component, when I attempted to duplicate the MDI list functionality I came across a problem, there isn’t an event that fires when a new MDI Child is created, closed, or has its text changed. The only MDI event exposed is one for when an MDI child is activated. This just happens to fire whenever a new child is created; but it doesn’t tell us if a new child was created or if focus was switched to another child.

In attempt to clean that up I created the MdiChildManager, the only bit of code to live between rewrites only this time I was turned into its own component.

What it does

The MdiChildManager provides events to know when an MDI child has been opened, closed, changed its visibility, or its text has changed (i.e. the title).

Its Events

public delegate void MdiChildActionEventHandler(object sender, 
    MdiChildActionEventArgs e);

public event MdiChildActionEventHandler MdiChildCreated;
public event MdiChildActionEventHandler MdiChildClosed;
public event MdiChildActionEventHandler MdiChildVisibilityChanged;
public event MdiChildActionEventHandler MdiChildTextChanged;

Each of these events tells when something important has happened relating to MDI children. Three of these are simply created by handling events from the MDI children so I won’t cover the code for that. Instead I will cover the code for the MdiChildCreated event since it isn’t obvious when that happens.

Let’s start with a basic class definition as well as the private variables we’ll need.

public class MdiChildManager : Component, IEnumerable, ISupportInitialize
{
  // "Key" for the child created event
  private static object eventMdiChildCreated = new object();

  private bool isInitialized = false;
  private Form parent;
  private Form host;
  private Form mdiChildClosed;
  private ArrayList mdiChildren;
 
  public MdiChildManager()
  {
    mdiChildren = new ArrayList();
  }

  public MdiChildManager(IContainer container) : this()
  {
    container.Add( this );
  }

  public MdiChildManager(Form mdiParent) : this()
  {
    this.Host = mdiParent;
  }
}

Here you can see several constructors are created, the first two are for the Windows Forms Designer and the third is for calling from code (which is what the MenuExtenderComponent does internally).

You can also see that the last two constructors call the first, which just initializes the ArrayList we’ll use to keep track of the MDI children already opened. The last constructor also sets the Host property right away. Now, it is time to start adding the only property that is needed.

public Form Host
{
  get
  {
    if( host == null && DesignMode )
    {
      IDesignerHost designerHost = 
            (IDesignerHost) GetService(typeof(IDesignerHost));

      if( designerHost != null && designerHost.RootComponent is Form )
        host = (Form) designerHost.RootComponent;
    }

    return host;
  }
  set
  {
    if( this.host != value && !isInitialized )
    {
      if( this.host != null && !DesignMode )
      {
        this.host.ParentChanged -= new EventHandler(OnParentChanged);
      }

      this.host = value;

      if( this.host != null && !DesignMode )
      {
        this.host.ParentChanged += new EventHandler(OnParentChanged);
      }

      if( !DesignMode )
      {
        // See if we have a parent already
        OnParentChanged( null, null );
      }
    }
  }
}

Host is used to control which Form this component is hosted on. This is important so that we can attach event handlers to the correct events on the correct objects. Unfortunately, the MdiChildActivate event is only fired on the Form that is set as the MDI Container (or MdiParent) so this is very important.

While there isn’t an MdiParentChanged event we can listen to, the MdiParent property and the Parent property are closely linked in the framework enough that the ParentChanged event will fire when the MdiParent changes so it is good enough for our needs.

The code for the get method of the Host property is relatively simple. If it is in DesignMode and the Host property is currently null, it will ask the design time environment for some information. In this case, what component is hosting it and what type it is. If it is a Form we assume this is the Form the component was dropped on and we return that as the value of Host. This in turn causes the designer to say “Hey, this value isn’t the default so we had better assign it back to the Host property in the generated code.” That is why the get method is so complicated, to provide a better design time experience.

The set method is a little bit different. I have decided that the Host property can only be changed before the component has finished initialization. I can’t think of any good reason to allow it to change (nor why you would want to) so I’m not going to go through the hassle of allowing it. The first if is just checking that the value is changing and that it hasn’t finished initialization yet. After that it unhooks any event handlers that was added to a previous Host, changes the value, and hooks up the ParentChanged event handler again. Lastly it runs the ParentChanged event handler to check if there is already a Parent attached to the form. That brings us to the OnParentChanged event handler. After that I will discuss the actual heart of the component.

private void OnParentChanged(object sender, EventArgs e)
{
	if( this.parent != null )
	{
		this.parent.MdiChildActivate -= new EventHandler(OnMdiChildActivate);
		mdiChildren.Clear();
	}

	if( host.IsMdiContainer )
		parent = host;
	else
		parent = host.MdiParent;

	if( this.parent != null )
	{
		this.parent.MdiChildActivate += new EventHandler(OnMdiChildActivate);
		OnMdiChildActivate( null, null );
	}
}

This code is relatively simple, but there are a few things to point out. The parent member variable will always reference the MDI parent, whether that happens to be the Host or some Form above the Host. That is why there are two variables to track the parent.

As I’ve mentioned before, the MdiChildActivate event is what drives this component so if the Parent changes the event handlers for it have to be disconnected and reconnected while the Parent object changes. In between it determines whether to use the Host or its MdiParent to hook the event handler to. Once that is done it calls the MdiChildActivate event handler manually so that any MDI children that already created will be registered by the component.

Now, the moment you’ve waited to see: The OnMdiChildActivate method.

private void OnMdiChildActivate(object sender, EventArgs e)
{
  foreach(Form child in parent.MdiChildren)
  {
    if( child != mdiChildClosed && !mdiChildren.Contains(child) )
    {
      mdiChildren.Add(child);
      
      HookupEventHandlers(child);

      // Fire the MdiChildCreated event
      OnMdiChildCreated(new MdiChildActionEventArgs(child));
    }
  }
}

It used to be more complicated, back when it did more than just track what MDI children were open. The only “gotcha” is that the MdiChildActivate event fires after an MDI child has been closed but before said child is removed from the MDI parent’s list of children. To handle this case, the component handles the Close event from the child and sets a member variable; this variable is then used to check that a newly found child isn’t the one we just closed.

The rest is fairly simple from there, so much so that I’m not going to show any code. The only hard event to detect was when a new MDI child was created and I just covered that. The other three MDI child events are simply bubbled by handling the corresponding events from the MDI children themselves. Since I needed to handle those events in the MenuExtenderComponent I chose to create a single place for event handling rather than having to deal with each child separately.

The MenuExtenderComponent

The component’s purpose is to let a developer quickly add images to their menus as well as allow different styles being applied to give it a little more flair. It accomplishes this through the use of owner drawn menus and implementing IExtenderProvider so that the existing Menu objects can be used without modification.

I’ve written an article on what the IExtenderProvider interface is and what it does so I will assume that you already know at least the basics.

The Properties and Extended-Properties have already been covered so I won’t have to go through that again instead I will focus on how I implemented the stuff behind the scenes.

Setting it Up

The MenuExtenderComponent implements ISupportInitialize so that it can put all of the initialization code in one place. This also ensures that when the initialization code runs it will be after all changes have been made to the MenuItems. In this case we aren’t really interested in being told that initialization has begun, so the BeginInit() method is empty.

That is not the case with EndInit() though; in that method the code goes through all registered MenuItem's and sets up the event hooking. If MDI support is enabled it’ll create a new MdiListManager which will ensure that the child menu items are kept up to date. Because items are added – causing the collection to change – the creation of the new menu items has to be delayed until after going through the loop.

void ISupportInitialize.EndInit()
{
  if( !DesignMode )
  {
    isInitialized = true;

    if( host != null )
      EnableMDISupport();

    foreach( DictionaryEntry de in this.menuItems )
    {
      MenuItem item = (MenuItem) de.Key;
      MenuInfo info = (MenuInfo) de.Value;

      if( info.Enabled )
      {
        HookupEventHandlers( item );
      }

      if( MdiSupport && item.MdiList )
      {
        this.mdiListManagers.Add( new MdiListManager( this, item ) );
      }
    }

    if( MdiSupport )
    {
      foreach(MdiListManager mlm in mdiListManagers)
      {
        mlm.SetupWindowsMenu();
      }
    }
  }
}

HookupEventHandlers is also responsible for turning on the OwnerDraw and hooking up the necessary event handlers for doing it. You can ignore the bit about the MdiList, that will be covered later in the MdiListManager sub-section.

Running with it

Now that the menu items are registered with the component we “simply” wait for windows to tell us when to measure and draw items. It isn’t quite so simple, but it is pretty close.

The only confusing part of the ordeal is dealing with the various MenuStyles.

public MenuStyle Style
{
  get
  {
    if( this.useGlobalStyle )
      return MenuStyle.Global;
    else
      return MenuExtenderComponent.GlobalStyle;
  }
  set
  {
    if( value == MenuStyle.Global )
    {
      this.useGlobalStyle = true;
    }
    else
    {
      this.useGlobalStyle = false;

      if( this.Style != value || value == MenuStyle.Custom )
      {
        MenuExtenderComponent.GlobalStyle = value;
      }
    }
  }
}

Here we have almost a tri-state flag. The get portion checks to see if the global style should be used, if it is it returns MenuStyle.Global rather than the real value. To return the real value would cause problems with the design time use of the component, so it will return either MenuStyle.Global or it will return the global value.

The set method will check if it should use the global value, if it is then it simply sets the flag basically telling the component to do nothing. If it isn’t supposed to use the global value then it proceeds to set the global value to whatever is set. This then forces the GlobalStyleChanged event to fire which will cause the next bit of code to run: UpdateDrawer.

public MenuDrawer UpdateDrawer()
{
  if( this.drawerNeedsUpdating )
  {
    lock( getDrawerLockObject )
    {
      // double the if so we only run this code once
      if( this.drawerNeedsUpdating )
      {
        this.drawerNeedsUpdating = false;

        if( drawer is IDisposable )
        {
          ((IDisposable) drawer).Dispose();
        }

        switch( MenuExtenderComponent.GlobalStyle )
        {
          case MenuStyle.Normal:
            drawer = new Drawers.NormalDrawer();
            break;
          case MenuStyle.OfficeXP:
            drawer = new Drawers.OfficeXPDrawer();
            break;
          case MenuStyle.Custom:
            drawer = OnGetDrawer();
                
            if( drawer == null )
            {
              throw new InvalidOperationException("...");
            }
        
            break;
        }
      }
    }
  }

  return drawer;
}

Not much here is new code; I employ the if-lock-if construct to ensure that the code will only run if it needs to. In the end it will return a new MenuDrawer object representing the new style to draw.

UpdateDrawer will be called the first time the menus are asked to redraw after the GlobalStyle has changed. This will happen soon afterwards because once the GlobalStyle changes it will ask Windows to re-measure and redraw all of the menu items.

But what happens when your new style wants a different size for the menu items? Luckily resetting the OwnerDraw property on the menu items will force windows to ask for the size again. The only downside is that in merged menu cases this can take a while, and while it is doing that the menu items will flicker as Windows does its own drawing then switches right back to doing an owner draw. Below you will find the code to set up the drawers for measuring and drawing. The properties for the MenuDrawer's will be covered in The MenuDrawer section below.

private void OnMeasureMenuItem(object sender, MeasureItemEventArgs e)
{
  // It appears there is some multi-threading going on
  // here, so we should lock just to make sure the drawer doesn't
  // get used by another thread before we are done with it
  lock( MenuExtenderComponent.measureLockObject )
  {
    MenuItem item = (MenuItem) sender;

    MenuInfo info = EnsureMenuInfoExists( item );

    UpdateDrawer();

    drawer.Extender = this;
    drawer.Bounds = Rectangle.Empty;
    drawer.Index = e.Index;
    drawer.MenuInfo = info;
    drawer.MenuItem = item;
    drawer.State = DrawItemState.None;

    drawer.Graphics = e.Graphics;

    Size sz = drawer.Measure();

    e.ItemWidth = sz.Width;
    e.ItemHeight = sz.Height;
  }
}

Like the comment says, while I was trying to fix something else I was seeing a weird problem where it seemed like some multi-threading was going on behind the scenes. Since the MenuDrawer's aren’t structured to be used in a multi-threaded fashion I added some lock statements to ensure this doesn’t happen.

private void OnDrawMenuItem(object sender, DrawItemEventArgs e)
{
  // It appears there is some multi-threading going on
  // here, so we should lock just to make sure the drawer doesn't
  // get used by another thread before we are done with it
  lock( MenuExtenderComponent.drawItemLockObject )
  {
    MenuItem item = (MenuItem) sender;

    MenuInfo info = EnsureMenuInfoExists( item );

    UpdateDrawer();

    drawer.Extender = this;
    drawer.Index = e.Index;
    drawer.MenuInfo = info;
    drawer.MenuItem = item;
    drawer.State = e.State;

    if( UseDoubleBuffer )
    {
      using( Bitmap backBuffer = 
             new Bitmap( e.Bounds.Width, e.Bounds.Height, e.Graphics ) )
      using( Graphics g = Graphics.FromImage( backBuffer ) )
      {
        drawer.Graphics = g;
        drawer.Bounds = new Rectangle( new Point( 0, 0 ), e.Bounds.Size );
        drawer.DrawMenu();

        e.Graphics.DrawImage( backBuffer, e.Bounds );
      }
    }
    else
    {
      drawer.Bounds = e.Bounds;
      drawer.Graphics = e.Graphics;

      drawer.DrawMenu();
    }
  }
}

Pretty similar to the measuring code, the only difference here is that if a double buffer is requested (the default) a new Bitmap and Graphics object is created to do the drawing on. Once the drawing is done then it gets drawn to the “real” Graphics object.

Dealing with MDI Environments

This is all fine and dandy, and if you were to roll it all up right now it would work fairly well. Except for one thing; the MenuItems that Windows creates for you to represent MDI children aren’t drawn in the new style!

This is where my component starts to earn its keep. By combining the MdiChildManager with this next bit of code, those extra menu items will be drawn by the component as well. Introducing the MdiListManager.

The MdiListManager

The MdiListManager takes the events from the MdiChildManager and adds and removes MenuItems for every MDI child opened in the MDI environment. In the very first version of this component, this was very complicated to do because the MdiChildManager and MdiListManager were both tucked in the code of the MenuExtenderComponent itself. Now that they are separate entities the code is less complicated overall. Proof that a bad design makes coding things hard and a good design can make it 100 times easier.

If you recall the MenuExtenderComponent.EndInit() method there were two pieces of code that dealt with the MdiListManagers.

foreach( DictionaryEntry de in this.menuItems )
{
  MenuItem item = (MenuItem) de.Key;
  MenuInfo info = (MenuInfo) de.Value;

  if( info.Enabled )
  {
    HookupEventHandlers( item );
  }

  if( MdiSupport && item.MdiList )
  {
    this.mdiListManagers.Add( new MdiListManager( this, item ) );
  }
}

if( MdiSupport )
{
  foreach(MdiListManager mlm in mdiListManagers)
  {
    mlm.SetupWindowsMenu();
  }
}

I briefly mentioned that the set up of the MdiListManager had to be split into two parts. The reason for this is that in addition to the MenuItems that will be created for each MDI child 1-2 more MenuItems will be created, an optional separator and a “Windows…” dialog that mimics the Windows dialog in VS.NET 2003. As we know, the contract for enumerable collections is that you cannot modify the collection while enumerating through it. Adding those two items would modify the collection of MenuItems associated with the component so that needs to be delayed.

This component only has one purpose – to help the MenuExtenderComponent – so it is implemented as an internal class.

Now for some more relevant code; we’ll start out with the basic skeleton to discuss the manager in pieces.

internal class MdiListManager
{
  private MenuExtenderComponent extender;
  private MenuItem mdiList;
  private MenuItem windowsMenuItem;
  private Hashtable mdiChildren;

  public MdiListManager( MenuExtenderComponent extender, MenuItem mdiList )
  {
    this.extender = extender;
    this.mdiList = mdiList;
    this.windowsMenuItem = null;
    this.mdiChildren = new Hashtable();

    this.mdiList.MdiList = false;
    this.mdiList.Popup += new EventHandler(OnMdiListPopup);

    this.extender.ChildManager.MdiChildClosed += 
      new MdiChildActionEventHandler(OnChildClosed);
    this.extender.ChildManager.MdiChildCreated += 
      new MdiChildActionEventHandler(OnChildCreated);
    this.extender.ChildManager.MdiChildTextChanged += 
      new MdiChildActionEventHandler(OnChildTextChanged);
    this.extender.ChildManager.MdiChildVisibilityChanged += 
      new MdiChildActionEventHandler(OnChildVisibilityChanged);
  }

  public void SetupWindowsMenu()
  {
  }
}

If you saw the public ChildManager property before and wondered why it was there, this is why. The MdiListManager needs access to the MdiChildManager created by the MenuExtenderComponent.

The set up of the manager is fairly simple. It stores a reference to the MenuExtenderComponent so it can (un)register new MenuItems with the extender. It also stores a reference to the MenuItem that had the MdiList property set to true. This is needed so the new MenuItems can get added to the correct MenuItem. It also sets up a hash table so that given an MDI child I can easily get a reference to the MenuItem representing that MDI child.

Next it sets the MdiList property on the first MenuItem to false; this stops Windows from adding its MenuItems to it, but not us. Finally it starts to add several event handlers. The first one just handles the Popup event of the MdiList item so the active child has a checkmark drawn next to it. The other four event handlers are used to respond to the events fired by the MdiChildManager to add, remove, rename, or change the visibility of the various MenuItems. I will refer MenuItems that previously had the MdiList property set to true as MDI Lists.

public void SetupWindowsMenu()
{
  if( mdiList.MenuItems.Count > 0 && 
      mdiList.MenuItems[mdiList.MenuItems.Count - 1].Text != "-" )
  {
    // Add a separator
    MenuItem separator = new MenuItem("-");
    separator.MergeType = MenuMerge.MergeItems;
    separator.MergeOrder = 998;

    extender.SetEnableExtension(separator, true );
    mdiList.MenuItems.Add( mdiList.MenuItems.Count, separator);
  }

  windowsMenuItem = new MenuItem();
  windowsMenuItem.MergeType = MenuMerge.MergeItems;
  windowsMenuItem.MergeOrder = 1000;
  windowsMenuItem.Text = "&Windows...";  // TODO: Localization support
  windowsMenuItem.Click += new EventHandler(OnWindowsMenuClick);

  extender.SetImage( windowsMenuItem, 
                     new System.Drawing.Bitmap( typeof( MdiListManager ), 
                                                "WindowsDialogIcon.bmp" ) );

  extender.SetEnableExtension( windowsMenuItem, true );

  mdiList.MenuItems.Add( mdiList.MenuItems.Count, windowsMenuItem );
}

Like the method name says above, this code sets up the Windows menu. First it does a check to see if there are already MenuItems added to the MDI List, if there are then it checks to see the last item is a separator. If it isn’t a separator then it will proceed to add a new separator to the parent MenuItem. This will separate any previous MenuItems from the ones generated by the manager.

Lastly it will add a MenuItem which will provide a dialog box to see all of the opened MDI children.

All of the generated MenuItems will have the MergeOrder and MergeType set. MergeType will be set to MenuMerge.MergeItems. The generated separator will have its MergeOrder set to 998, the “Windows” dialog will be set to 1000, and the ones generated for each MDI child will be set to 999. Each of the MDI child items will have its MergeType set to MenuMerge.Add because there will be multiple MenuItems with the same MergeOrder value.

Now that the MdiListManager is set up it is time to start dealing with the events provided by the MdiChildManager.

private void OnMdiListPopup(object sender, EventArgs e)
{
  foreach( DictionaryEntry de in mdiChildren )
  {
    MenuItem mi = (MenuItem) de.Value;
    Form child = (Form) de.Key;

    Form mdiParent = child.MdiParent;

    if( mdiParent.ActiveMdiChild == child )
      mi.Checked = true;
    else
      mi.Checked = false;
  }
}

The code here is rather simple, and included only for completeness sake. It enumerates through each MDI child and MenuItem pair in the hash table, comparing the child with the MDI parent’s ActiveMdiChild property. If they are equal the associated MenuItem gets checked, otherwise it stays unchecked.

private void OnChildCreated(object sender, MdiChildActionEventArgs e)
{
  MenuItem mi = new MenuItem();

  mi.Text = e.MdiChild.Text.Replace( "&", "&&" );
  mi.Click += new EventHandler(OnMenuChildClicked);
  mi.RadioCheck = true;
  mi.MergeOrder = 999;
  mi.MergeType = MenuMerge.Add;

  extender.SetEnableExtension( mi, true );
  mdiList.MenuItems.Add( windowsMenuItem.Index, mi );
  mdiChildren.Add( e.MdiChild, mi );
}

When a new MDI child is created we start off by creating a new MenuItem to go with it. The Text of the MenuItem is the same as the Text property of the child, but we replace any “&” that may be present in the text with “&&” to prevent an accelerator from being accidentally created. After that an event handler is added to the Click event to set the focus to the correct MDI child when the corresponding MenuItem is clicked. It also sets the RadioCheck property to true to represent that only one of the MDI children can have focus at a time.

Once that is done, it registers the new MenuItem with the MenuExtenderComponent, adds it to the MDI List, just before the “Windows…” menu item, finally it adds the MenuItem and the MDI child to its hash table of pairs.

private void OnChildClosed(object sender, MdiChildActionEventArgs e)
{
  MenuItem mi = (MenuItem) mdiChildren[ e.MdiChild ];

  mdiList.MenuItems.Remove( mi );
  mdiChildren.Remove( e.MdiChild );

  // Remove item and remove all references to the item
  extender.SetEnableExtension( mi, false, true ); 
}

When an MDI child is closed the first thing to do is to retrieve the associated MenuItem from the hash table, then proceed to remove all references of it and the child from the MdiListManager, the MDI List MenuItem, and the MenuExtenderComponent. To finally remove all references a special overload of the MenuExtenderComponent.SetEnableExtension is used. In addition to the usual two parameters, a third tells the component if it should remove the MenuItem right out of the hash table it uses to keep track of menu items it needs to measure and draw. If this special overload isn’t used it merely sets the Enabled extended-property on the MenuItem to false.

private void OnChildTextChanged(object sender, MdiChildActionEventArgs e)
{
  MenuItem mi = (MenuItem) mdiChildren[ e.MdiChild ];
  
  mi.Text = e.MdiChild.Text.Replace("&", "&&");
}

private void OnChildVisibilityChanged(object sender, MdiChildActionEventArgs e)
{
  MenuItem mi = (MenuItem) mdiChildren[ e.MdiChild ];
  
  mi.Visible = e.MdiChild.Visible;
}

The above two event handlers just update the text or visibility of the MenuItem to reflect changes in the MDI child, nothing special going on there.

Even the code to handle the Click events isn’t all that special, but to be complete about everything.

private void OnMenuChildClicked(object sender, EventArgs e)
{
  Form child = null;

  foreach( DictionaryEntry de in mdiChildren )
  {
    if( de.Value == sender )
    {
      child = (Form) de.Key;
      break;
    }
  }

  child.Focus();
}

private void OnWindowsMenuClick(object sender, EventArgs e)
{
  WindowsDialog dialog = new WindowsDialog( this.extender.ChildManager );
  dialog.ShowDialog( this.extender.Host );
}

In the first event handler, it sets the focus to whichever child was selected from the MdiList menu. It does this by enumerating through the MDI child to MenuItem hash table until it finds the MenuItem corresponding to the child that was chosen. Now that we have the child it can then call its Focus() method.

The second event handler is responsible for showing the Windows dialog.

The Windows dialog doesn’t have anything special going on for it, so you can refer to the source if you want to see how it works; I don’t think it needs talking about here.

The Windows dialog with some text missing because of a bad Windows XP Visual Style

I have no idea why some of the button text is missing; I can only assume it is because of the Windows XP Visual Style that was being run on my PC at the time.

The IMdiChild Interface

By itself the Windows dialog looks rather empty because it doesn’t have access to much information; just the Text property from each MDI Child. To help fill it in a little I created the IMdiChild interface.

public interface IMdiChild
{
  bool DocumentModified { get; }

  string [] FieldNames { get; }

  string GetFieldValues( string name );

  void SaveDocument();
}

When the Windows dialog is created it obtains a reference to the MdiChildManager owned by the MenuExtenderComponent that created the dialog; speaking more correctly, the MdiListManager that created the dialog.

Implementing the interface is fairly easy, the DocumentModified property should just return if – get this – the document shown in the MDI child has been modified since it was last saved, created, or opened. The SaveDocument method should do any saving of the document shown in the MDI child window.

The other two items in the interface are used to fill up the list view that takes up most of the space in the dialog.

The FieldNames property should return an array of strings, where each string returned will represent one column in the list view. Once the Windows dialog has queried of the opened MDI children for the field names they provide, it then asks each MDI child to return a value for each of the field names it found by calling GetFieldValues.

When implementing these two properties there are three things to keep in mind:

  1. The names returned from the FieldNames property will be displayed in the column header, as-is.
  2. GetFieldValues will be called with the name of each column header created, possibly including ones that weren’t returned by the FieldNames property.
  3. GetFieldValues should return an empty string (“”) to represent no value.

License

The code and binary component follows the FreeBSD license:

MenuExtenderComponent Copyright © 2004 James T. Johnson. All rights reserved.

Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

  1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
  2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution

THIS SOFTWARE IS PROVIDED BY JAMES T. JOHNSON “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FREEBSD PROJECT OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

This article is Copyright © 2004 James T. Johnson and requires my written permission to be reproduced elsewhere.

Continue reading the next part

Article History

  • March 18, 2004
    • Initial Version
Advertisements

About James

I am a Senior Developer/Consultant for InfoPlanIT, LLC. I previously spent over 7 years as a Product Manager for what eventually became ComponentOne, a division of GrapeCity. While there, I helped to create ActiveReports 7, GrapeCity ActiveAnalysis, and Data Dynamics Reports.

Discussion

No comments yet.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Archive

%d bloggers like this: