0
votes

I see my version of delphi, doesn't have Key Events(OnKeyDown, OnKeyUp, OnKeyPress) events for TPaintBox. I would like to process something like that. Had somebody a paintbox with these events?

2
The TPaintBox is a TGraphicControl descendant and can't get the focus, so it can't even receive keyboard events. So you would have to derive the paint box from another class, TCustomControl can be the right one for you. In other words, let the TPaintBox as it is and look for an alternative control or develop your own from TCustomControl ;)TLama
You could write a keyboard hook as described by Zarko Gajic.LU RD
@TLama You should post that as an answer. Or the answer. :)GolezTrol
@LURD, dirty, dirty way. Please avoid to use the hooks as much as possible. In this case would be enough to set the KeyPreview to True and in form's OnKeyDown etc. events check which one of the paint boxes is "focused"; well, but how it can be focused ?TLama
@user You have asked 14 questions here and accepted only one answer. Please can you review your previous questions and where there is an acceptable answer, accept the best one. Read more about it here: meta.stackexchange.com/questions/5234 and in the faqDavid Heffernan

2 Answers

10
votes

Like TLama said, you'll need to inherit from TCustomControl. But you'll need some additional code to publish all keyboard events. You could choose the easy way and inherit from TPanel since TPanel already exposes a Canvas and a number of keyboard events.

But here's some code to show how to create and register a new control that published the properties of TCustomControl and introduces a new OnPaint event:

If you create a new Package, add this unit, and install it, you will have a new TGTPaintBox control that can have the focus (although you don't see it). It can retrieve keyboard input too.

unit uBigPaintbox;

interface

uses Windows, Classes, Messages, Controls;

type
  TGTPaintBox = class(TCustomControl)
  private
    FOnPaint: TNotifyEvent;
  protected
    // Three methods below are for transparent background. This may not work that great,
    // and if you don't care about it, you can remove them.
    procedure WMEraseBkGnd(var Msg: TWMEraseBkGnd); message WM_ERASEBKGND;
    procedure CreateParams(var Params: TCreateParams); override;
    procedure SetParent(AParent: TWinControl); override;
  protected
    procedure Paint; override;
    procedure MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Integer); override;
  public
    property Canvas;
  published
    // Introduce OnPaint event
    property OnPaint: TNotifyEvent read FOnPaint write FOnPaint;
    // Publish keyboard and mouse events.
    property OnKeyPress;
    property OnKeyDown;
    property OnKeyUp;
    property OnClick;
    property OnDblClick;
    property OnMouseUp;
    property OnMouseDown;
    property OnMouseMove;
    // And some other behavioral property that relate to keyboard input.
    property TabOrder;
    property TabStop;
  end;

procedure Register;

implementation

procedure Register;
begin
  RegisterComponents('GolezTrol', [TGTPaintBox]);
end;

{ TGTPaintBox }

procedure TGTPaintBox.CreateParams(var Params: TCreateParams);
begin
  inherited CreateParams(Params);
  Params.ExStyle := Params.ExStyle or WS_EX_TRANSPARENT;
end;

procedure TGTPaintBox.MouseDown(Button: TMouseButton; Shift: TShiftState; X,
  Y: Integer);
begin
  inherited;

  // Focus the control when it is clicked.
  if not (csDesigning in ComponentState) and CanFocus then
    SetFocus;
end;

procedure TGTPaintBox.Paint;
begin
  inherited;
  // Call paint even if it is assigned.
  if Assigned(FOnPaint) then
    FOnPaint(Self);
end;

procedure TGTPaintBox.SetParent(AParent: TWinControl);
var
  NewStyle: Integer;
begin
  inherited;
  if AParent = nil then
    Exit;

  // Make sure the parent is updated too behind the control.
  NewStyle := GetWindowLong(AParent.Handle, GWL_STYLE) and not WS_CLIPCHILDREN;
  SetWindowLong(AParent.Handle, GWL_STYLE, NewStyle);
end;

procedure TGTPaintBox.WMEraseBkGnd(var Msg: TWMEraseBkGnd);
begin
  SetBkMode(Msg.DC, TRANSPARENT);
  Msg.Result := 1;
end;

end.

I've added some functionality in an attempt to make the control transparent, because a PaintBox is too. One disadvantage is that you need to repaint the parent to clear the previously drawn content. In the demo application this is easy enough. I just invalidate the form instead of the control. :p
If you don't need it, you can remove WMEraseBkGnd, CreateParams and SetParent from the control.

Small demo: Put a label on a form. Put a TGTPaintBox on top of it and make it a bit larger. Then add a timer, and maybe some other controls.

Make sure you set the TabStop property of the GTPaintBox to True.

Then, implement the following events;

// To repaint the lot.
procedure TForm1.Timer1Timer(Sender: TObject);
begin
  Invalidate;
end;

// Capture key input and save the last entered letter in the tag.
procedure TForm1.GTPaintBox1KeyPress(Sender: TObject; var Key: Char);
begin
  if Key in ['a'..'z'] then
    TGTPaintBox(Sender).Tag := Integer(Key);
end;

// Paint the control (this is called every second, when the timer invalidates the form
procedure TForm1.GTPaintBox1Paint(Sender: TObject);
var
  PaintBox: TGTPaintBox;
begin
  PaintBox := TGTPaintBox(Sender);

  // Draw a focus rect too. If you want the control to do this, you would normally
  // implement it in the control itself, and make sure it invalides as soon as it 
  // receives or loses focus.
  if PaintBox.Focused then
    PaintBox.Canvas.DrawFocusRect(PaintBox.Canvas.ClipRect);

  // It just draws the character that we forced into the Tag in the KeyPress event.
  PaintBox.Canvas.TextOut(Random(200), Random(200), Char(PaintBox.Tag));
end;
4
votes

You could also create a frame with the paintbox on it (aligned to alClient) and reuse the frame as needed. TFrame is a windowed control so it has all the keyboard events. They are not published but you can assign them in code.