Jump to content
aehimself

Form no longer repaints after WM_SETREDRAW

Recommended Posts

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:

 

image.png.45b43eb52e720ac2fbf55e4cb8e2d097.png

 

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

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

The window handle is probably getting recreated between BeginUpdate and EndUpdate.

What Peter said 🙂

Edited by Anders Melander

Share this post


Link to post

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

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 by aehimself

Share this post


Link to post

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

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
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.

image.png.996ba692c3e93469c7a38f14b34353e8.png

image.png.ae1662e9ee581a30641a7a1a56c00dee.png

 

FWIW there was no flicker to eliminate in the first place...

Unit5.pas

Unit5.dfm

Share this post


Link to post

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 by aehimself

Share this post


Link to post
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
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

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:

 

image.thumb.png.1de747f102db941c64ef5018cf90d594.png

 

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
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

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×