Link to home
Start Free TrialLog in
Avatar of mdlittle
mdlittle

asked on

How to add events / properties to a TImage control

I want to add the following events to a TImage control and have never done it before:

OnKeyDown
OnKeyPress
OnKeyUp

and the following properties:

TabStop
TabOrder

Code sample please.
Avatar of msa2003
msa2003

Strange question... TImage is not a control and it could never receive focus for user input. This meand that you could never implement such properties with TImage.
Please explain in details what task you are working with and I'll try to provide a solution...
Avatar of mdlittle

ASKER

OK, I have an image button that I like and works fine. However, my application will use the keyboard more than the mouse and I can not "tab" to this image button or use the enter key to simulate a mouse click.

If you have a solution (keep in mind I have 40 or 50 of these buttons in my application), I will award you the points.

What about the OnKey events?
You should use another component instead of TImage if you wont to handle keyboard events. I'll explore this problem and tell you when I'll get some results. It is interesting for me.
A quick solution. Use TBitBtn instead of TImage. Image must be placed in Glyph property. Define the size of an image such way that it could be larger than the button at least by 1 pixel (have an invisible margin). The button will look like a single image and a color found in the lower left corner if an image will be transparent (it is why the picture must be larger than a button). If you don't wont the picture to be transparent you must use a different color to a border.

Another way is to place a button on the form and set it's Left property to negative value. This will lead the button to hide from user, but it remains active and still could process user input. The next you should specify button's TabOrder and TabStop properties and OnKeyDown, OnKeyPress, OnKeyUp as if it were the image. Also process OnEnter event to make your TImage display a focus (simply using DrawFocusRect(Rect(2, 2, Image1.Width - 2, Image1.Height - 2)) method or another way) and OnExit event to hide focus (may be also DrawFocusRect(Rect(2, 2, Image1.Width - 2, Image1.Height - 2))).
hi, I haven't tried this before but maybe is possible to this situation: try and create a custom component deriving from TImage and then add the TControl events and properties that you want.  I am sorry for not being more specific but I haven't tried this before, and I don't know if it will work. I'll try and find some time to try it out and let you know.

good luck
The sample Delphi 6 code is here: http://www.serge.dsip.net/downloads/image-button.zip

You could also ask me to re-make it to Delphi 4 (this will take a time).
ysd: Sorry, you are not right. It would be ideal to create a component deriving from both TImage and TControl, but Delphi does not allow multiple inheritance. Another way is to create TImage descendant (component) and implement low-level windows API functions to represent a windowed control. But it is too complex. I suppose that linking TImage to invisible button like I did in my example is an easiest way.

P. S. This idea could be also implemented as component. But now I have no enough time to realize it. Maybe, later...
Are you just working with a TImage or is it some other sort of control?  The problem is a TImage (and other non TWinControls) cannot get focus.  (BTW msa2003 TImage apready descends from TControl) I could really easily write you one that could if you like, but if you are using some other type of control (beyond TImage) then that wouldn't help.  

A work around you can do without creating descended controls would be to put the TImage on a TPanel and then hide the borders.  Then place an TEdit (use Edit / Send to Back) under the TPanel.  The reason for the TPanel is your cannot place an image on top of a TWinControl.  Now code the TEdit to handle the keyboard events.  Since it is behind the TPanel it will never get seen.  I would make the image change (maybe put the borders on the TPanel) when the TEdit has focus so the user will know where their cursor is.

That should provide you with the functionality you need and the flexability to use any control for your image rendering you want.  If you want a TWinControl descendant Image control that can accept focus, let me know.
JimMcKeeth: your solution is too complex. First, uf the image plays a role of button, maybe it is better to use TButton as an event handler (just like I realized in http://www.serge.dsip.net/downloads/image-button.zip ). Next, my example TImage IS NOT a TWinControl descendant, it just uses TButton component as an event handler, no more (a small trick ;) ). Finally, there is no matter where an event handler component (either TButton or TEdit, or any other TWinControl descendant) is located within the form. But if you will hide it with a TPanel component it will be harder to move/resize it in design-time.
I suggest you to use TGraphicControl.
Hi ginsonic, I'm back at least. Seems you have some Q for me.

sorry for off-topic.

___
Igor
unit eoImage;

interface

uses
  Windows, Messages, SysUtils, Classes, Controls, ExtCtrls;

type
  TeoImage    =  class(TImage)
    private    { Private declarations }
      FTabStop:  Boolean;
      FTabOrder: Integer;
      procedure  SetTabStop(Value: Boolean);
      procedure  SetTabOrder(Value: Integer);
    protected  { Protected declarations }
      procedure  MouseUp(Button: TMouseButton; Shift: TShiftState; X, Y: Integer); override;
      procedure  MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Integer); override;
      procedure  MouseMove(Shift: TShiftState; X, Y: Integer); override;
    public     { Public declarations }
     constructor Create(AOwner: TComponent); override;
     destructor  Destroy; override;
    published  { Published declarations }
      property   TabStop: Boolean read FTabStop write SetTabStop default False;
      property   TabOrder: Integer read FTabOrder write SetTabOrder default 0;
  end;

procedure Register;

implementation

procedure Register;
begin
  RegisterComponents('EsoftBG', [TeoImage]);
end;

constructor TeoImage.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  with Constraints do
  begin
    MinHeight := 32;
    MinWidth := 32;
  end;
  Height := 64;
  Width := 96;
end;

procedure  TeoImage.SetTabStop(Value: Boolean);
begin
  FTabStop := Value;
end;

procedure  TeoImage.SetTabOrder(Value: Integer);
begin
// you need to write code here ......
  FTabOrder := Value;
end;

procedure  TeoImage.MouseUp(Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
begin
// you need to write code here ......
end;

procedure TeoImage.MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
begin
  BringToFront; // may be
// you need to write code here ......
end;

procedure TeoImage.MouseMove(Shift: TShiftState; X, Y: Integer); //override;
begin
// you need to write code here ......
end;

destructor  TeoImage.Destroy;
begin
  inherited Destroy;
end;

end.
esofdbg: oops... "You need to write code here" does not seem to be a complete solution ;)
All great solutions but not really work for me. msa2000, your solution would not work because I cannot have 50 duplicate buttons as your code suggests. esoftbg, I already have mouseover, etc. I need OnKeyDown, Press and Up as my question states. JimMcKeeth, I am using ImgBtn already and was wondering if the envents I listed above could be added to this control:

http://www.delphi32.com/vcl/248/

This button works great execpt for the above events/properties.

Suggestions??
mdlittle wants to add the following events to a TImage control and have never done it before:

OnKeyDown
OnKeyPress
OnKeyUp

and the following properties:

TabStop
TabOrder

Code sample please.
I HAVE NOT BEEN WRITE A COMPLETE SOLUTION ! I JUST HAVE BEEN SHOW THE EXAMPLE. I DON'T KNOW WHAT HE WANTS TO DO WITH EVENTS AND PROPERTIES. I JUST HAVE BEEN SHOW THE WAY FOR CREATING NEW EVENTS AND PROPERTIES.
here will be component I made for you:
http://www.grava.lv/files/pro/imgex.zip
you should only change in you units and dfm's class name from TImage to TImageEx - all properties must remain (at least I think so).

it's half an hour work - if something don't work tell me and I will fix it.

wbr, mo.
How about this - If someone can add the events and properties to this control then I will award them 2000 points. I will create 3 other questions worth 2000 points if you can add:

OnKeyDown
OnKeyPress
OnKeyUp

and the following properties:

TabStop
TabOrder

to this control:

http://www.delphi32.com/vcl/248/
Ok, I changed it to descend from TBitBtn instead of TImage.  I think this satisfies what you are looking for.  If not let me know because I have another idea for how to do it, but it would require more changes.

http://www.bsdg.org/downloads/ImgBtn.zip

My sample http://www.serge.dsip.net/downloads/image-button.zip has already implemented two solutions: TImage and TBitBtn.

Now I have no time... sorry. I'll be back.
Jim ,the pic, picdown and picup don't seem to work?
ASKER CERTIFIED SOLUTION
Avatar of msa2003
msa2003

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
I'll continue testing.
msa2000:

This is EXACTLY what I needed. Very Cool. Double click is not working. Is this something I can fix.

I will award you the points and create 3 more question worth 500 points each. Can you fix the double click?

send me your email address to mike@little.tv and I will send you the 3 other question numbers. You will need to post a response to the question so I can award the points.

Thanks so much for you help.
mdlittle: I disabled doubleclicks myself to fix the following bug: if the user double clicks on the image and does not release a mouse button the image turns to initial state unlike a default TButton. If you need to remove this fix, just comment a string in ImgBtn.pas (#114, in TImgBtn constructor):

Self.ControlStyle := Self.ControlStyle - [csDoubleClicks];

Also I found a little bug and I'll fix it soon. Now TImgBtn does not process OnKeyDown, OnKeyPress and OnKeyUp events if it had received "focus" by mouseclick (because actually it is not a control).

Another bug I found (inherited from http://www.delphi32.com/vcl/248/ original unit) is that the button becomes initially "PicUp" if "Picture" property is not defined until you move a mouse pointer over it. Please respond if you had experienced such problem.

My e-mail address is msa@dsip.net (also declared in the head of updated ImgBtn.pas). Prior to give me extra points please assure that it is 100% legal. Sorry for this comment, but I'm new to this site and don't know some rules. I had already declined Mocarts's points (sorry and thanx) because I was not sure what to do with'em.
I guess I'm a little late to this party. . . but you might can get some Ideas from this



unit PicButton;

interface
uses
  Windows, Messages, Classes, Graphics, Controls, Buttons;

type
TMyState = (bsUp, bsDown, bsHigh, bsDisabled);

TPicButton = class(TBitBtn)
    private
    FCanvas: TCanvas;
    FOver: Boolean;
    Fbk: TBitBtnKind;
    FLayout: TButtonLayout;
    FMargin: Integer;
    FStyle: TButtonStyle;
    FNormalPic, FHiLightPic, FDownPic, FDisabledPic, FGbmp: TBitmap;
    procedure setNormal(Value : TBitMap);
    procedure setHiLight(Value : TBitMap);
    procedure setDown(Value : TBitMap);
    procedure setDisabled(Value : TBitmap);
    procedure DrawItem(const DrawItemStruct: TDrawItemStruct);
    procedure CNMeasureItem(var Message: TWMMeasureItem); message CN_MEASUREITEM;
    procedure CNDrawItem(var Message: TWMDrawItem); message CN_DRAWITEM;
    procedure CMFontChanged(var Message: TMessage); message CM_FONTCHANGED;
    procedure CMTextChanged(var Message: TMessage); message CM_TEXTCHANGED;
    procedure CMEnabledChanged(var Message: TMessage); message CM_ENABLEDCHANGED;

    protected
    property Action;
    property Cancel;
    property Default;
    property Glyph: TBitmap read FGbmp write FGbmp;
    property Kind: TBitBtnKind read Fbk write Fbk;
    property Layout: TButtonLayout read FLayout write FLayout default blGlyphLeft;
    property Margin: Integer read FMargin write FMargin;
    property ModalResult;
    property NumGlyphs: Integer read FMargin write FMargin;
    property Style: TButtonStyle read FStyle write FStyle;
    property Spacing: Integer read FMargin write FMargin;
    procedure CMMouseLeave(var Message: TMessage); message CM_MOUSELEAVE;
    procedure CMMouseEnter(var Message: TMessage); message CM_MOUSEENTER;

    public
    { Public declarations }
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;

    published
    { Published declarations }
    property PicNormal   : TBitMap read FNormalPic write setNormal;
    property PicHiLight  : TBitMap read FHiLightPic write setHiLight;
    property PicPushed   : TBitMap read FDownPic write setDown;
    property PicDisabled : TBitmap read FDisabledPic write setDisabled;
    property Caption;
    property Enabled;
    property ParentShowHint;
    property ParentBiDiMode;
    property ShowHint;
    property TabOrder;
    property TabStop;
    property Visible;
    property OnEnter;
    property OnExit;

end;

procedure Register;

implementation

procedure Register;
  begin
    RegisterComponents('Samples', [TPicButton]);
  end;

constructor TPicButton.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  ControlStyle:= ControlStyle - [csSetCaption];
  FCanvas := TCanvas.Create;
  FOver := False;
  FNormalPic  := TBitMap.Create;
  FHiLightPic := TBitmap.Create;
  FDownPic  := TBitmap.Create;
  FDisabledPic := TBitmap.Create;
end;

destructor TPicButton.Destroy;
begin
  inherited Destroy;
  FCanvas.Free;
  FNormalPic.Free;
  FHiLightPic.Free;
  FDownPic.Free;
  FDisabledPic.Free;
end;

procedure TPicButton.setNormal(Value: TBitMap);
begin
FNormalPic.Assign(Value);
if not FNormalPic.Empty then
  begin
  Height := FNormalPic.Height;
  Width := FNormalPic.Width;
  end;
Invalidate;
end;

procedure TPicButton.setHiLight(Value: TBitMap);
begin
FHiLightPic.Assign(Value);
end;

procedure TPicButton.setDown(Value: TBitMap);
begin
FDownPic.Assign(Value);
end;

procedure TPicButton.setDisabled(Value: TBitMap);
begin
FDisabledPic.Assign(Value);
end;
   
procedure TPicButton.CNMeasureItem(var Message: TWMMeasureItem);
begin
  with Message.MeasureItemStruct^ do
  begin
    itemWidth := Width;
    itemHeight := Height;
  end;
end;
   
procedure TPicButton.CNDrawItem(var Message: TWMDrawItem);
begin
  DrawItem(Message.DrawItemStruct^);
end;

procedure TPicButton.CMTextChanged(var Message: TMessage);
begin
  Invalidate;
end;

procedure TPicButton.CMMouseLeave(var Message: TMessage);
  begin
  FOver := False;
  if Enabled then
  Invalidate;
  end;

procedure TPicButton.CMMouseEnter(var Message: TMessage);
  begin
  FOver := True;
  if Enabled then
  Invalidate;
  end;
   
procedure TPicButton.DrawItem(const DrawItemStruct: TDrawItemStruct);
var
  IsDown, IsFocus: Boolean;
  State: TMyState;
  cRect: TRect;
  Pic: TBitmap;
begin
  FCanvas.Handle := DrawItemStruct.hDC;
  cRect := ClientRect;
   
  with DrawItemStruct do
  begin
    IsDown := itemState and ODS_SELECTED <> 0;
    IsFocus := itemState and ODS_FOCUS <> 0;
   
    if not Enabled then State := bsDisabled
    else if IsDown then State := bsDown
    else if FOver then
    State := bsHigh else
    State := bsUp;
  end;

FCanvas.Font := Self.Font;
FCanvas.Brush.Style := bsClear;
case State of
  bsUp: Pic := FNormalPic;
  bsDown: begin
          if FDownPic.Empty then Pic := FHiLightPic else Pic := FDownPic;
          cRect := Rect(2,2,Width+2,Height+2)
          end;
  bsHigh: Pic := FHiLightPic;
  bsDisabled: begin
              Pic := FDisabledPic;
              FCanvas.Font.Color := clSilver;
              end;
  else Pic := FNormalPic;
  end;

if Pic.Empty and (Not FNormalPic.Empty) then
  Pic := FNormalPic;

if not Pic.Empty then
  FCanvas.Draw(0,0,Pic);

if Length(Caption) > 0 then
    DrawText(FCanvas.Handle,PChar(Caption),-1,cRect,DT_SINGLELINE or DT_VCENTER or DT_CENTER);

  if IsFocus then
  begin
    cRect := ClientRect;
    InflateRect(cRect, -4, -4);
    FCanvas.Pen.Color := clWindowFrame;
    FCanvas.Brush.Color := clBtnFace;
    DrawFocusRect(FCanvas.Handle, cRect);
  end;
   
  FCanvas.Handle := 0;
end;
   
procedure TPicButton.CMFontChanged(var Message: TMessage);
begin
  inherited;
  Invalidate;
end;
   
procedure TPicButton.CMEnabledChanged(var Message: TMessage);
begin
  inherited;
  Invalidate;
end;

end.



 - - - - - - - - - - - - - - - - - - - - - - - -
This is sort of put together, but gives you something to look at, for double clicks you might use the -

procedure WMLButtonDblClk(var Message: TWMLButtonDblClk);
      message WM_LBUTTONDBLCLK;
Now please check an updated version at http://www.serge.dsip.net/downloads/ImgBtn.zip . I fixed two bugs in my code (one of them is listed below, another one was related to picture-changing logic). I still could not fix two bugs inherited from the original version: doubleclick bug and the bug of empty "Picture" property. First of them could be fixed by disabling double clicking (which will allow the control to process last MouseDown). Last of them is related to Delphi internal bug (I tried Delphi 6/7) which leads the "Picture" property to change at design-time.
MSA:

New button is broken. When the mouse is over one button the previous button still shows focused. It appears that the mouse and keys are working independant of each other. Also, key press does not change the image to the picdown image. Am I doing something wrong?
just a sugestion, you should really not use a TImage for a picture button component, you may have lots of problems trying to get normal button behavior, if you want button behavior then derive your component from a some button,
Slick812,

I really dont care what it is derived from. I just cant believe that there are no buttons available that support the following:

Transparent png images (not just bitmaps)
I use TPNGImage (get it at http://pngdelphi.sourceforge.net) and it turns any image desendant into supporting PNG images. I really need this. I also need mouse enter and leave events, tab, keypress, keyup,etc. The control I linked above support this but does not support key enter, etc.

HELP!!!!!
mdlittle: I will process your request in 2-4 hours. You may send me a source with a bug so it could be not so complex for me to locate it.
mdlittle:

> When the mouse is over one button the previous button still shows focused

It is a normal state. When you move mouse over the button it doesn't receive focus. Any control receives focus after mouse left button click or "Tab" key press. I also realized this.

If you would like the "MouseOver" event to cause the button to get focus, try the following in ImgBtn.pas:

procedure TImgBtn.WMMouseEnter( var Msg: TWMMouse );
begin
  if not FSupported then Exit;
  FEntered := True;
  if FDown then Picture := FPicDown else Picture := FPicUp;
  if Assigned( FOnMouseEnter ) then FOnMouseEnter( Msg );
  FButton.SetFocus;
end;

> I use TPNGImage (get it at http://pngdelphi.sourceforge.net) and it turns any image desendant into supporting PNG images. I really need this.

The easy way is to use BMP's with ImgBtn "Transparency" property turned to "true". Also this will decrease calculation times and increase stability. If you care about executable size, just pack it usind UPX or another Win32 EXE-packer.

I had also downloaded pngimage143.zip from http://pngdelphi.sourceforge.net/ but did not explored it yet.
OK Here is a test project. Also I tried your new code and it does not work. Check out the test project here. You only need the ImgBtn control.

http://67.114.155.61/project1.zip

Let me know if you have any problems.
> Also I tried your new code and it does not work - what the way it does not work?

I'll check your test project.
During design time it causes Delphi to crash.
> During design time it causes Delphi to crash.

Strange... I tested it, and all seemed to be OK... It look like a Delphi internal bug.
I am talking about the new code you posted above:

procedure TImgBtn.WMMouseEnter( var Msg: TWMMouse );
begin
 if not FSupported then Exit;
 FEntered := True;
 if FDown then Picture := FPicDown else Picture := FPicUp;
 if Assigned( FOnMouseEnter ) then FOnMouseEnter( Msg );
 FButton.SetFocus;
end;
I am talking about the new code you posted above:

procedure TImgBtn.WMMouseEnter( var Msg: TWMMouse );
begin
 if not FSupported then Exit;
 FEntered := True;
 if FDown then Picture := FPicDown else Picture := FPicUp;
 if Assigned( FOnMouseEnter ) then FOnMouseEnter( Msg );
 FButton.SetFocus;
end;
Yes. I understand you. I tested this code with Delphi 7 instead of Delphi 6 and found the same problem you described when moving the mouse pointer over the button. It happens because of the same problem I described later. This problem also leads to "Picture" property change from normal "Pic" value to "PicUp" when developer moves mouse pointer over the button at design-time. This problem is inherited from the original version and I still didn't found a solution. I had already placed myself a question https://www.experts-exchange.com/questions/20544204/Determining-if-method-runs-at-design-time.html . If I'll get the answer it is the way to solve the problem.

When I posted a comment you graded as answer, I posted EXACTLY what you asked. A didn't think about the bugs in original code and/or Delphi. Now I need to go deeper and it is complex enough.
I appreciate your help and if you want will award more points. No problem.
you might try a TPicture, instead of a TBitmap in my control


    property PicNormal   : TPicture read FNormalPic write setNormal;
   property PicHiLight  : TPicture read FHiLightPic write setHiLight;
   property PicPushed   : TPicture read FDownPic write setDown;
   property PicDisabled : TPicture read FDisabledPic write setDisabled;

I beleave that the TPicture will accept the variour ADD ON Image types like GIF and PNG
mdlittle: Don't mention it, I do not need more points. I just wont to say what I do, no more. I'll help you. No problem. I found an answer on my question https://www.experts-exchange.com/questions/20544204/Determining-if-method-runs-at-design-time.html but still had no time to implement if. I'll tell when I do.
Michael:
Well, I put updated version here: http://www.serge.dsip.net/downloads/ImgBtn.zip . Check it. I fixed ALL bugs that I had found. So now it is not a draft. I had COMPLETELY rewritten original functions so now it seems to work more stable. Also I added new properties:

TImgBtn.Default - specifies if the button is default. Now this property is only for design-time (I'll improve it in the future versions). If you will specify a number of default buttons actually only the last will be default allthrough Default property will indicate "True". In future versions I'll add a new PicDefault property which will separately store a picture of a default button state thus letting change "Default" state at runtime.

TImgBtn.MouseFocus - if "true" leads the button to receive focus when the mouse pointer is moved over it. If "true" TImgBtn.PicFocused property is ignored. TImgBtn.PicUp property used instead.

TImgBtn.ProcessDblClicks - if "true" (default) leads a control to procedss double mouse clicks. When "false" makes control interpretate double click as two separate clicks.

P. S. I viewed your example and found it very huge. It seems that internally TPNGImage component stores images like a default BMP's. I suppose that you could use TBitmap or TJPEGImage with TImgBtn.Transparency property to get the same or better results.

Slick812: usage of a descendant of TBitBtn is not the best way. I think that direct TWinControl descendant is better.
msa20003,

Very nice. Thanks for all you help. I am very happy with the performance of the button. If you decide to update the button in the future please email me a copy.

Slick812, I too appreciate all you help but I really can't use a TBitBtn control.
OK. I think to create a special page within my site. I like this component because it is the first Delphi component I ever made :o)

Good luck!