2017-01-04 24 views
4

Delphi XE6を使用して、私はTdateTimePickerのようなコントロールを作成していますが、いくつかの理由で、TMonthCalendarが埋め込まれたTButtonedEditを使用しています。フル最低限のデモは、次のとおりです。Delphiのコンポーネントの配置ヒント

、右ボタンがクリックされたときに示されている月のカレンダー(スタイル= WS_POPUPと)で所望の選択が行われたとき、私はそれを隠すように私はそれが起こってしまっていますユーザーが離れてナビゲートし、脱出するなど

unit DateEditBare1; 

interface 

uses 
    Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics, 
    Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.ExtCtrls, Vcl.ImgList, Vcl.ComCtrls, Vcl.StdCtrls, 
    CommCtrl; 

type 

    TespMonthCalendar = class(TMonthCalendar) 
    procedure DoCloseUp(Sender: TObject); 
    private 
    FDroppedDown: boolean; 
    FManagerHandle: HWND; // just a convenience to avoid having to assume its in the owner 

    procedure CNNotify(var Message: TWMNotify); message CN_NOTIFY; 
    procedure SetWindowDIMs; 
    protected 
    procedure CreateParams(var Params: TCreateParams); override; 
    procedure CreateWnd; override; 
    procedure KeyDown(var Key: Word; Shift: TShiftState); override; 
    procedure WMActivate(var Msg: TWMActivate); message WM_ACTIVATE; 
    procedure CMHintShow(var Message: TCMHintShow); message CM_HINTSHOW; 
end; 

    TespDateEdit = class(TButtonedEdit) 
    private 
    FMonthCalendar: TespMonthCalendar; 

    procedure DoRightButtonClick(Sender: TObject); 
    protected 
    procedure CreateWnd; override; 
    procedure CMHintShow(var Message: TCMHintShow); message CM_HINTSHOW; 
    public 
    constructor Create(AOwner:TComponent); override; 
    property MonthCalendar: TespMonthCalendar read FMonthCalendar write FMonthCalendar; 
    end; 

    TfrmDateEditBare1 = class(TForm) 
    Edit1: TEdit; 
    procedure FormCreate(Sender: TObject); 
    private 
    espDateEdit1: TespDateEdit; 
    public 
    end; 

var 
    frmDateEditBare1: TfrmDateEditBare1; 

implementation 

{$R *.dfm} 

var 
    _espdateEdit_ImageList: TImageList=nil; 

//------------------------------------------------------------------------------ 


function MakeImageList(const ResNames: array of String): TImageList; 
var 
    ResBmp: TBitmap; 
    I: Integer; 
begin 
    { Create an image list. } 
    _espdateEdit_ImageList := TImageList.Create(nil); 
    _espdateEdit_ImageList.Width := 24; 
    _espdateEdit_ImageList.Height := 16; 
    Result := _espdateEdit_ImageList; 

    for I := 0 to Length(ResNames) - 1 do 
    begin 
    ResBmp := TBitmap.Create(); 
    try 
     { Try to load the bitmap from the resource. } 
     try 
     //ResBmp.LoadFromResourceName(HInstance, ResNames[I]); 
     ResBmp.SetSize(24,16); 

     ResBmp.Transparent := true; 
     except 
     ResBmp.Free(); 
     Result.Free(); 
     Exit; 
     end; 
     Result.Add(ResBmp, nil); 
    finally 
     ResBmp.Free; 
    end; 
    end; 
end; 



// Aowner is ignored for now 
function GetImageList: TImageList; 
begin 
    if _espdateEdit_ImageList = nil then 
    result := MakeImageList(['CalendarDrop', 'CalendarDropShifted']) 
    else 
    result := _espdateEdit_ImageList; 
end; 

//------------------------------------------------------------------------------ 



procedure TfrmDateEditBare1.FormCreate(Sender: TObject); 
begin 
    espDateEdit1:= TespDateEdit.Create(self); 
    espDateEdit1.Parent := self; 
    espDateEdit1.left := 100; 
    espDateEdit1.top := 100; 
    espDateEdit1.Visible := true; 

end; 

//------------------------------------------------------------------------------ 


{ TespMonthCalendar } 

procedure TespMonthCalendar.CMHintShow(var Message: TCMHintShow); 
begin 
    inherited; 
    if Message.HintInfo.HintControl=Self then 
    begin 
    Message.HintInfo.HintPos := self.ClientToScreen(Point(0, self.Height + 1)); 
    Message.HintInfo.HideTimeout := 1000; 
// Message.HintInfo.ReshowTimeout := 1500; // setting this does not help 
    end; 
end; 


procedure TespMonthCalendar.CreateParams(var Params: TCreateParams); 
begin 
    inherited CreateParams(Params); 

    with Params do 
    begin 
    Style := WS_POPUP; 
    WindowClass.Style := WindowClass.Style or CS_SAVEBITS ; 
    if CheckWin32Version(5, 1) then 
     WindowClass.Style := WindowClass.style or CS_DROPSHADOW; 
    end; 
end; 


procedure TespMonthCalendar.CreateWnd; 
begin 
    inherited; 
    // Get/set the dimensions of the calendar 
    SetWindowDIMs; 
end; 


procedure TespMonthCalendar.SetWindowDIMs; 
var 
    ReqRect: TRect; 
    MaxTodayWidth: Integer; 
begin 
    FillChar(ReqRect, SizeOf(TRect), 0); 
    // get required rect 
    Win32Check(MonthCal_GetMinReqRect(Handle, ReqRect)); 
    // get max today string width 
    MaxTodayWidth := MonthCal_GetMaxTodayWidth(Handle); 
    // adjust rect width to fit today string 
    if MaxTodayWidth > ReqRect.Right then 
    ReqRect.Right := MaxTodayWidth; 
    // set new height & width 
    Width := ReqRect.Right ; 
    Height:= ReqRect.Bottom ; 
end; (* SetWindowDIMs *) 




procedure TespMonthCalendar.CNNotify(var Message: TWMNotify); 
begin 
    // hand off control of the selection to the boss i.e. the espDateEdit that I belong to 
    // skip for demo ... just closeup 
    if (Message.NMHdr^.code = MCN_SELECT) then 
    DoCloseUp(self); 
    inherited; 
end; (*CNNotify*) 




procedure TespMonthCalendar.KeyDown(var Key: Word; Shift: TShiftState); 
begin 
    if Key = VK_ESCAPE then 
    begin 
    Key := 0; 
    DoCloseUp(self); 
    end 
    else 
    inherited KeyDown(Key, Shift); 
end; 


procedure TespMonthCalendar.WMActivate(var Msg: TWMActivate); 
begin 
    if (Msg.Active <> WA_INACTIVE) then 
    // tell form to paint itself as though it still has focus (as we are no outside the form with POPUP) 
    SendMessage(screen.ActiveForm.Handle, WM_NCACTIVATE, WPARAM(True), -1) 
    else 
    DoCloseUp(self); 
    inherited; 
end; 




procedure TespMonthCalendar.DoCloseUp(Sender: TObject); 
begin 
    if FDroppedDown then 
    begin 
    FDroppedDown := false; 
    Hide; 
    // put focus back on dateedit so that checking is done if we leave here to go on to another control 
    SendMessage(FManagerHandle, WM_ACTIVATE, WPARAM(True), -1); // less assumptions this way 
    end; 
end; 


//------------------------------------------------------------------------------ 

{ TespDateEdit } 

procedure TespDateEdit.CMHintShow(var Message: TCMHintShow); 
begin 
    inherited; 
    if Message.HintInfo.HintControl=Self then 
    Message.HintInfo.HintPos := self.ClientToScreen(Point(0, 21)); 
end; 


constructor TespDateEdit.Create(AOwner: TComponent); 
begin 
    inherited Create(AOwner); 
    if not(csDesigning in ComponentState) then 
    begin 
    FmonthCalendar := TespMonthCalendar.Create(self); 

    self.hint  := 'DUMMY HINT for Edit Box'; 
    FMonthCalendar.Hint := 'Select required Date,' + ^M^J + 'or ESCape to close the calendar.'; 
    FMonthCalendar.ShowHint := true; 
    end; 

    Width  := 100; 
    Height  := 21; 
    Images  := GetImageList; 
    Text   := ''; // FormatdateTime('dd/mm/yy', Date); // not for demo 
    ShowHint  := True; 

    DoubleBuffered := true; // reduces flicker when passing thru and within control 
    RightButton.ImageIndex  := 0; 
    RightButton.PressedImageIndex := 1; 
    RightButton.Visible   := True; 

    OnRightButtonClick := DoRightButtonClick; 
end; 



procedure TespDateEdit.CreateWnd; 
var 
    P: TWinControl; 
begin 
    inherited CreateWnd; 
    if not(csDesigning in ComponentState) then 
    begin 
    FMonthCalendar.left := -900; 
    P := self.Parent; 
    while (P <> nil) and not (P is TCustomForm) do 
     P := P.parent; 
    FmonthCalendar.Parent  := P; // ie form (or the topmost non nil entry in the tree) 

    FmonthCalendar.FManagerHandle := self.Handle; 
    FMonthCalendar.Hide; 
    FmonthCalendar.OnExit := FmonthCalendar.DoCloseUp; 
    end; 
end; 




procedure TespDateEdit.DoRightButtonClick(Sender: TObject); 
var 
    dt: Tdate; 
    TopLeft: TPoint; 
    Rect: TRect; 
begin 
    if FmonthCalendar.FdroppedDown then 
    begin 
    FMonthCalendar.DoCloseUp(nil); 
    exit; 
    end; 

    // load non-zero date into calendar as the selected date ... skip for demo 

    TopLeft    := self.ClientToScreen(Point(0, 0)); // i.e. screen co-ords of top left of edit box 
    monthCalendar.left := TopLeft.X - 3 ;    // shift a poopsie to line up visually 
    monthCalendar.Top := TopLeft.Y + self.Height - 2; 

    // only move it if it exceeds screen bounds ... skip this for demo 

    FmonthCalendar.FDroppedDown := true; 
    MonthCal_SetCurrentView(FmonthCalendar.handle, MCMV_MONTH); 
    FmonthCalendar.Show; 

    // showing is not enough - need to grab focus to get kbd events happening on the calendar 
    FmonthCalendar.SetFocus; 

    inherited OnRightButtonClick; 
end; 

//------------------------------------------------------------------------------ 

initialization 
finalization 
    FreeAndNil(_espdateEdit_ImageList); 


end. 

は、私が編集ボックスとTMonthCalendarの両方のために別のヒントを追加したいが、私は表示されたヒントは、関連する制御を曖昧にしないことを確実にしたかったです。 編集ボックスでは、CM_HINTSHOWメッセージを正常に傍受し、そのためにHintInfo.HintPosを設定しました。ここまでは順調ですね。

質問1更新:今すぐ表示しています。もともと、私はTCustomHintを使うことができるようにパイプ文字を含めるヒントのテキストを設定しました。パイプ文字を削除するとヒントが表示されました。 BUTこのヒントは自分自身を隠すわけではなく、TmonthCalendarが表示されている間は画面上にとどまります。どうすれば "自己隠す"ことができますか?

質問2:どちらのコントロールにもTCustomHintを使用すると、CMHintShowプロシージャは決して起動しません。ですから、私がTCustomHintを使って余分なコントロールを提供したいのであれば、それがどのようにポジショニング戦略を変更するのでしょうか? (そして、私は "アプリケーション"レベル、例えばOnShowHint経由で何もしたくない - これらのコントロールに固有のものでなければならない)

+1

作業ケースなしで推測するのは難しい... –

+0

@Sertac - 私は完全な機能ユニットを追加しましたこれをデモする。ただし、含まれていないリソースファイルから(編集ボタンイメージ用の)イメージリストのイメージをロードします。 – TomB

+0

心配しなくても、 "LoadFromResourceName"を "ResBmp.SetSize(24、24);"に置き換えてリソースの読み込みをバイパスすることができます。もちろん、あなたはビットマップを漏らしていますが、どんな場合でも素敵な再現があります。 –

答えて

2

ヒントは質問に記載されているように画面は無期限に表示されますが、実際には非表示になるとすぐに表示が再開されます。

その理由は、VCLはヒントコントロールが子ウィンドウであるとみなします。これは、Parentプロパティがnilではないためです。質問のコードの場合、月のカレンダーはポップアップウィンドウになるように変異させての浮動小数点を表示しますが、VCLがそれを知っている限り、その親はまだフォームです。これにより、アプリケーションのActivateHintプロシージャのヒント矩形の計算が正しく行われません。一方、アプリケーションのプロシージャHintMouseMessageは、コントロールがペアレント化されているかどうかは気にしません。次に、コントロール上でマウスポインタを移動しなくても、VCLはマウスポインタを推測してヒントの境界線を離してから再入力します。ここで

は、問題の単純化された複製である:上記のコードで

unit Unit1; 

interface 

uses 
    Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics, 
    Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.ExtCtrls, Vcl.StdCtrls; 

type 
    TPanel = class(vcl.extctrls.TPanel) 
    protected 
    procedure CreateParams(var Params: TCreateParams); override; 
    end; 

    TForm1 = class(TForm) 
    Button1: TButton; 
    Panel1: TPanel; 
    procedure FormCreate(Sender: TObject); 
    end; 

var 
    Form1: TForm1; 

implementation 

{$R *.dfm} 

{ TPanel } 

procedure TPanel.CreateParams(var Params: TCreateParams); 
begin 
    inherited; 
    Params.Style := WS_POPUPWINDOW or WS_THICKFRAME; 
end; 

{ TForm1 } 

procedure TForm1.FormCreate(Sender: TObject); 
begin 
    Button1.Hint := 'Button1'; 
    Panel1.Hint := 'Panel1'; 
    ShowHint := True; 
    Application.HintHidePause := 1000; 
    Left := 0; 
    Top := 0; 
    Panel1.ParentBackground := False; 
    Panel1.Left := 0; 
    Panel1.Height := 50; 
    Panel1.Top := Top + Height; 
end; 

end. 

それが隠された後、それはタイムアウトし、一方のパネルのヒントは、図示し直す際に、ボタンのヒントが非表示になります。ヒントがアクティブになったときにポインタの位置の重要性を観察できるように、ウィンドウを意図的にその位置に配置しました。下からパネルへのマウスポインタを入力すると、ヒントは1回だけ表示され、次に非表示になります。しかし、上からパネルを入力すると、問題が表示されます。

修正は簡単です。CM_HINTSHOWメッセージハンドラでヒント矩形を変更できます。コントロールが浮動しているので、複雑な計算は必要ありません。また、問題のカレンダーを修正により変更された再生の場合は、:

type 
    TPanel = class(vcl.extctrls.TPanel) 
    protected 
    procedure CreateParams(var Params: TCreateParams); override; 
    procedure CMHintShow(var Message: TCMHintShow); message CM_HINTSHOW; 
    end; 

    TForm1 = class(TForm) 
    ... 

{ TPanel } 

procedure TPanel.CMHintShow(var Message: TCMHintShow); 
begin 
    inherited; 
    if (GetAncestor(Handle, GA_ROOT) = Handle) and Assigned(Parent) then 
    Message.HintInfo.CursorRect := Rect(0, 0, Width, Height); 
end; 


質問2については


、カスタムヒントウィンドウには、残念ながら位置可能なように設計されていないようです。ヒントウィンドウはローカルで作成され、それを保持したり、他の方法で位置を指定したりするためのヒントウインドウはありません。私が考えることができる唯一の方法は、ヒントウィンドウをパラメータとして公開するカスタムヒントのペイントメソッドの1つをオーバーライドすることです。したがって、ペイントメッセージを受信するとすぐにヒントウィンドウを再配置することができます。ここで

は(通常の(非浮動)制御用)作業例です:

unit Unit1; 

interface 

uses 
    Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics, 
    Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.ExtCtrls, Vcl.StdCtrls; 

type 
    TForm1 = class(TForm) 
    Button1: TButton; 
    procedure FormCreate(Sender: TObject); 
    end; 

var 
    Form1: TForm1; 

implementation 

{$R *.dfm} 

type 
    TMyCustomHint = class(TCustomHint) 
    private 
    FControl: TControl; 
    public 
    procedure NCPaintHint(HintWindow: TCustomHintWindow; DC: HDC); override; 
    end; 

procedure TMyCustomHint.NCPaintHint(HintWindow: TCustomHintWindow; DC: HDC); 
var 
    Pt: TPoint; 
begin 
    Pt := FControl.ClientToScreen(Point(0, 0)); 
    SetWindowPos(HintWindow.Handle, 0, Pt.X, Pt.Y + FControl.Height, 0, 0, 
     SWP_NOSIZE or SWP_NOZORDER or SWP_NOACTIVATE); 
    inherited; 
end; 

//-------- 

procedure TForm1.FormCreate(Sender: TObject); 
begin 
    ShowHint := True; 
    Button1.Hint := 'button1 hint'; 
    Button1.CustomHint := TMyCustomHint.Create(Self); 
    TMyCustomHint(Button1.CustomHint).FControl := Button1; 
end; 

end. 
+1

ありがとうございます。これは本当に有益です。 – TomB

関連する問題