Jump to content

balabuev

Members
  • Content Count

    241
  • Joined

  • Last visited

  • Days Won

    2

Posts posted by balabuev


  1. 2 hours ago, pyscripter said:

    Could you tell us how?

    I've replaced the treeview with a frame to remove the mess with the second pair of unneeded scrollbar windows. I've simulated some resource allocation on WM_CREATE in a frame, which is then freed on WM_DESTROY, similar to tree view.

     

    • Run the demo.
    • Switch to any VCL Style.
    • Press "Bring to front" button.
    • Close the form

    Source.zip

     

     

    • Like 1

  2. 34 minutes ago, pyscripter said:

    Could you tell us how?

    I need some time to modify our demo to show this in one simple step. The problem is that we have currently four scrollbar windows, from which only two belong to list view (other two belong to tree view). This hides the bug, when they are not in the needed specific order relative to each other.


  3. 1 minute ago, Attila Kovacs said:

    Have to fire up a new form to check.

    Not necessary. During the runtime of the application you can just create and destory as many ListViews dynamically as you want. I can create and destroy 1000 ListViews in a loop. All on the same parent panel.

     

    Anyway, this is up to application. We cannot predict this.

     

     


  4. 3 minutes ago, Attila Kovacs said:

    Those are parented to the parent of the ListView and will be destroyed by the window manager

    We have actually two parts in each TScrollWindow:

    • Real window represented by window handle
    • Delphi object of TScrollWindow class.

    First one will be deleted by window manager, but the second - not. However, even the first one will be deleted only when the parent panel will be destroyed. Which is not acceptable as a good quality fix.

     

    I'm actually have somewhat similar idea: we should work on a slightly lower layer. Let's style hooks be destroyed like they are destroyed now. But, TScrollingStyleHook.Destroy should not destroy its owned TScrollWindow objects immediatelly. Instead it should hide them (make non-visible) and collect in some global list for later asynchronous destrution.

     

     


  5. 1 hour ago, Attila Kovacs said:

    if not (csDestroying in Control.ComponentState) then

    You cannot use this condition, because this effectively leaves the corresponding style hook in the FControls dictionary forever. So, you'll have a leak of style hooks.

     

    2 minutes ago, Attila Kovacs said:

    stylehooks are no windowed controls like the scrollbar

    Yes, but look at TScrollingStyleHook.Destroy method. It destroys FVertScrollWnd and FHorzScrollWnd owned objects, which are of TScrollWindow class.

     

     


  6. 19 minutes ago, Attila Kovacs said:

    cut nested calls to DestroyWindow 

    Did you checked it somehow? I think, this will not change anything, because we still destroy control's style hook. Which will internally destroy its own parts, like scrollbar windows.


  7. @pyscripter, forget about handles recreation in CM_STYLECHANGED. First, because it may happen by some other reason; recreation of handles is a part of VCL - nothing to do with that. Second, because this anyway does not fix the bug, since the user can change Z-order explicitly using ListView.BringToFront, for example.

     

    I can reproduce the bug even with this your workaround:

    procedure TListView.WndProc(var Message: TMessage);
    begin
      if (Message.Msg = CM_STYLECHANGED) then
      begin
        if ViewStyle = vsReport then
          UpdateColumns;
      end
      else
        inherited;
    end;

     

     

     


  8. 4 hours ago, Attila Kovacs said:

    If we skip one control it's fine again (4 buttons, freeing Nr. 3 on Tag1, not Nr.2).

     

    I can imagine a linked list iteration inside DestroyWindow function, like:

    cur := FirstChild;
    while cur <> nil do
    begin
      nxt := cur.Next;
      ProcessNode(cur);
      cur := nxt;
    end;

     

    If ProcessNode will (as in our case) recursively delete the next node from the list - the function will fail, because it already fetched the pointer to the next node into its local variable.

    However, if ProcessNode will delete the next next node (the node after next) - nothing bad will happen.

     

    56 minutes ago, pyscripter said:

    Shouldn't the ListView TScrollWindows be above the ListView after it gets recreated?

     

    I think it's not necessary. For example, when scrollbars are hidden these controls may stay untouched. On the other hand this may be the simplest fix.

     

     

     

    • Like 1

  9. Part 2:

     

    The demo with simple three buttons on a panel.

    • RunTestClick destroys the panel handle using DestroyWindow function.
    • Button1 is the first in Z-order, and thats why it will first receive WM_DESTROY message. In the message handler it will destroy Button2 recursively.
    • And the test shows, that in this case Button3 will not receive WM_DESTROY.

    ButtonsTest.zip

     

     

    • Like 4

  10. I've solved this puzzle :classic_tongue:

     

    In short - this is not about handles recreation, this is about handles destruction. And the order of destruction plays essential role here.

     

    First, I want to recall again, that the process starts in form's destructor, which calls DestroyWindow function. And as specified in MSDN, DestroyWindow will actually destory all children window handles recursively, and each of destorying window should receive WM_DESTROY message. It vital to understand that the destruction of a handle does not imply the destruction of the corresponding control. Control can exist without its handle. Opposite is not possible - each control destroys its own handle during the control destruction.

     

    So, from this viewpoint, form destruction is a two stage process:

    1. A single call to DestroyWindow in a form destructor deallocates all handles of all child controls on the form.
    2. And only after that, child controls are destroyed recursively.

     

    So, what is happening in the demo. Given the following panel child order:

     

    image.png.0ebcb5f3d44660b19a6f60ed60160b5a.png.070331aa9709c351a8cd674fdbcf8bdf.png

     

    • List view is the first child control of the panel, which will receive the WM_DESTROY message due to form's DestroyWindow call.
    • List view's TWinControl.WMDestroy message handler will eventually call the following: TStyleManager.Notification(snControlDestroyed, Self);
    • The above notification will force the style hook to destroy two owned TScrollWindow controls, which at this time have its handles allocated, and so, each of the TScrollWindow control, will call DestroyWindow recursively.

     

    So, this recursive calls of the DestroyWindow (where the nested function call destroys the window, which was already enqueued for destruction in outer function call) is the cause of the bug.

     

    This may be either: illegal usage of DestroyWindow function by Delphi or Windows bug. I don't know.

     

    Part two is comming, in which I'll reproduce this strange behavior with a simple three buttons placed on a panel (no relation to VCL Styles at all).

     

     

     

    • Like 2
    • Thanks 1

  11. Yet another evidence: It's sufficient simply to reorder children in Z-order for bug to disappear. And Z-order is the order in which EnumChildWindows function reports child windows. And so, it's most probably the order in which DestroyWindow function processes child windows.

     

    function MoveScrollWindows(hwnd: HWND; lParam: LPARAM): BOOL; stdcall;
    var
      c: TWinControl;
    begin
      c := FindControl(hwnd);
      if c is TScrollingStyleHook.TScrollWindow then
        SetWindowPos(hwnd, HWND_TOP, 0, 0, 0, 0, SWP_NOMOVE + SWP_NOSIZE);
      Result := True;
    end;
    
    procedure TMainForm.FormClose(Sender: TObject; var Action: TCloseAction);
    begin
      EnumChildWindows(ClientPanel.Handle, @MoveScrollWindows, 0);
    end;

     

    • Like 2

  12. 16 hours ago, pyscripter said:

    The issue results from the TListView being recreated as a response to the CM_STYLECHANGED message

    Good observation. And its quite strange that your workaround affects the issue, because:

    • In the demo you can switch between VCL Styles any times and, then, switch back to Windows style. Obviously, in this case CM_STYLECHANGED will be processed one or more times, but no bug will occurred an the end.
    • CM_STYLECHANGED does not received after form's close button click.

    So, there should be some resulting side effect - a difference in the application's state with and without workaround. So, I've decided again to look at the child windows of the panel. The code:

     

    var
      s: string;
    
    function EnumProc(hwnd: HWND; lParam: LPARAM): BOOL; stdcall
    var
      c: TWinControl;
    begin
      c := FindControl(hwnd);
      if c = nil then
        s := s + 'nil'
      else
        s := s + c.ClassName;
      s := s + #13#10;
      Result := True;
    end;
    
    procedure TMainForm.Button1Click(Sender: TObject);
    begin
      s := '';
      EnumChildWindows(ClientPanel.Handle, @EnumProc, 0);
      ShowMessage(s);
    end;

     

    With the system style:

    image.png.d1926889a258159a8be5d3c9e96ec329.png

     

    With VCL style:

    image.png.0ebcb5f3d44660b19a6f60ed60160b5a.png

     

    And with the workaround from @pyscripter:

    image.png.818d30b21793c0ffccdcb0c2c05ec3b8.png

     

    So, with workaround, we have at least different child windows order.

     

    • Like 2

  13. 2 minutes ago, Lajos Juhász said:

    It's enough to insert anything between ShowModal and Free that will call an Application.ProcessMessages

     

    Yes. You right. But the original code was:

    with TLogInForm.Create(nil) do
    try
      Result := ShowModal = mrOk;
    finally
      Free;
    end;

  14. 3 minutes ago, Attila Kovacs said:

    Who by?

    By Windows I guess. If some window becomes destroyed, all its pending messages are removed from the queue.

     

    5 minutes ago, Attila Kovacs said:

    It's a race condition

    No. Unless you explicitly call Application.ProcessMessages [potentially] in a loop.

     

     

×