How to change the ClientRect of a form? - delphi

Please refer to another question here: Resizing borderless form from different constraints than far edges?
This previous question has been resolved, but I have another similar question. Since I am building a custom shaped form with a different client area, I need to change the ClientRect area of this form. The form has some special drawing of some curved edges and such, but that part's irrelevant. I need to change the ClientRect of the form to represent a new client area where components are allowed to be dropped, and ignore anything put outside of those bounds.
(I have a borderless form, I'm drawing my own border which is a much different size than the standard windows border.)
This solution will kind-of change the way that my previous question works, but that'll be another topic which I'm sure I'll figure out on my own, should be very simple. I just need to be able to properly set this in the first place.

type
TForm1 = class(TForm)
..
private
procedure WmNCCalcSize(var Msg: TWMNCCalcSize); message WM_NCCALCSIZE;
..
..
procedure TForm1.WmNCCalcSize(var Msg: TWMNCCalcSize);
begin
inherited;
if Msg.CalcValidRects then begin
InflateRect(Msg.CalcSize_Params.rgrc[0], -10, -6);
Msg.Result := 0;
end;
end;
Please, carefully read WM_NCCALCSIZE's documentation though, including the remarks section and also NCCALCSIZE_PARAMS, as I'm not sure this is what you want. But this is your message..

Related

Highlight controls in themed Delphi App with Delphi Tokyo

Using Delphi Tokyo 10.2, with Stylized Themes. I am trying to highlight components on the form, e.g., ComboBoxes, EditTexts, etc. For example, if a user entered invalid data, I would like to highlight the component.
In the past, we just colored components Red, and the color persisted through resizes/movement/repaints in general. Now with theming, we need to do a bit more to get the color to show and persist.
I have tried disabling each component's StyleElements [seFont, seClient, seBorder] properties to force show the color. This works but seems kludgy, particularly when there are many components being validated. Also, simply coloring a component red might not look right with some of the themes.
I have also tried simply drawing a red rectangle around the components using WinAPI SetRop2(..). E.g., here is some clever code, I tweaked to take a TWinControl and Draw a redbox around it; I can also remove the redbox using a similar call. This works:
…but doesn't persist through repaints, obviously. It seems like adding custom paint methods might be an overkill here. Unless, there is some better way?
Other things I have considered:
All of the components sit on panels, and I have considered using a protected hack to draw red rects on the panel's canvas around the components, but again, more custom paint routines…
I am also considering drawing TShapes dynamically as needed, but this strikes me as silly.
There must be others in the same situation, e.g., data entry validation that worked neatly in older versions of Delphi, but doesn't look so good when themed. What is the best approach when using themes? The SetRop2(..) approach seems to be the cleanest, but can someone suggest a simple way to make the color persist? I would welcome other ideas, too. Thank you.
EDIT
So maybe, just dynamically drawing TShapes around the invalid responses isn't so bad. They persist through repaints and don't descend from TWinControl, meaning they automatically show up behind the control they are highlighting.
This works quite well for me and I hope it's helpful to others.
// assuming owning control will be free'd properly and
// will in turn free HI_LITE Box.
//
// tantamount to adding an instance variable, TShape, to existing Control,
// since class helpers don't allow. And I don't want to descend
// new controls just to have a hiLiteBox Instance Variable.
procedure HiLiteMe(aControl : TWinControl; HILITE_FLAG : Boolean = TRUE; aColor : TColor = clRed);
const OFFSET = 4; // specify the offset of the border size of the box.
const BOX_NAME_PREFIX = 'HI_LITE_BOX_';
var
hiLiteBox : TShape; // reference created on stack, but object created on the heap,
uniqueBoxName : String; // so use the persistent aControl's owned component list to maintain the reference.
begin
uniqueBoxName := BOX_NAME_PREFIX + aControl.Name; // uniquename for each associated HiLiteBox.
HiLiteBox := aControl.FindComponent(uniqueBoxName) as TShape; // phishing for the HiLiteBox if it was previously created.
if NOT Assigned(hiLiteBox) then // create HiLiteBox and make persist outside this proc.
begin
if NOT HILITE_FLAG then exit; // don't create a box if we're just going to hide it anyway.
hiLiteBox := TShape.Create(aControl); // Create HiLiteBox, setting aControl as owner, quicker retrieval using aControl.findComponent
hiLiteBox.Parent := aControl.Parent; // Render the box on the control's parent, e.g., panel, form, etc.
hiLiteBox.Name := uniqueBoxName;
hiLiteBox.Pen.Color := aColor; // Color the Pen
hiLiteBox.Pen.Width := offset-1; // Make the Pen just slightly smaller than the offset.
hiLiteBox.Brush.Color := clWindow; // Choose a brush color, to fill the space between the pen and the Control
hiLiteBox.Left := aControl.Left - offset;
hiLiteBox.Width := aControl.Width + offset*2;
hiLiteBox.Top := aControl.Top - offset;
hiLiteBox.Height := aControl.Height + offset*2;
end;
hiLiteBox.Visible := HILITE_FLAG; // Show/Hide HiLite as appropriate.
end;
Called like this to HiLite with a red and blue box...
begin
HiLiteMe(checkListBox1, TRUE, clRed); // Draw a RedBox around the CheckListBox, e.g., Invalid.
HiLiteMe(bitBtn3, TRUE, clBlue); // Draw a Blue Box around the Button, e.g., Required.
end;
Called like this to remove HiLites…
begin
HiLiteMe(checkListBox1, FALSE); // Draw a RedBox around the CheckListBox, e.g., Invalid.
HiLiteMe(bitBtn3, FALSE); // Draw a Blue Box around the Button, e.g., Required.
end;
I suggest having a red TShape on only one side of the control (e.g. just the left or bottom) that you show or hide.

Delphi TImage32 - how to make the component invisible if no picture is loaded?

If you place a normal TImage component on a form or panel over other components >> it is invisible at runtime as long no picture is loaded. So other things under it are visible.
But TImage32 is painting a gray box by default.
How do I make the it invisible while leaving the setting: .Visible:=True; if no picture is loaded?
(I still need events working on the component, like OnClick...)
Yes, this is a duplicate question, BUT the solution-link from the previous topic is dead. :(
While I still have access to the newsgroup posts, I don't know how the topic ID relates to the topic title (which is all I have). However, based an a search in the newsgroup I found several places where TImage32Ex was mentioned. I guess that this component (which is not part of the core library) was part of the solution in some way.
So, while the extension pack where this component comes from is no longer maintained, let's dig deeper in what it did.
First, I must say that TImage32 will always paint (copy) the content of its buffer to the display. This means whatever graphic is behind this component will get overwritten by default.
The trick TImage32Ex does is to get the parents content and draws it into the buffer.
With adaptions the code looks like this
var
P: TPoint;
SaveIndex: Integer;
begin
SaveIndex := SaveDC(Buffer.Handle);
try
GetViewportOrgEx(Buffer.Handle, P);
SetViewportOrgEx(Buffer.Handle, P.X - Left, P.Y - Top, nil);
IntersectClipRect(Buffer.Handle, 0, 0, Parent.ClientWidth, Parent.ClientHeight);
Parent.Perform(WM_ERASEBKGND, Buffer.Handle, 0);
Parent.Perform(WM_PAINT, Buffer.Handle, 0);
finally
RestoreDC(Buffer.Handle, SaveIndex);
end;
end;
The above code draws (WM_PAINT) the parent's content onto the buffer.
For example if you want to make the TPaintBox32 instance called PaintBox32 to be transparent just add the following code to the 'PaintBuffer' handler:
procedure TForm3.PaintBox32PaintBuffer(Sender: TObject);
var
P: TPoint;
SaveIndex: Integer;
begin
SaveIndex := SaveDC(PaintBox32.Buffer.Handle);
try
GetViewportOrgEx(PaintBox32.Buffer.Handle, P);
SetViewportOrgEx(PaintBox32.Buffer.Handle, P.X - PaintBox32.Left, P.Y - PaintBox32.Top, nil);
IntersectClipRect(PaintBox32.Buffer.Handle, 0, 0, PaintBox32.Parent.ClientWidth, PaintBox32.Parent.ClientHeight);
PaintBox32.Parent.Perform(WM_ERASEBKGND, PaintBox32.Buffer.Handle, 0);
PaintBox32.Parent.Perform(WM_PAINT, PaintBox32.Buffer.Handle, 0);
finally
RestoreDC(PaintBox32.Buffer.Handle, SaveIndex);
end;
end;
Note: While this works basically, it might not capture the parent's sub controls properly. This is especially true for TWinControl descendants. While there are solutions around to cover this scenario as well, it's far more complicated to cover this in every detail (e.g. the blinking cursor of an underlying TEdit instance)
I use a timage to mask a progress bar and give it a shape.
What I do is load a png with parts that are transparent and then place it over my progress bar.
I think this should achieve your goal. Place a transparent png in your timage.
Cheers,
E.

How to move a transparent form without border in Delphi?

I already know and have used these methods to create a form without boders that can be moved.
Move form without border style
I'm using the WMNCHitTest override.
The MouseDown event don't work at all.
This form is very simple it is displaying a countdown, a number that changes very second and that's all. The number being painted using a big TLabel with big fonts.
But I also made this form transparent by using Delphi's standard form properties.
Now, if I try to click on the form to move it, the only area I can use is the lines drawing the changing numbers, even if they are not so thin, this is not practical.
I'd like the user to be able to move the numbers to any position of the screen by clicking anywhere near the numbers, let's say inside a "0" or an "8".
I'd think about drawing a transparent rectangle over the numbers and be that the clickable area, but the effect would be the same, the click would go throu.
I know an application that does this, so it is possible, but how?
procedure TfrmCountDown.Timer1Timer(Sender: TObject);
begin
iCount := iCount - 1;
lblTime.Caption := FormatFloat('00', iCount);
end;
procedure TfrmCountDown.FormCreate(Sender: TObject);
begin
iCount := 60;
BorderStyle:=bsNone;
Self.Color := clGray;
Self.TransparentColor := true;
Self.TransparentColorValue := clGray;
end;
procedure TfrmCountDown.WMNCHitTest(var Message: TWMNCHitTest);
var
Pt: TPoint;
begin
Pt := ScreenToClient(SmallPointToPoint(Message.Pos));
if Pt.Y < 160 then
Message.Result := HTCAPTION
else
inherited;
end;
VCL Form Transparency (by which presume you to mean the TransparentColor property, as opposed to the Alpha properties) uses Layered Windows to achieve the transparent drawing. Using this technique any transparent area of the window is not only transparent visually but also with respect to hit testing.
i.e. Using VCL form transparency, the transparent areas in your form may as well not exist at all.
What could work is to turn off the VCL form transparency and instead implement your form using the WS_EX_TRANSPARENT window style. This enables visual transparency but allows you to handle hit testing to make different areas of your form transparent, or not, with respect to clicks.
Unfortunately WS_EX_TRANSPARENT is not a complete "transparency" solution - it only tells Windows that your form is transparent, but you also then have to take additional steps to actually be properly transparent, or to interpret what "transparent" means for your specific form.
This means it complicates the visual rendering of your form and you will also have to override the paint mechanism to properly draw your form. From how you describe the content on your form this does not sound like it will be too arduous however, though it is far some straightforward (I do not have a working example unfortunately).
At the very least you will probably be best to replace your TLabel with calls to select an appropriate font and render text into the window client area with a transparent background. But there will be additional house keeping required.
In your WM_NCHITTEST handler, respond with HTNOWHERE for those areas of your form which you wish to be interpreted as "click through" areas, and HTCAPTION for the areas that you wish to support dragging (i.e. from what you describe, within a region defined by the bounds of your text).
Unfortunately I think you will find that there are lots of fiddly aspects to the implementation of painting a transparent window.

How to make a groupbox's background transparent?

How to set Groupbox to transparent? I found this
Transparent,
But in my case, I put TImage and put a background image, then the Groupbox, I don't know how can I make the groupbox transparent, and show the image as a background.
I tried searching this on google, but can't find the answer, and as much as possible, i want to use VCL Application.
I think you'll need to take over painting the group box. Here's a simple example using an interposer class. Place this class in the same unit as your form, before your form is declared:
type
TGroupBox = class(StdCtrls.TGroupBox)
protected
procedure Paint; override;
end;
TForm1 = class(TForm)
GroupBox1: TGroupBox;
....
end;
....
procedure TGroupBox.Paint;
begin
Canvas.Draw(0, 0, SomeGraphicObjectContainingYourBackground);
inherited;
end;
The output looks like this:
You may want to customise the rest of the painting. Perhaps it's enough to draw the background inside the group box so that the caption and frame appear as normal. Specify different coordinates in the call to Canvas.Draw if you want that. If you need the background to cover the entire parent canvas then your call to Draw needs to pass -Left and -Top for the coordinates.
Or perhaps you want to take over the drawing of the frame. Do that by not calling the inherited Paint method and doing your own work.
To avoid flicker, you are actually best off moving this painting code into WM_ERASEBKGND. That makes things a little more complex, but not much. The code would look like this:
type
TGroupBox = class(StdCtrls.TGroupBox)
protected
procedure WMEraseBkgnd(var Message: TWmEraseBkgnd); message WM_ERASEBKGND;
end;
procedure TGroupBox.WMEraseBkgnd(var Message: TWmEraseBkgnd);
begin
Canvas.Handle := Message.DC;
try
Canvas.Draw(-Left, -Top, SomeGraphicObjectContainingYourBackground);
finally
Canvas.Handle := 0;
end;
Message.Result := 1;
end;
If you were going to do this properly, you'd want to make a proper component rather than hacking around with an interposer.

How can obtain the image which uses windows 7 to draw the parent nodes in a treeview control?

I'm working in a custom control which mix two windows controls (listview and treeview). In some point, I need to draw the image which uses windows 7 (with themes enabled) to identify the parent nodes, I'm using the DrawThemeBackground function with the TVP_GLYPH part and the GLPS_CLOSED state (I tried with all the parts and states related to the TREEVIEW class without luck), but the result image always is the old (+) or (-).
This image show the issue
I want to draw the Arrow image (inside of black circle) instead of the (+) sign (inside of orange circle).
This is the sample code which I use to draw the image.
uses
UxTheme;
procedure TForm40.Button1Click(Sender: TObject);
var
iPartId : integer;
iStateId: integer;
hTheme : THandle;
begin
hTheme := OpenThemeData(Handle, VSCLASS_TREEVIEW);
iPartId := TVP_GLYPH;
iStateId:= GLPS_CLOSED;
//iPartId := TVP_TREEITEM;
//iStateId:= TREIS_NORMAL;
if hTheme <> 0 then
try
//if (IsThemeBackgroundPartiallyTransparent(hTheme, iPartId, iStateId)) then
// DrawThemeParentBackground(Handle, PaintBox1.Canvas.Handle, nil);
DrawThemeBackground(hTheme, PaintBox1.Canvas.Handle, iPartId, iStateId, Rect(0, 0, 31, 31), nil);
finally
CloseThemeData(hTheme);
end;
end;
I check a couple of tools like the application made by Andreas Rejbrand and this too, but I can't find the image which I want.
My question is : how I can obtain the arrow image?
UPDATE
Thanks to the answer posted for Stigma I found additional resources to the values of the parts and states of the Explorer::Treeview class.
VisualStyleRenderer and themes
CodeProject
First of all, in the case of an ordinary ListView or TreeView, one can simply call SetWindowTheme on its handle to apply the proper sort of styling. The example from its MSDN page is as follows:
SetWindowTheme(hwndList, L"Explorer", NULL);
Since we are talking about a custom control, I am not so sure that applies here however. But since SetWindowTheme causes the WM_THEMECHANGED message to be sent to the proper window, it implies that you will just need to use the proper OpenThemeData call for the specific sub theme.
I think Luke's comment is correct. You probably just need to pass 'Explorer::Treeview' rather than the plain style. So, barring years of not having touched Delphi/Pascal:
hTheme := OpenThemeData(Handle, 'Explorer::Treeview');
You must set SetWindowTheme(Handle, 'explorer', nil); before painting to ensure that OpenThemeData will use new explorer style theme. Of course, window handle must be the same for both functions.

Resources