I have a set of TImage instances layed out on a panel. The TImages represent icons (see attached screenshot). I would like to draw a red rectangle around a given TImage instance when the user selects it by clicking on it. Not sure how to proceed...
Edit: why am I not using a TToolbar? Reason 1: I don't like the default "look and feel" of the TToolbar and I want to have more control on it. Reason 2: This control is not really a TToolbar. It should rather be considered as a sort of "bookmark" element, which displays different text in the memo field depending on which "bookmark" is selected.
The accepted solution using Remy Lebeau's suggestion is shown below:
I would suggest using a TPaintBox instead of a TImage. Load your image into an appropriate TGraphic class (TBitmap, TIcon, TPNGImage, etc) and then draw it onto the TPaintBox in its OnPaint event. That is all a TImage really does (it holds a TGraphic that is drawn onto its Canvas when painted). You can then draw a red rectangle on top of the image when needed. For example:
procedure TMyForm.PaintBox1Click(Sender: TObject);
begin
PaintBox1.Tag := 1;
PaintBox1.Invalidate;
PaintBox2.Tag := 0;
PaintBox2.Invalidate;
end;
procedure TMyForm.PaintBox2Click(Sender: TObject);
begin
PaintBox1.Tag := 0;
PaintBox1.Invalidate;
PaintBox2.Tag := 1;
PaintBox2.Invalidate;
end;
procedure TMyForm.PaintBox1Paint(Sender: TObject);
begin
PaintBox1.Canvas.Draw(MyImage1, 0, 0);
if PaintBox1.Tag = 1 then
begin
PaintBox1.Canvas.Brush.Style := bsClear;
PaintBox1.Canvas.Pen.Color := clRed;
PaintBox1.Canvas.Rectangle(PaintBox1.ClientRect);
end;
end;
procedure TMyForm.PaintBox2Paint(Sender: TObject);
begin
PaintBox2.Canvas.Draw(MyImage2, 0, 0);
if PaintBox2.Tag = 1 then
begin
PaintBox2.Canvas.Brush.Style := bsClear;
PaintBox2.Canvas.Pen.Color := clRed;
PaintBox2.Canvas.Rectangle(PaintBox2.ClientRect);
end;
end;
Alternatively, you can derive a new class from TImage and override its virtual Paint() method to draw the rectangle after default drawing. For example:
type
TMyImage = class(TImage)
private
FShowRectangle: Boolean;
procedure SetShowRectangle(Value: Boolean);
protected
procedure Paint; override;
public
property ShowRectangle: Boolean read FShowRectangle write SetShowRectangle;
end;
procedure TMyImage.SetShowRectangle(Value: Boolean);
begin
if FShowRectangle <> Value then
begin
FShowRectangle := Value;
Invalidate;
end;
end;
type
TGraphicControlAccess = class(TGraphicControl)
end;
procedure TMyImage.Paint;
begin
inherited;
if FShowRectangle then
begin
with TGraphicControlAccess(Self).Canvas do
begin
Brush.Style := bsClear;
Pen.Color := clRed;
Rectangle(ClientRect);
end;
end;
end;
procedure TMyForm.MyImage1Click(Sender: TObject);
begin
MyImage1.ShowRectangle := true;
MyImage2.ShowRectangle := false;
end;
procedure TMyForm.MyImage2Click(Sender: TObject);
begin
MyImage1.ShowRectangle := false;
MyImage2.ShowRectangle := true;
end;
I would modify the proposals. there would be no problem with the objects on the form type the following :
TImage = class(ExtCtrls.TImage)
private
FShowRectangle: Boolean;
procedure SetShowRectangle(Value: Boolean);
protected
procedure Paint; override;
public
property ShowRectangle: Boolean read FShowRectangle write SetShowRectangle;
end;
I would recommend using a TRectangle. You can add an bitmap (bitmap, jpg, etc) via the Fill propery and set the Stroke property for the border.
You also can set the xRadius and yRadius properties for rounded borders.
Related
With other styles controls, I would normally add a background rectangle to a custom ListboxItem style and define a color animation with Trigger: IsMouseOver=true, but it does not work in this situation.
Only if I set HitTest := True for the background rectangle, then the hover animation works, but then the ListBox does not respond to clicks on the item, and you cannot select an item.
How do I add the hover effect to the ListBox?
I ran into the same problem a while ago.
The only workaround i could find is to skip the style and create your own listboxitem. Only thing is your text will disappear, so i added a label to display the text.
Its not the great, but it worked in my case
type
TMouseOverListBoxItem = class(TListBoxItem)
private
FBackGround: TRectangle;
FHoverAni: TColorAnimation;
FLabel: TLabel;
procedure BackgroundClicked(Sender: TObject);
protected
procedure DoTextChanged; override;
public
procedure AfterConstruction; override;
end;
procedure TMouseOverListBoxItem.AfterConstruction;
const
cStart = TAlphaColorRec.White;
cStop = TAlphaColorRec.Yellow;
begin
inherited;
// Create background
FBackGround := TRectangle.Create(Self);
FBackGround.Parent := Self;
FBackGround.Fill.Color := cStart;
FBackGround.Align := TAlignLayout.Contents;
FBackGround.HitTest := True;
FBackGround.Sides := [];
FBackGround.OnClick := BackgroundClicked;
// Create mouse over animation
FHoverAni := TColorAnimation.Create(FBackGround);
FHoverAni.Parent := FBackGround;
FHoverAni.Trigger := 'IsMouseOver=true';
FHoverAni.TriggerInverse := 'IsMouseOver=false';
FHoverAni.StartValue := cStart;
FHoverAni.StopValue := cStop;
FHoverAni.PropertyName := 'Fill.Color';
// Create label to show text. Background will hide original text
FLabel := TLabel.Create(FBackGround);
FLabel.Parent := FBackGround;
FLabel.Align := TAlignLayout.Client;
end;
procedure TMouseOverListBoxItem.BackgroundClicked(Sender: TObject);
begin
if Assigned(OnClick) then
OnClick(Self)
else if Assigned(ListBox.OnItemClick) then
ListBox.OnItemClick(ListBox, Self);
end;
procedure TMouseOverListBoxItem.DoTextChanged;
begin
inherited;
FLabel.Text := Self.Text;
end;
Create a VCL Forms Application, put a TBalloonHint (Name: balloonhintTest) and a TButton (Name: btnTest) on the form and write this code:
procedure TForm2.FormCreate(Sender: TObject);
begin
balloonhintTest.HideHint;
balloonhintTest.Style := bhsStandard;
end;
procedure TForm2.btnTestMouseEnter(Sender: TObject);
begin
if not balloonhintTest.ShowingHint then
begin
balloonhintTest.Title := 'My Title';
balloonhintTest.Description := 'MyDescription';
balloonhintTest.ShowHint(Self.ClientToScreen(Point(btnTest.Left + (btnTest.Width div 2), btnTest.Top + btnTest.Height)));
end;
end;
procedure TForm2.btnTestMouseLeave(Sender: TObject);
begin
balloonhintTest.HideHint;
end;
Now run the program and hover your mouse pointer over the button.
This is how it looks when the window is on the UPPER PART OF THE SCREEN:
And this is how it looks when the window is on the LOWER PART OF THE SCREEN:
As you can see - although the Hint coordinates are always the same - the hint is displayed DOWNWARDS in the first case (desired result) and UPWARDS in the second case (obviously not the desired result), depending on the vertical position of the window on the screen.
So how can I display the balloon hint in this case always DOWNWARDS independently from the screen position?
(Please note: I am not interested in the other overloadings of the ShowHint method - I just want to know how to display the hint always downwards in the above case, as this is only the simplified scenario of a more complex case).
Probably easiest way is to create your own class based on TBalloonHint as
type
TMyHint = class(TBalloonHint)
strict private
FControl: TControl;
public
procedure PaintHint(HintWindow: TCustomHintWindow); override;
constructor Create(AOwner: TComponent; const AControl: TControl);
end;
constructor TMyHint.Create(AOwner: TComponent; const AControl: TControl);
begin
inherited Create(AOwner);
FControl := AControl;
end;
procedure TMyHint.PaintHint(HintWindow: TCustomHintWindow);
var
Point: TPoint;
begin
Point := FControl.Parent.ClientToScreen(TPoint.Create(FControl.Left, FControl.Top + FControl.Height));
HintWindow.Top := Point.Y;
inherited;
end;
create it as
procedure TMainForm.FormCreate(Sender: TObject);
begin
balloonHintTest := TMyHint.Create(Self, btnTest);
balloonHintTest.Style := bhsStandard;
end;
I think it is even easier if you just apply a check on whether the showing point is on the lower part of the form to which the balloonhint belongs, and if yes add, sth like that. The height of the balloonHint can be calculated via textHeight
procedure TForm2.FormCreate(Sender: TObject);
begin
balloonhintTest.HideHint;
balloonhintTest.Style := bhsStandard;
end;
procedure TForm2.btnTestMouseEnter(Sender: TObject);
begin
if not balloonhintTest.ShowingHint then
begin
balloonhintTest.Title := 'My Title';
balloonhintTest.Description := 'MyDescription';
TPoint pointCheck = Self.ClientToScreen(Point(btnTest.Left + (btnTest.Width div 2), btnTest.Top + btnTest.Height));
if(pointCheck.y>0.5*Self.Height)
int yShift = balloonhintTest.Height;
pointCheck.y = pointCheck.y - yShift;
end if
balloonhintTest.ShowHint(pointCheck);
end;
end;
procedure TForm2.btnTestMouseLeave(Sender: TObject);
begin
balloonhintTest.HideHint;
end;
i would like to change to gray color of the Texthint of my TEdits.
I allready found this https://stackoverflow.com/a/31550017/1862576 and tried to change to color via SendMessage like this
procedure TEdit.DoSetTextHint(const Value: string);
var
Font: TFont;
begin
if CheckWin32Version(5, 1) and StyleServices.Enabled and HandleAllocated then
begin
Font := TFont.Create;
try
Font.Assign(self.Font);
Font.Color := clGreen;
Font.Size := 20;
SendTextMessage(Handle, EM_SETCUEBANNER, WPARAM(1), Value);
SendMessage(Handle, WM_SETFONT, Integer(Font.Handle), Integer(True));
finally
// Font.Free;
end;
end;
end;
It changes the size of the font but not the color.
Thanks for your help.
The cue banner is a feature built in to the underlying Win32 EDIT control that TEdit wraps. It is not managed by the VCL at all. There is no Win32 API exposed to manage the coloring of the cue banner text. If you want custom coloring, you will have to stop using the native cue banner functionality and custom-draw the edit control manually by handling its WM_ERASEBKGND and/or WM_PAINT messages directly (see How do i custom draw of TEdit control text?). Otherwise, you will have to find a third-party Edit control that supports custom coloring. Or use TRichEdit instead of TEdit so you can set text colors as needed.
Definition:
Type
HitColor = class helper for tEdit
private
procedure SetTextHintColor(const Value: TColor);
function GetTextHintColor: TColor;
procedure fixWndProc(var Message: TMessage);
published
property TextHintColor : TColor read GetTextHintColor write SetTextHintColor;
end;
Implementation:
procedure HitColor.fixWndProc(var Message: TMessage);
var
dc : HDC ;
r : TRect ;
OldFont: HFONT;
OldTextColor: TColorRef;
Handled : boolean;
begin
Handled := false;
if (Message.Msg = WM_PAINT) and (Text = '') and not Focused then
begin
self.WndProc(Message);
self.Perform(EM_GETRECT, 0, LPARAM(#R));
dc := GetDC(handle);
try
OldFont := SelectObject(dc, Font.Handle );
OldTextColor := SetTextColor(DC, ColorToRGB(GetTextHintColor));
FillRect(dc,r,0);
DrawText(DC, PChar(TextHint), Length(TextHint), R, DT_LEFT or DT_VCENTER or DT_SINGLELINE or DT_NOPREFIX);
finally
SetTextColor(DC, OldTextColor);
SelectObject(DC, OldFont);
ReleaseDC(handle,dc);
end;
Handled := true;
end;
if not Handled then WndProc(Message);
end;
function HitColor.GetTextHintColor: TColor;
begin
result := tag;
end;
procedure HitColor.SetTextHintColor(const Value: TColor);
begin
tag := Value;
WindowProc := fixWndProc ;
end;
Usage:
edit1.TextHintColor := clred;
I have TActionMainMenuBar placed on the form, which looks like this:
Now, it looks perfectly fine except that blank gap on the left where images should go. Since I don't have need to draw images in the menu, how can I hide that gap completely? Haven't been able to find any properties which I can use to hide this, and Google queries returned no results on the topic.
Below sample tries to demonstrate what it would take to use your own menu style. It just tries to gain the space from the unused images but you can override any aspect of the drawing, see 'xpactnctrls.pas' for possible implementation.
type
TBarStyle = class(TXPStyleActionBars)
public
function GetControlClass(ActionBar: TCustomActionBar;
AnItem: TActionClientItem): TCustomActionControlClass; override;
end;
TMenuStyle = class(TXPStyleMenuItem)
protected
procedure CalcLayout; override;
public
procedure CalcBounds; override;
end;
var
BarStyle: TBarStyle;
function TBarStyle.GetControlClass(ActionBar: TCustomActionBar;
AnItem: TActionClientItem): TCustomActionControlClass;
begin
Result := inherited GetControlClass(ActionBar, AnItem);
if ActionBar is TCustomActionPopupMenu then
Result := TMenuStyle;
end;
procedure TMenuStyle.CalcLayout;
begin
inherited;
GlyphPos := Point(-16, GlyphPos.Y);
end;
procedure TMenuStyle.CalcBounds;
var
R: TRect;
begin
inherited;
R := TextBounds;
OffsetRect(R, -16, 0);
TextBounds := R;
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
ActionMainMenuBar1.ActionManager.Style := BarStyle;
end;
initialization
BarStyle := TBarStyle.Create;
RegisterActnBarStyle(BarStyle);
finalization
UnregisterActnBarStyle(BarStyle);
BArStyle.Free;
How to dim / fade all other windows of an application in Delphi 2009.
Form has an AlphaBlend property, but it controls only transparency level. But it would be nice if we can have something like this
(Concentrated window) . Even stackoverflow.com does that, when we try to insert a link/ image etc in the post.
How can we achieve this in a delphi application?
Here is a unit I just knocked together for you.
To use this unit drop a TApplication component on your main form and in the OnModalBegin call _GrayForms and then in the OnModalEnd call the _NormalForms method.
This is a very simple example and could be made to be more complex very easily. Checking for multiple call levels etc....
For things like system (open, save, etc) dialogs you can wrap the dialog execute method in a try...finally block calling the appropriate functions to get a similar reaction.
This unit should work on Win2k, WinXP, Vista and should even work on Win7.
Ryan.
unit GrayOut;
interface
procedure _GrayForms;
procedure _GrayDesktop;
procedure _NormalForms;
implementation
uses windows, classes, forms, Contnrs, Types, Graphics, sysutils;
var
gGrayForms : TComponentList;
procedure _GrayDesktop;
var
loop : integer;
wScrnFrm : TForm;
wForm : TForm;
wPoint : TPoint;
begin
if not assigned(gGrayForms) then
begin
gGrayForms := TComponentList.Create;
gGrayForms.OwnsObjects := true;
for loop := 0 to Screen.MonitorCount - 1 do
begin
wForm := TForm.Create(nil);
gGrayForms.Add(wForm);
wForm.Position := poDesigned;
wForm.AlphaBlend := true;
wForm.AlphaBlendValue := 64;
wForm.Color := clBlack;
wForm.BorderStyle := bsNone;
wForm.Enabled := false;
wForm.BoundsRect := Screen.Monitors[loop].BoundsRect;
SetWindowPos(wForm.handle, HWND_TOP, 0,0,0,0, SWP_NOSIZE or SWP_NOMOVE);
wForm.Visible := true;
end;
end;
end;
procedure _GrayForms;
var
loop : integer;
wScrnFrm : TForm;
wForm : TForm;
wPoint : TPoint;
wScreens : TList;
begin
if not assigned(gGrayForms) then
begin
gGrayForms := TComponentList.Create;
gGrayForms.OwnsObjects := true;
wScreens := TList.create;
try
for loop := 0 to Screen.FormCount - 1 do
wScreens.Add(Screen.Forms[loop]);
for loop := 0 to wScreens.Count - 1 do
begin
wScrnFrm := wScreens[loop];
if wScrnFrm.Visible then
begin
wForm := TForm.Create(wScrnFrm);
gGrayForms.Add(wForm);
wForm.Position := poOwnerFormCenter;
wForm.AlphaBlend := true;
wForm.AlphaBlendValue := 64;
wForm.Color := clBlack;
wForm.BorderStyle := bsNone;
wForm.Enabled := false;
wForm.BoundsRect := wScrnFrm.BoundsRect;
SetWindowLong(wForm.Handle, GWL_HWNDPARENT, wScrnFrm.Handle);
SetWindowPos(wForm.handle, wScrnFrm.handle, 0,0,0,0, SWP_NOSIZE or SWP_NOMOVE);
wForm.Visible := true;
end;
end;
finally
wScreens.free;
end;
end;
end;
procedure _NormalForms;
begin
FreeAndNil(gGrayForms);
end;
initialization
gGrayForms := nil;
end.
I have done something similar for showing a modal form trying to keep the implementation as simple as possible. I don't know if this will fit your needs, but here it is:
function ShowModalDimmed(Form: TForm; Centered: Boolean = true): TModalResult;
var
Back: TForm;
begin
Back := TForm.Create(nil);
try
Back.Position := poDesigned;
Back.BorderStyle := bsNone;
Back.AlphaBlend := true;
Back.AlphaBlendValue := 192;
Back.Color := clBlack;
Back.SetBounds(0, 0, Screen.Width, Screen.Height);
Back.Show;
if Centered then begin
Form.Left := (Back.ClientWidth - Form.Width) div 2;
Form.Top := (Back.ClientHeight - Form.Height) div 2;
end;
result := Form.ShowModal;
finally
Back.Free;
end;
end;
I'm not sure about the "right" way to do it, but in order to "fade-to-white", what you can do is place your form in another completely white form (white background color, no controls).
So when your form is in 0% transparency, it will show as a regular form, but when it's in 50% transparency it will be faded to white. You can obviously choose other colors as your background.
I'm looking forward to seeing other answers...
EDIT: after seeing your "Jedi Concentrate" link, it seems that a dark-gray background will mimic the Expose effect better.
One way to do this is to place another form behind your dialog, this form would have no borders, and would contain a single image. This image would be a capture of the entire desktop from just before the dialog popped up, then run through a transform to lower the luminosity of each pixel by 50%. One trick that works quite well here is to use a black form, and to only include ever other pixel. If you know for certain that you will have theme support, you can optionally use a completely black form and use the alphablend and alphablendvalue properties..this will allow the OS to perform the luminosity transformation for you. An alphablendvalue of 128 is = 50%.
EDIT
As mghie pointed out, there is the possibility of a user pressing alt-tab to switch to another application. One way to handle this scenario would be to hide the "overlay" window in the application.OnDeactivate event, and to show it on the application.OnActivate event. Just remember to set the zorder of the overlay window lower than your modal dialog.
I created a similar effect to the Jedi Concentrate with a Form sized to the Screen.WorkArea with Color := clBlack and BorderStyle := bsNone
I found setting the AlphaBlendValue was too slow to animate nicely, so I use SetLayeredWindowAttributes()
The unit's code:
unit frmConcentrate;
{$WARN SYMBOL_PLATFORM OFF}
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs;
type
TFadeThread = class(TThread)
private
fForm: TForm;
public
constructor Create(frm: TForm);
procedure Execute; override;
end;
TConcentrateFrm = class(TForm)
procedure FormDestroy(Sender: TObject);
procedure FormClick(Sender: TObject);
private
{ Private declarations }
fThread: TFadeThread;
public
{ Public declarations }
end;
procedure StartConcentrate(aForm: TForm = nil);
var
ConcentrateFrm: TConcentrateFrm;
implementation
{$R *.dfm}
procedure StartConcentrate(aForm: TForm = nil);
var
Hnd: HWND;
begin
try
if not Assigned(ConcentrateFrm) then
ConcentrateFrm := TConcentrateFrm.Create(nil)
else
Exit;
ConcentrateFrm.Top := Screen.WorkAreaTop;
ConcentrateFrm.Left := Screen.WorkAreaLeft;
ConcentrateFrm.Width := Screen.WorkAreaWidth;
ConcentrateFrm.Height := Screen.WorkAreaHeight;
Hnd := GetForegroundWindow;
SetWindowLong(ConcentrateFrm.Handle, GWL_EXSTYLE,
GetWindowLong(ConcentrateFrm.Handle, GWL_EXSTYLE) or WS_EX_LAYERED
);
SetLayeredWindowAttributes(
ConcentrateFrm.Handle,
ColorToRGB(clBlack),
0,
LWA_ALPHA
);
ConcentrateFrm.Show;
if Assigned(aForm) then
aForm.BringToFront
else
SetForegroundWindow(Hnd);
ConcentrateFrm.fThread := TFadeThread.Create(ConcentrateFrm);
Application.ProcessMessages;
ConcentrateFrm.fThread.Resume;
except
FreeAndNil(ConcentrateFrm);
end;
end;
procedure TConcentrateFrm.FormClick(Sender: TObject);
var
p: TPoint;
hnd: HWND;
begin
GetCursorPos(p);
ConcentrateFrm.Hide;
hnd := WindowFromPoint(p);
while GetParent(hnd) 0 do
hnd := GetParent(hnd);
SetForegroundWindow(hnd);
Release;
end;
procedure TConcentrateFrm.FormDestroy(Sender: TObject);
begin
ConcentrateFrm := nil;
end;
{ TFadeThread }
constructor TFadeThread.Create(frm: TForm);
begin
inherited Create(true);
FreeOnTerminate := true;
Priority := tpIdle;
fForm := frm;
end;
procedure TFadeThread.Execute;
var
i: Integer;
begin
try
// let the main form open before doing this intensive process.
Sleep(300);
i := 0;
while i < 180 do
begin
if not Win32Check(
SetLayeredWindowAttributes(
fForm.Handle,
ColorToRGB(clBlack),
i,
LWA_ALPHA
)
) then
begin
RaiseLastOSError;
end;
Sleep(10);
Inc(i, 4);
end;
except
end;
end;
end.