2007. október 21., vasárnap

Creating a roll-up form


Problem/Question/Abstract:

How can I create a form that will roll up; that is, a form that when clicked will reduce its height to nothing but the title bar?

Answer:

I have seen a demo of a commercially available component in DCU form that does this and it's pretty slick. Because it's a component, the implementation is really nice. Just drop it in and you're off.

What I'm showing here does the pretty much the same thing, but is coded directly into the form. I did this because building a component would have required more event-handling code than I cared to perform. However, with Delphi 2.0's Object Repository, it's a very simple to add a form with this functionality into it and use it over and over again.

Let's look at the code, then discuss it:

unit testmain;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  ExtCtrls, enhimage, StdCtrls, Printers, rollup, Buttons, ShellAPI;

type
  TForm1 = class(TForm)
    procedure FormCreate(Sender: TObject);
  private
    { Private declarations }
    FOldHeight: Integer;
    procedure WMNCRButtonDown(var Msg: TWMNCRButtonDown); message WM_NCRBUTTONDOWN;
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.DFM}

procedure TForm1.FormCreate(Sender: TObject);
begin
  FOldHeight := ClientHeight;
end;

procedure TForm1.WMNCRButtonDown(var Msg: TWMNCRButtonDown);
var
  I: Integer;
begin
  if (Msg.HitTest = HTCAPTION) then
    if (ClientHeight = 0) then
    begin
      I := 0;
      while (I < FOldHeight) do
      begin
        I := I + 40;
        if (I > FOldHeight) then
          I := FOldHeight;
        ClientHeight := I;
        Application.ProcessMessages;
      end;
    end
    else
    begin
      FOldHeight := ClientHeight;
      I := ClientHeight;
      while (I > 0) do
      begin
        I := I - 40;
        if (I < 0) then
          I := 0;
        ClientHeight := I;
        Application.ProcessMessages;
      end;
    end;
end;

end.

First, by way of synopsis, the roll-up/down occurs in response to a WM_NCRBUTTONDOWN message firing off and the WMNCRButtonDown procedure handling the message, telling the window to roll up/down depending upon the height of the client area. WM_NCRBUTTONDOWN fires whenever the right mouse button is clicked in a "non-client" area, such as a border, menu or, for our purposes, the caption bar of a form. (The client area of a window is the area within the border where most of the interesting activity usually occurs. In general, the Windows API restricts application code to drawing only within the client area.)

Delphi encapsulates the WM_NCRBUTTONDOWN in a TWMNCRButtonDown type, which is actually an assignment from a TWMNCHitMessage type that has the following structure:

type
  TWMNCHitMessage = record
    Msg: Cardinal;
    HitTest: Integer;
    XCursor: SmallInt;
    YCursor: SmallInt;
    Result: Longint;
  end;

Table 1 below discusses the parameters of the TWMCHitMessage structure in more detail:

Table 1 - TWMNCHitMessage record fields

Parameter
Type
Description
Msg
Cardinal
Each Windows message has an integer value which is its assigned ID
HitTest
Integer
This is a constant value that is returned by an internal Windows callback function that specifies the area on window when the message fired. Look in the Win32 Developer's Reference under WM_NCRBUTTONDOWN for values of nHitTest. For our purposes, we'll use HTCAPTION as the test value.
XCursor
SmallInt
This is the X value of the cursor position relative to the top left corner of the window
YCursor
SmallInt
This is the Y value of the cursor position relative to the top left corner of the window
Result
LongInt
The result value of WM_NCRBUTTONDOWN. Should be 0 if the application handled the message.


Now that you know about the message, let's look more closely at the code.

It's easy to create message wrappers in Delphi to deal with messages that aren't handled by an object by default. Since a right-click on the title bar of a form isn't handled by default, I had to create a wrapper. The procedure procedure WMNCRButtonDown(var Msg : TWMNCRButtonDown); message WM_NCRBUTTONDOWN; is the wrapper I created. All that goes on in the procedure is the following:

If the value of the message's HitTest field is equal to HTCAPTION (which means a right-click on the caption bar) then,
If height of the form's client area is equal to 0 then

1. Roll the form down

else if the height of the form's client area is not equal to 0 then

1. Roll the form up

In order to make this work, I had to create a variable called FOldHeight and set its value at FormCreate whenever the form was to be rolled up. FOldHeight is used as a place for the form to remember what size it was before it was re-sized to 0. When a form is to be rolled up, FOldHeight is immediately set to the current ClientHeight, which means you can interactively set the form's size, and the function will always return the form's ClientHeight to what it was before you rolled it up.

So what use is this? Well, sometimes I don't want to iconize a window; I just want to get it out of the way so I can see what's underneath. Having the capability to roll a form up to its title bar makes it a lot easier to see underneath a window without iconizing it, then having to Alt-tab back to it. (If you are familiar with the Macintosh platform, the System 7.5 environment offers a very similar facility called a "window shade," and makes a roll-up sound when the shade goes up.)

On an ending note, my good friend and colleague, Peter Jagielski, gave me a challenge: to create the effect of imploding and exploding windows. About eight years ago, he wrote an article in the DB Advisor for doing exploding and imploding windows in Paradox for DOS. When he saw the code for this, he smugly said, "That's pretty slick, but can you do exploding and imploding windows like I did?" How could I pass up that challenge? So be on the lookout for an example of exploding and imploding windows in a future tip.

By the way, a big thanks goes to Keith Bartholomess of TeamBorland for turning me on to the WM_NCRBUTTONDOWN message. I wouldn't have been able to write the event code without Keith pointing this message out to me.

Nincsenek megjegyzések:

Megjegyzés küldése