Click here to Skip to main content
15,884,629 members
Articles / Programming Languages / C#
Article

Table Size Selector Control

Rate me:
Please Sign up or sign in to vote.
4.76/5 (23 votes)
3 Feb 20039 min read 101K   1.9K   45   16
Dropdown Control that mimics the Microsoft Word Table Selector dropdown.

Sample Image - TableSizeSelector.jpg

Introduction

I'm currently writing a simple HTML editor. I needed a control that I could put in the toolbar that allows the user to quickly insert a new table. Since this control is intended to appear in the toolbar we wanted the input to be quick. In other words, no popup dialog. Just specify the row and column size of the table and voila, a new table appears in the HTML source. Because this article addresses the control and the issues I encountered while writing it, I've included a simple test harness to demonstrate how the control works.

Of course, Microsoft does this in MS Word©. Since this is such a nice control I decided to mimic it as closely as possible. I believe that I've successfully identified the mouse and keyboard functionality for this control. If you happen to find additional functionality that I failed to cover please let me know.

This control also deals with the issue of positioning across multiple screens. In my research for this control, I noticed that this issue is either not dealt with properly or just plain ignored. Other issues I'll touch include, controlling flickering effects when painting your form, double buffering and setting the initial size of your form.

Using the code

How do I get this code into my project?

Option #1

Of course the simplest way is to copy the code into your project. The functionality is captured in two source files TableSizeSelector.cs and TableSizeSelectorDropDown.cs. Now build your project. The obvious downside to this is that, you'll have to keep the files up to date in your project. By doing this you won't have to reference an additional DLL (mine) in your project. Assuming this all worked out as planned, you can add the control to the toolbar by following the directions below.

Option #2

Extract the TableSizeSelector.dll from the demo project to your hard drive. (Or build it from the source files.) Add a reference to the DLL in your project. You can now follow the directions below to add the control to your toolbar.

Add the Control to the Toolbar

The basic steps to adding the TableSizeSelector control to your toolbox are:

  1. Right click on the toolbar in Visual Studio .NET. Select 'Customize Toolbar'.
  2. Select the '.NET Framework Components' tab.
  3. Select the DLL or EXE that contains the definition of the TableSizeSelector. This DLL is called TableSizeSelector.dll in the demo project.

That's it! You can now drag and drop the TableSizeSelector button to your form.

What about customizing the thing?

I'm glad you asked!

First off, it's important to understand that TableSizeSelector derives from System.Windows.Forms.Button, while TableSizeSelectorDropDown derives from System.Windows.Forms.Form. Both controls extend the properties of their respective base class.

You'll notice that the TableSizeSelector button control in the demo project just looks like a plain old everyday button. This is on purpose. It's up to you to make it look pretty by adding an image to the button or some similar method.

Each control has its own copy of the properties. I could have just had TableSizeSelectorDropDown keep a reference to its parent TableSizeSelector, but I wanted the TableSizeSelectorDropDown to be available as an independent piece of code. These properties impact the look and behavior of the TableSizeSelectorDropDown. The TableSizeSelector just passes the property values you set for it, to the drop down control. When the drop down closes, the relevant properties are read by the TableSizeSelector and updated appropriately.

OK, enough of that gibberish. Let's see some code! The following code snippet shows the declaration of the property variables for both of the controls. Please note the default values of these properties.

C#
private int   cellSpacing       = 4;
private int   displayCellSize   = 20;
private Size  displaySize       = new Size(4,4);
private Size  tableSize         = new Size(0,0);
private Size  maxTableSize      = new Size(0,0);
private Color selectedRectColor = SystemColors.Highlight;
private Color textColor         = SystemColors.WindowText;
private Color outlineRectColor  = SystemColors.WindowText;

And here is an explanation of how each of these properties affects the behavior of the control.

Property Name Description
CellSpacing Sets the padding between each of the selection rectangles.
DisplayCellSize Sets the size of the selection rectangles.
DisplaySize Controls the number of rectangles initially shown in the drop down control. This value is updated in the TableSizeSelectorDropDown as the selection window grows.
TableSize (Read-only) The table size selected by the user. This value will read as Size(0,0) if the user cancels out of the control.
MaxTableSize Controls the largest table size the user can select. This value is ignored when set to its default value.
SelectedRectColor Color used to draw the selected rectangles.
TextColor Color of the text outputted to the control.
OutlineRectColor Color used to outline the dropdown and each of the individual rectangles drawn on the drop down window.

The easiest thing to do is to play with the demo project and see what happens. All of these properties can be set through the IDE in Visual Studio .NET.

You must have included events?

Of course, of course. What's so great about a custom control without a couple of new events to track of?

Just like the properties, both TableSizeSelector and TableSizeSelector expose the same set of events. Since I'm sure you're bored again, here is some more code for you to read.

The declaration of our delegates:

C#
public delegate void 
    TableSizeChangedEventHandler (object sender, TableSizeEventArgs e);
public delegate void 
    TableSizeSelectedOKEventHandler (object sender, TableSizeEventArgs e);
public delegate void 
    TableSizeSelectedCancelEventHandler (object sender, System.EventArgs e);

And the corresponding events.

C#
public event TableSizeChangedEventHandler TableSizeChanged;
public event TableSizeSelectedOKEventHandler TableSizeSelectedOK;
public event TableSizeSelectedCancelEventHandler TableSizeSelectedCancel;
Event Name Description
TableSizeChanged Fired whenever the selected table size changes.
TableSizeSelectedOK Fired when the user successfully selects a new TableSize.
TableSizeSelectedCancel Fired when the user cancels out of the TableSizeSelectorDropDown.

Again, the easiest way to tie into these events is to use the functionality provided by in Visual Studio .NET. If you just insist on writing the code yourself here is a sample of how you would catch the TableSizeSelectedOK event.

If you haven't already done so, add the following line to the top of your file.

C#
using CabotSoftware.Controls;

Add a new variable to your form like this:

C#
private TableSizeSelector YourTableSizeSelector;

For brevity, I'm going to skip all of the allocation and setup of the control on the form.

Declare a new method in your code with the appropriate signature. This method might look something like this:

C#
private void 
    YourTableSizeSelector_TableSizeSelectedOK
    (object sender, TableSizeEventArgs e)
{
    string DebugOut = "TableSizeSelectedOK Event. Table Size: " 
        + e.NewTableSize.ToString();
    System.Diagnostics.Debug.WriteLine(DebugOut);
}

In the constructor of your form add the following line of code:

C#
this.YourTableSizeSelector.TableSizeSelectedOK += 
    new TableSizeSelectedOKEventHandler
    (this.YourTableSizeSelector_TableSizeSelectedOK);

Done! Well at least in regards to how to use the control.

Points of Interest

I hope you find this interesting because some of these things really left me puzzled!

What's the big deal about a few screens?

This was fun.

Everyone knows that when you move or set the size of a window you have to account for the edges of the screen. When writing the code for this control, I wanted it behave properly. You'll notice that if you drag the demo project window far enough down on the screen, the dropdown will actually appear on the top edge of the button. Now move it over to the right. Try to get it so the Select Table Size button appears just over the clock on your taskbar.

If you're lucky enough to have two monitors move the dialog so that the Select Table Size button straddles both screens. You'll notice that when you click the button the TableSizeSelectorDropDown will be displayed on the same screen that the bottom left corner of its parent TableSizeSelector button appears on.

C#
private void CalcMaxSize()
{
    Screen DisplayOnScreen = Screen.FromPoint(new 
        Point(ParentRectangle.X, ParentRectangle.Bottom));
    Rectangle rect = RectangleToScreen(ClientRectangle);

    // Abbreviated from here
    //...
}

Figuring out which screen you are on is so easy in the .NET Framework. I used the Screen.FromPoint method to calculate which screen the lower left corner of the parent button appeared on. After that I just used the available methods from the Screen object to figure out how to place my control and to calculate its maximum size.

Hey, you're giving me a headache!

Well it's not because my control is flickering. (If it is please don't tell anyone, you'll embarrass me!)

After a bit of searching, I figured out that most straight forward technique to control flickering is to place the following lines of control into the constructor of your control.

C#
SetStyle(ControlStyles.UserPaint, true);
SetStyle(ControlStyles.AllPaintingInWmPaint, true);
SetStyle(ControlStyles.DoubleBuffer, true);

That worked pretty well, but it still didn't solve all of my flickering problems. After a bit more searching I found that I had to turn on the ControlStyles.ResizeRedraw style for my form. The snippet for this is shown below.

C#
SetStyle(ControlStyles.ResizeRedraw, true);

Basically, I needed to redraw the entire form every time it's resized. If your control isn't being resized on the fly like this one, then you can disregard this property. If you're interested, remove this line of code and start playing with the control. You should see some flickering when the control is growing from the bottom up. (Move the dialog back to the bottom of the screen to do this.)

You promised to talk about double buffering!

You're right, I did.

If you've been paying attention I kind of did already. You'll notice that one of the styles I set in my constructor was ControStyles.DoubleBuffer. I took this all a bit further and utilized a technique I read about from the folks at Got DOT NET. Basically, you create a bitmap in memory. You make all of your GDI calls to this bitmap. When you're finished you push the bitmap back to the screen. Here is an abbreviated version of the OnPaint method of the TableSizeSelectorDropDown control.

C#
protected override void OnPaint(System.Windows.Forms.PaintEventArgs e)
{
    // Abbreviated for readability.
    // ...    

    System.Drawing.Graphics g = e.Graphics;

    //create our offscreen bitmap
    Bitmap localBitmap = new Bitmap(ClientRectangle.Width,
        ClientRectangle.Height);
    Graphics bitmapGraphics = Graphics.FromImage(localBitmap);
    bitmapGraphics.Clear(this.BackColor);

    // Do all your drawing onto the bitmap
    // ....

    //push our bitmap forward to the screen
    g.DrawImage(localBitmap, 0, 0);

    bitmapGraphics.Dispose();
    localBitmap.Dispose();
}

Now that was pretty easy wasn't it?

What's the deal with that sizing issue you mentioned?

I had a really hard time sizing the <codelang=cs>TableSizeSelectorDropDown control when it first dropped down. For the life of me I couldn't figure this out. After a while I put a break point in the OnSizeChanged event and examined the stack. I've boldened the call to the System.Windows.Forms.Form.SetBoundsCore.

at CabotSoftware.Controls.TableSizeSelectorDropDown.OnSizeChanged(EventArgs)
at System.Windows.Forms.Control.UpdateBounds
        (Int32, Int32, Int32, Int32, Int32, Int32)
at System.Windows.Forms.Control.UpdateBounds
        (Int32, Int32, Int32, Int32)
at System.Windows.Forms.Control.SetBoundsCore
        (Int32, Int32, Int32, Int32, BoundsSpecified)
at System.Windows.Forms.Form.SetBoundsCore
        (Int32, Int32, Int32, Int32, BoundsSpecified)
at System.Windows.Forms.Control.SetBounds
        (Int32, Int32, Int32, Int32, BoundsSpecified)
at System.Windows.Forms.Control.set_Size(Size)
at System.Windows.Forms.Control.SetClientSizeCore(Int32, Int32)
at System.Windows.Forms.Form.SetClientSizeCore(Int32, Int32)
at System.Windows.Forms.Control.set_ClientSize(Size)
at System.Windows.Forms.Form.set_ClientSize(Size)
at CabotSoftware.Controls.TableSizeSelectorDropDown.InitializeComponent()
at CabotSoftware.Controls.TableSizeSelectorDropDown..ctor(Control)
at CabotSoftware.Controls.TableSizeSelector.OnClick(EventArgs)

I figured out that the internal working of the .NET Framework was modifying the size of my control. I tried a number of approaches, including commenting out the default line of code you get in your form that probably looks like this.

C#
this.AutoScaleBaseSize = new System.Drawing.Size(5, 13);

I finally fixed this by overriding the SetBoundsCore method in my form. Here is the code I included in my form.

C#
protected override void 
    SetBoundsCore(int x, int y, int w, int h, BoundsSpecified bs)
{
    base.SetBoundsCore( x, y, CalcWidth(), CalcHeight(), bs);
}

That's it folks. I hope you found some of this useful.

History

  • Release Version 1.0.1130.21991 - February 4, 2003 -Initial release.

Copyright

Copyright © 2002-2003 by Cabot Software, Inc.

This article, as well as any accompanying source code and compiled binaries, are subject to local and international copyright laws and all rights are reserved. All code is original unless otherwise noted.

The code described in this article is available in the public domain and can be incorporated, either in it's entirely or modified, into client applications.

When using any of the accompanying code, please provide acknowledgment of the author. All final products must include the phrase “Portions copyright © 2002-2003 by Cabot Software, Inc.” in the about screen, or where appropriate.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Web Developer
United States United States
I've been writing code for over eleven years now. I started out writing C on a VAX system. I've been writing code for Microsoft Windows for nine years. I've coded everything from online streaming solutions to front end applications for the travel industry. My main focus has been on C++, ATL and COM but I've recently been doing quite a bit of work with .NET and C#.

I'm married with two kids and have a golden retriever named Rufus.

Comments and Discussions

 
GeneralNot like in Word Pin
Toli Cuturicu1-May-06 3:14
Toli Cuturicu1-May-06 3:14 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.