Как смоделировать раскрывающуюся форму в Delphi?
как я могу создать "выпадающий" окно с использованием Delphi?
все, что выходит за пределы этой точки, является исследовательским усилием; и никоим образом не связано с ответом.
Исследования
создание правильного раскрывающегося списка требует много частей, чтобы тщательно работать вместе. Я предполагаю, что людям не нравится сложный вопрос, и я бы предпочел задать семь отдельных вопросов, каждый из которых касается одной крошечной части проблемы. Все, что следует мой исследования в решении простой проблемы.
обратите внимание на определяющие характеристики раскрывающегося окна:
- 1. раскрывающийся список простирается за пределы it's "владелец" окно
- 2. на "владелец" окно сохраняет фокус; раскрывающийся список никогда не крадет фокус
- 3. в выпадающее окно имеет drop-shadow
это вариант Delphi того же вопроса, который я задал в WinForms:
ответ в WinForms должен был использовать ToolStripDropDown class
. Это вспомогательный класс, который превращает любую форму в выпадающем.
давайте сделаем это в Delphi
я начну с создания безвкусной раскрывающейся формы, это служит примером:
далее я сброшу кнопку, это будет то, что я нажимаю, чтобы сделать выпадающий список:
и, наконец, я подключу некоторый начальный код, чтобы показать форму, где она должна быть в 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, а не клик. Нажмите неверно, так как раскрывающийся список отображается без необходимости щелчка. Один из нерешенных вопросов-как скрыть раскрывающийся список, если пользователь снова опускает кнопку мыши. Но мы оставим это для человека, который ответит на вопрос, чтобы решить. Все в этом вопросе-исследовательские усилия, а не решение.
и мы уходим:
теперь, как это сделать правильно?
Первое, что мы сразу замечаем, это отсутствие тени. Это потому что нам нужно применить 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;
это исправляет это:
Фокуса
следующий вопрос-это вызов .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;
таким образом, окно владельца выглядит так, как будто оно все еще имеет фокус (кто знает, правильно ли это сделать - это только выглядит как это все еще имеет фокус):
засучив
к счастью, 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
за их спиной:
как это решить?
также странно, что сама 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;
но он не работает, он просто сидит и издевается надо мной:
теперь снова показывает, когда я хочу закрыть
если выпадет combobox, и пользователь пытается событие mousedown на кнопке реальный элемент управления Windows ComboBox не просто снова показывает элемент управления, но вместо этого скрывает его:
раскрывающийся список также знает, что в настоящее время "упал вниз", что полезно, чтобы он мог рисовать себя, как если бы он был в "упала" режим. Что нам нужно, так это способ узнать, что раскрывающийся список опустился, и способ узнать, что раскрывающийся список больше не опускается. Какая-то логическая переменная:
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) элементы управления TWinControl
s, пока это не необходимо (не-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;