Этот вопрос относится к другому вопросу, который я недавно задал здесь , но гораздо более конкретно о том, что мне нужно, чем мой последний вопрос, так как предыдущий я не знал, как объяснить, или даже то, что я хотел делаем.
У меня есть пользовательский элемент управления, который я создаю, он выглядит (и в конечном итоге будет работать) аналогично тому, как работает панель задач Windows. Она имеет кнопку главного меню в крайнем левом углу и динамическое количество других кнопок, выравнивающих элемент управления. Это можно рассматривать как какой-то элемент управления списком, просто с 1 дополнительной кнопкой (меню). Кнопку меню считаю индексом -1, а первая динамическая кнопка имеет индекс 0.
Честно говоря, есть 3 проблемы, с которыми я сталкиваюсь (как указано ниже), и все они являются результатом того факта, что я никогда еще не писал элемент управления, достаточно продвинутый.
- Введение нажатий клавиш (перехват сообщений клавиатуры), чтобы позволить пользователю перемещаться по элементам в этом элементе управления - Обработчик сообщений при нажатии клавиши никогда не срабатывает.
- Поскольку я буду ловить клавишу табуляции и использовать ее для перемещения по элементам в моем элементе управления, как передать фокус следующему / последнему элементу управления родителя элемента управления (в порядке табуляции), когда пользователь достигнет конца (или начало при использовании Shift + Tab)?
- Как я уже говорил, я никогда не продвигался вперед с контролем, и я хотел бы убедиться, что у меня хорошее начало. Видите ли вы другие исправления в моем коде, которые необходимо сделать? На всякий случай, если вы обнаружите что-то необычное в моем коде.
Я думал, что задам все 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.