Delphi World - это проект, являющийся сборником статей и малодокументированных возможностей  по программированию в среде Delphi. Здесь вы найдёте работы по следующим категориям: delphi, delfi, borland, bds, дельфи, делфи, дэльфи, дэлфи, programming, example, программирование, исходные коды, code, исходники, source, sources, сорцы, сорсы, soft, programs, программы, and, how, delphiworld, базы данных, графика, игры, интернет, сети, компоненты, классы, мультимедиа, ос, железо, программа, интерфейс, рабочий стол, синтаксис, технологии, файловая система...
Липкие окошки

Едет программист в такси. Вдруг машина останавливает и дальше ну никак не едет. Водитель и так, и эдак... Программист и советует: - А вы закройте лишние окна. Поможет...

В статье рассматривается приём создания обработчиков сообщений, которые позволяют форме при перетаскивании "прилипать" к краям экранной области.

Конечно же в Win API такой возможности не предусмотрено, поэтому мы воспользуемся сообщениями Windows. Как нам извесно, Delphi обрабатывает сообщения через события, генерируя его в тот момент, когда Windows посылает сообщений приложению. Однако некоторые сообщения не доходят до нас. Например, при изменении размеров формы, генерируется событие OnResize, соотвествующее сообщению WM_SIZE, но при перетаскивании формы никакой реакции не происходит. Конечно же форма может получить это сообщение, но изначально никаких действий для данного сообщения не предусмотрено.

Итак, при перемещении, окну посылается сообщение WM_MOVING. Обрабатывая данной сообщение, приложение может отслеживать размер и расположение перетаскиваемого квадрата и, при необходимости, изменять их.

Так же существует сообщение WM_WINDOWPOSCHANGING, которое посылается окну, в случае, если его размер, расположение или место в Z порядке собираются измениться, как результат вызова функции SetWindowPos либо другой функции управления окном.

Чаще всего с сообщением передаются дополнительные параметры, которые сообщают нам необходимую информацию. Например, сообщение WM_MOVE, указывающее на то, что форма изменила своё местоположение, так же передаёт в параметре LPARAM новые координаты X и Y.

Сообщение WM_WINDOWPOSCHANGING передаёт нам ТОЛЬКО один параметр - указатель на структуру WindowPos, которая содержит информацию о новом размере и местоположении окна. Вот как выглядит структура WindowPos:


TWindowPos = packed record
  hwnd: HWND; {Identifies the window.}
  hwndInsertAfter: HWND; {Window above this one}
  x: Integer; {Left edge of the window}
  y: Integer; {Right edge of the window}
  cx: Integer; {Window width}
  cy: Integer; {Window height}
  flags: UINT; {Window-positioning options.}
end;

Наша задача проста: нам необходима, чтобы форма прилипла к краю экрана, если она находится на определённом расстоянии от окна (допустим 20 пикселей).

Пример

К новой форме добавьте Label, один контрол Edit и четыре Check boxes. Измените имя контрола Edit на edStickAt. Измените имена чекбоксов на chkLeft, chkTop, и т.д... Для установки количества пикселей используем edStickAt, который будет использоваться для определения необходимого расстояния до края экрана достаточного для приклеивания формы.

Нас интересует только одно сообщение WM_WINDOWPOSCHANGING. Обработчик для данного сообщения будет объявлен в секции private. Ниже приведён полный код этого процедуры "прилипания" вместе с комментариями. Обратите внимание, что Вы можете предотвратить "прилипание" формы к определённому краю, путё снятия нужной галочки.

Для получения рабочей области декстопа (минус панель задач, панель Microsoft и т.д.), используем SystemParametersInfo, первый параметр которой SPI_GETWORKAREA.


...
private
  procedure WMWINDOWPOSCHANGING(var Msg: TWMWINDOWPOSCHANGING);
  message WM_WINDOWPOSCHANGING;
...

procedure TfrMain.WMWINDOWPOSCHANGING(var Msg: TWMWINDOWPOSCHANGING);
const
  Docked: Boolean = FALSE;
var
  rWorkArea: TRect;
  StickAt : Word;
begin
  StickAt := StrToInt(edStickAt.Text);

  SystemParametersInfo(SPI_GETWORKAREA, 0, @rWorkArea, 0);

  with Msg.WindowPos^ do
  begin
    if chkLeft.Checked then
      if x <= rWorkArea.Left + StickAt then
      begin
        x := rWorkArea.Left;
        Docked := TRUE;
      end;

    if chkRight.Checked then
      if x + cx >= rWorkArea.Right - StickAt then
      begin
        x := rWorkArea.Right - cx;
        Docked := TRUE;
      end;

    if chkTop.Checked then
      if y <= rWorkArea.Top + StickAt then
      begin
        y := rWorkArea.Top;
        Docked := TRUE;
      end;

    if chkBottom.Checked then
      if y + cy >= rWorkArea.Bottom - StickAt then
      begin
        y := rWorkArea.Bottom - cy;
        Docked := TRUE;
      end;

    if Docked then
    begin
      with rWorkArea do
      begin
        // не должна вылезать за пределы экрана
        if x < Left then
          x := Left;
        if x + cx > Right then
          x := Right - cx;
        if y < Top then
          y := Top;
        if y + cy > Bottom then
          y := Bottom - cy;
      end; {ширина rWorkArea}
    end;
  end; {с Msg.WindowPos^}

  inherited;
end;

end.

Теперь достаточно запустить проект и перетащить форму к любому краю экрана.

Вот собственно и всё.

Комментарии:

Автор: Nashev

а так короче... И, ИМХО, лучше:


procedure TCustomGlueForm.WMWindowPosChanging1(var Msg: TWMWindowPosChanging);
var
  WorkArea: TRect;
  StickAt : Word;
begin
  StickAt := 10;
  SystemParametersInfo(SPI_GETWORKAREA, 0, @WorkArea, 0);
  with WorkArea, Msg.WindowPos^ do
  begin
    // Сдвигаем границы для сравнения с левой и верхней сторонами
    Right:=Right-cx;
    Bottom:=Bottom-cy;
    if abs(Left - x) <= StickAt then
      x := Left;
    if abs(Right - x) <= StickAt then
      x := Right;
    if abs(Top - y) <= StickAt then
      y := Top;
    if abs(Bottom - y) <= StickAt then
      y := Bottom;
  end;
  inherited;
end;

В проекте осталось 2 глюка:

  1. Если у формы, к которой прицепили другую форму за правую/нижнюю границы попробовать переместить эти границы, прицепленная форма останется на месте но все равно будет прикрепленной.
  2. Иногда 3 формы прикрепляются друг к другу, и иначе, как воспользовавшись 1-ым глюком, их не расцепить.

Для использования сделанного в своих проектах надо добавить в проект, и свои формы создавать, наследуя от него, например, через мастер "File/New..."

В принципе, если липкость нужна без прилипания (а это уже работает без глюков) можно выкинуть все методы, кроме


procedure WMWindowPosChanging(var Msg: TWMWindowPosChanging);
message WM_WINDOWPOSCHANGING;

и все переменные, а в самом WMWindowPosChanging удалить все упоминания этих переменных.

Проект Delphi World © Выпуск 2002 - 2004
Автор проекта: ___Nikolay