Как смоделировать раскрывающуюся форму в Delphi?

как я могу создать "выпадающий" окно с использованием Delphi?

все, что выходит за пределы этой точки, является исследовательским усилием; и никоим образом не связано с ответом.

Исследования

создание правильного раскрывающегося списка требует много частей, чтобы тщательно работать вместе. Я предполагаю, что людям не нравится сложный вопрос, и я бы предпочел задать семь отдельных вопросов, каждый из которых касается одной крошечной части проблемы. Все, что следует мой исследования в решении простой проблемы.


обратите внимание на определяющие характеристики раскрывающегося окна:

enter image description here

  • 1. раскрывающийся список простирается за пределы it's "владелец" окно
  • 2. на "владелец" окно сохраняет фокус; раскрывающийся список никогда не крадет фокус
  • 3. в выпадающее окно имеет drop-shadow

это вариант Delphi того же вопроса, который я задал в WinForms:

ответ в WinForms должен был использовать ToolStripDropDown class. Это вспомогательный класс, который превращает любую форму в выпадающем.

давайте сделаем это в Delphi

я начну с создания безвкусной раскрывающейся формы, это служит примером:

enter image description here

далее я сброшу кнопку, это будет то, что я нажимаю, чтобы сделать выпадающий список:

enter image description here

и, наконец, я подключу некоторый начальный код, чтобы показать форму, где она должна быть в OnClick:

procedure TForm3.Button1MouseDown(Sender: TObject; 
      Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
var
    frmPopup: TfrmPopup;
    pt: TPoint;
begin
    frmPopup := TfrmPopup.Create(Self);

    //Show the form just under, and right aligned, to this button
    pt := Self.ClientToScreen(Button1.BoundsRect.BottomRight);
    Dec(pt.X, frmPopup.ClientWidth);

    frmPopup.Show(Self, Self.Handle, pt);
end;

редактировать: поменял на событие mousedown, а не клик. Нажмите неверно, так как раскрывающийся список отображается без необходимости щелчка. Один из нерешенных вопросов-как скрыть раскрывающийся список, если пользователь снова опускает кнопку мыши. Но мы оставим это для человека, который ответит на вопрос, чтобы решить. Все в этом вопросе-исследовательские усилия, а не решение.

и мы уходим:

enter image description here

теперь, как это сделать правильно?

Первое, что мы сразу замечаем, это отсутствие тени. Это потому что нам нужно применить CS_DROPSHADOW окна стиль:

procedure TfrmPopup.CreateParams(var Params: TCreateParams);
const
    CS_DROPSHADOW = 020000;
begin
    inherited CreateParams({var}Params);

    Params.WindowClass.Style := Params.WindowClass.Style or CS_DROPSHADOW;
end;

это исправляет это:

enter image description here

Фокуса

следующий вопрос-это вызов .Show на всплывающем окне заставляет его украсть фокус (строка заголовка приложения указывает, что он потерял фокус). Sertac приходит с решением этого.

  • когда всплывающее окно получает это WM_Activate сообщение, указывающее, что это получение фокуса (т. е. Lo(wParam) <> WA_INACTIVE):
  • отправить родительскую форму a WM_NCActivate(True, -1), чтобы указать, что он должен рисовать себя так, как он все еще имеет фокус

мы занимаемся WM_Activate:

protected
   procedure WMActivate(var Msg: TWMActivate); message WM_ACTIVATE;

и реализации:

procedure TfrmPopup.WMActivate(var Msg: TWMActivate);
begin
    //if we are being activated, then give pretend activation state back to our owner
    if (Msg.Active <> WA_INACTIVE) then
        SendMessage(Self.PopupParent.Handle, WM_NCACTIVATE, WPARAM(True), -1);

    inherited;
end;

таким образом, окно владельца выглядит так, как будто оно все еще имеет фокус (кто знает, правильно ли это сделать - это только выглядит как это все еще имеет фокус):

enter image description here

засучив

к счастью, Sertac уже решает проблему того, как закрыть окно всякий раз, когда пользователь щелкает:

  • когда всплывающее окно получает это WM_Activate сообщение о том, что он теряет фокус (т. е. Lo(wParam) = WA_INACTIVE):
  • отправить владельцу управления уведомление о том, что мы сворачиваем
  • освободите всплывающую форму

добавим, что к нашему существующему WM_Activate обработчик:

procedure TfrmPopup.WMActivate(var Msg: TWMActivate);
begin
    //if we are being activated, then give pretend activation state back to our owner
    if (Msg.Active <> WA_INACTIVE) then
        SendMessage(Self.PopupParent.Handle, WM_NCACTIVATE, WPARAM(True), -1);

    inherited;

    //If we're being deactivated, then we need to rollup
    if Msg.Active = WA_INACTIVE then
    begin
        //TODO: Tell our owner that we've rolled up

        //Note: The parent should not be using rollup as the time to read the state of all controls in the popup.
        //      Every time something in the popup changes, the drop-down should give that inforamtion to the owner
        Self.Release; //use Release to let WMActivate complete
    end;
end;

скольжение выпадающего меню

выпадающие элементы управления использовать AnimateWindow сдвиньте капля-вниз. От собственного Microsoft combo.c:

if (!(TEST_EffectPUSIF(PUSIF_COMBOBOXANIMATION))
      || (GetAppCompatFlags2(VER40) & GACF2_ANIMATIONOFF)) {
   NtUserShowWindow(hwndList, SW_SHOWNA);
} 
else 
{
   AnimateWindow(hwndList, CMS_QANIMATION, (fAnimPos ? AW_VER_POSITIVE :
            AW_VER_NEGATIVE) | AW_SLIDE);
}

после проверки, следует ли использовать анимацию, они используют AnimateWindow показать окно. Мы можем использовать SystemParametersInfo с SPI_GetComboBoxAnimation:

определяет ли скольжени-открытое влияние для поля со списком включены. The pvParam параметр должен указывать на BOOL переменной, которая получает правда для enabled или FALSE для инвалидов.

в нашем новоосвященном TfrmPopup.Show способ, мы можем проверить, если анимация клиентской области, а вызов AnimateWindow или Show в зависимости от предпочтений пользователя:

procedure TfrmPopup.Show(Owner: TForm; NotificationParentWindow: HWND;
      PopupPosition: TPoint);
var
    pt: TPoint;
    comboBoxAnimation: BOOL;
begin
    FNotificationParentWnd := NotificationParentWindow;

    //We want the dropdown form "owned" by (i.e. not "parented" to) the OwnerWindow
    Self.Parent := nil; //the default anyway; but just to reinforce the idea
    Self.PopupParent := Owner; //Owner means the Win32 concept of owner (i.e. always on top of, cf Parent, which means clipped child of)
    Self.PopupMode := pmExplicit; //explicitely owned by the owner

    //Show the form just under, and right aligned, to this button
    Self.BorderStyle := bsNone;
    Self.Position := poDesigned;
    Self.Left := PopupPosition.X;
    Self.Top := PopupPosition.Y;

    if not Winapi.Windows.SystemParametersInfo(SPI_GETCOMBOBOXANIMATION, 0, @comboBoxAnimation, 0) then
        comboBoxAnimation := False;

    if comboBoxAnimation then
    begin
        //200ms is the shell animation duration
        AnimateWindow(Self.Handle, 200, AW_VER_POSITIVE or AW_SLIDE or AW_ACTIVATE);
    end
    else
        inherited Show;
end;

редактировать: оказывается, есть SPI_GETCOMBOBOXANIMATION который, вероятно, должен использовать over SPI_GETCLIENTAREAANIMATION. Что указывает на глубины сложности, скрытые за тонким "как имитировать раскрывающийся список". Моделирование выпадающего списка требует много вещей.

проблема в том, что формы Delphi в значительной степени падают мертвыми, если вы пытаетесь использовать ShowWindow или AnimateWindow за их спиной:

enter image description here

как это решить?

также странно, что сама Microsoft использует:

  • ShowWindow(..., SW_SHOWNOACTIVATE) или
  • AnimateWindow(...) *(без AW_ACTIVATE)

для отображения выпадающего списка без активация. И все же шпионаж на ComboBox с Spy++ я вижу WM_NCACTIVATE летают.

в прошлом люди моделировали окно слайда, используя повторные вызовы для изменения Height раскрывающейся формы от таймера. Это не только плохо, но и изменяет размер формы. Скорее чем скользить вниз, форма растет вниз; вы можете видеть, что все элементы управления меняют свою компоновку по мере появления раскрывающегося списка. Нет, имея раскрывающуюся форму, это реальный размер, но слайд вниз-это то, что нужно здесь.

я знаю AnimateWindow и Дельфы никогда не ладили. И вопрос был задан, много, задолго до того, как StackOverflow прибыл. Я даже спрашивал об этом в 2005 году в группах новостей. Но это не помешает мне спросить еще раз.

я попытался заставить свою форму перерисовать после этого оживляет:

AnimateWindow(Self.Handle, 200, AW_VER_POSITIVE or AW_SLIDE or AW_ACTIVATE);
Self.Repaint;
Self.Update;
Self.Invalidate;

но он не работает, он просто сидит и издевается надо мной:

enter image description here

теперь снова показывает, когда я хочу закрыть

если выпадет combobox, и пользователь пытается событие mousedown на кнопке реальный элемент управления Windows ComboBox не просто снова показывает элемент управления, но вместо этого скрывает его:

enter image description here

раскрывающийся список также знает, что в настоящее время "упал вниз", что полезно, чтобы он мог рисовать себя, как если бы он был в "упала" режим. Что нам нужно, так это способ узнать, что раскрывающийся список опустился, и способ узнать, что раскрывающийся список больше не опускается. Какая-то логическая переменная:

private
   FDroppedDown: Boolean;

и мне кажется, что мы нужно сказать хозяину, что мы закрываемся (т. е. потери активации!--45-->). хост тогда должен быть ответственный за уничтожение всплывающего окна. (хост не может нести ответственность за уничтожение всплывающего окна; это приводит к неразрешимому состоянию гонки). Поэтому я создаю сообщение, используемое для уведомления владельца о том, что мы закрываемся:

const
   WM_PopupFormCloseUp = WM_APP+89;

Примечание: я не знаю, как люди избегают постоянных конфликтов сообщений (особенно с CM_BASE начинается с $B000 и CN_BASE начинается с $BC00).

основываясь на активации/деактивации Sertac рутина:

procedure TfrmPopup.WMActivate(var Msg: TWMActivate);
begin
    //if we are being activated, then give pretend activation state back to our owner
    if (Msg.Active <> WA_INACTIVE) then
        SendMessage(Self.PopupParent.Handle, WM_NCACTIVATE, WPARAM(True), -1);

    inherited;

    //If we're being deactivated, then we need to rollup
    if Msg.Active = WA_INACTIVE then
    begin
        //DONE: Tell our owner that we've rolled up
        //Note: We must post the message. If it is Sent, the owner
        //will get the CloseUp notification before the MouseDown that
        //started all this. When the MouseDown comes, they will think
        //they were not dropped down, and drop down a new one.
        PostMessage(FNotificationParentWnd, WM_PopupFormCloseUp, 0, 0);

        Self.Release; //use release to give WM_Activate a chance to return
    end;
end;

и тогда мы должны изменить наше событие mousedown код, чтобы понять, что раскрывающийся список все еще существует:

procedure TForm3.Edit1MouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
var
    frmPopup: TfrmPopup;
    pt: TPoint;
begin
    //If we (were) dropped down, then don't drop-down again.
    //If they click us, pretend they are trying to close the drop-down rather than open a second copy
    if FDroppedDown then
    begin
        //And since we're receiving mouse input, we by defintion must have focus.
        //and since the drop-down self-destructs when it loses activation, 
        //it can no longer be dropped down (since it no longer exists)
        Exit;
    end;

    frmPopup := TfrmPopup.Create(Self);

    //Show the form just under, and right aligned, to this button
    pt := Self.ClientToScreen(Edit1.BoundsRect.BottomRight);
    Dec(pt.X, frmPopup.ClientWidth);

    frmPopup.Show(Self, Self.Handle, pt);
    FDroppedDown := True;
end;

и я думаю, что это

помимо AnimateWindow головоломка, я, возможно, смог бы использовать свои исследовательские усилия для решения всех проблем, которые я могу придумать, чтобы:

имитировать раскрывающуюся форму в Delphi

конечно, все это может оказаться напрасным. Он может оказаться, что есть функция VCL:

TComboBoxHelper = class;
public
   class procedure ShowDropDownForm(...);
end;

в этом случае это был бы правильный ответ.

2 ответов


в самом низу procedure TForm3.Button1Click(Sender: TObject); вы называете frmPopup.Show; изменить на ShowWindow(frmPopup.Handle, SW_SHOWNOACTIVATE); и после этого вам нужно позвонить frmPopup.Visible := True; иначе компоненты в форме не будут отображаться

так новая процедура выглядит так:

uses
  frmPopupU;

procedure TForm3.Button1Click(Sender: TObject);
var
  frmPopup: TfrmPopup;
  pt: TPoint;
begin
  frmPopup := TfrmPopup.Create(Self);
  frmPopup.BorderStyle := bsNone;

  //We want the dropdown form "owned", but not "parented" to us
  frmPopup.Parent := nil; //the default anyway; but just to reinforce the idea
  frmPopup.PopupParent := Self;

  //Show the form just under, and right aligned, to this button
  frmPopup.Position := poDesigned;
  pt := Self.ClientToScreen(Button1.BoundsRect.BottomRight);
  Dec(pt.X, frmPopup.ClientWidth);
  frmPopup.Left := pt.X;
  frmPopup.Top := pt.Y;

  //  frmPopup.Show;
  ShowWindow(frmPopup.Handle, SW_SHOWNOACTIVATE);
  //Else the components on the form won't show
  frmPopup.Visible := True;
end;

но это не помешает вам всплывающее окно от кражи фокуса. Чтобы предотвратить это, вам нужно переопределить WM_MOUSEACTIVATE событие в вашей всплывающей форме

type
  TfrmPopup = class(TForm)
...
    procedure WMMouseActivate(var Message: TWMMouseActivate); message WM_MOUSEACTIVATE;
...
  end;

и реализации

procedure TfrmPopup.WMMouseActivate(var Message: TWMMouseActivate);
begin
  Message.Result := MA_NOACTIVATE;
end;

я решил играть arround с вашим всплывающим окном: первое, что я добавил, была кнопка закрытия. Просто простой TButton, который в своем событии onCLick вызывает Close:

procedure TfrmPopup.Button1Click(Sender: TObject);
begin
  Close;
end;

но это только скроет форму, чтобы освободить ее, я добавил OnFormClose событие:

procedure TfrmPopup.FormClose(Sender: TObject; var Action: TCloseAction);
begin
  Action := caFree;
end;

затем, наконец, я подумал, что было бы смешно добавить функцию изменения размера

я сделал это путем переопределения WM_NCHITTEST сообщение :

procedure TfrmPopup.WMNCHitTest(var Message: TWMNCHitTest);
const
  EDGEDETECT = 7; //adjust to suit yourself
var
  deltaRect: TRect; //not really used as a rect, just a convenient structure
begin
  inherited;

  with Message, deltaRect do
  begin
    Left := XPos - BoundsRect.Left;
    Right := BoundsRect.Right - XPos;
    Top := YPos - BoundsRect.Top;
    Bottom := BoundsRect.Bottom - YPos;

    if (Top < EDGEDETECT) and (Left < EDGEDETECT) then
      Result := HTTOPLEFT
    else if (Top < EDGEDETECT) and (Right < EDGEDETECT) then
      Result := HTTOPRIGHT
    else if (Bottom < EDGEDETECT) and (Left < EDGEDETECT) then
      Result := HTBOTTOMLEFT
    else if (Bottom < EDGEDETECT) and (Right < EDGEDETECT) then
      Result := HTBOTTOMRIGHT
    else if (Top < EDGEDETECT) then
      Result := HTTOP
    else if (Left < EDGEDETECT) then
      Result := HTLEFT
    else if (Bottom < EDGEDETECT) then
      Result := HTBOTTOM
    else if (Right < EDGEDETECT) then
      Result := HTRIGHT;
  end;
end;

Итак, наконец я закончил с этим :

unit frmPopupU;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls;

type
  TfrmPopup = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
    procedure FormClose(Sender: TObject; var Action: TCloseAction);
    procedure FormCreate(Sender: TObject);
  private
    procedure WMMouseActivate(var Message: TWMMouseActivate); message WM_MOUSEACTIVATE;
    procedure WMNCHitTest(var Message: TWMNCHitTest); message WM_NCHITTEST;
  public
    procedure CreateParams(var Params: TCreateParams); override;
  end;

implementation

{$R *.dfm}

{ TfrmPopup }

procedure TfrmPopup.Button1Click(Sender: TObject);
begin
  Close;
end;

procedure TfrmPopup.CreateParams(var Params: TCreateParams);
const
  CS_DROPSHADOW = 020000;
begin
  inherited CreateParams({var}Params);
  Params.WindowClass.Style := Params.WindowClass.Style or CS_DROPSHADOW;
end;

procedure TfrmPopup.FormClose(Sender: TObject; var Action: TCloseAction);
begin
  Action := caFree;
end;

procedure TfrmPopup.FormCreate(Sender: TObject);
begin
  DoubleBuffered := true;
  BorderStyle := bsNone;
end;

procedure TfrmPopup.WMMouseActivate(var Message: TWMMouseActivate);
begin
  Message.Result := MA_NOACTIVATE;
end;

procedure TfrmPopup.WMNCHitTest(var Message: TWMNCHitTest);
const
  EDGEDETECT = 7; //adjust to suit yourself
var
  deltaRect: TRect; //not really used as a rect, just a convenient structure
begin
  inherited;

  with Message, deltaRect do
  begin
    Left := XPos - BoundsRect.Left;
    Right := BoundsRect.Right - XPos;
    Top := YPos - BoundsRect.Top;
    Bottom := BoundsRect.Bottom - YPos;

    if (Top < EDGEDETECT) and (Left < EDGEDETECT) then
      Result := HTTOPLEFT
    else if (Top < EDGEDETECT) and (Right < EDGEDETECT) then
      Result := HTTOPRIGHT
    else if (Bottom < EDGEDETECT) and (Left < EDGEDETECT) then
      Result := HTBOTTOMLEFT
    else if (Bottom < EDGEDETECT) and (Right < EDGEDETECT) then
      Result := HTBOTTOMRIGHT
    else if (Top < EDGEDETECT) then
      Result := HTTOP
    else if (Left < EDGEDETECT) then
      Result := HTLEFT
    else if (Bottom < EDGEDETECT) then
      Result := HTBOTTOM
    else if (Right < EDGEDETECT) then
      Result := HTRIGHT;
  end;
end;

end.

надеюсь, что вы можете использовать его.

полный и функциональный код

следующий блок был протестирован только в Delphi 5 (эмулированная поддержка PopupParent). Но помимо этого, он делает все, что нужно раскрывающемуся списку. Sertac решил AnimateWindow проблема.

unit DropDownForm;

{
    A drop-down style form.

    Sample Usage
    =================

        procedure TForm1.SpeedButton1MouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
        var
            pt: TPoint;
        begin
            if FPopup = nil then
                FPopup := TfrmOverdueReportsPopup.Create(Self);
            if FPopup.DroppedDown then //don't drop-down again if we're already showing it
                Exit;

            pt := Self.ClientToScreen(SmartSpeedButton1.BoundsRect.BottomRight);
            Dec(pt.X, FPopup.Width);

            FPopup.ShowDropdown(Self, pt);
        end;

    Simply make a form descend from TDropDownForm.

        Change:
            type
                TfrmOverdueReportsPopup = class(TForm)

        to:
            uses
                DropDownForm;

            type
                TfrmOverdueReportsPopup = class(TDropDownForm)
}

interface

uses
    Forms, Messages, Classes, Controls, Windows;

const
    WM_PopupFormCloseUp = WM_USER+89;

type
    TDropDownForm = class(TForm)
    private
        FOnCloseUp: TNotifyEvent;
        FPopupParent: TCustomForm;
        FResizable: Boolean;
        function GetDroppedDown: Boolean;
{$IFNDEF SupportsPopupParent}
        procedure SetPopupParent(const Value: TCustomForm);
{$ENDIF}
    protected
        procedure CreateParams(var Params: TCreateParams); override;
        procedure WMActivate(var Msg: TWMActivate); message WM_ACTIVATE;
        procedure WMNCHitTest(var Message: TWMNCHitTest); message WM_NCHITTEST;

        procedure DoCloseup; virtual;

        procedure WMPopupFormCloseUp(var Msg: TMessage); message WM_PopupFormCloseUp;

{$IFNDEF SupportsPopupParent}
        property PopupParent: TCustomForm read FPopupParent write SetPopupParent;
{$ENDIF}
  public
        constructor Create(AOwner: TComponent); override;

        procedure ShowDropdown(OwnerForm: TCustomForm; PopupPosition: TPoint);
        property DroppedDown: Boolean read GetDroppedDown;
        property Resizable: Boolean read FResizable write FResizable;

        property OnCloseUp: TNotifyEvent read FOnCloseUp write FOnCloseUp;
  end;

implementation

uses
    SysUtils;

{ TDropDownForm }

constructor TDropDownForm.Create(AOwner: TComponent);
begin
    inherited;

    Self.BorderStyle := bsNone; //get rid of our border right away, so the creator can measure us accurately
    FResizable := True;
end;

procedure TDropDownForm.CreateParams(var Params: TCreateParams);
const
    SPI_GETDROPSHADOW = 24;
    CS_DROPSHADOW = 020000;
var
    dropShadow: BOOL;
begin
    inherited CreateParams({var}Params);

    //It's no longer documented (because Windows 2000 is no longer supported)
    //but use of CS_DROPSHADOW and SPI_GETDROPSHADOW are only supported on XP (5.1) or newer
    if (Win32MajorVersion > 5) or ((Win32MajorVersion = 5) and (Win32MinorVersion >= 1)) then
    begin
        //Use of a drop-shadow is controlled by a system preference
        if not Windows.SystemParametersInfo(SPI_GETDROPSHADOW, 0, @dropShadow, 0) then
            dropShadow := False;

        if dropShadow then
            Params.WindowClass.Style := Params.WindowClass.Style or CS_DROPSHADOW;
    end;

{$IFNDEF SupportsPopupParent} //Delphi 5 support for "PopupParent" style form ownership
    if FPopupParent <> nil then
        Params.WndParent := FPopupParent.Handle;
{$ENDIF}
end;

procedure TDropDownForm.DoCloseup;
begin
    if Assigned(FOnCloseUp) then
        FOnCloseUp(Self);
end;

function TDropDownForm.GetDroppedDown: Boolean;
begin
    Result := (Self.Visible);
end;

{$IFNDEF SupportsPopupParent}
procedure TDropDownForm.SetPopupParent(const Value: TCustomForm);
begin
    FPopupParent := Value;
end;
{$ENDIF}

procedure TDropDownForm.ShowDropdown(OwnerForm: TCustomForm; PopupPosition: TPoint);
var
    comboBoxAnimation: BOOL;
    i: Integer;

const
    AnimationDuration = 200; //200 ms
begin
    //We want the dropdown form "owned" by (i.e. not "parented" to) the OwnerForm
    Self.Parent := nil; //the default anyway; but just to reinforce the idea
    Self.PopupParent := OwnerForm; //Owner means the Win32 concept of owner (i.e. always on top of, cf Parent, which means clipped child of)
{$IFDEF SupportsPopupParent}
    Self.PopupMode := pmExplicit; //explicitely owned by the owner
{$ENDIF}

    //Show the form just under, and right aligned, to this button
//  Self.BorderStyle := bsNone; moved to during FormCreate; so can creator can know our width for measurements
    Self.Position := poDesigned;
    Self.Left := PopupPosition.X;
    Self.Top := PopupPosition.Y;

    //Use of drop-down animation is controlled by preference
    if not Windows.SystemParametersInfo(SPI_GETCOMBOBOXANIMATION, 0, @comboBoxAnimation, 0) then
        comboBoxAnimation := False;

    if comboBoxAnimation then
    begin
        //Delphi doesn't react well to having a form show behind its back (e.g. ShowWindow, AnimateWindow).
        //Force Delphi to create all the WinControls so that they will exist when the form is shown.
        for i := 0 to ControlCount - 1 do
        begin
            if Controls[i] is TWinControl and Controls[i].Visible and
                    not TWinControl(Controls[i]).HandleAllocated then
            begin
                TWinControl(Controls[i]).HandleNeeded;
                SetWindowPos(TWinControl(Controls[i]).Handle, 0, 0, 0, 0, 0,
                        SWP_NOSIZE or SWP_NOMOVE or SWP_NOZORDER or SWP_NOACTIVATE or SWP_SHOWWINDOW);
            end;
        end;
        AnimateWindow(Self.Handle, AnimationDuration, AW_VER_POSITIVE or AW_SLIDE or AW_ACTIVATE);
        Visible := True; // synch VCL
    end
    else
        inherited Show;
end;

procedure TDropDownForm.WMActivate(var Msg: TWMActivate);
begin
    //If we are being activated, then give pretend activation state back to our owner
    if (Msg.Active <> WA_INACTIVE) then
        SendMessage(Self.PopupParent.Handle, WM_NCACTIVATE, WPARAM(True), -1);

    inherited;

    //If we're being deactivated, then we need to rollup
    if Msg.Active = WA_INACTIVE then
    begin
        {
            Post a message (not Send a message) to oursleves that we're closing up.
            This gives a chance for the mouse/keyboard event that triggered the closeup
            to believe the drop-down is still dropped down.
            This is intentional, so that the person dropping it down knows not to drop it down again.
            They want clicking the button while is was dropped to hide it.
            But in order to hide it, it must still be dropped down.
        }
        PostMessage(Self.Handle, WM_PopupFormCloseUp, WPARAM(Self), LPARAM(0));
    end;
end;

procedure TDropDownForm.WMNCHitTest(var Message: TWMNCHitTest);
var
    deltaRect: TRect; //not really used as a rect, just a convenient structure
    cx, cy: Integer;
begin
    inherited;

    if not Self.Resizable then
        Exit;

    //The sizable border is a preference
    cx := GetSystemMetrics(SM_CXSIZEFRAME);
    cy := GetSystemMetrics(SM_CYSIZEFRAME);

    with Message, deltaRect do
    begin
        Left := XPos - BoundsRect.Left;
        Right := BoundsRect.Right - XPos;
        Top := YPos - BoundsRect.Top;
        Bottom := BoundsRect.Bottom - YPos;

        if (Top < cy) and (Left < cx) then
            Result := HTTOPLEFT
        else if (Top < cy) and (Right < cx) then
            Result := HTTOPRIGHT
        else if (Bottom < cy) and (Left < cx) then
            Result := HTBOTTOMLEFT
        else if (Bottom < cy) and (Right < cx) then
            Result := HTBOTTOMRIGHT
        else if (Top < cy) then
            Result := HTTOP
        else if (Left < cx) then
            Result := HTLEFT
        else if (Bottom < cy) then
            Result := HTBOTTOM
        else if (Right < cx) then
            Result := HTRIGHT;
    end;
end;

procedure TDropDownForm.WMPopupFormCloseUp(var Msg: TMessage);
begin
    //This message gets posted to us.
    //Now it's time to actually closeup.
    Self.Hide;

    DoCloseup; //raise the OnCloseup event *after* we're actually hidden
end;

end.

как создать раскрывающееся окно с помощью Delphi?

вы собрали все биты и куски, которые вы суммировали, нет ни одного класса/функции VCL, который бы создавал выпадающую форму.

есть несколько моментов, чтобы упомянуть в вашем исследовании, хотя.


во-первых, вы путаете активацию с фокусом. Фокус не сохраняется в вызывающей форме, когда перед ним появляется другое окно, активация есть-или так кажется. Фокус, где ввод клавиатуры идет, это, очевидно, На либо выскочил/упал окно или на элемент управления в нем.


ваша проблема с элементами управления не отображается с AnimateWindow это то, что VCL не создает базовые собственные (OS) элементы управления TWinControls, пока это не необходимо (не-wincontrols не являются проблемой). Что касается VCL, создание их обычно не требуется, пока они не будут видны, то есть когда вы установите Visible вашего форма для true (или call Show), который вы не можете с тех пор не будет анимации, если, конечно, вы не установите visible после того, как анимация.

это также отсутствующее требование при попытке обновить форму:

AnimateWindow(Self.Handle, 200, AW_VER_POSITIVE or AW_SLIDE or AW_ACTIVATE);
Self.Repaint;
Self.Update;
Self.Invalidate;

обратите внимание, что в приведенной выше цитате из вопроса, ни один из звонков не. Но там нечего рисовать, форма даже не visible еще.

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

...
if comboBoxAnimation then
begin
  for i := 0 to ControlCount - 1 do
    if Controls[i] is TWinControl and Controls[i].Visible and
        not TWinControl(Controls[i]).HandleAllocated then begin
      TWinControl(Controls[i]).HandleNeeded;
      SetWindowPos(TWinControl(Controls[i]).Handle, 0, 0, 0, 0, 0,
          SWP_NOSIZE or SWP_NOMOVE or SWP_NOZORDER or SWP_NOACTIVATE or
          SWP_SHOWWINDOW);
    end;
  AnimateWindow(Handle, 200, AW_VER_POSITIVE or AW_SLIDE or AW_ACTIVATE);
  Visible := True; // synch VCL
end
else
  ...

это просто пример, показывающий, что форма вне экрана или любой другой творческий метод может работать одинаково хорошо. Вот,в ответ, я достигаю того же, установив высоту анимированной формы в " 0 " Перед установкой visible к истине (мне больше нравится подход в этом ответе..).


что касается не падения снова, когда форма уже упала, вам не нужно публиковать сообщение в вызывающую форму для этого. На самом деле не делайте этого, это требует ненужного сотрудничества со стороны вызывающей формы. Всегда будет только один экземпляр, который нужно сбросить, поэтому вы можете использовать global:

  TfrmPopup = class(TForm)
    ...
    procedure FormDestroy(Sender: TObject);
  private
    FNotificationParentWnd: HWND;
    class var
      FDroppedDown: Boolean;
  protected
    ...


procedure TfrmPopup.Show(Owner: TForm; NotificationParentWindow: HWND;
  ...

  if not FDroppedDown then begin
      if comboBoxAnimation then begin

        // animate as above

        Visible := True; // synch with VCL
        FDroppedDown := True;
      end
      else
        inherited Show;
    end;
end;

procedure TfrmPopup.FormDestroy(Sender: TObject);
begin
  FDroppedDown := False;
end;