I saw the introduction of the TActivityIndicator in Delphi 10 Seattle and thought cool I could make use of this somewhere. I wanted to use this to show that some dynamically created sections of my form were still loading the data before populating the form. So I thought I'd do this before I start loading my data in FormShow, where self is the form.
indicator := TActivityIndicator.Create(self);
indicator.IndicatorSize := TActivityIndicatorSize.aisLarge;
Sadly when I try to create them dynamically and set then TActivityIndicator.IndicatorSize I get an exception ... EInvalidOperation with message 'Control '<name>' has no parent window' Which stepping through the VCL takes me to Vcl.Controls TWinControl.CreateWnd specifically
if (WndParent = 0) and (Style and WS_CHILD <> 0) then
if (Owner <> nil) and (csReading in Owner.ComponentState) and
(Owner is TWinControl) then
WndParent := TWinControl(Owner).Handle
else
raise EInvalidOperation.CreateFmt(SParentRequired, [Name]);
I've checked Owner is the form which is of course a TWinControl but (csReading in Owner.ComponentState) returns false. Stepping through Owner.ComponentState = [] on FormCreate and [csFreeNotification] on FormShow.
I've found that if you try to change the IndicatorSize of a TActivityIndicator that was created at design time then it works perfectly. So what am I missing here or is it not possible to create TActivityIndicators at runtime?
The error message is pretty clear. You need to assign a Parent on which the activity indicator will draw itself. The Owner is the component responsible for freeing the control when the Owner is destroyed; the Parent is the control on which the control will be drawn (parented) for display.
The solution is to assign that parent in code:
Indicator := TActivityIndicator.Create(Self);
Indicator.Parent := Self; // <-- here
// Set any other properties here
The same issue is common on all visual controls (such as TEdit, TLabel, TMemo, and so forth), which all need to have a Parent assigned in order to have a place to paint themselves. And in some cases, a Parent is required in order for various properties in the child control to function correctly when they depend on the child having an HWND window, which requires a Parent window, and so on.
If I understand your intent, I think you're going to be disappointed, however. TActivityIndicator is pretty static; it's not threaded, which means it will cease updating if your form is busy and doesn't process timer messages (which it uses internally).
Related
Good evening guys.
I'm currently designing a social networking client for Twitter and Facebook in Firemonkey FM2 (delphi) and I'm experiencing a frustrating issue. At present, I've only got the Twitter code in process, but the issue is related to the [re]drawing of visual objects.
I've created a custom-styled TListboxItem layout in a stylebook consisting of multiple child components such as TText, TButton, and TImage. I've already dealt with connecting to Twitter and retrieving feed details. Each item retrieved is added to a TListbox and styled using my custom ListboxItem style layout.
Now, the issue is related to updating information on items in the list that aren't visible. For example, the items that are visible in the list without scrolling show their information correctly. Those that aren't visible besides the final item in the list have several of their details not set/visible. When I scroll the list downwards, and then back up, there's often 1 of the items that was originally visible will now be missing it's information.
To explain this a little more, i've got a TImage (known as photo) which is used to show the photo of the person who posted the 'tweet'. I've got the standard TText (known as text) used to show the contents/text of the tweet itself. I've got 2 buttons (known as Like and Share) used to perform their respective functions. I've then finally got another TText (known as NameDate) used to show the name of the tweeter and the date the tweet was posted.
I'm using this code to create the object and modify the data it shows;
for i := 0 to TwitObj.Statuses.Count-1 do
begin
FeedItem := TListBoxItem.Create(LBFeed);
FeedItem.Parent := LBFeed;
FeedItem.StyleLookup := 'FeedItem';
FeedItem.WordWrap := True;
FeedItem.StyledSettings := [TStyledSetting.ssFamily, TStyledSetting.ssSize, TStyledSetting.ssStyle, TStyledSetting.ssFontColor, TStyledSetting.ssOther];
NameDate := Feeditem.FindStyleResource('txtnamedate') as TText;
Photo := FeedItem.FindStyleResource('photo') as TImage;
Like := FeedItem.FindStyleResource('btnlike') as TButton;
Share := FeedItem.FindStyleResource('btnshare') as TButton;
Share.Text := 'Retweet';
Like.Text := 'Favorite';
NameDate.Text := Twitobj.Statuses.Items[i].User.Name +
'(#'+TwitObj.Statuses.Items[i].User.ScreenName+
') - '+DateTimeToStr(TwitObj.Statuses.Items[i].CreatedAt);
FeedItem.Text := TwitObj.Statuses.Items[i].Text;
begin
if DirectoryExists('imagecache\') = false then CreateDir('imagecache\');
if FileExists('imagecache\'+TwitObj.Statuses.Items[i].User.ScreenName+'.jpg') = False then
begin
try
rcv := TMemoryStream.Create;
GtPhoto.URL := TwitObj.Statuses.Items[i].User.ImageURL;
GtPhoto.RcvdStream := rcv;
GtPhoto.Get;
rcv.SaveToFile('imagecache\'+TwitObj.Statuses.Items[i].User.ScreenName+'.jpg');
finally
Rcv.Free;
end;
end;
end;
Photo.Bitmap.LoadFromFile('imagecache\'+TwitObj.Statuses.Items[i].User.ScreenName+'.jpg');
GTPhoto is a standard ICS HTTP Client component, while TwitObj is my Twitter component. You can see that I'm saving the photo to a directory instead of streaming it. This was merely to check whether it was an issue with streams, but it's probably advisable to used a cache of some sort anyway.
The images download correctly, and the information for the relevant StyleResources in the custom ListBoxItem layout is updated as expected, but only for items that are visible without scrolling. If I scroll down the list, only the Text of each item is correct, while the other resources which were set at runtime have returned to the way they're designed in the stylebook (i.e. blank text, image, etc).
Am I missing something here? I understand the design intents of Bitmaps were changed in XE3 for the sake of performance, but surely Embarcadero wouldn't have overlooked something like this. Surely it's not expected for us to create each item inside the parent at runtime (and thus dealing with alignments and such) instead of using a stylebook resource, is it?
Any assistance or insight would be greatly appreciated.
FireMonkey can load and unload the style for a control at any moment. It was rather lax with this in FM1, but under FM2, styling elements are removed when a control is not visible and reapplied when it becomes visible again (in order to conserve memory, mainly in preparation for Mobile Studio).
What you need to do is override the ApplyStyle method. In it look up and set data in your style elements. This will probably mean that your control(s) need to cache what will be passed to the style.
Also note that if you are caching references to style elements (i.e. what you get back from FindStyleResource) then these will be freed when the style is unloaded and your pointers will be invalid. If so, you need to override FreeStyle and nil any pointers you may have cached.
In Lazarus I'm trying this:
TabSaveButton := TButton.Create(nil);
with TabSaveButton do
begin
Parent:=NewTab;
Width:=75;
Height:= 25;
Top:=530;
Left:=715;
Caption:='Save';
end;
And it works. I.e., I get the button and it's clickable, and it is the child of a dynamically created tab sheet.
But the following does not show the button, nor errors:
TabSaveButton := TButton.Create(NewTab);
with TabSaveButton do
begin
Width:=75;
Height:= 25;
Top:=530;
Left:=715;
Caption:='Save';
end;
Why does the second method not work?
Is this the same effect on both Lazarus and Delphi?
The argument of Create sets the owner of the control. The owner is the component responsible for freeing the component in question. For instance, if you free a component, then all components owned by it are also freed. The parent is a completely different thing. It is the window (control) hosting the control in question.
There is no difference between Delphi and Lazarus here.
I'm was trying to write a dll library in Delphi wih a function that creates an instance of a TFrame descendant and returns it. But when I imported this function in an application, every time I called it I would get an exception like "the 'xxx' control has no parent window". I'm not 100% sure, but the exception appeared in the constructor of that class when any of GUI controls was accessed.
Could you please tell me what the reason of that behaviour is? Should I just use TForm descendants instead or is there a better solution?
Thank you!
About the error
That error message is raised from the Controls.pas unit, from the TWinControl.CreateWnd method. Essentially that code is used to create the Window handle for your TWinControl descendant (TFrame, TButton, TEdit... if it can have keyboard focus it's an TWinControl descendant), and it's actually an very sensible error message: You can't have a Window without an WindowParent, and since we're talking about the VCL here, it makes a lot of sense to try and get the parent window handle from TWinControl.Parent; And that's not assigned.
That's not WHY the error message is popping up. You get to see that error message because some of the code you're using to set up the frame requires an Window handle for some operation. It could be anything, like setting the Caption of some component (that internally requires an window handle do to some calculation). I personally really hate it when that happens. When I create GUI's from code I try to delay the assignment of Parent as much as possible, in an attempt to delay the creation of the window, so I got bitten by this many times.
Specific to your DLL usage, possible fix
I'm going to put my psycho mind reader hat on. Since you need to return a FRAME from your DLL, and you can't return the actual Frame because that's an Delphi-specific object and you're not allowed to return Delphi-specific objects over DLL boundaries, my guess is you're returning an Window Handle, as all the nice API's do, using a function definition like this:
function GiveMeTheNiceFrame:HWND;
The trouble is, that routine requires the creation of the actual Window Handle, by a call to TWinControl.CreateWnd, and in turn that call requires an parent window handle to set up the call to Windows.CreateWindowEx, and the routine can't get an parent window handle, so it errors out.
Try replacing your function with something allong the lines of:
function GiveMeTheNiceFrame(OwnerWindow:HWND):HWND;
begin
Result := TMyNiceFrame.CreateParanted(OwnerWindow).Handle;
end;
... ie: use the CreateParented(AParentWindow:HWND) constructor, not the usual Create(AOwner:TComponent) and pass an owner HWND to your DLL.
There are a few important things to remember:
When using DLLs, both your DLL and your EXE each have an Application instance that are struggling for control. The Controls in your DLL will see the Application instance that belongs to the DLL; the Controls in your EXE will see the Application instance that belongs to the EXE. That struggle is not there when using packages, as then there will only be one Application instance.
Frames are Controls, but they are not Forms.
When using Controls in an application, they cannot visually exist without a parent Control (usually a Form or a container that has a parent hierarchy towards a Form).
Some Controls cannot expose their full functionality unless they exist visually and have a valid parent.
Try to reproduce your problem inside the EXE; if you cannot reproduce, it is probably the first thing in the above list.
--jeroen
Sounds like you simply need to assign the component (a form or part of a form, like a panel) that holds the frame to theframe.parent.
You cannot do GUI work before it is assigned. Frames are parts of forms for reuse, and normally need to assign some parent to them.
Move the GUI code to onshow or a procedure you call explicitely, so that the calling code can assign parent.
Or make the parent a parameter in the function.
I found this (CreateParams is called as part of CreateWnd):
procedure TCustomFrame.CreateParams(var Params: TCreateParams);
begin
inherited;
if Parent = nil then
Params.WndParent := Application.Handle;
end;
And Application.Handle = 0 so it always throws the error later in CreateWnd.
After reading this
Delphi: How to call inherited inherited ancestor on a virtual method?
I have solved it by overriding CreateParams in my frame to miss out the tCustomFrame version:
type
tCreateParamsMethod = procedure(var Params: TCreateParams) of object;
type
tMyScrollingWinControl = class(TScrollingWinControl);
procedure TDelphiFrame.CreateParams(var Params: TCreateParams);
var
Proc: tCreateParamsMethod;
begin
TMethod(Proc).Code := #TMyScrollingWinControl.CreateParams;
TMethod(Proc).Data := Self;
Proc(Params);
end;
Now it's just throwing errors when trying to set the focus on subcontrols, which I think I will fix by intercepting WM_FOCUS but we'll how it goes from here.
function CreateFrame(hwndParent: HWnd): HWnd; stdcall;
var
frame: tFrame;
begin
Result := 0;
try
frame := TDelphiFrame.CreateParented(hwndParent);
Result := frame.Handle;
except on e: Exception do
ShowMessage(e.Message);
end;
end;
You can avoid this message by assigning nil to the parent OnClose event, sometimes it works:
SomeControl.Parent := nil;//Before free your TControl
SomeControl.Free;
I think this is very cool solution. I think it is not tried before :)
I'm using a Dummy Parent (which is a Form).
function MyFrame_Create(hApplication, hwndParent:THandle; X, Y, W, H:Integer):Pointer; stdcall;
var Fr: TMyFrame;
F: TForm;
CurAppHandle: THandle;
begin
CurAppHandle:=Application.Handle;
Application.Handle:=hApplication;
//---
F:=TForm. Create(Application);//Create a dummy form
F.Position:=poDesigned;
F.Width:=0; F.Top:=0; F.Left:=-400; F.Top:=-400;//Hide Form
F.Visible:=True;
//---
Fr:=TMyFrame.Create(Application);
Fr.Parent:=F;//Set Frame's parent
//Fr.ParentWindow:=hwndParent;
Windows.SetParent(Fr.Handle, hwndParent);//Set Frame's parent window
if CurAppHandle>0 then Application.Handle:=CurAppHandle;
//---
Fr.Left:=X;
Fr.Top:=Y;
Fr.Width:=W;
Fr.Height:=H;
Result:=Fr;
end;//MyFrame_Create
procedure MyFrame_Destroy(_Fr:Pointer); stdcall;
var Fr: TMyFrame;
F: TObject;
begin
Fr:=_Fr;
F:=Fr.Parent;
Fr.Parent:=Nil;
if (F is TForm) then F.Free;
//SetParent(Fr.Handle, 0);
//Fr.ParentWindow:=0;
Fr.Free;
end;//MyFrame_Destroy
My application has many many mdi forms and they are created after successfull user login. How can I best hide this creation process? It looks stupid and it takes longer time while mdi forms are painted after new form is created and so on.
So far I have used LockWindowUpdate, which doesn't hide everything, but I would like to use a splash screen showing the creation progress, but I can't with LockWindowUpdate.
Best Regards
Janne
To create MDI child forms invisible you set their Visible property to False, and in addition you have to disable the VCL behaviour of force-showing them during creation. This happens by the FormStyle property setter of TCustomForm, which sets Visible to True for MDI child forms.
If you set the FormStyle in the object inspector, then the property setter will be called during form creation already, and the form will not be shown immediately, but only after the construction is complete. This allows you to reset the request to show the form, by overriding the AfterConstruction() method like so:
procedure TMDIChild.AfterConstruction;
begin
Exclude(FFormState, fsVisible);
inherited;
end;
This will create an invisible MDI child form.
To test this you can create a new MDI application in the IDE, override the method in the child form class like shown above, and simulate a long initialization:
procedure TMainForm.FileNew1Execute(Sender: TObject);
var
i: integer;
begin
for i := 1 to 10 do begin
CreateMDIChild('NONAME' + IntToStr(MDIChildCount + 1));
Update;
Sleep(500);
end;
for i := 0 to MDIChildCount - 1 do
MDIChildren[i].Visible := True;
end;
Without the overridden AfterConstruction() method it will create and show a MDI child every half second. With the overridden method it will show them all after a busy period of 5 seconds, which will give you the chance to show your splash screen instead.
Important:
Using LockWindowUpdate() to reduce flicker or suppress any screen output is wrong, wrong, wrong. Don't do it, read the series of Raymond Chen articles on the topic to understand why that is so.
I had a similar problem with flickering MDI childs. I used combination of overrinding AfterConstruction and WM_SETREDRAW message from this tip:
Controlling the placement of fsMDIChild windows in Delphi
SendMessage(Application.MainForm.ClientHandle, WM_SETREDRAW, False, 0);
try
Child := TChildForm.Create(Self);
Child.Left := ...;
Child.Top := ...;
Child.Show;
finally
SendMessage(Application.MainForm.ClientHandle, WM_SETREDRAW, True, 0);
InvalidateRect(Application.MainForm.ClientHandle, nil, True);
end;
And everything works fine.
try this code, it's work for me
try
SendMessage(Application.MainForm.ClientHandle,WM_SETREDRAW,0,0);
FormChild:=TBaseChildForm.Create(application);
FormChild.Caption:='Form '+IntToStr(n);
FormChild.Show;
finally
SendMessage(Application.MainForm.ClientHandle,WM_SETREDRAW,1,0);
RedrawWindow(Application.MainForm.ClientHandle, nil, 0, RDW_FRAME or RDW_INVALIDATE or RDW_ALLCHILDREN or RDW_NOINTERNALPAINT);
end;
How do I get the KeyDown event to work in a Delphi (2007) MDI Applications Parent window, even if a Child window has focus?
I would like to implement a shortcut key (F1) that brings up a help screen in a MDI application, I have added the KeyDown procedure to the MDI Parent window and enabled KeyPreview in both the Parent and Child windows, but it does not work as expected.
If I put a break point in the Parents KeyDown code I can see it never executes, even it there are no child windows open. But if I add the same code to the child window it works fine.
Is there a way to get the parent window to receive the key presses, even if the child window has focus, as adding the code to 25+ forms seams a little wasteful?
I had the exact same problem this week! I fixed it by creating an action in the ActionManager on the mainform. This action opens the help file and has the F1-key set as shortcut. It also works for all MDI child screens.
You could use a local (global is not needed) keyboard hook. You could also derive all your MDI Child forms from a signle form base class and implement it there once. You will find that this design comes in handy for other problems as well.
edit
Application wide hotkeys/shortcuts can also be implemented with the TApplication.OnShortCut event. See http://delphi.about.com/od/adptips2004/a/bltip0904_3.htm
F1 is already the standard help shortcut which triggers TApplication.OnHelp. So maybe you want to use the OnHelp event? And if you use the HelpFile, HelpContext, HelpType and HelpKeyword properties you probably don't even need to implement any code at all.
How do I get the KeyDown event to work in a Delphi (2007) MDI Applications Parent window, even if a Child window has focus?
As a more generic solution (for applications other than F1 for help) I use code similar to this to trap a keydown event in the main form. This gets all keys no matter what, even when an MDI child is active. In this example I'm doing the opposite of what you are trying to do (I want the message to be handled by my child form instead of the main form), but the concept of catching the keys in the parent is the same).
Application.OnMessage := AppMessage;
procedure TMainForm.Appmessage(var Msg: TMsg; var Handled: Boolean);
var
message: TWMKey;
begin
If (msg.message = WM_KEYDOWN) and
( LoWord(msg.wparam) = VK_TAB ) and
(GetKeyState( VK_CONTROL ) < 0 ) and
Assigned( ActiveMDIChild ) then
Begin
Move( msg.message, message.msg, 3*sizeof(Cardinal));
message.result := 0;
Handled := ActiveMDIChild.IsShortcut( message );
End;
end;
F1 help processing is built into Delphi, so all you have to do is handle the help messages properly. This may be as little as setting the helpfile property for the application. You can set particular pages using the form's help??? properties.
Basically, just use the help system supplied and forget keydown. This is Delphi - you don't have to work hard.