Creating a dynamic Delphi form - ensuring that mouse messages are handled correctly

I have an app layout based on a treeView on the left and a panel on the right. The panel contains a different TForm class depending on the selected tree node (a kind of "explorer"). Only one form is rendered, which displays the underlying data stored elsewhere, and the form is instantiated and destroyed on every new node tree.

This all works fine except for the following scenario. Click a button on the form that triggers an action that takes a second or so. During this action, a call to Application.ProcessMessages may appear. Now, shortly before completing this action, the User clicks the new tree node. This wmMousedown message is processed so that the form is immediately released. The action code then returns to the form code to find that self has changed and calls AV.

My question is, is there a way to know that all of the form's messages have been processed and finished before I allow the form to be released? Modals seem to do this when the close button is clicked, because they pause until closed if busy ...

Thanks Brian

+2


source to share


5 answers


To answer the actual question in the last paragraph ... :)

If you call Release on a form, this posts a message to the form, which will cause it to " Free " when it receives that message.



Since the message is message ed to the message queue, it will only arrive after all / all other current messages for that form have been processed, neatly reaching exactly what you are asking for, I think.

+4


source


Avoid using Application.ProcessMessages at all costs!

The most common misuse I see for this is to let the GUI repaint for example. after updating the title of the label. The safest way to update the GUI is to explicitly redraw any affected controls using the Update method (which bypasses paint messages and forces the control to redraw directly). Or it could be the Update method - or it could be one or both! It's sad to say that I can never remember my head!

Application.ProcessMessages causes "reentry" problems, as you've found, effectively creating potential new, short, "basic message loops" within your code that can lead to complex diagnostics and difficult to reproduce problems.



In this case, I would consider using Application.ProcessMessages and see if an alternative approach could be developed to exclude its use from your code, instead of trying to fix a lot of other things to deal with the problems caused by Application.ProcessMessages .

NOTE. One exception to the rule is that using Application.ProcessMessages to maintain a responsive Cancel button in a progress message / dialog is relatively safe as long as that progress / dialog is modal and the rest of your application is efficient disabled while this dialog is presented, so only that the Cancel button can respond to any messages

+7


source


Inject the IsBusy property into each class of the placed form (using inheritance is to implement this property in the parent form). Before deleting a form from the hosting panel (whether by calling "Free" or just navigating from the panel), check if its IsBusy is not correct. If your posted form is busy, wait for it to finish and then delete it. You can even add a way to notify the posted form so that it interrupts its long running task.

This will not only help your current problem, but you will also be able to misinform some of the business logic inside your forms.

So the code for changing the shape in your TreeView should have the following code inside:

    {FCurrentForm is a reference to currently placed form on panel}
    if (FCurrentForm.IsBusy) do
    begin
      {remember some information that will be used to create new form}
      FNewFormToBeAdded := ... 
    end
    else
    begin
      FreeCurrentForm();
      PlaceNewFormOnPanel();
    end;

      

So you should have some kind of routine like:

   procedure THostForm.NotifyMeAboutTaskFinished;
   begin
     if FNewFormToBeAdded <> 0 than 
     begin
       FreeCurrentForm();
       PlaceNewFormOnPanel();
     end; 
   end;

      

And in your HOSTED form, you can have

procedure TSomeHostedForm.btnDoLongTaskClick(Sender : TObject);
begin
  IsBusy := true;
  try
    {... do some tikme taking task ...}
  finally
    IsBusy :=false;
    NotifyHostingFormIAMNotBusyAnymore();
  end;
end;

      

+1


source


Your TreeView.OnClick can call the CloseQuery method of the currently active form. If CloseQuery returns false, don't change them. Then you can parse the standard CloseQuery in Forms that need it.

In the forms displayed on the panel, you will need to track some state to see if you can actually close or not. I have my long running processes checking the stop status as well. I usually have a cancel button, but any call to CloseQuery will also interrupt the long running process. My CloseQuery usually looks like this:

procedure TBatchPoster.FormCloseQuery(Sender: TObject; var CanClose: Boolean); begin inherited;

  if FProcessRunning = true then
  begin
    FStop := true;
    CanClose := false;
  end;
end;

      

code>

Typically, if the user clicks once and nothing changes, by the time they try to click a second time, the lengthy process is stopped and the second click succeeds in reshaping.

This is actually the same as smok1's answer, but since you are already using the form, you don't need to add a new property.

0


source


While I agree with both "Deltics" answers, there is another option - depending on whether you need to free the form.

On the FormClose form, the event sets the caHide action. This will hide, not destroy the form. You will need to keep track of which forms you assigned (perhaps using the "Data" pointer in the TTreeNode).

0


source







All Articles