Ловить нажатия клавиш для перемещения элементов, нарисованных вручную, внутри пользовательского элемента управления? - PullRequest
3 голосов
/ 05 января 2012

Этот вопрос относится к другому вопросу, который я недавно задал здесь , но гораздо более конкретно о том, что мне нужно, чем мой последний вопрос, так как предыдущий я не знал, как объяснить, или даже то, что я хотел делаем.

У меня есть пользовательский элемент управления, который я создаю, он выглядит (и в конечном итоге будет работать) аналогично тому, как работает панель задач Windows. Она имеет кнопку главного меню в крайнем левом углу и динамическое количество других кнопок, выравнивающих элемент управления. Это можно рассматривать как какой-то элемент управления списком, просто с 1 дополнительной кнопкой (меню). Кнопку меню считаю индексом -1, а первая динамическая кнопка имеет индекс 0.

Честно говоря, есть 3 проблемы, с которыми я сталкиваюсь (как указано ниже), и все они являются результатом того факта, что я никогда еще не писал элемент управления, достаточно продвинутый.

  1. Введение нажатий клавиш (перехват сообщений клавиатуры), чтобы позволить пользователю перемещаться по элементам в этом элементе управления - Обработчик сообщений при нажатии клавиши никогда не срабатывает.
  2. Поскольку я буду ловить клавишу табуляции и использовать ее для перемещения по элементам в моем элементе управления, как передать фокус следующему / последнему элементу управления родителя элемента управления (в порядке табуляции), когда пользователь достигнет конца (или начало при использовании Shift + Tab)?
  3. Как я уже говорил, я никогда не продвигался вперед с контролем, и я хотел бы убедиться, что у меня хорошее начало. Видите ли вы другие исправления в моем коде, которые необходимо сделать? На всякий случай, если вы обнаружите что-то необычное в моем коде.

Я думал, что задам все 3 вопроса в одном, потому что они все связаны с тем же модулем, который я публикую ниже ...

Компонент TJDTaskbar

unit JDTaskbar;

interface

uses
  Classes, Windows, SysUtils, Controls, StdCtrls, ExtCtrls, StrUtils,
  Graphics, Forms, Messages;

type
  TJDTaskbar = class;
  TJDTaskbarItem = class;
  TJDTaskbarItems = class;

  TJDTaskHandle = Integer;  //Future use
  TFocusIndex = -1..MaxInt; //Range of possible indexes in list

  //Mimics the Windows taskbar for managing forms in an application
  //Main component
  TJDTaskbar = class(TCustomControl)
  private
    FButtonColor: TColor;
    FItems: TJDTaskbarItems;
    FButtonHover: TColor;
    FButtonWidth: Integer;
    FButtonText: TCaption;
    FButtonCaption: TCaption;
    FButtonFont: TFont;
    FFocusIndex: TFocusIndex;
    function GetColor: TColor;
    procedure SetButtonColor(const Value: TColor);
    procedure SetColor(const Value: TColor);
    procedure SetButtonHover(const Value: TColor);
    procedure ItemEvent(Sender: TObject);
    procedure SetButtonWidth(const Value: Integer);
    procedure SetButtonText(const Value: TCaption);
    procedure SetButtonCaption(const Value: TCaption);
    procedure SetButtonFont(const Value: TFont);
    procedure ButtonFontEvent(Sender: TObject);   
    procedure WMSetFocus(var Message: TWMSetFocus); message WM_SETFOCUS;
    procedure WMKillFocus(var Message: TWMSetFocus); message WM_KILLFOCUS; 
    procedure WMNCHitTest(var Message: TWMNCHitTest); message WM_NCHITTEST;
    procedure CMEnter(var Message: TCMEnter); message CM_ENTER;
    procedure CMExit(var Message: TCMExit); message CM_EXIT;
    procedure WMKeyDown(var Message: TWMKeyDown); message WM_KEYDOWN;
    procedure WMKeyUp(var Message: TWMKeyUp); message WM_KEYUP;
  protected
    procedure Paint; override;
    procedure WMGetDlgCode(var Msg: TMessage); message WM_GETDLGCODE;
    procedure KeyDown(var Key: Word; Shift: TShiftState); override;
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
    function GetItemSize: Integer;
    function NewTask(AForm: TForm): TJDTaskbarItem;
    function ButtonRect: TRect;
    function ItemRect(const Index: Integer): TRect;
    procedure MoveFocus(const StepBy: Integer);
    property Items: TJDTaskbarItems read FItems;
  published
    property Align;
    property Anchors;
    property ButtonCaption: TCaption read FButtonCaption write SetButtonCaption;
    property ButtonFont: TFont read FButtonFont write SetButtonFont;
    property Color: TColor read GetColor write SetColor;
    property ButtonColor: TColor read FButtonColor write SetButtonColor;
    property ButtonHover: TColor read FButtonHover write SetButtonHover;
    property ButtonWidth: Integer read FButtonWidth write SetButtonWidth;
    property ButtonText: TCaption read FButtonText write SetButtonText;
    property Visible;
  end;

  TJDTaskbarItems = class(TObject)
  private
    FLastHandle: TJDTaskHandle;
    FItems: TStringList;
    FOwner: TJDTaskbar;
    FOnEvent: TNotifyEvent;
    procedure Event;
    function GetItem(Index: Integer): TJDTaskbarItem;
    function NewHandle: TJDTaskHandle;
    procedure SetItem(Index: Integer; const Value: TJDTaskbarItem);
  public
    constructor Create(AOwner: TJDTaskbar);
    destructor Destroy; override;
    function Count: Integer;
    function Add(AForm: TForm): TJDTaskbarItem;
    procedure Delete(const Index: Integer);
    procedure Clear;
    property Items[Index: Integer]: TJDTaskbarItem read GetItem write SetItem; default;
  published
    property OnEvent: TNotifyEvent read FOnEvent write FOnEvent;
  end;

  TJDTaskbarItem = class(TObject)
  private
    FForm: TForm;
    FOwner: TJDTaskbarItems;
    FPinned: Bool;
    FCaption: TCaption;
    FOnEvent: TNotifyEvent;
    FHandle: TJDTaskHandle;
    procedure SetCaption(const Value: TCaption);
    procedure SetPinned(const Value: Bool);
    procedure Event;
  public
    constructor Create(AOwner: TJDTaskbarItems; AForm: TForm; AHandle: TJDTaskHandle);
    destructor Destroy; override;
    property Form: TForm read FForm;
    property Handle: TJDTaskHandle read FHandle;
  published
    property Pinned: Bool read FPinned write SetPinned;
    property Caption: TCaption read FCaption write SetCaption;
    property OnEvent: TNotifyEvent read FOnEvent write FOnEvent;
  end;

procedure Register;

implementation

procedure Register;
begin
  RegisterComponents('JD Custom', [TJDTaskbar]);
end;

{ TJDTaskbar }

constructor TJDTaskbar.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  TabStop:= True;
  ControlStyle:= ControlStyle + [csCaptureMouse,csClickEvents];
  FButtonFont:= TFont.Create;
  FButtonFont.OnChange:= ButtonFontEvent;
  FButtonCaption:= 'Menu';
  FButtonFont.Color:= clWhite;
  FButtonFont.Size:= 12;
  FButtonFont.Style:= [fsBold];
  Parent:= TWinControl(AOwner);
  FItems:= TJDTaskbarItems.Create(Self);
  FItems.OnEvent:= ItemEvent;
  inherited Color:= clNavy;
  FButtonColor:= clNavy;
  FButtonHover:= clBlue;
  FButtonWidth:= 80;
  FFocusIndex:= -1;
  Invalidate;
end;

destructor TJDTaskbar.Destroy;
begin
  FButtonFont.Free;
  FItems.Free;
  inherited;
end;

function TJDTaskbar.GetColor: TColor;
begin
  Result:= inherited Color;
end;

function TJDTaskbar.GetItemSize: Integer;
begin
  Result:= ClientHeight - 4;
end;

procedure TJDTaskbar.ItemEvent(Sender: TObject);
begin
  Invalidate;
end;

procedure TJDTaskbar.Paint;
var
  C: TCanvas;   //Canvas to work on
  Br: TBrush;   //Canvas brush
  Pn: TPen;     //Canvas pen
  R: TRect;     //Cliprect of taskbar
  X: Integer;   //Loop index
  L: Integer;   //Running left position
  BS: Integer;  //Item width/height
  MG: Integer;  //Margin between buttons
  BTR: TRect;   //Button rect
  I: TJDTaskbarItem;  //Temp item in loop
begin
  //Prepare Variables
  C:= Self.Canvas;
  R:= C.ClipRect;
  Br:= C.Brush;
  Pn:= C.Pen;
  BS:= GetItemSize;
  MG:= 3;
  L:= FButtonWidth + 2 + MG;

  //Draw taskbar background
  Br.Style:= bsSolid;
  Pn.Style:= psClear;
  Br.Color:= Color;
  C.FillRect(R);

  //Draw main menu button   
  Br.Style:= bsSolid;
  Pn.Style:= psSolid;
  if (Focused) and (FFocusIndex = -1) then begin
    Br.Color:= FButtonColor;
    Pn.Color:= clGray;
  end else begin
    Br.Color:= FButtonColor;
    Pn.Color:= clBlack;
  end;
  C.RoundRect(2, 2, FButtonWidth + 2, ClientHeight - 2, 4, 4);
  //Text
  BTR:= Rect(4, 4, FButtonWidth, ClientHeight - 4);
  C.Font.Assign(FButtonFont);
  DrawText(C.Handle, PChar(FButtonCaption), Length(FButtonCaption), BTR,
    DT_CENTER   or DT_VCENTER);


  //Draw taskbar icons  
  if (Focused) and (FFocusIndex >= 0) then begin
    Br.Color:= FButtonColor;
    Pn.Color:= clGray;
  end else begin
    Br.Color:= FButtonColor;
    Pn.Color:= clBlack;
  end;
  for X:= 0 to FItems.Count - 1 do begin
    I:= FItems[X];
    R:= ItemRect(X);
    C.RoundRect(R.Left, R.Top, R.Right, R.Bottom, 4, 4);
    L:= L + BS + MG;
  end;

end;

procedure TJDTaskbar.SetButtonColor(const Value: TColor);
begin
  if Value <> FButtonColor then begin
    FButtonColor := Value;
    Invalidate;
  end;
end;

procedure TJDTaskbar.SetButtonHover(const Value: TColor);
begin
  if Value <> FButtonHover then begin
    FButtonHover := Value;
    Invalidate;
  end;
end;

procedure TJDTaskbar.SetButtonText(const Value: TCaption);
begin
  if Value <> FButtonText then begin
    FButtonText := Value;
    Invalidate;
  end;
end;

procedure TJDTaskbar.SetButtonWidth(const Value: Integer);
begin
  if Value <> FButtonWidth then begin
    FButtonWidth := Value;
    Invalidate;
  end;
end;

procedure TJDTaskbar.SetButtonCaption(const Value: TCaption);
begin
  if Value <> FButtonCaption then begin
    FButtonCaption := Value;
    Invalidate;
  end;
end;

procedure TJDTaskbar.SetColor(const Value: TColor);
begin
  if Value <> inherited Color then begin
    inherited Color:= Value;
    Invalidate;
  end;
end;

procedure TJDTaskbar.SetButtonFont(const Value: TFont);
begin
  FButtonFont.Assign(Value);
end;

procedure TJDTaskbar.ButtonFontEvent(Sender: TObject);
begin
  Invalidate;
end;

function TJDTaskbar.NewTask(AForm: TForm): TJDTaskbarItem;
begin
  Result:= FItems.Add(AForm);
end;

function InRect(const Point: TPoint; const Rect: TRect): Bool;
begin
  Result:= (Point.X >= Rect.Left) and (Point.X <= Rect.Right)
    and (Point.Y >= Rect.Top) and (Point.Y <= Rect.Bottom);
end;

procedure TJDTaskbar.WMKillFocus(var Message: TWMSetFocus);
begin
  Invalidate;
end;

procedure TJDTaskbar.WMSetFocus(var Message: TWMSetFocus);
begin
  Invalidate;
end;

//I know this procedure is a weird mess, plan to clean it up
procedure TJDTaskbar.WMNCHitTest(var Message: TWMNCHitTest);
var
  P: TPoint;
  CR: TCursor;
  X: Integer;
  DI: Bool;
begin    
  DI:= True;
  CR:= crDefault;
  with Message do begin
    if (csDesigning in ComponentState) and (Parent <> nil) then begin
      Result := HTCLIENT;
    end else begin
      P:= Point(Message.XPos, Message.YPos);
      P:= Self.ScreenToClient(P);
      if InRect(P, ButtonRect) then begin    
        DI:= False;
        Result:= HTCLIENT;
        FFocusIndex:= -1;
        CR:= crHandPoint;
      end else begin
        for X:= 0 to FItems.Count - 1 do begin
          if InRect(P, ItemRect(X)) then begin
            DI:= False;
            Result:= HTCLIENT;
            FFocusIndex:= X;
            CR:= crHandPoint;
            Break;
          end;
        end;
      end;
    end;
  end;   
  if DI then begin
    inherited;
  end;
  if CR <> Cursor then begin
    Cursor:= CR;
  end;
end;

function TJDTaskbar.ButtonRect: TRect;
begin
  Result:= Rect(
    2,
    2,
    FButtonWidth + 2,
    GetItemSize + 2
  );
end;

function TJDTaskbar.ItemRect(const Index: Integer): TRect;
var
  Z: Integer;
begin
  Z:= GetItemSize;
  Result.Top:= 2;
  Result.Bottom:= Z + 2;
  Result.Left:= FButtonWidth + 4 + ((Z + 2) * Index);
  Result.Right:= Result.Left + Z;
end;

procedure TJDTaskbar.CMEnter(var Message: TCMEnter);
begin
  //Haven't tried yet
end;

procedure TJDTaskbar.CMExit(var Message: TCMExit);
begin
  //Haven't tried yet
end;

//Why doesn't this ever trigger?
procedure TJDTaskbar.WMKeyDown(var Message: TWMKeyDown);
begin
  //I tried handling it here but a few issues, including it never triggered
  //and how do I determine shift state?
end;

procedure TJDTaskbar.WMKeyUp(var Message: TWMKeyUp);
begin
  //Haven't tried yet
end;

procedure TJDTaskbar.WMGetDlgCode(var Msg: TMessage);
begin
  inherited;
  Msg.Result:= Msg.Result or DLGC_WANTTAB;
end;

//Why doesn't this ever trigger either?
procedure TJDTaskbar.KeyDown(var Key: Word; Shift: TShiftState);
begin   
  case Key of
    VK_TAB: begin
      if(ssShift in Shift)then begin
        if FFocusIndex = -1 then begin
          //Go to prior control?
        end else begin
          //Go back a space
          MoveFocus(-1);
        end;
      end else begin
        if FFocusIndex >= FItems.Count - 1 then begin
          //Go to next control?
        end else begin
          //Go forward a space
          MoveFocus(1);
        end;
      end;
    end;
    VK_LEFT: begin
      MoveFocus(-1);
    end;
    VK_RIGHT: begin
      MoveFocus(1);
    end;
    VK_UP: begin
      MoveFocus(-1);
    end;
    VK_DOWN: begin
      MoveFocus(1);
    end;
    VK_RETURN: begin
      //Future use
    end;
    else inherited;
  end;
  Invalidate;
end;

//Moves +/- in internal focus      //1 or -1
procedure TJDTaskbar.MoveFocus(const StepBy: Integer);
var
  R: Integer;
begin
  if (FFocusIndex = -1) and (StepBy < 0) then
    FFocusIndex:= FItems.Count - 1
  else if (FFocusIndex >= FItems.Count - 1) then
    FFocusIndex:= -1
  else begin
    R:= FFocusIndex + StepBy;
    if R < -1 then R:= -1;
    if R > FItems.Count - 1 then R:= FItems.Count - 1;
    FFocusIndex:= R;
  end;
  Invalidate;
end;

{ TJDTaskbarItems }

constructor TJDTaskbarItems.Create(AOwner: TJDTaskbar);
begin
  FOwner:= AOwner;
  FItems:= TStringList.Create;
end;

destructor TJDTaskbarItems.Destroy;
begin
  Clear;
  FItems.Free;
  inherited;
end;

function TJDTaskbarItems.Add(AForm: TForm): TJDTaskbarItem;
var
  S: String;
  H: TJDTaskHandle;
begin
  S:= 'New Taskbar Item';
  H:= Self.NewHandle;
  Result:= TJDTaskbarItem.Create(Self, AForm, H);
  FItems.AddObject(S, Result);
end;

function TJDTaskbarItems.Count: Integer;
begin
  Result:= FItems.Count;
end;

procedure TJDTaskbarItems.Event;
begin
  if assigned(FOnEvent) then FOnEvent(Self);
end;

procedure TJDTaskbarItems.Clear;
begin
  while FItems.Count > 0 do
    Delete(0);
end;

procedure TJDTaskbarItems.Delete(const Index: Integer);
begin
  if (Index >= 0) and (Index < FItems.Count) then begin
    TJDTaskbarItem(FItems.Objects[Index]).Free;
    FItems.Delete(Index);
  end else begin

  end;
end;

function TJDTaskbarItems.GetItem(Index: Integer): TJDTaskbarItem;
begin
  if (Index >= 0) and (Index < FItems.Count) then begin
    Result:= TJDTaskbarItem(FItems.Objects[Index]);
  end else begin

  end;
end;

procedure TJDTaskbarItems.SetItem(Index: Integer;
  const Value: TJDTaskbarItem);
begin
  if (Index >= 0) and (Index < FItems.Count) then begin
    FItems.Objects[Index]:= Value;
  end else begin

  end;
end;

function TJDTaskbarItems.NewHandle: TJDTaskHandle;
begin
  FLastHandle:= FLastHandle + 1;
  Result:= FLastHandle;
end;

{ TJDTaskbarItem }

constructor TJDTaskbarItem.Create(AOwner: TJDTaskbarItems; AForm: TForm; 
  AHandle: TJDTaskHandle);
begin
  FOwner:= AOwner;
  FForm:= AForm;
  FHandle:= AHandle;
end;

destructor TJDTaskbarItem.Destroy;
begin
  inherited;
end;

procedure TJDTaskbarItem.Event;
begin
  if assigned(FOnEvent) then FOnEvent(Self);
end;

procedure TJDTaskbarItem.SetCaption(const Value: TCaption);
begin
  if Value <> FCaption then begin
    FCaption := Value;
    Event;
  end;
end;

procedure TJDTaskbarItem.SetPinned(const Value: Bool);
begin
  if Value <> FPinned then begin
    FPinned := Value;
    Event;
  end;
end;

end.

Sample

Вот пример использования этой панели задач. В моем тестовом проекте я пока что создаю его динамически, а не публикую на своем поддоне. Хотя процедура регистрации там есть.

unit uTaskMain;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, JDTaskbar, ExtCtrls, StdCtrls, Buttons;

type
  TForm1 = class(TForm)
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
  private
    FTaskbar: TJDTaskbar;
  public
    property Taskbar: TJDTaskbar read FTaskbar;
  end;

var
  Form1: TForm1;

implementation

//Form2 is in Unit2
uses Unit2;

{$R *.dfm}

procedure TForm1.FormCreate(Sender: TObject);
var
  T: TJDTaskbarItem;
begin
  FTaskbar:= TJDTaskbar.Create(nil);
  FTaskbar.Parent:= Self;
  FTaskbar.Align:= alBottom;
  FTaskbar.Color:= clBlue;
  FTaskbar.Height:= 26;
  //Mimic adding a few icons to taskbar using "Form2"
  T:= FTaskbar.NewTask(Form2);
  T:= FTaskbar.NewTask(Form2);
  T:= FTaskbar.NewTask(Form2);
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin           
  FTaskbar.Free;
end;

end.

1 Ответ

5 голосов
/ 05 января 2012
Фреймворк

VCL имеет свои собственные способы обработки ключей, цикл обработки сообщений приложения пересылает сообщения ключей с константами CN_... Так, например, вместо:

procedure WMKeyDown(var Message: TWMKeyDown); message WM_KEYDOWN;

вы бы перехватили CN_KEYDOWN:

procedure CNKeyDown(var Message: TWMKeyDown); message CN_KEYDOWN;


Для пункта (2) вы можете использовать FindNextControl (или даже лучше SelectNext :)) формы.

Кроме того, вы, вероятно, захотите вызвать inherited в своих обработчиках сообщений.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...