{ /*************************************************************************** TaskDlgEmulation.pp ----------------- Implements TaskDialog Window on systems that do not support it natively This unit was originally a part of the freeware Synopse mORMot framework, licensed under a MPL/GPL/LGPL tri-license; version 1.19. It has been relicensed with permission from Arnaud Bouchez, the original author, and all contributors. The original name is SynTaskDialog.pas ***************************************************************************/ ***************************************************************************** This file is part of the Lazarus Component Library (LCL) See the file COPYING.modifiedLGPL.txt, included in this distribution, for details about the license. ***************************************************************************** } unit TaskDlgEmulation; { This unit tries to emulate the functionality of Windows Vista and higher TaskDialogIndirect. It also adds capabilities that TaskDialogIndirect does not have, currently: * Query via combobox * Query via single line edit, which supports masking the input for use eith e.g. passwords The emulated dialog does not aim to be visually (near) exactly the same as the Vista+ native dialog. This dialog is invoked by Dialogs.TTaskDialog.Execute on systems that do not support the native Vista+ dialog, and it is also used as a fallback in case the native Vista+ dialog fails (when passed invalid combination of arguments). The dialog therefore uses the Flags property of Dialogs.TTaskDialog, but not all of these flags are supported (yet) in the emulated dialog. } {$mode ObjFPC}{$H+} interface uses Classes, SysUtils, LazUTF8, LCLType, LCLStrConsts, LCLIntf, LMessages, InterfaceBase, ImgList, LCLProc, DateUtils, Math, ComCtrls, LResources, Menus, Graphics, Forms, Controls, StdCtrls, ExtCtrls, Buttons, Dialogs, DialogRes; type TTaskDialogElement = ( tdeContent, tdeExpandedInfo, tdeFooter, tdeMainInstruction, tdeEdit, tdeVerif); { TLCLTaskDialog } TLCLTaskDialog = class(TForm) private const RadioIndent = 16; ComboBoxHeight = 22; QueryEditHeight = 22; LargeImageSize = 32; SmallImageSize = 16; CommandLinkButtonHeight = 40; RadioVSpacing = 16; LabelVSpacing = 16; CommandLinkButtonVSpacing = 2; BevelMargin = 2; BevelHeight = 2; ProgressBarHeight = 20; ProgressBarVSpacing = 16; private /// the Task Dialog structure which created the form FDlg: TTaskDialog; FVerifyChecked: Boolean; FExpanded: Boolean; CommandLinkButtonWidth: Integer; CommandLinkButtonMargin: Integer; CommandLinkButtonSpacing: Integer; //Height of TBitBtns ButtonHeight: Integer; //Height of TButtons GlobalLeftMargin: Integer; ExpandHeightRequired: Integer; Timer: TTimer; TimerStartTime: TTime; RadioButtonArray: array of TRadioButton; //CustomButtons, Radios: TStringList; DialogCaption, DlgTitle, DlgText, ExpandButtonCaption, CollapseButtonCaption, ExpandedText, FooterText, VerificationText: String; CommonButtons: TTaskDialogCommonButtons; TopPanel: TPanel; MidPanel: TPanel; BottomPanel: TPanel; MainImage: TImage; FooterImage: TImage; ExpandedTextBevel: TBevel; /// the labels corresponding to the Task Dialog main elements Element: array[tdeContent..tdeMainInstruction] of TLabel; /// the Task Dialog query selection list QueryCombo: TComboBox; /// the Task Dialog optional query single line editor QueryEdit: TEdit; /// the Task Dialog optional checkbox VerifyCheckBox: TCheckBox; /// the Expand/Collapse button ExpandBtn: TButton; /// ProgressBar: TProgressBar; procedure GetDefaultButtons(out aButtonDef, aRadioDef: TModalResult); procedure InitCaptions; procedure InitGlobalDimensionsAndStyle(ACustomButtonsTextLength: Integer; out aWidth, aFontHeight: Integer); function GetGlobalLeftMargin: Integer; procedure AddMainIcon(out ALeft,ATop: Integer; AGlobalLeftMargin: Integer; AParent: TWinControl); procedure AddPanels; procedure AddProgressBar(const ALeft: Integer; var ATop: Integer; AWidth: Integer; AParent: TWinControl); procedure AddRadios(const ARadioOffSet, AWidth, ARadioDef, AFontHeight, ALeft: Integer; var ATop: Integer; AParent: TWinControl); procedure AddCommandLinkButtons(const ALeft: Integer; var ATop: Integer; AWidth, AButtonDef, AFontHeight: Integer; AParent: TWinControl); procedure AddButtons(const ALeft: Integer; var ATop, AButtonLeft: Integer; AWidth, AButtonDef: Integer; APArent: TWinControl); procedure AddCheckBox(const ALeft: Integer; var ATop, XB: Integer; AWidth: Integer; APArent: TWinControl); procedure AddExpandButton(const ALeft: Integer; var ATop, XB: Integer; AWidth: Integer; APArent: TWinControl); function AddBevel(var ATop: Integer; aWidth: Integer; AParent: TWinControl; Hidden: Boolean = False): TBevel; procedure AddFooter(var ALeft: Integer; var ATop: Integer; AFontHeight, AWidth: Integer; APArent: TWinControl); function AddLabel(const AText: string; BigFont: Boolean; const ALeft: Integer; var ATop: Integer; AFontHeight, AWidth: Integer; APArent: TWinControl; Hidden: Boolean = False): TLabel; procedure AddQueryCombo(const ALeft: Integer; var ATop: Integer; AWidth: Integer; AParent: TWinControl); procedure AddQueryEdit(var X,Y: Integer; AWidth: Integer; AParent: TWinControl); procedure SetupTimer; procedure ResetTimer; procedure ExpandDialog; procedure CollapseDialog; function FindButtonByButtonID(ID: Integer): TCustomButton; function FindRadioButtonByButtonID(ID: Integer): TRadioButton; procedure DoDialogConstructed; procedure DoDialogCreated; procedure DoDialogDestroyed; procedure OnButtonClicked(Sender: TObject); procedure OnRadioButtonClick(Sender: TObject); procedure OnVerifyClicked(Sender: TObject); procedure OnTimer(Sender: TObject); procedure OnExpandButtonClicked(Sender: TObject); procedure DoOnHelp; procedure SetProgressBarType(var Msg: TLMessage); message TDM_SET_MARQUEE_PROGRESS_BAR; procedure SetProgressBarRange(var Msg: TLMessage); message TDM_SET_PROGRESS_BAR_RANGE; procedure SetProgressBarPos(var Msg: TLMessage); message TDM_SET_PROGRESS_BAR_POS; procedure ClickVerification(var Msg: TLMessage); message TDM_CLICK_VERIFICATION; procedure ClickButton(var Msg: TLMessage); message TDM_CLICK_BUTTON; procedure ClickRadioButton(var Msg: TLMessage); message TDM_CLICK_RADIO_BUTTON; procedure EnableButton(var Msg: TLMessage); message TDM_ENABLE_BUTTON; procedure EnableRadioButton(var Msg: TLMessage); message TDM_ENABLE_RADIO_BUTTON; procedure UpdateElementText(var Msg: TLMessage); message TDM_UPDATE_ELEMENT_TEXT; protected procedure SetupControls; public procedure KeyDown(var Key: Word; Shift: TShiftState); override; constructor CreateNew(AOwner: TComponent; Num: Integer = 0); override; destructor Destroy; override; procedure AfterConstruction; override; procedure DoShow; override; function Execute(AParentWnd: HWND; out ARadioRes: Integer): Integer; public end; function ExecuteLCLTaskDialog(const ADlg: TCustomTaskDialog; AParentWnd: HWND; out ARadioRes: Integer): Integer; type TLCLTaskDialogIcon = ( tiBlank, tiWarning, tiQuestion, tiError, tiInformation, tiNotUsed, tiShield); TLCLTaskDialogFooterIcon = ( tfiBlank, tfiWarning, tfiQuestion, tfiError, tfiInformation, tfiShield); function IconMessage(Icon: TTaskDialogIcon): string; implementation type TTaskDialogAccess = class(TCustomTaskDialog) end; var LDefaultFont: TFont; function DefaultFont: TFont; begin if LDefaultFont<>nil then Exit(LDefaultFont); LDefaultFont := TFont.Create; LDefaultFont.Name := 'default'; LDefaultFont.Style := []; LDefaultFont.Size := 10; Result := LDefaultFont; {$IFDEF WINDOWS} if Screen.Fonts.IndexOf('Calibri')>=0 then begin LDefaultFont.Size := 11; LDefaultFont.Name := 'Calibri'; end else begin if Screen.Fonts.IndexOf('Tahoma')>=0 then LDefaultFont.Name := 'Tahoma' else LDefaultFont.Name := 'Arial'; end; {$ENDIF} end; const LCL_IMAGES: array[TTaskDialogIcon] of Integer = ( 0 {tdiNone}, idDialogWarning {tdiWarning}, idDialogError {tdiError}, idDialogInfo {tdiInformation}, idDialogShield {tdiShield}, idDialogConfirm {tdiQuestion}); const TD_BTNMOD: array[TTaskDialogCommonButton] of Integer = ( mrOk, mrYes, mrNo, mrCancel, mrRetry, mrAbort); function TD_BTNS(button: TTaskDialogCommonButton): pointer; begin case button of tcbOK: Result := @rsMbOK; tcbYes: Result := @rsMbYes; tcbNo: Result := @rsMbNo; tcbCancel: Result := @rsMbCancel; tcbRetry: Result := @rsMbRetry; tcbClose: Result := @rsMbClose; end; end; function IconMessage(Icon: TTaskDialogIcon): string; begin case Icon of tdiWarning: Result := rsMtWarning; tdiQuestion: Result := rsMtConfirmation; tdiError: Result := rsMtError; tdiInformation, tdiShield: Result := rsMtInformation; else Result := ''; end; end; function ExecuteLCLTaskDialog(const ADlg: TCustomTaskDialog; AParentWnd: HWND; out ARadioRes: Integer): Integer; var DlgForm: TLCLTaskDialog; begin //debugln('ExecuteLCLTaskDialog'); Result := -1; DlgForm := TLCLTaskDialog.CreateNew(ADlg); try Result := DlgForm.Execute(AParentWnd, ARadioRes); finally FreeAndNil(DlgForm); end; end; constructor TLCLTaskDialog.CreateNew(AOwner: TComponent; Num: Integer); begin if (AOwner is TCustomTaskDialog) then begin FDlg := TTaskDialog(AOwner); if (csDesigning in FDlg.ComponentState) then AOwner:=nil; // do not inherit csDesigning, a normal taskdialog should be shown end; inherited CreateNew(AOwner, Num); RadioButtonArray := nil; FExpanded := False; CommandLinkButtonWidth := -1; KeyPreview := True; //DoDialogCreated; end; destructor TLCLTaskDialog.Destroy; begin DoDialogDestroyed; inherited Destroy; end; procedure TLCLTaskDialog.AfterConstruction; begin inherited AfterConstruction; //DoDialogConstructed; end; procedure TLCLTaskDialog.DoShow; begin inherited DoShow; { If we call GetHandle in AfterConstrucion this triggers a CreateWnd, but later on (as a consequence of using CreateNew ??) the window gets destroyed and recreated with a different handle, so we cannot call FDlg.InternalSetDialogHandle in CreateNew or AfterConstruction. And since we want to have a valid FDlg.Handle in all OnDialogXXX events, we do it here. } DoDialogConstructed; DoDialogCreated; end; function TLCLTaskDialog.Execute(AParentWnd: HWND; out ARadioRes: Integer): Integer; var mRes, I: Integer; begin //debugln(['TLCLTaskDialog.Execute: Assigned(FDlg)=',Assigned(FDlg)]); if not Assigned(FDlg) then Exit(-1); SetupControls; //set form parent if (AParentWnd <> 0) then for I := 0 to Screen.CustomFormCount-1 do if Screen.CustomForms[I].Handle = AParentWnd then begin PopupParent := Screen.CustomForms[I]; Break; end; if not Assigned(PopupParent) then PopupParent := Screen.ActiveCustomForm; if Assigned(PopupParent) then PopupMode := pmExplicit; Result := ShowModal; if Assigned(QueryCombo) then begin FDlg.QueryItemIndex := QueryCombo.ItemIndex; FDlg.QueryResult := QueryCombo.Text; end else begin if Assigned(QueryEdit) then FDlg.QueryResult := QueryEdit.Text; end; if VerifyCheckBox<>nil then begin if VerifyCheckBox.Checked then FDlg.Flags := FDlg.Flags + [tfVerificationFlagChecked] else FDlg.Flags := FDlg.Flags - [tfVerificationFlagChecked] end; ARadioRes := 0; for i := 0 to high(RadioButtonArray) do if RadioButtonArray[i].Checked then ARadioRes := i+TaskDialogFirstRadioButtonIndex; end; procedure TLCLTaskDialog.GetDefaultButtons(out aButtonDef, aRadioDef: TModalResult); begin if FDlg.RadioButtons.DefaultButton<> nil then aRadioDef := FDlg.RadioButtons.DefaultButton.Index else aRadioDef := 0; if FDlg.Buttons.DefaultButton<>nil then aButtonDef := FDlg.Buttons.DefaultButton.ModalResult else aButtonDef := TD_BTNMOD[FDlg.DefaultButton]; end; procedure TLCLTaskDialog.InitCaptions; begin DialogCaption := FDlg.Caption; DlgTitle := FDlg.Title; DlgText := FDlg.Text; ExpandButtonCaption := FDlg.ExpandButtonCaption; CollapseButtonCaption := FDlg.CollapseButtonCaption; ExpandedText := FDlg.ExpandedText; FooterText := FDlg.FooterText; VerificationText := FDlg.VerificationText; if (DialogCaption = '') then if (Application.MainForm = nil) then DialogCaption := Application.Title else DialogCaption := Application.MainForm.Caption; if (DlgTitle = '') then DlgTitle := IconMessage(FDlg.MainIcon); end; procedure TLCLTaskDialog.InitGlobalDimensionsAndStyle(ACustomButtonsTextLength: Integer; out aWidth, aFontHeight: Integer); begin PixelsPerInch := 96; // we are using 96 PPI in the code, scale it automatically at ShowModal Font.PixelsPerInch := 96; BorderStyle := bsDialog; if (tfAllowDialogCancellation in FDlg.Flags) then BorderIcons := [biSystemMenu] else BorderIcons := []; if (tfPositionRelativeToWindow in FDlg.Flags) then Position := poOwnerFormCenter else Position := poScreenCenter; if not (tfEmulateClassicStyle in FDlg.Flags) then Font := DefaultFont; aFontHeight := Font.Height; if (aFontHeight = 0) then aFontHeight := Screen.SystemFont.Height; aWidth := FDlg.Width; if (aWidth <= 0) then begin aWidth := Canvas.TextWidth(DlgTitle); if (aWidth > 300) or (Canvas.TextWidth(DlgText) > 300) or (ACustomButtonsTextLength > 40) then aWidth := 480 else aWidth := 420; end else if (aWidth < 120) then aWidth := 120; ClientWidth := aWidth; if (tfEmulateClassicStyle in FDlg.Flags) then begin CommandLinkButtonMargin := 7; CommandLinkButtonSpacing := 7; ButtonHeight := 22; end else begin CommandLinkButtonMargin := 24; CommandLinkButtonSpacing := 10; ButtonHeight := 28; end; Height := 200; //debugln(['Font: Name=',Font.Name,', Size=',Font.Size,', Height=',Font.Height]); end; function TLCLTaskDialog.GetGlobalLeftMargin: Integer; begin if (tfEmulateClassicStyle in FDlg.Flags) then Result := 10 else Result := 16; end; procedure TLCLTaskDialog.AddMainIcon(out ALeft,ATop: Integer; AGlobalLeftMargin: Integer; AParent: TWinControl); var aDialogIcon: TTaskDialogIcon; begin MainImage := nil; if not (tfUseHIconMain in FDlg.Flags) then begin aDialogIcon := FDlg.MainIcon; if (LCL_IMAGES[aDialogIcon]<>0) then begin MainImage := TImage.Create(Self); MainImage.Parent := AParent; MainImage.Images := DialogGlyphs; MainImage.ImageIndex := DialogGlyphs.DialogIcon[LCL_IMAGES[aDialogIcon]]; end; end else begin if Assigned(FDlg.CustomMainIcon) and not (FDlg.CustomMainIcon.Empty) then begin MainImage := TImage.Create(Self); MainImage.Parent := AParent; MainImage.Picture.Assign(FDlg.CustomMainIcon); end; end; if Assigned(MainImage) then begin MainImage.SetBounds(AGlobalLeftMargin, AGlobalLeftMargin, LargeImageSize, LargeImageSize); MainImage.Stretch := True; MainImage.StretchOutEnabled := False; MainImage.Proportional := True; MainImage.Center := True; ALeft := MainImage.Width+AGlobalLeftMargin*2; ATop := MainImage.Top; if (tfEmulateClassicStyle in FDlg.Flags) then inc(ATop, 8); end else begin ALeft := AGlobalLeftMargin; ATop := AGlobalLeftMargin; end; end; procedure TLCLTaskDialog.AddPanels; begin { Create 3 different panels: - the top panel holds main icon, title, text and expanded text - the mid panel holds radiobuttons, commandlinkbuttons and query's (basically everything that comes after ExpandedText and needs to be on a "colored" panel) - the bottom panel has the rest of the controls The top and mid panel have a distinct color (unless tfEmulateClassicStyle is set) The reason for the 3 panel setup is that it makes it a lot easier to displace the controls when Expand or Collapse is invoked: just move/resize the appropriate panels, no need to iterate the individual controls on it. } TopPanel := TPanel.Create(Self); TopPanel.Parent := Self; TopPanel.Align := alTop; TopPanel.BorderStyle := bsNone; TopPanel.BevelOuter := bvNone; if not (tfEmulateClassicStyle in FDlg.Flags) then TopPanel.Color := clWindow; TopPanel.Name := 'TopPanel'; //for debugging purposes TopPanel.Caption := ''; MidPanel := TPanel.Create(Self); MidPanel.Parent := Self; MidPanel.Top := TopPanel.Top + TopPanel.Height + 1; MidPanel.Align := alTop; MidPanel.BorderStyle := bsNone; MidPanel.BevelOuter := bvNone; MidPanel.Color := TopPanel.Color; MidPanel.Name := 'MidPanel'; //for debugging purposes MidPanel.Caption := ''; BottomPanel := TPanel.Create(Self); BottomPanel.Parent := Self; BottomPanel.Top := MidPanel.Top + MidPanel.Height + 1; BottomPanel.Align := alCLient; BottomPanel.BorderStyle := bsNone; BottomPanel.BevelOuter := bvNone; BottomPanel.Name := 'BottomPanel'; //for debugging purposes BottomPanel.Caption := ''; end; procedure TLCLTaskDialog.AddProgressBar(const ALeft: Integer; var ATop: Integer; AWidth: Integer; AParent: TWinControl); begin Inc(ATop, ProgressBarVSpacing); ProgressBar := TProgressBar.Create(Self); if (tfShowMarqueeProgressBar in FDlg.Flags) then ProgressBar.Style := pbstMarquee else begin ProgressBar.Style := pbstNormal; ProgressBar.Min := FDlg.ProgressBar.Min; ProgressBar.Max := FDlg.ProgressBar.Max; ProgressBar.Position := FDlg.ProgressBar.Position; end; ProgressBar.SetBounds(ALeft, ATop, AWidth-ALeft-GlobalLeftMargin, ProgressBarHeight); Inc(ATop, ProgressBar.Height + ProgressBarVSpacing); ProgressBar.Parent := AParent; end; procedure TLCLTaskDialog.AddRadios(const ARadioOffSet, AWidth, ARadioDef, AFontHeight, ALeft: Integer; var ATop: Integer; AParent: TWinControl); var i: Integer; aHint: String; begin SetLength(RadioButtonArray,FDlg.RadioButtons.Count); for i := 0 to FDlg.RadioButtons.Count-1 do begin RadioButtonArray[i] := TRadioButton.Create(Self); with RadioButtonArray[i] do begin Parent := AParent; Tag := FDlg.RadioButtons[i].Index + TaskDialogFirstRadioButtonIndex; AutoSize := False; SetBounds(ALeft+RadioIndent,ATop,aWidth-(2*RadioIndent)-ALeft, (6-AFontHeight) + ARadioOffset); Caption := FDlg.RadioButtons[i].Caption; inc(ATop,Height + ARadioOffset); if not (tfNoDefaultRadioButton in FDlg.Flags) and ((i=0) or (i=aRadioDef)) then Checked := True; OnClick := @OnRadioButtonClick; end; end; inc(ATop,24); end; procedure TLCLTaskDialog.AddCommandLinkButtons(const ALeft: Integer; var ATop: Integer; AWidth, AButtonDef, AFontHeight: Integer; AParent: TWinControl); var i: Integer; CommandLink: TBitBtn; aHint: String; begin inc(ATop,8); for i := 0 to FDlg.Buttons.Count-1 do begin CommandLink := TBitBtn.Create(Self); with CommandLink do begin Parent := AParent; Font.Height := AFontHeight-3; CommandLinkButtonWidth := aWidth - ALeft - GlobalLeftMargin; SetBounds(ALeft,ATop,CommandLinkButtonWidth,CommandLinkButtonHeight); Caption := FDlg.Buttons[i].Caption; Hint := FDlg.Buttons[i].CommandLinkHint; if (Hint <> '') then ShowHint := True; inc(ATop,Height+CommandLinkButtonVSpacing); ModalResult := i+TaskDialogFirstButtonIndex; OnClick := @OnButtonClicked; if ModalResult=aButtonDef then ActiveControl := CommandLink; if (tfEmulateClassicStyle in FDlg.Flags) then begin Font.Height := AFontHeight - 2; Font.Style := [fsBold] end; Margin := CommandLinkButtonMargin; Spacing := CommandLinkButtonSpacing; if not (tfUseCommandLinksNoIcon in FDlg.Flags) then begin Images := LCLGlyphs; ImageIndex := LCLGlyphs.GetImageIndex('btn_arrowright'); end; end; end; inc(ATop,24); end; procedure TLCLTaskDialog.AddButtons(const ALeft: Integer; var ATop, AButtonLeft: Integer; AWidth, AButtonDef: Integer; APArent: TWinControl); var CurrTabOrder, i: Integer; Btn: TTaskDialogCommonButton; function AddButton(const s: string; AModalResult, ACustomButtonIndex: integer): TButton; var WB: integer; begin WB := Canvas.TextWidth(s)+52; dec(AButtonLeft,WB); if AButtonLeft= 0) and (FDlg.ButtonIDToModalResult(TaskDialogFirstButtonIndex+ACustomButtonIndex) = AButtonDef) then Result.Default := True; end else begin case AModalResult of mrOk: begin Result.Default := True; if CommonButtons=[tcbOk] then Result.Cancel := True; end; mrCancel: Result.Cancel := True; end;//case end;//else if Assigned(FDlg.Buttons.DefaultButton) and Result.Default then ActiveControl := Result else if AModalResult=aButtonDef then ActiveControl := Result; end; begin //debugln(['TLCLTaskDialog.AddButtons: ALeft=',ALeft,', aWidth=',aWidth,', AParent=',DbgSName(AParent),', AParent.ClientWidth=',AParent.ClientWidth]); if MidPanel.ControlCount > 0 then CurrTabOrder := MidPanel.TabOrder else CurrTabOrder := TopPanel.TabOrder; inc(ATop, 16); AButtonLeft := aWidth; if not (tfUseCommandLinks in FDlg.Flags) then for i := FDlg.Buttons.Count-1 downto 0 do AddButton(FDlg.Buttons[i].Caption,i+TaskDialogFirstButtonIndex,i); for Btn := high(TTaskDialogCommonButton) downto low(TTaskDialogCommonButton) do begin if (Btn in CommonButtons) then AddButton(LoadResString(TD_BTNS(Btn)), TD_BTNMOD[Btn],-1); end; end; procedure TLCLTaskDialog.AddCheckBox(const ALeft: Integer; var ATop, XB: Integer; AWidth: Integer; APArent: TWinControl); begin //debugln(['TLCLTaskDialog.AddCheckBox: ALeft=',ALeft]); VerifyCheckBox := TCheckBox.Create(Self); with VerifyCheckBox do begin Parent := AParent; if (ALeft+16+Canvas.TextWidth(VerificationText) > XB) then begin inc(ATop,32); XB := aWidth; end; SetBounds(ALeft,ATop,XB-ALeft,24); Caption := VerificationText; Checked := FVerifyChecked; OnClick := @OnVerifyClicked; end; end; procedure TLCLTaskDialog.AddExpandButton(const ALeft: Integer; var ATop, XB: Integer; AWidth: Integer; APArent: TWinControl); var CurrTabOrder: TTabOrder; WB, AHeight: Integer; begin if MidPanel.ControlCount > 0 then CurrTabOrder := MidPanel.TabOrder else CurrTabOrder := TopPanel.TabOrder; if (ExpandButtonCaption = '') then begin if (CollapseButtonCaption = '') then begin ExpandButtonCaption := rsShowDetails; CollapseButtonCaption := rsHideDetails; end else ExpandButtonCaption := CollapseButtonCaption; end; if (CollapseButtonCaption = '') then CollapseButtonCaption := ExpandButtonCaption; WB := Max(Canvas.TextWidth(ExpandButtonCaption), Canvas.TextWidth(CollapseButtonCaption)) +32;//52; if (ALeft+WB > XB) then begin inc(ATop,32); XB := aWidth; end; ExpandBtn := TButton.Create(Self); ExpandBtn.Parent := AParent; if (tfEmulateClassicStyle in FDlg.Flags) then AHeight := 22 else AHeight := 28; ExpandBtn.SetBounds(ALeft,ATop,WB-12,AHeight); if not (tfExpandedByDefault in FDlg.Flags) then ExpandBtn.Caption := ExpandButtonCaption else ExpandBtn.Caption := CollapseButtonCaption; ExpandBtn.ModalResult := mrNone; ExpandBtn.TabOrder := CurrTabOrder; ExpandBtn.OnClick := @OnExpandButtonClicked; Inc(ATop, AHeight+8); end; function TLCLTaskDialog.AddBevel(var ATop: Integer; aWidth: Integer; AParent: TWinControl; Hidden: Boolean): TBevel; begin Result := TBevel.Create(Self); with Result do begin Parent := AParent; //if (FooterImage<>nil) and (ATop