【问题标题】:How to use window focus messages for a Delphi on-screen keyboard form如何为 Delphi 屏幕键盘表单使用窗口焦点消息
【发布时间】:2011-08-20 02:20:26
【问题描述】:

我的应用程序需要一个内置的屏幕数字小键盘。由于各种原因,我不能使用TMS Software 或其他商业组件产品。我对下面显示的基于按钮的解决方案非常满意,但我还看不到如何解决焦点切换问题,即单击按钮会激活键盘表单,并且我失去了我想要字符的焦点控制。如果我将键盘按钮保留在目标表单中,我的解决方案就可以工作,但我想要一个独立于表单的解决方案。有没有办法禁用按钮激活或知道焦点来自哪里,以便我可以使用类似 Scree.ActiveControl :=??放回去?

【问题讨论】:

  • 要获得具有键盘焦点的窗口的句柄,请尝试使用GetFocus,但是如何创建无法聚焦的窗口我现在不能告诉你,但你可以获得灵感,例如来自TRxCalcEdit,它有这样一个弹出小键盘。

标签: forms delphi keyboard focus


【解决方案1】:

我的最终解决方案如下。这将创建一个带边框的数字键盘,并且 - 是的 - 如果单击或调整边框大小,它会激活,但单击按钮不会从目标表单/控件中窃取焦点。简单地使用 CreateParams 对我不起作用 - 它似乎需要 WMMouseActivate 消息。

我已将它与我发现的将密钥发布到操作系统的例程相结合,而不仅仅是集中控制。请注意,下面的代码假定来自祖先表单的一些简单支持,用于设置默认大小和位置。感谢您的所有帮助。

unit UArtScreenKeyboardForm;

interface

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

type
  TArtScreenKeyboardForm = class(TArtBaseForm)
    procedure FormShow(Sender: TObject);
    procedure FormClose(Sender: TObject; var Action: TCloseAction);
    procedure FormCreate(Sender: TObject);
    procedure WMMouseActivate(var Message: TWMMouseActivate); message WM_MOUSEACTIVATE;
    procedure FormResize(Sender: TObject);
  private
    { Private declarations }
    procedure DoOnbuttonClick(ASender: TObject);
    procedure DrawButtons;
  protected
    procedure SetDefaultSizeAndPosition; override;
  public
    { Public declarations }
  end;



procedure ArtScreenKeyboardForm_Show;
procedure ArtScreenKeyboardForm_Hide;


implementation

{$R *.DFM}

uses
  UArtLibrary;

type
  TButtonKind = (
    bk0,
    bk1,
    bk2,
    bk3,
    bk4,
    bk5,
    bk6,
    bk7,
    bk8,
    bk9,
    bkPlus,
    bkMinus,
    bkDel,
    bkDiv,
    bkMul,
    bkEquals,
    bkDecPt,
    bkEnter );

const
  ButtonCaptions : array[TButtonKind] of string = (
    '0',
    '1',
    '2',
    '3',
    '4',
    '5',
    '6',
    '7',
    '8',
    '9',
    '+',
    '-',
    'Back',
    '/',
    '*',
    '=',
    '.',
    'Enter' );

  ScanCodes : array[TButtonKind] of cardinal = (
    Ord( '0' ),
    Ord( '1' ),
    Ord( '2' ),
    Ord( '3' ),
    Ord( '4' ),
    Ord( '5' ),
    Ord( '6' ),
    Ord( '7' ),
    Ord( '8' ),
    Ord( '9' ),
    VK_ADD,
    VK_SUBTRACT,
    8, {BACKSPACE}
    VK_DIVIDE,
    VK_MULTIPLY,
    Ord( '=' ),
    Ord( '.' ),
    VK_RETURN );


var
  ArtScreenKeyboardForm: TArtScreenKeyboardForm = nil;


procedure PostKeyEx32(key: Word; const shift: TShiftState; specialkey: Boolean) ;
 {
Parameters :
* key : virtual keycode of the key to send. For printable keys this is simply the ANSI code (Ord(character)) .
* shift : state of the modifier keys. This is a set, so you can set several of these keys (shift, control, alt, mouse buttons) in tandem. The TShiftState type is declared in the Classes Unit.
* specialkey: normally this should be False. Set it to True to specify a key on the numeric keypad, for example.

Description:
Uses keybd_event to manufacture a series of key events matching the passed parameters. The events go to the control with focus. Note that for characters key is always the upper-case version of the character. Sending without any modifier keys will result in a lower-case character, sending it with [ ssShift ] will result in an upper-case character!
}
type
  TShiftKeyInfo = record
    shift: Byte ;
    vkey: Byte ;
  end;

  ByteSet = set of 0..7 ;

const
  shiftkeys: array [1..3] of TShiftKeyInfo =
    ((shift: Ord(ssCtrl) ; vkey: VK_CONTROL),
    (shift: Ord(ssShift) ; vkey: VK_SHIFT),
    (shift: Ord(ssAlt) ; vkey: VK_MENU)) ;
var
  flag: DWORD;
  bShift: ByteSet absolute shift;
  j: Integer;
begin
  for j := 1 to 3 do
  begin
    if shiftkeys[j].shift in bShift then
      keybd_event(shiftkeys[j].vkey, MapVirtualKey(shiftkeys[j].vkey, 0), 0, 0) ;
  end;
  if specialkey then
    flag := KEYEVENTF_EXTENDEDKEY
  else
    flag := 0;

 keybd_event(key, MapvirtualKey(key, 0), flag, 0) ;
  flag := flag or KEYEVENTF_KEYUP;
  keybd_event(key, MapvirtualKey(key, 0), flag, 0) ;

 for j := 3 downto 1 do
  begin
    if shiftkeys[j].shift in bShift then
      keybd_event(shiftkeys[j].vkey, MapVirtualKey(shiftkeys[j].vkey, 0), KEYEVENTF_KEYUP, 0) ;
  end;
end;




procedure TArtScreenKeyboardForm.DoOnbuttonClick(ASender: TObject);
var
  Btn : TSpeedButton;
  Kind : TButtonKind;
begin
  Btn := ASender as TSpeedButton;
  Kind := TButtonKind(StrToIntDef( Copy( Btn.Name, 4, MaxStrLen ), 0 ));
  PostKeyEx32( ScanCodes[Kind], [], False );

  // As suggested also:
  //PostMessage(GetFocus, WM_KEYDOWN, Ord('A'), 0 );
  // PostMessage(GetFocus, WM_KEYDOWN, VK_NUMPAD1, MakeLong(0, MapVirtualKey(VK_NUMPAD1, 0)));



end;



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

procedure ArtScreenKeyboardForm_Show;
begin
  If ArtScreenKeyboardForm = nil then
    begin
    ArtScreenKeyboardForm := TArtScreenKeyboardForm.Create( Application );
    ArtScreenKeyboardForm.Show;
    end;

 Application.ProcessMessages;
end;





procedure ArtScreenKeyboardForm_Hide;
begin
 If ArtScreenKeyboardForm <> nil then
   begin
   ArtScreenKeyboardForm.Free;
   ArtScreenKeyboardForm := nil;
   end;
end;


procedure TArtScreenKeyboardForm.FormShow(Sender: TObject);
begin
  DrawButtons;
end;

procedure TArtScreenKeyboardForm.SetDefaultSizeAndPosition;
begin
  inherited;
  Width := 300;
  PlaceControl( Self, cpWorkAreaTopLeft );
end;

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


procedure TArtScreenKeyboardForm.FormCreate(Sender: TObject);
begin
  Constraints.MinWidth  := 200;
  Constraints.MinHeight := (120 * 5)  div 4;
end;


procedure TArtScreenKeyboardForm.DrawButtons;

  procedure AddButton( ATop, ALeft, AWidth, AHeight : integer; AKind : TButtonKind );

    function WidthPix( AValue : integer ) : integer;
    begin
      Result := AValue * (ClientWidth div 4);
    end;

    function HeightPix( AValue : integer ) : integer;
    begin
      Result := AValue * (ClientHeight div 5);
    end;

  var
    Button : TSpeedButton;
  begin
    Button := TSpeedButton.Create( Self );
    Button.Parent := Self;

    Button.Left := WidthPix( ALeft );
    Button.Top  := HeightPix( ATop );
    Button.Width := WidthPix( AWidth );
    Button.Height := HeightPix( AHeight );
    Button.Visible := True;

    Button.Name := Format( 'btn%d', [Ord( AKind )] );
    Button.Caption := ButtonCaptions[ AKind ];

    button.OnClick := DoOnbuttonClick;
  end;



var
  I : integer;
begin
  Height := (Width * 5) div 4;

  ApplyScreenIconTitleFontToFont( Font );

  Font.Size := Font.Size + ((Height-250) div 30);

  Font.Style := Font.Style + [fsBold];
  Font.Color := clGray;

  For I := ComponentCount-1 downto 0 do
    If Components[I] is TSpeedButton then
      Components[I].Free;

  Addbutton( 0, 0, 1, 1, bkDel      );
  Addbutton( 0, 1, 1, 1, bkEquals   );
  Addbutton( 0, 2, 1, 1, bkDiv      );
  Addbutton( 0, 3, 1, 1, bkMul      );

  Addbutton( 1, 0, 1, 1, bk7        );
  Addbutton( 1, 1, 1, 1, bk8        );
  Addbutton( 1, 2, 1, 1, bk9        );
  Addbutton( 1, 3, 1, 1, bkMinus    );

  Addbutton( 2, 0, 1, 1, bk4        );
  Addbutton( 2, 1, 1, 1, bk5        );
  Addbutton( 2, 2, 1, 1, bk6        );
  Addbutton( 2, 3, 1, 1, bkPlus     );

  Addbutton( 3, 0, 1, 1, bk1        );
  Addbutton( 3, 1, 1, 1, bk2        );
  Addbutton( 3, 2, 1, 1, bk3        );
  Addbutton( 3, 3, 1, 2, bkEnter    );

  Addbutton( 4, 0, 2, 1, bk0        );
  Addbutton( 4, 2, 1, 1, bkDecPt    );
end;

procedure TArtScreenKeyboardForm.FormResize(Sender: TObject);
begin
  DrawButtons;
end;

initialization
finalization
  FreeAndNil( ArtScreenKeyboardForm );
end.

【讨论】:

    【解决方案2】:

    我不知道如何用单击它时无法聚焦的框架创建窗口,所以下面的一个是没有边框的。正如 Andreas 所说,使用 TSpeedButtons。

    type
      TKeypadForm = class(TForm)
        SpeedButton1: TSpeedButton;
        procedure SpeedButton1Click(Sender: TObject);
      private
        procedure CreateParams(var Params: TCreateParams); override;
        procedure WMMouseActivate(var Message: TWMMouseActivate); message WM_MOUSEACTIVATE;
      end;
    
    procedure TKeypadForm.CreateParams(var Params: TCreateParams);
    begin
      inherited CreateParams(Params);
      Params.Style := WS_POPUP or WS_THICKFRAME;
    end;
    
    procedure TKeypadForm.WMMouseActivate(var Message: TWMMouseActivate);
    begin
      Message.Result := MA_NOACTIVATE;
    end;
    
    procedure TKeypadForm.SpeedButton1Click(Sender: TObject);
    begin
      PostMessage(GetFocus, WM_KEYDOWN, VK_NUMPAD1, MakeLong(0, MapVirtualKey(VK_NUMPAD1, 0)));
    end;
    

    这是显示键盘窗口的方法

    procedure TForm18.Edit1KeyDown(Sender: TObject; var Key: Word;
      Shift: TShiftState);
    begin
      case Key of
        VK_RETURN: ShowWindow(KeypadForm.Handle, SW_SHOWNOACTIVATE);
        VK_ESCAPE: ShowWindow(KeypadForm.Handle, SW_HIDE);
      end;
    end;
    

    【讨论】:

    • 而且恕我直言,不可能创建框架点击不聚焦窗口的框架窗口,从用户的角度来看这会很混乱。
    • 谢谢,你是对的 - 鼠标消息很重要,CreateParams 不是。布赖恩。
    【解决方案3】:

    您可以在键盘上使用 TSpeedButton 来执行此任务。 TSpeedButton 不会强化焦点。但是表格可以。这很丑陋,即使您将焦点放回主窗体,焦点也会在两个窗体之间闪烁。所以我会尝试创建一个没有焦点的表单。

    一个名为 WS_EX_NOACTIVATE 的标志可用于创建一个窗口(窗体),当用户单击它时它不会成​​为前台窗口。此外,当用户最小化或关闭前台窗口时,系统不会将此窗口置于前台。

    要创建不可激活的表单,请将 CreateParams 方法重写为:

    procedure TMainForm.CreateParams(var Params: TCreateParams) ;
     //const WS_EX_NOACTIVATE = $8000000;
     begin
       inherited;
       Params.ExStyle := Params.ExStyle + WS_EX_NOACTIVATE;
     end;
    

    Delphi 创建窗体时,Create 方法调用 CreateWindowEx API 函数来创建实际的窗口。

    在执行 CreateWindowEx 之前,会调用 CreateParams 方法 - CreateParams 允许您在创建窗口时更改其默认样式以满足您的特定需求。

    【讨论】:

    • 仍然可以聚焦窗口。即使您将包含 WM_MOUSEACTIVATE 处理,您的按钮也会获得焦点。
    • 谢谢,但 CreateParams 似乎对我没有任何帮助。我字体知道为什么。但请参阅下面我自己的答案。布赖恩。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2022-06-14
    • 1970-01-01
    • 2011-08-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多