|
There is no built-in feature of the Win32 API that allows windows to snap to each other, so we will have to add this functionality ourselves. To do this, we add two message handlers to our form. The first one, WM_SETTINGCHANGE. This one updates our snap area if the user changes the size of the screen, etc. The brunt of the work will be handled by WM_WINDOWPOSCHANGING. This message is sent whenever
the window is moved or resized or focused, but all we really care about is when it's moved.
To set a message handler for your form, you use the MESSAGE_MAP macros:
BEGIN_MESSAGE_MAP
MESSAGE_HANDLER(<message>,<msg struct type>,<handler>);
MESSAGE_HANDLER(...);
....
END_MESSAGE_MAP(TForm);
For the WM_SETTINGCHANGE message, we have several things:
First, a private RECT structure (work_area) that tells us what our current work area is. We'll update this with an
UpdateWorkArea function. UpdateWorkArea will be called from our message handler
Here is how it will look (you can see how it all fits together below in the code section):
//in our header file
MESSAGE_HANDLER(WM_SETTINGCHANGE,TMessage,WMSettingChanged);
//---------------------------------------------------------------------------
void __fastcall Tsnapform::WMSettingChanged(TMessage &msg)
{
UpdateWorkArea();
}
//---------------------------------------------------------------------------
void __fastcall Tsnapform::UpdateWorkArea()
{
SystemParametersInfo(SPI_GETWORKAREA, 0, &work_area, 0);
//get the size of the desktop or whatever
}
|
Now we will set about making our WM_WINDOWPOSCHANGING handler. This will do what is necessary to have a nice snap-to window; you'll even be able to tell it how far to snap from.
As above, we tell our form about our message handler, then put it in a function. In this case, the function will be WMWindowPosChanging. The code is listed below, but with the comments it should be relatively straight forward.
WM_WINDOWPOSCHANGING sends us a WINDOWPOS structure through the lParam parameter. This structure tells us where our window is going and how big it's going to be. We can change this info through the WINDOWPOS structure. This is how we get it to snap.
There are a couple of private class members used in this function:
snapped (bool) tells it if it's snapped or not
snapwin (HWND) the window to snap to
thresh (int) is the number of pixels at which to snap
msg.WindowPos (WINDOWPOS) is the WINDOWPOS structure from lParam
void __fastcall Tsnapform::WMWindowPosChanging(TWMWindowPosChanging &msg)
{
RECT sr; //rect to snap to
snapped=false;
//test window
if (snapwin && IsWindowVisible(snapwin)) //if we should snap to another window
{
if (GetWindowRect(snapwin,&sr)) //get it's bounds
{
//if we're within the snap threshold then snap
if ( (msg.WindowPos->x <= (sr.right+thresh)) &&
(msg.WindowPos->x >= (sr.right-thresh)) ) {
if ((msg.WindowPos->y > sr.top) && (msg.WindowPos->y < sr.bottom)) {
//disallow "open air" snaps
snapped=true;
msg.WindowPos->x = sr.right;
}
}
else if ((msg.WindowPos->x + msg.WindowPos->cx) >= (sr.left-thresh) &&
(msg.WindowPos->x + msg.WindowPos->cx) <= (sr.left+thresh)) {
if ((msg.WindowPos->y > sr.top) && (msg.WindowPos->y < sr.bottom)) {
snapped=true;
msg.WindowPos->x = sr.left-msg.WindowPos->cx;
}
}
if ( (msg.WindowPos->y <= (sr.bottom+thresh)) &&
(msg.WindowPos->y >= (sr.bottom-thresh)) ) {
if ((msg.WindowPos->x > sr.left) && (msg.WindowPos->x < sr.right)) {
snapped=true;
msg.WindowPos->y = sr.bottom;
}
}
else if ((msg.WindowPos->y + msg.WindowPos->cy) <= (sr.top+thresh) &&
(msg.WindowPos->y + msg.WindowPos->cy) >= (sr.top-thresh)) {
if ((msg.WindowPos->x > sr.left) && (msg.WindowPos->x < sr.right)) {
snapped=true;
msg.WindowPos->y = sr.top-msg.WindowPos->cy;
}
}
}
}
//test screen
sr = work_area;
//we have to test this in reverse so we switch the testing of the rect
if (abs(msg.WindowPos->x) <= (sr.left+thresh)) {
snapped=true;
msg.WindowPos->x = sr.left;
}
else if ((msg.WindowPos->x + msg.WindowPos->cx) >= (sr.right-thresh) &&
(msg.WindowPos->x + msg.WindowPos->cx) <= (sr.right+thresh)) {
snapped=true;
msg.WindowPos->x = sr.right-msg.WindowPos->cx;
}
if (abs(msg.WindowPos->y) <= (sr.top+thresh)) {
snapped=true;
msg.WindowPos->y = sr.top;
}
else if ((msg.WindowPos->y+msg.WindowPos->cy) >= (sr.bottom-thresh) &&
(msg.WindowPos->y+msg.WindowPos->cy) <= (sr.bottom+thresh)) {
snapped=true;
msg.WindowPos->y = sr.bottom-msg.WindowPos->cy;
}
}
|
The one part that really warrants explanation is testing the work area (screen). When we test for the window to snap, we only snap right-to-left, left-to-right, top-to-bottom, and bottom-to-top. (This is more explanatory if you run the example application). When we test for the screen we must reverse the order, snapping the right of our window to the right
edge of the scree, etc.
|

| mainunit.h |
//---------------------------------------------------------------------------
#ifndef mainunitH
#define mainunitH
//---------------------------------------------------------------------------
#include <Classes.hpp>
#include <Controls.hpp>
#include <StdCtrls.hpp>
#include <Forms.hpp>
//---------------------------------------------------------------------------
class Tsnapform : public TForm
{
__published: // IDE-managed Components
TEdit *Edit1;
TButton *Button1;
TEdit *Edit2;
TButton *Button2;
TCheckBox *move;
void __fastcall Button1Click(TObject *Sender);
void __fastcall Button2Click(TObject *Sender);
private: // User declarations
HWND snapwin;
RECT work_area;
bool snapped;
bool winprocthing;
int thresh;
void __fastcall SettingChanged(TMessage &msg);
void __fastcall WMWindowPosChanging(TWMWindowPosChanging &msg);
void __fastcall UpdateWorkArea();
public: // User declarations
__fastcall Tsnapform(TComponent* Owner);
__fastcall ~Tsnapform();
BEGIN_MESSAGE_MAP
MESSAGE_HANDLER(WM_WINDOWPOSCHANGING,TWMWindowPosChanging,WMWindowPosChanging);
MESSAGE_HANDLER(WM_SETTINGCHANGE,TMessage,SettingChanged);
END_MESSAGE_MAP(TForm);
};
//---------------------------------------------------------------------------
extern PACKAGE Tsnapform *snapform;
//---------------------------------------------------------------------------
#endif
|
| mainunit.cpp |
//---------------------------------------------------------------------------
#include <vcl.h>
#pragma hdrstop
#include "mainunit.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
Tsnapform *snapform;
//---------------------------------------------------------------------------
__fastcall Tsnapform::Tsnapform(TComponent* Owner)
: TForm(Owner)
{
snapped=false;
UpdateWorkArea(); //set our workarea rect
thresh = StrToInt(Edit2->Text); //get threshold
snapwin = FindWindow(Edit1->Text.c_str(),NULL); //get window to snap to
}
//---------------------------------------------------------------------------
__fastcall Tsnapform::~Tsnapform()
{
}
//---------------------------------------------------------------------------
void __fastcall Tsnapform::SettingChanged(TMessage &msg)
{
UpdateWorkArea();
}
//---------------------------------------------------------------------------
void __fastcall Tsnapform::WMWindowPosChanging(TWMWindowPosChanging &msg)
{
RECT sr;
snapped=false;
//test window
if (snapwin && IsWindowVisible(snapwin))
{
if (GetWindowRect(snapwin,&sr))
{
if ( (msg.WindowPos->x <= (sr.right+thresh)) &&
(msg.WindowPos->x >= (sr.right-thresh)) ) {
if ((msg.WindowPos->y > sr.top) && (msg.WindowPos->y < sr.bottom)) {
snapped=true;
msg.WindowPos->x = sr.right;
}
}
else if ((msg.WindowPos->x + msg.WindowPos->cx) >= (sr.left-thresh) &&
(msg.WindowPos->x + msg.WindowPos->cx) <= (sr.left+thresh)) {
if ((msg.WindowPos->y > sr.top) && (msg.WindowPos->y < sr.bottom)) {
snapped=true;
msg.WindowPos->x = sr.left-msg.WindowPos->cx;
}
}
if ( (msg.WindowPos->y <= (sr.bottom+thresh)) &&
(msg.WindowPos->y >= (sr.bottom-thresh)) ) {
if ((msg.WindowPos->x > sr.left) && (msg.WindowPos->x < sr.right)) {
snapped=true;
msg.WindowPos->y = sr.bottom;
}
}
else if ((msg.WindowPos->y + msg.WindowPos->cy) <= (sr.top+thresh) &&
(msg.WindowPos->y + msg.WindowPos->cy) >= (sr.top-thresh)) {
if ((msg.WindowPos->x > sr.left) && (msg.WindowPos->x < sr.right)) {
snapped=true;
msg.WindowPos->y = sr.top-msg.WindowPos->cy;
}
}
}
}
//test screen
sr = work_area;
if (abs(msg.WindowPos->x) <= (sr.left+thresh)) {
snapped=true;
msg.WindowPos->x = sr.left;
}
else if ((msg.WindowPos->x + msg.WindowPos->cx) >= (sr.right-thresh) &&
(msg.WindowPos->x + msg.WindowPos->cx) <= (sr.right+thresh)) {
snapped=true;
msg.WindowPos->x = sr.right-msg.WindowPos->cx;
}
if (abs(msg.WindowPos->y) <= (sr.top+thresh)) {
snapped=true;
msg.WindowPos->y = sr.top;
}
else if ((msg.WindowPos->y+msg.WindowPos->cy) >= (sr.bottom-thresh) &&
(msg.WindowPos->y+msg.WindowPos->cy) <= (sr.bottom+thresh)) {
snapped=true;
msg.WindowPos->y = sr.bottom-msg.WindowPos->cy;
}
}
//---------------------------------------------------------------------------
void __fastcall Tsnapform::UpdateWorkArea()
{
SystemParametersInfo(SPI_GETWORKAREA, 0, &work_area, 0);
//get the size of the desktop or whatever
}
//---------------------------------------------------------------------------
void __fastcall Tsnapform::Button1Click(TObject *Sender)
{
snapwin = FindWindow(Edit1->Text.c_str(),NULL);
}
//---------------------------------------------------------------------------
void __fastcall Tsnapform::Button2Click(TObject *Sender)
{
thresh = StrToInt(Edit2->Text);
}
//---------------------------------------------------------------------------
|
|
Connect with Us