Как использовать сообщения о фокусе окна для формы экранной клавиатуры Delphi

#forms #delphi #keyboard #focus

#формы #delphi #клавиатура #фокус

Вопрос:

Мне нужна встроенная экранная цифровая клавиатура в моем приложении. По разным причинам я не могу использовать программное обеспечение TMS или другие коммерческие предложения компонентов. Я очень доволен решением на основе кнопок, показанным ниже, но я пока не вижу, как решить проблему с переключением фокуса, когда нажатие кнопки активирует форму клавиатуры, и я теряю сфокусированный элемент управления, в который я хотел поместить символы. Мое решение работает, если я оставлю кнопки клавиатуры в целевой форме, но я хотел бы решение, не зависящее от формы. Есть ли способ отключить активацию кнопки или узнать, откуда пришел фокус, чтобы я мог использовать что-то вроде Scree.ActiveControl :=?? чтобы вернуть его обратно?

введите описание изображения здесь

Комментарии:

1. Для получения дескриптора окна с фокусом клавиатуры попробуйте использовать GetFocus , но как создать несфокусируемое окно, я не могу вам сейчас сказать, но вы можете черпать вдохновение, например, из TRxCalcEdit , у него есть такая всплывающая цифровая панель.

Ответ №1:

Для этой задачи вы можете использовать кнопку 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 вызывает API-функцию CreateWindowEx для создания фактического окна.

Перед выполнением CreateWindowEx вызывается метод CreateParams — CreateParams позволяет вам изменять стиль окна по умолчанию, когда оно создается, в соответствии с вашими конкретными потребностями.

Комментарии:

1. Тем не менее, вы можете сфокусировать окно. Даже если вы включите WM_MOUSEACTIVATE, обработка ваших кнопок получит фокус.

2. Спасибо, но CreateParams, похоже, ничего не делает для меня. Я знаю, почему. Но смотрите мой собственный ответ ниже. Брайан.

Ответ №2:

Я не знаю, как создать окно с рамкой, которая не фокусируется при нажатии на нее, поэтому следующее окно без рамки. И, как упоминал Андреас, используйте 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;
  

Комментарии:

1. И, ИМХО, невозможно создать окно с рамкой, щелчок по рамке которого не фокусирует окно, это было бы довольно запутанно с точки зрения пользователей.

2. Спасибо, вы были правы — сообщение мыши было жизненно важным, CreateParams — нет. Брайан.

Ответ №3:

Мое окончательное решение заключается в следующем. Это создает цифровую панель с рамкой и — да — она активируется, если щелкнуть по границе или изменить ее размер, но нажатие кнопок не отвлекает фокус от целевой формы / элемента управления. Простое использование 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.