Is there a simple and convenient way to tell when all the Components, Frames, and Child Controls on a TForm are fully painted? I'm basically looking for the equivalent of the onload() event found in web pages but in the context of Delphi forms. Preferably this technique would still work even if some form elements aren't currently visible and also it would fire again after a form resize occurred. I've got some form element "jiggling" going on as different form elements auto-adjust their size and I'd like to turn off repaints until it's all done to get a cleaner look.
Delphi Controls have a BeginUpdate and EndUpdate procedure. Back when I did Delphi programming - it has been a while - we would want to load a grid that took a lot of data, we would call BeginUpdate before the load and then EndUpdate after load finished. This told the control to quit receiveing messages (ie. scrolling as new records loaded) and wait to update at one time - making everything "look" faster. You may be able to set controls to BeginUpdate mode until re-paint finish then EndUpdate to sync everything up.
Take a look at DoubleBuffered property:
Determines whether the control’s image
is rendered directly to the window or
painted to an in-memory bitmap first.
Delphi syntax: property
DoubleBuffered: Boolean; Description
When DoubleBuffered is false, the
windowed control paints itself
directly to the window. When
DoubleBuffered is true, the windowed
control paints itself to an in-memory
bitmap that is then used to paint the
window. Double buffering reduces the
amount of flicker when the control
repaints, but is more memory
intensive. When a windowed control is
a dock site and has an associated dock
manager, it must be double-buffered.
Note: Some controls, such as
TRichEdit, can’t paint themselves into
a bitmap. For such controls,
DoubleBuffered must be set to false.
Related
I have noticed something very strange. I am persisting the top, left, width, and height properties of a form when it is closing, and using this information to restore the form's last position when it is once again opened by calling SetBounds using the previously stored information. This works well, but only if the form's Position property is set to poDefault at design time. If set to something else, such as poDesigned, poScreenCenter, or poMainFormCenter, SetBounds does not restore the form's previous position and size.
Here's the strange part. What appears to matter is what the Position property is set to at design time. I can change the value of this property at runtime to poDefault and the call to SetBounds still does not work correctly. I have tried something like the following
if Self.Position <> poDefault then
Self.Position := poDefault;
in both the form's OnCreate event handler, as well as from an overridden constructor (and have set Position to poDefault in the constructor, and called SetBounds in the OnCreate event handler). In all cases, changing the form's Position property to poDefault at runtime does not fix the problem that I've observed with SetBounds. The only consistent pattern that I have found is that SetBounds works as it should only if the form's Position property was poDefault at design time.
There are other things that I've noticed with respect to how SetBounds works when a form's Position property is not set to poDefault at design time. For example, a form whose Position property is set to poScreenCenter at design time will not necessarily appear centered on the screen if you call SetBounds. However, it does not appear in the top-left location defined by SetBounds, nor does it respect the width and height specified in the call to SetBounds. Let me repeat, however, that I am setting the Position property of the form to poDefault before calling SetBounds. I've even stuck a call to Application.ProcessMessages between the two operations, but that doesn't fix the problem.
I have tested this extensively with Delphi 10.1 Berlin running on Windows 10. I have also tested it using Delphi XE6 on Windows 7. Same results.
If you have doubts, create a VCL application with four forms. On the first form place three buttons, and add something like the following OnClick to each button:
with TForm2.Create(nil) do
try
ShowModal;
finally
Release;
end;
where the constructor creates TForm2, then TForm3 and TForm4.
On the OnCreate of forms 2 through 4, add the following code:
if Self.Position <> poDefault then
Self.Position := poDefault;
Self.SetBounds(500,500,500,500);
On form2, set Position to poDefault, on form3 set Position to poScreenCenter, and on form4 leave Position set to the default, poDefaultPosOnly. Only form2 will appear at 500, 500, with a width of 500 and a height of 500.
Does anyone have a logical explanation for this result?
poDefault and friends mean "let Microsoft Windows position this form's window when the form would create and show it".
You just created Delphi object - but I wonder if it also has created/shown Windows object (HWND handle and all corresponding Windows internal structures). Especially with themed applications, not ones using standard pre-XP look and feel - they tend to ReCreateHWND when showing, because pre-loading those fancy Windows Themes is relatively expensive operation and only should be done when needed.
I think your default bounds (every property value set in the constructor might be considered a default non-tuned value, to be tuned later after object being constructed) are correctly ignored when you (or TApplication - that makes little difference for the topic) finally do FormXXX.Show.
It is during "make me a window and display it" sequence when your form looks at its properties and tells to MS Windows something like "now I want to create your internal HWND-object and position it at default coordinates/size at your discretion".
And that is absolutely correct behaviour - otherwise WHEN and HOW could TForm apply the Position property??? It just makes no sense to ask Windows for coordinates of a window that does not exists on the screen yet and maybe never would. Windows offers default coords/sizes for the this very second it being asked, looking how many other windows are there and where they are positioned ( and AMD/NVidia video drivers might also apply their correction to it).
It would make little sense, to acquire defaults now, and apply them two hours later when everything would probably be different - different amount of other windows and different positions of those, different set of monitors attached and with different resolutions, etc.
Just consider a "desktop replacement" type of notebook. It was set upon the table connected to large stationary external monitor. Then - let's imagine it - I run your application and it created the tform Delphi object and in the constructor it asked MS Windows for position - and Windows rightfully offered the position at that very secondary large monitor. But then an hour later I unplugged the notebook and walked away with it. Now an hour later I tell your application to show the form - and it will do what? display it with coordinates belonging to that now-detached external display? Outside of the viewport of the notebook's internal display that I only have at the moment? Should this form be displayed in the now "invisible" position just because when I started the application back then that spot was still visible there yet??? Way to confuse users for no gain, I think.
So the only correct behaviour would be to ask Windows for default coords this very second WHEN the form is going from hidden to visible and not a second earlier.
And that means that if you want to move your form - you should do it after it was being show. Place your Self.SetBounds(500,500,500,500); into OnShow event handler. So let the MS Windows materialize your form into default position like required by poDefault in Position property - and move your Window after that. Attempts to move the window that does not exist yet look correctly futile to me.
Either PRESET your form ( in constructing sequence) to explicitly ignore MS Windows defaults and use pre-set cords (via poDesigned value), or let the form ask Windows coordinates, but MOVE it with SetBounds after it got visible via OnShow handler.
Is it possible to paint a TProgressBar on a TSpeedButton, behind text and icon ?
I have no idea how to get started on this (assuming it's possible).
How would I go about it ?
In this particular case I use the button to start and stop a process, and it would be nice to display the process in that button as well.
No, this is not possible with the standard TSpeedButton without creating your own descendant.
TSpeedButton does all of its drawing in response to the WM_PAINT message, and there is no way for you to inject another control behind the content that is drawn, because the drawing would erase the area where your control is drawing itself. You can see this yourself; you have the source code for TSpeedButton in almost every Delphi and C++ Builder version.
In addition, a TSpeedButton is a graphical control, not a windowed control (it derives from TGraphicControl instead of TWinControl), so it does not have a window handle to be used as the parent for other controls.
My goal is to create a simple forms editor like the one that we find on Delphi IDE.
Right now the user can select and add the components making it parent of a TPanel that is the holder of the form. For simplicity, please consider also TPanel as the visual components added to the form.
I have 2 missing parts I want to find out ideas/code to help complete:
1 - how to move the created visual component? The same effect that in IDE for moving the visual component, for example Tpanel, around, chaning its top and left position
2 - how to draw that hooks for the component with focus on the form editor
3 - how to resize using the hooks
I only want the part related to handle the visual part. I am not generating DFM or anything like that.
Simply put your moving code needs to do this:
When the mouse goes down, check if the mouse position is over a control that can be dragged. If so, then set a variable named FDragControl to refer to that control. This code lives in an OnMouseDown event handler.
When the mouse moves, if FDragControl is not nil, move the control. This code lives in an OnMouseMove event handler.
When the mouse goes up, set FDragControl to nil.
That's pretty much all there is to it. The main nuance is that you must also remember the X, Y values of the mouse when the drag commenced. So in your OnMouseDown handler you write:
FStartMousePos := Point(X, Y);
FStartDragControlPos := Point(FDragControl.Left, FDragControl.Top);
And then in the OnMouseMove your position code reads:
FDragControl.Left := FStartDragControlPos.X + (X-FStartX);
FDragControl.Top := FStartDragControlPos.Y + (Y-FStartY);
You will also need to capture the mouse when you start dragging.
The resizing code is similar. Again, you need to decide in the OnMouseDown that you are resizing rather than dragging, but the code still involves handling mouse down, move and up events.
As for painting, you need to force a repaint whenever one of your event handlers changes a property that will influence the visual appearance of your form. You can use the value of FDragControl to decide whether or not to use special drawing of your control and indicate that it is being dragged. And likewise for resizing.
I've not coded up a full working implementation since your question is high level and conceptual. The implementation is down to you.
// I have made this an answer as I have just read your latest update which really should have been made as an edit to your original question but, anyway.
You can download the Cindy Components Pack and use the cyResizer Component which will do pretty much everything you need and is very customisable as well.
You can download it from here: http://sourceforge.net/projects/tcycomponents/
Searching more for an answer I could find these articles:
How to Move and Resize Controls at Run Time
http://delphi.about.com/library/weekly/aa102505a.htm
How to Add Size Handles to Controls being Resized at Run-Time
http://delphi.about.com/library/weekly/aa110105a.htm
Pretty much with all the information to complete this task with source code example.
These articles show how to implement and use a TMover class. I have done it and work correctly.
I have also downloaded the TcyComponents Pack and used the TcyResizer. It is a full featured form editor with pretty much everything that is required for a Delphi like forms editor. I recommend. It comes with source code and works fine with XE2 version.
To thwart the nit-pickers, let me start with, I searched here with this and could not find an answer, and yes, also I did scroll through the "Similar questions."...
Adding shortcuts to a TForm
I want to drag and drop some shortcuts from the Desktop to a TForm in my application. I am using Anders Melander's brilliant Drag Drop Suite (DDS).
I tried putting a TImage on the form but the DDS does not drop to an Image so I added a TPanel with a TImage on it. I could then drop on the panel and assign the image to the TImage.Picture. Problem was the Panel has no Transparent Property so the shortcut on the form looks clunky with the visible Panel behind it.
I need to be able to drop to the TImage or make the underlying TPanel transparent.
Can anyone help with code-snippets for either of those options, or better yet, a method of dropping a Shortcut directly on to my Form.
Thanks
Coincidentally I needed to make a TWinControl (the base for every visible control with a window handle, including TPanel) transparent. I found numerous results and applied them to this answer.
It's been a while since I implemented drag and drop, but I assume you call some API and pass it the handle of the panel? That answers the question why you can't use TImage. TImage is a graphic control, a control without a handle, that relies on its parent for recieving messages and drawing itself.
It should be possible to use the form, though, since that has a handle too.
If the TImage is directly on the TForm, then let the TForm handle the drop, no TPanel needed. OLE Drag&Drop operations (which Ander's components implement) provide coordinates where dragging and dropping occurs. The TForm should be able to detect when a drag is over the area occupied by the TImage and what type of data is being dragged, and only allow dropping of supported types within that area, extracting the dropped data and updating the TImageas needed, and denying anything else that does not match that criteria.
I have a form with several splitters and panels. In the middle is a panel that ends up with a TWebBrowser set to align alClient.
In the past this has worked well. However, on Windows 7 with Internet Explorer 8 the the browser is not correctly resized. Everything else (i.e. the panels) are the correct size, just not the web browser. Sometimes when you click into the browser, or more often when you scroll the browser will jump to the correct size. That does not happen 100% of the time though.
I'm trying to deal with the resize directly and force the control to change size. I can't seem to find a method that makes any difference (i.e. .Invalidate; .Repaint; .Update;)
TWebBrowser is an OLE control (ActiveX) that wraps the Internet Explorer control. Any ideas on how I can make the resize happen?
Updated Background:
I narrowed this down to only happening when I have a child form that I change the parent to site it inside another form. My TWebBrowser control is on a child form that I use anytime I need to show an HTML document.
In my parent form I have a Grid, a splitter, and a panel with the grid set to Align top and the panel set to align client. My child form (called THtmlViewer) has its parent set to the panel. The THtmlViewer form is set to alClient and TWebBrowser control on the child form is also set to align client.
If I do anything in the form resize of the THtmlViewer form I run into this issue. Anything being directly in FormResize or indirectly using the align properties. However, if I call my resize from the parent form everything works fine.
The key seems to be turning off the resize code in the THtmlViewer form. If I leave a OnResize handler or the alignment set then it does not matter what I do in the parent, the resize is not done correctly.
I am still confused why this is needed and why I can't force it to update in the correct resize (THtmlViewer).
As a side note I also noticed that Delphi 2007 on Windows 7 has the exactly same problem with the "Welcome Page" in the IDE.
I have worked around the issue by adding a method to set OnResize of the child form to nil and then call my internal ForceResize from the parent's OnResize handler. I would still like to deal with this all from the THtmlViewer form and would accept an answer that let's me avoid this hack.
To resize controls when their container changes size, you have two other options besides using "just" the Align property:
Use the Anchors property without the Align property, that is, set the Align property to alCustom or alNone, then set the anchors according to your needs. As the Align property is sort of short-hand for setting the Anchors, this is dependent upon the same alignment processing so may not work in your case.
Provide a handler for the OnResize event of the container on which the TWebBrowser sits and manually set the TWebBrowser's height and width to those of the container (optionally adjusted for any margins you want to have within the container).
That said, I don't have Windows7, so I can't vouch that this won't take more twiddling.
If your TWebBrowser jumps to the correct size on a scroll or click, it indicates that the control was sized correctly, just never received or processed the messages to actually respond to the changes.
This can be because realigning controls tries to prevent unnecessary repaints and recursions and somehow got mixed up. Using the OnResize method may then get better results as it "speaks to the TWebBrowser directly".
Repaint is "just" shorthand for Invalidate and Update unless the control has csOpaque in its ControlStyle, in which case painting seems more immediate. So you could try to fiddle with the Transparent property of the TWebBrowser and/or the container it sits on.
If all else fails, you could try to send a WM_PAINT (or similar) to the TWebBrowser directly. For exmample in the OnResize event of the TWebBrowser or the container it sits on. The a WM_Paint handler of the TWebBrowser calls OleDraw, which talks to the ActiveX directly using the ClientRect, which should have the correct dimensions when called from an OnResize event.
I had a similar problem.
It was solved by putting a TPanel "underneath" the TWebBrowser, and aligning the web browser to alClient.
Maybe the same solution for this question works: Resize problem using AcroPDF in Delphi
In OnResize:
if Visible and (WebBrowser1 <> nil) then
begin
FocusControl(nil);
FocusControl(WebBrowser1);
end;
I'd try something like
changing WebBrowser1.Align to alLeft,
setting WebBrowser1.Width to WebBrowser1.Width - 1 and
setting WebBrowser1.Align to alClient again
in an OnResize handler.