Tuesday, 30 September 2008

Custom WinForms Controls - Forcing “Always on Top”

Having read the following question from MattBerry on the student forum for the MCAD I am studying towards:

Does anyone know how to make a custom control the top most control on a
form i.e. its z-order.
I know forms create the z-order in the order the controls are created,
and I know you can use the control.BringToFront method after all
controls have been created.
What I want to know is, is it possible to set this z-order in the
custom control's code, so that it can be encapsulated?

It sparked my curiosity, can it be done? I have never tried anything like this myself. And yes, there is a side of me that would get REALLY pissed off with using a control that did this, but I thought “what the hell?” lets have a look and see what we can do.

Now, a bit of disclosure, it has been a while since I done any Windows development, so this may well be possible to do a lot easier, but hey, it took me 15 minutes :P

Getting Started

I had no real idea where to start as I only remember the most important properties/methods for WinForms controls. So I created a class for a new control, then sat there going through the Intellisense to see if any of them sparked my curiosity:

image

I pretty much set here looking at this for 5 minutes while I got reacquainted with the Control class.

Heading Down the Right Path

I came across a property that caught my eye:

image Looks interesting right? I then checked out it’s article on MSDN. It turns out it tells you if this control is “top level” in the control hierarchy (i.e. is it contained) and got the nice sense of disappointment as I realised it would not give me that I really needed. But it did get me thinking, “if I know I am in a container, then I should be able to get a reference to the container, right?”

So I started scrabbling around to see if I can get a reference to the container control, Control.GetContainerControl sounds like it will do the job nicely, so I hacked together some code:

   1: protected override void OnCreateControl()
   2: {
   3:     base.OnCreateControl();
   4:  
   5:     // Determine if this is the top level Control.
   6:     bool isTop = this.GetTopLevel();
   7:  
   8:     if (!isTop)
   9:     {
  10:         // Bubble Up
  11:         Form container = this.GetContainerControl() as Form;
  12:         if (container == null)
  13:             throw new 
  14:                 Exception("Control must be on a Form!");
  15:  
  16:         container.Controls.Remove(this);
  17:         Control[] temp = new 
  18:             Control[container.Controls.Count];
  19:         container.Controls.CopyTo(temp, 0);
  20:  
  21:         container.Controls.Clear();
  22:         container.Controls.Add(this);
  23:         container.Controls.AddRange(temp);
  24:     }
  25: }

Yes, I agree its not too pretty, and could be more efficient etc, but it seems to do the work. So what is it doing?

  • First we call the base method, as we tend to do that for most overrides to make sure we don’t miss any important base logic.
  • We then determine if we are a top-level control, or a control within a hierarchy via GetTopLevel();
  • If we are in a hierarchy, then we simply:
    • Remove our control from the container’s Controls collection.
    • Copy the rest of the controls in the container to a temp collection.
    • Clear the collection.
    • Add our control back to the container (now the first control).
    • Add the controls we saved back to the collection.

A Quick Test

To give it a quick test, I added the following rendering logic to paint a box:

   1: protected override void OnPaint(PaintEventArgs e)
   2: {
   3:     using (Graphics g = e.Graphics)
   4:     {
   5:         Rectangle bounds = new 
   6:             Rectangle(0, 0, this.Width-1, this.Height-1);
   7:         g.FillRectangle(Brushes.Cyan, bounds);
   8:         g.DrawRectangle(Pens.Black, bounds);
   9:     }
  10: }

I then chucked our control on a form and added a button on top of it (you can right click and say “bring to front” if required).

When first added, it looks like it is not working, but that is because the ZOrderControl’s logic has not had a chance to fire.

Hit F6 to build and see what happens.

image image

NOTE:

This has been far from tested and of course there may be issues with messing with the control hierarchy. I simply wanted to find a way to do the goal required. I tested obvious things like control events still firing etc, but use this at your own risk!

Share:  digg it! del.icio.us Live Technorati Facebook

No comments:

Post a Comment