How can I use an action to determine a control's visibility?
Asked Answered
G

3

7

I'm trying to use an action to control the visibility of a control. My code looks like this:

Pascal file

unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, ActnList, StdCtrls;

type
  TForm1 = class(TForm)
    Button1: TButton;
    ActionList1: TActionList;
    Action1: TAction;
    CheckBox1: TCheckBox;
    procedure Action1Update(Sender: TObject);
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.Action1Update(Sender: TObject);
begin
  (Sender as TAction).Visible := CheckBox1.Checked;
end;

end.

Form file

object Form1: TForm1
  object Button1: TButton
    Left = 8
    Top = 31
    Action = Action1
  end
  object CheckBox1: TCheckBox
    Left = 8
    Top = 8
    Caption = 'CheckBox1'
    Checked = True
    State = cbChecked
  end
  object ActionList1: TActionList
    Left = 128
    Top = 8
    object Action1: TAction
      Caption = 'Action1'
      OnUpdate = Action1Update
    end
  end
end

When the form first runs the button is visible and the check box is checked. Then I un-check the check box and the button disappears. When I re-check the check box the button fails to re-appear.

I think the reason for this can be found inside the following local function in TCustomForm.UpdateActions:

procedure TraverseClients(Container: TWinControl);
var
  I: Integer;
  Control: TControl;
begin
  if Container.Showing and not (csDesigning in Container.ComponentState) then
    for I := 0 to Container.ControlCount - 1 do
    begin
      Control := Container.Controls[I];
      if (csActionClient in Control.ControlStyle) and Control.Visible then
          Control.InitiateAction;
      if (Control is TWinControl) and (TWinControl(Control).ControlCount > 0) then
        TraverseClients(TWinControl(Control));
    end;
end;

The check of Control.Visible appears to block my action ever getting a chance to update itself again.

Have I diagnosed the issue correctly? Is this by design or should I submit a QC report? Does anyone know of a workaround?

Gulosity answered 12/4, 2012 at 16:24 Comment(7)
Uhm, why are you using OnUpdate ?Lobectomy
@TLama: Because that is what it is for. Most often, I guess, you set Enabled rather than Visible, though.Charlenacharlene
This IS a bug, or at least a Misbegotten-Feature. You should be able to set Enabled and Visible on any action, and any linked control should be refreshed to contain that value of Enabled and that value of Visible. If ActionUpdate is required here, then it breaks the kind of decoupling you're trying to do. The checkbox now has to be coupled to the action. I would just STOP using OnUpdate for any changes to visible, and simply set the action visible or not visible, as a single line, in the checkbox onclick.Refine
Related to #8447627Voluptuary
@SveinBringsli Yeah, that's exactly the same question and my question is a dupe.Gulosity
A dupe it may be, but you did more research :)Voluptuary
Although the question may be a dupe, the answers here are much betterStandpipe
S
8

Your diagnosis is correct. Actions have worked that way since they were first introduced to Delphi.

I expect it's by design (an optimization to avoid excessive updating of the text and other visual aspects of invisible controls, probably), but that doesn't mean the design is good.

Any workaround I can imagine will involve your checkbox code directly manipulating the affected actions, which isn't very elegant since it shouldn't have to be aware of everything else it might affect — that's what the OnUpdate event is supposed to do for you. When the checkbox gets checked, call Action1.Update, or if that doesn't work, then Action1.Visible := True.

You could also put your action-updating code in the TActionList.OnUpdate event instead.

Subjective answered 12/4, 2012 at 16:43 Comment(1)
Thanks. I'm amazed I've not noticed this before but I'm reassured that you were already aware of the issue. I already have an override of TCustomForm.UpdateActions in my common base class form. So I can do work in there to make the updating happen the way I want it to. That's probably the easiest route for me.Gulosity
Y
4

Yes your diagnosis is correct as Rob already said and explained. A work around is not to use individual TAction's OnUpdate event handlers, but to use the TActionList's OnUpdate event handler.

procedure TForm1.ActionList1Update(Action: TBasicAction; var Handled: Boolean);
begin
  Handled := True;
  Action1.Visible := CheckBox1.Checked;
end;

You do need at least one visible control with a linked action for this to work, otherwise the ActionList's OnUpdate handler won't be called either.

Btw, when Actions and ActionLists were first introduced, Ray Konopka (Raize components) wrote a couple of articles on their implementation and gave very sound advice on how to use them. Ever since, I have adopted the practice of to use the OnExecute of each individual Action, but to use the OnUpdate of the ActionList. Also, the first thing I do in that handler is to set Handled to True so it won't be called more than necessary, and to only ever change an Action's Enabled property once in that handler so the GUI won't flicker as a result of turning it off and then on.

Article by Ray Konopka is "Effectively using Action Lists" : http://edn.embarcadero.com/article/27058 Ray used to have three articles on his own site, but on embarcadero there is but one, but it may well be the "combined" version (don't have the originals handy).

Ytterbia answered 12/4, 2012 at 19:6 Comment(1)
Thanks for this. I'll look up Ray's articles. I was never really aware of OnUpdate for the list but can imagine that it would result in a good centralisation of code.Gulosity
D
2

ActionUpdate event is not called when related controls are not visible. Try to explicity call ActionUpdate on Checkbox1's click event.

Dygall answered 12/4, 2012 at 16:35 Comment(8)
That's like saying "stop using actions".Gulosity
No David. Actions are good. But as Rob said, their design is evil in your scenario.Tenatenable
But the best solution to the problem cannot be coupling my GUI controls together. Avoiding that hell is why I started using actions in the first place.Gulosity
mmm... (Sender as TAction).Visible := CheckBox1.Checked; could be seem as coupling also. Transverse that line in the OnClick event of the Checkbox. Keep it simple! ;)Tenatenable
@ChristopherRamírez: The important difference is that Sender is an action, not a GUI control. This action may be used by zero, one, or twenty-four GUI controls.Charlenacharlene
OK that's a coupling. But what about all the other ways in which the checkbox might change state. What if there were two or three flags that determined visibility? And what about the initial setting when the form loads and when the check box clicked event does not fire? I remember life before actions. Not pretty.Gulosity
You're right @David. I don't know if Delphi's TCheckbox has a OnChange event like VS does.Tenatenable
@ChristopherRamírez Oh don't even get me started on WinForms ;-) No actions there!Gulosity

© 2022 - 2024 — McMap. All rights reserved.