aehimself 396 Posted October 17, 2019 Hello, I already used WM_SETREDRAW to disable flickering when resizing / loading components successfully, however today I faced an interesting issue. I created a TForm descendant (DoubleBuffered, if it makes any difference) with .BeginUpdate and .EndUpdate methods as follows: Procedure TMyForm.BeginUpdate; Begin SendMessage(Self.Handle, WM_SETREDRAW, Ord(False), 0); // LockWindowUpdate(Self.Handle); End; Procedure TMyForm.EndUpdate; Begin SendMessage(Self.Handle, WM_SETREDRAW, Ord(True), 0); Self.Repaint; // LockWindowUpdate(0); End; I am trying to create an "expandable" dialog box, where there is a button to show additional information about the message (if available). It looks like this: It contains three elements: a top panel with alTop (with the alLeft picture and a alClient caption), a bottom panel for buttons with alBottom and an alClient memo. Expanding / collapsing logic is: ExpandCollapseButton.Glyph.Assign(nil); If _expanded Then Begin ImageList.GetBitmap(1, ExpandCollapseButton.Glyph); Self.Constraints.MaxHeight := 0; Self.Height := _savedheight; Self.Constraints.MinHeight := Self.Height - ExtendedMessageMemo.Height + 21; End Else Begin ImageList.GetBitmap(0, ExpandCollapseButton.Glyph); _savedheight := Self.Height; Self.Constraints.MinHeight := 0; Self.ClientHeight := TopMessagePanel.Height + ButtonsPanel.Height; Self.Constraints.MinHeight := Self.Height; Self.Constraints.MaxHeight := Self.Height; End; Everything works like a charm until I try to wrap this block in the .BeginUpdate / .EndUpdate method. The form is alive (reacts to keypresses; even expands itself if I assign the expand method to a keypress), but unclickable, unmovable... like if the ending WM_SETREDRAW would have never been called. Both SendMessage functions returns with success. Extra: if I swap the ending SendMessage with PostMessage, the form works normally, only the moving parts are not repainted (in my case the memo and the button panel); guess this is because the .Repaint is performed before the message is processed. I already tried sending a WM_PAINT, a simple .Invalidate instead of .Repaint, even repainting the whole screen without success. The question is obvious. What am I overlooking, why WM_SETREDRAW is working fine with everything else, but a form? 🙂 Cheers! Share this post Link to post
PeterBelow 238 Posted October 17, 2019 Just a guess: check the value of self.handle in both methods to verify they are the same. VCL forms are prone to recreate their window handle at the drop of a hat... Share this post Link to post
Anders Melander 1783 Posted October 17, 2019 (edited) The window handle is probably getting recreated between BeginUpdate and EndUpdate. What Peter said 🙂 Edited October 17, 2019 by Anders Melander Share this post Link to post
aehimself 396 Posted October 17, 2019 @PeterBelow, @Anders Melander Good point, I tend to forget about this. However in this case the handle seems to be the same, even when during the destructor. Share this post Link to post
Anders Melander 1783 Posted October 17, 2019 Try this then: type TMyForm = ... private FUpdateCount: integer; FLockedHandle: HWND; end; procedure TMyForm.BeginUpdate; begin Inc(FUpdateCount); if (FUpdateCount = 1) then begin FLockedHandle := Handle; SendMessage(FLockedHandle, WM_SETREDRAW, Ord(False), 0); end; end; procedure TMyForm.EndUpdate; begin Dec(FUpdateCount); if (FUpdateCount = 0) and (FLockedHandle = Handle) then begin SendMessage(FLockedHandle, WM_SETREDRAW, Ord(True), 0); RedrawWindow(FLockedHandle, nil, 0, RDW_FRAME or RDW_INVALIDATE or RDW_ALLCHILDREN); end; end; Share this post Link to post
aehimself 396 Posted October 17, 2019 (edited) Unfortunately no joy for me, window is still frozen on screen. All logic executes only once and goes in the SendMessage block on first try. This message box is displayed with .ShowModal, if that can affect the functionality. Edit: I also saw that 'ownerless' windows can behave like this; and my dialog is created from a class procedure: Class Procedure TMyDialogForm.ShowMe; Var mdf: TMyDialogForm; Begin mdf := TMyDialogForm.Create(nil); Try [...] mdf.ShowModal; Finally FreeAndNil(mdf); End; End; Unfortunately though, changing nil to Application.MainForm had no effect. Edited October 17, 2019 by aehimself Share this post Link to post
Anders Melander 1783 Posted October 17, 2019 The owner doesn't matter. Specifying nil here is just fine. FreeAndNil in not necessary since the variable is local. The only thing I can think of is if you are using some sort of skinning/styles that might have hooked into the paint mechanism. Personally I would just get rid of the Constraints, set TForm.AutoSize=True and Show/Hide the panel with the memo. The user won't be able to resize the dialog but at least the memo has scrollbars. Share this post Link to post
aehimself 396 Posted October 17, 2019 I know that FreeAndNil is unnecessary here, I just personally use it instead of .Free every single time. A few cycles here is better than a possible false Assigned check somewhere else 🙂 I do use VCL Styles in my application, but starting with a fresh project and importing only the message dialog unit has the exact same results. I'll try to strip it down further, as unfortunately the window has to be resizable 😞 Share this post Link to post
Anders Melander 1783 Posted October 17, 2019 49 minutes ago, aehimself said: I just personally use it instead of .Free every single time. Not a good idea IMO. I use it rigorously on member variables to catch stale pointers but I consider the consequences every time I use it. Sometimes it should not be used: https://www.devexpress.com/Support/Center/Question/Details/T812550/race-condition-in-threaded-load-can-cause-access-violation Anyway, I tried to reproduce your problem but couldn't. I've attached my test case. FWIW there was no flicker to eliminate in the first place... Unit5.pas Unit5.dfm Share this post Link to post
aehimself 396 Posted October 17, 2019 (edited) Ooooooookay. Progress. I'm calling the expand logic upon creating and closing the form to set / reset the dimensions. Procedure TForm2.FormClose(Sender: TObject; Var Action: TCloseAction); Begin Self.Visible := False; SendMessage(Self.Handle, WM_SETREDRAW, Ord(False), 0); SendMessage(Self.Handle, WM_SETREDRAW, Ord(True), 0); End; procedure TForm2.FormCreate(Sender: TObject); begin SendMessage(Self.Handle, WM_SETREDRAW, Ord(False), 0); SendMessage(Self.Handle, WM_SETREDRAW, Ord(True), 0); end; If you execute this window with ShowModal, it will freeze upon opening. If you execute it with .Show, it will never disappear (despite having it's .Visible property at false) but remain unpainted for it's lifetime. The problem is calling these upon creating and closing the form. I just don't know why. Yet. Edit: The flicker is there because of the OnClose logic, as I was expanding the window and it popped full-size for a moment before disappearing. The whole story started there, and however it can be done by hiding it before the logic, it disturbs me A LOT on why this method is not working 🙂 Edited October 17, 2019 by aehimself Share this post Link to post
timfrost 78 Posted October 17, 2019 22 minutes ago, aehimself said: The flicker is there because of the OnClose logic Have you tried setting caHide in the Action parameter in your FormClose method? Share this post Link to post
aehimself 396 Posted October 17, 2019 1 hour ago, timfrost said: Have you tried setting caHide in the Action parameter in your FormClose method? No, I did not, as caHide is the default value of Action. The reason why the form pops back up can be found on MSDN: "If the application sends the WM_SETREDRAW message to a hidden window, the window becomes visible (that is, the operating system adds the WS_VISIBLE style to the window)." Maybe this is the reason why it fails to initialize in Modal...? Share this post Link to post
aehimself 396 Posted October 17, 2019 WM_SETREDRAW adds the WS_VISIBLE style, but this does not trigger the .Visible property to change. Since according to Delphi the window is not visible it does not even try to hide it - this is why the form stays visible forever. This is why I love to code, we always learn something new: During the OnCreate event the form is invisible. Forcing it visible without being fully created (property loading takes place in a later stage) seems to render it in an unusable state... like it does receive the enable redraw message, but omits it without processing... or something. Removing these calls from the OnCreate and OnClose method solves the issue completely. Verdict: do not use WM_SETREDRAW in any form during construction or destruction. I'll just stick to hiding it before the final expansion to eliminate the flicker, but it's good to know what we can and cannot do with WM_SETREDRAW. Share this post Link to post
David Heffernan 2345 Posted October 17, 2019 This is the wrong way to deal with flicker. Do it the right way and this problem vanishes. Share this post Link to post
aehimself 396 Posted October 18, 2019 12 hours ago, David Heffernan said: This is the wrong way to deal with flicker. Do it the right way and this problem vanishes. Agreed, this is why I decided not to go with this route after all. But, you also have to admit that this was a funny little thing and it might help others later on! Share this post Link to post