|
"Post-it". Sizable windows without border or title.
Copyright © 2000 Alirio A. Gavidia B..
To see more of my articles visit Programación Orientada en Delphi (Site in Spanish)
Translated by Ernesto De Spirito with the author's permission
Why a borderless and titleless form?
Four are the fundamental facts in the present essay: The first of them
is the existence of programs that mimic Post-it (those little sticky pages
that come in notebooks and flood offices with flashy colors). Second,
the use of certain undocumented Windows function to drag forms. Third,
the problem of how to create an application where the main form is not
visible. And fourth, using the Windows task bar.
The programs that mimic Post-it I take as reference are mainly TurboNote in
Windows and the one of KDE in Linux. Both programs are easily
acquirable and do more than the one I present here. However, I consider
this a good teaching example.
It comes to my mind another program that presents itself without title:
the round (analog) clock that comes with Power Toys for Windows.
It will be left for a future article.
Let's get started
The obvious is to imitate a post-it note in some way. The first thought is
a form with a title bar without the usual buttons (minimize, maximize, etc.)
and completely with a plain color. This alternative is possible and
requires a bit specialized Windows management. In particular I have chosen
a more economic alternative regarding code (or at least that's what I think);
and this is eliminating the title bar and form borders (like you would do
in a "splash-screen") and place a panel and a memo in a form, the
first aligned to top and the second to the client area.
Managing a form without borders or title bar
The form with just a panel and a memo will respond without problems to
keyboard actions, but it is obvious it won't respond to mouse drags and size
changes with the mouse (without borders from where to hold it). The drag is
resolved with an undocumented action, at least that's how it was some time
ago, thru the message SysCommand. The line shown here, executed
from a form, places it in drag state.
Perform(wm_SysCommand, $f012, 0)
The matter with the messages doesn't get limited to this. The resize modes
are also defined that way. For this purpose we defined a set of constants
with the appropriate values starting from $f012.
sc_DragMove = $f012;
sc_Leftsize = $f001;
sc_Rightsize = $f002;
sc_Upsize = $f003;
sc_UpLeftsize = $f004;
sc_UpRightsize = $f005;
sc_Dnsize = $f006;
sc_DnLeftsize = $f007;
sc_DnRightsize = $f008;
| Note: If you wonder where these values came from, well, what
can I say... I got to know the first one by a Marco Cantú book called
"Delphi Developers' Handbook", the rest came out from trial and
error. Let me tell you that $f040 produces a disastrous result. Also $f020
and $f030 minimize and maximize respectively, which is not necessary for
our application. I don't know if any of this works outside Windows 98. |
Additionally, in the MouseDown event of the panel and the memo,
where we send the messages, it is necessary to call ReleaseCapture before
sending the indicated messages.
I won't extend much with the code. It's easier to analyze it from Delphi than
from an HTML page or a DOC. But notice that it has to be distinguished where
a click happened in the panel to know if it is required to resize or just drag
the form. Something similar happens with the memo for the resize of the bottom
and lateral sides.
procedure TfrmNota.Panel1MouseDown(
Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
begin
ReleaseCapture;
if y<2 then
begin
if x<4 then
Perform(wm_SysCommand, sc_UpLeftsize, 0)
else if x>(Panel1.ClientWidth-4) then
Perform(wm_SysCommand, sc_UpRightsize, 0)
else
Perform(wm_SysCommand, sc_UpSize, 0)
end
else
Perform(wm_SysCommand, sc_DragMove, 0)
end;
The sources are part of this article and
you will be able to see the note working in detail.
In the attached example, apart from the mouse event handling we make use of the
Windows Registry to store information relative to the note itself (like its position,
font type and color). I presume that if you understand what we have done up to
this point, you don't need explanations about the use of the Windows Registry
(anyway, its use seems to be well documented in the Delphi Help, and there are
examples and information about it in the Internet).
An invisible main form
When I made this program I hadn't though of a really silly problem. The user
shall be able to create and destroy notes, but he can't destroy the main
form because this finishes the application. Well, then simply with not
showing the main form it should suffice. If you want to understand this
silly problem create an application in which apart from the main there
exists a secondary form; then make the application start showing only
the secondary form. My attempts resulted in failure. Now I know this gets
accomplished with
Application.ShowMainForm := False;
in the main program before creating the forms, but back then I didn't know
it and I took advantage of the problem to give it an original solution...
It's not a bug, it's a feature
Thinking around the matter, the solution was converting the main form in
a splash screen that lasts a certain amount of time and then hides. This
code gets executed in a timer:
procedure TfrmMain.Timer1Timer(Sender: TObject);
begin
SaveNotes(Sender);
if not oculto then // if not hidden
begin
oculto := True;
ShowWindow(Handle, SW_HIDE);
ShowWindow(Application.Handle, SW_HIDE);
SetWindowLong( Application.Handle, GWL_EXSTYLE,
GetWindowLong(Application.Handle, GWL_EXSTYLE)
or WS_EX_TOOLWINDOW and not WS_EX_APPWINDOW);
ShowWindow(Application.Handle, SW_SHOW);
end
end;
With this the main form gets hidden (the next line right after the assignment
of the "oculto" (hidden) variable, that by the way is False), and also
the presence of the application in the task bar.
The rest of the code is relatively simple and contains some routines
to save and restore the content and configuration of the notes (the first
to disk and the second to the Registry). I believe it doesn't deserve a
deep discussion and therefore I leave to the reader the interpretation
of the routine.
The project as such contains five forms:
note, is the note with the panel and the memo.
GenConfig, is the configuration form of the application, currently only configures formats and counter of the clock.
Config, note configuration (color, alignment and fonts)
Main, is the main form, apart from this it works as a splash screen.
Acerca (About), program version.
As a separate comment, note that due to the presence of a Timer it is possible to
force an "auto save" for the text in the notes.
This is achieved going thru all the forms of type note and
saving their contents from the memo. The routine named SaveNotes is
executed from the timer (having the precaution of not saving what hasn't
changed). Each note has the public variable Changed that is
modified when saved and in the OnChange event of the memo.
"Pop-up menu"
The application doesn't have a main menu because it doesn't have a main form.
All actions different than interaction with the memo field and mouse
movements are done by pop-up menus. There are two: one in the note and one
in the icon of the task bar (next to the clock). For this one we used the RX.
Therefore at least for this version you will need to have the RX to be able
to compile these sources. If you don't
know them, them you should do it, they are free ("freeware") and very good.
|