in the InputChangedEventHandler, eventArgs->inputs() only seem to return the inputs in the same group of the changed input?

in the InputChangedEventHandler, eventArgs->inputs() only seem to return the inputs in the same group of the changed input?

hans.billiet
Advocate Advocate
111 Views
4 Replies
Message 1 of 5

in the InputChangedEventHandler, eventArgs->inputs() only seem to return the inputs in the same group of the changed input?

hans.billiet
Advocate
Advocate

I'm writing an add-in in C++. All works fine so far. Below is a screenshot of the Add-In.

hansbilliet_0-1756661693824.png

 

I now wanted to add a final UI-feature. The user has to select 2 points. When "Select Point A" is selected, I want the UI to automatically jump to the second button to "Select Point B". I would use the "->hasFocus(true)".

 

The idea was to do this in the InputChangedEventHandler. When the "Select Point A" (type SelectionCommandInput) has a point selected, then I would grab the "Select Point B" (also type SelectionCommandInput) and focus it.

 

The code looks like this:

 

class ThickLineInputChangedEventHandler : public InputChangedEventHandler
{
public:
    void notify(const Ptr<InputChangedEventArgs>& eventArgs) override
    {
        Ptr<CommandInputs> inputs = eventArgs->inputs();
        Ptr<CommandInput> changed = eventArgs->input();
        if (!inputs || !changed)
            return;

        if (changed->id() == kSelPointAId)
        {
Ptr<SelectionCommandInput> selA = changed->cast<SelectionCommandInput>();
            if (selA && selA->selectionCount() == 1)
            {
                // User just finished picking point A -> set focus to point B
                Ptr<SelectionCommandInput> selB = inputs->itemById(kSelPointBId)->cast<SelectionCommandInput>();
                if (selB)
                    selB->hasFocus(true);
            }
        }
        ... rest of code

(kSelPointAId and kSelPointBId are static const char* that contains the id's of the 2 fields)

 

This code crashes because of a nullPtr. And the reason is that "inputs->itemById(kSelPointBId)" returns a nullPtr. But WHY?

 

The reason seems to have to do with groups. In my CommandCreatedEventHandler, I create 2 groups. One for "Point A" and one for "Point B". If you look at the UI screenshot, you will see this. The  relevant code in the CommandCreatedEventHandler looks like this:

 

... previous code of CommandCreatedEventHandler

        // Group A & B to mirror UI layout
        Ptr<GroupCommandInput> grpA = inputs->addGroupCommandInput(kGroupA, "Point A");
        grpA->isExpanded(true);
        Ptr<CommandInputs> giA = grpA->children();

        Ptr<GroupCommandInput> grpB = inputs->addGroupCommandInput(kGroupB, "Point B");
        grpB->isExpanded(true);
        Ptr<CommandInputs> giB = grpB->children();

        // ---- Point A block ----
        {
            // Select Point A
            Ptr<SelectionCommandInput> selA = giA->addSelectionInput(kSelPointAId, "Select Point A", "Pick the start point (A)");
            addPointSelectionFilters(selA);

            ... remaining items of group A
        }

        // ---- Point B block ----
        {
            // Select Point B
            Ptr<SelectionCommandInput> selB = giB->addSelectionInput(kSelPointBId, "Select Point B", "Pick the end point (B)");
            addPointSelectionFilters(selB);

            ... remaining items of group B

 

It now seems that the line "Ptr<CommandInputs> inputs = eventArgs->inputs();" in the InputChangedEventHandler does NOT ALWAYS return all the inputs of the dialog. If the changed input, retrieved through "Ptr<CommandInput> changed = eventArgs->input();", belongs to a group, then "inputs" seems only to return the inputs of that same group, and nothing more.

 

Is this by design, or is this some bug?

 

And this of course explains why my code is crashing. I can not reach to the kSelPointBId through the inputs provided when kSelPointAId has changed. Reason is that I only get the group A inputs, which means that "inputs->itemById(kSelPointBId)" returns a nullPtr.

 

Is there a way to get all inputs in InputChangedEventHandler? Of course, I can use a global variable "g_inputs" that is getting its value during the create event. But I was just wondering if there is a standard way.

 

I can not attach my cpp file (not allowed?). But I think that the necessary code fragments are in my post. If not, don't hesitate to ask more details.

0 Likes
Accepted solutions (1)
112 Views
4 Replies
Replies (4)
Message 2 of 5

Jorge_Jaramillo
Collaborator
Collaborator

Hi,

 

I believe you are getting NULL pointer in "inputs->itemById(kSelPointBId)" because that commandId is not a child of the root commandInputs.

Look for it in the child group with a sentence like the following:

 

inputs->itemById("Point B")->children->itemById(kSelPointBId)"

 

which follows the hierarchy you build in the CommandCreatedEventHandler.

 

I hope this could help.

 

Regards,

Jorge Jaramillo

 

Message 3 of 5

hans.billiet
Advocate
Advocate

Hello Jorge,

 

I'm not convinced that your answer is correct. I did some more analysis which kind of proofs that "eventArgs->inputs()" in InputChangedEventHandler seems not always to contain the complete list.

 

First I added some code to dump the inputs to the Output of Fusion.

 

inline void DumpInputs(const Ptr<CommandInputs>& ins, std::string_view tag)
{
    if (!ins) return;
    LogFusion(std::string("[DumpInputs] ") + std::string(tag));
    for (size_t i = 0; i < ins->count(); ++i) {
        auto ci = ins->item(i);
        if (!ci) continue;
        LogFusion(std::string("  id='") + ci->id() + "'  type=" + ci->objectType());
    }
}

(For completeness, LogFusion is a small inline function with body "if (_app) _app->log(s.c_str());")

 

I now use this DumpInputs in my InputChangedEventHandler.

 

class ThickLineInputChangedEventHandler : public InputChangedEventHandler
{
public:
    void notify(const Ptr<InputChangedEventArgs>& eventArgs) override
    {
        Ptr<CommandInputs> inputs = eventArgs->inputs();
        Ptr<CommandInput> changed = eventArgs->input();
        if (!inputs || !changed)
            return;

        if (changed->id() == kSelPointAId)
        {
			DumpInputs(inputs, std::string("InputChanged ") + kSelPointAId);
			Ptr<SelectionCommandInput> selA = changed->cast<SelectionCommandInput>();
            if (selA && selA->selectionCount() == 1)
            {
                // User just finished picking point A -> set focus to point B
                Ptr<SelectionCommandInput> selB = g_AllInputs->itemById(kSelPointBId)->cast<SelectionCommandInput>();
                if (selB)
                    selB->hasFocus(true);
            }
        }

        if (changed->id() == kFeatATypeId)
        {
            DumpInputs(inputs, std::string("InputChanged ") + kFeatATypeId);
            updateFeatureInputs(inputs, kFeatATypeId, kFeatAWidthId, kFeatALengthId);
        }

        if (changed->id() == kFeatBTypeId)
        {
            DumpInputs(inputs, std::string("InputChanged ") + kFeatBTypeId);
            updateFeatureInputs(inputs, kFeatBTypeId, kFeatBWidthId, kFeatBLengthId);
        }

        if (changed->id() == kWidthId)
        {
			DumpInputs(inputs, std::string("InputChanged ") + kWidthId);
            Ptr<ValueCommandInput> widthIn = inputs->itemById(kWidthId)->cast<ValueCommandInput>();
            double widthVal = widthIn ? widthIn->value() : 0.0;

            Ptr<ValueCommandInput> aW = inputs->itemById(kFeatAWidthId)->cast<ValueCommandInput>();
            Ptr<ValueCommandInput> bW = inputs->itemById(kFeatBWidthId)->cast<ValueCommandInput>();
            if (aW) aW->minimumValue(widthVal);
            if (bW) bW->minimumValue(widthVal);
        }
    }
} _thickLineInputChangedHandler;

(In the meantime, I have used a global g_AllInputs so that I don't rely on the local inputs variable to access Point B when Point A is changed - so my code works)

 

Now it gets interesting. I refer to the dialog at the start of this post, where you see the structure of my inputs.

 

If I change the value of "Width", then DumpInputs gives me all the fields in one flat list.

 

[DumpInputs] InputChanged tl_width

id='tl_graphic' type=adsk::core::ImageCommandInput

id='tl_sep1' type=adsk::core::SeparatorCommandInput

id='tl_width' type=adsk::core::ValueCommandInput

id='tl_sep2' type=adsk::core::SeparatorCommandInput

id='tl_groupA' type=adsk::core::GroupCommandInput

id='tl_selPointA' type=adsk::core::SelectionCommandInput

id='tl_leadA' type=adsk::core::ValueCommandInput

id='tl_featA_type' type=adsk::core::DropDownCommandInput

id='tl_featA_width' type=adsk::core::ValueCommandInput

id='tl_featA_length' type=adsk::core::ValueCommandInput

id='tl_groupB' type=adsk::core::GroupCommandInput

id='tl_selPointB' type=adsk::core::SelectionCommandInput

id='tl_leadB' type=adsk::core::ValueCommandInput

id='tl_featB_type' type=adsk::core::DropDownCommandInput

id='tl_featB_width' type=adsk::core::ValueCommandInput

id='tl_featB_length' type=adsk::core::ValueCommandInput

id='tl_errorBox' type=adsk::core::TextBoxCommandInput

 

But when I select point A, which is an input that is part of group A, then DumpInputs only lists the inputs of that group A.

 

[DumpInputs] InputChanged tl_selPointA

id='tl_selPointA' type=adsk::core::SelectionCommandInput

id='tl_leadA' type=adsk::core::ValueCommandInput

id='tl_featA_type' type=adsk::core::DropDownCommandInput

id='tl_featA_width' type=adsk::core::ValueCommandInput

id='tl_featA_length' type=adsk::core::ValueCommandInput

 

It is pretty logic that in this case, when I try to get "tl_selPointB" to put the focus on it, that I get a nullPtr. Using "->children" as you suggested even doesn't make sense, because the shortened list doesn't contain any items from group B.

 

If I change the "Feature A Type" (the dropdown), which is also part of Group A, then as expected, I again get only the inputs of that group A. Seems very consistent.

 

[DumpInputs] InputChanged tl_featA_type

id='tl_selPointA' type=adsk::core::SelectionCommandInput

id='tl_leadA' type=adsk::core::ValueCommandInput

id='tl_featA_type' type=adsk::core::DropDownCommandInput

id='tl_featA_width' type=adsk::core::ValueCommandInput

id='tl_featA_length' type=adsk::core::ValueCommandInput

 

Conclusion: It seems to me like proven that the "eventArgs->inputs()" in InputChangedEventHandler only includes the items that are included in the same group. I assume that nested groups are also returned, because if I take an item at the top, it also returns the items contained in the groups underneath it. In this case, I even don't have to use "->children", because all items are listed as a flat list.

Message 4 of 5

Jorge_Jaramillo
Collaborator
Collaborator
Accepted solution

Hi @hans.billiet ,

 

You are right. The args.inputs for a change value in a command input inside a group contains only the command inputs of the same level.

I was able to check it with the snippet code below.

 

The solution to get the values from command inputs in others group / levels could be:

1.  To use args.inputs.command.commandInputs which has access to the full command inputs tree.

2. Keep the reference of the command input from the command created handler function into a global variable; then in the command input changed handler use that global variable without the need to look for the command input in the command inputs structure.

 

Here is the code I use to test it (I wrote a DumpInput's Python function):

_handlers = []
def test_command_created(args: adsk.core.CommandCreatedEventArgs):
    global _handlers

    cmd: adsk.core.Command = args.command
    inputs: adsk.core.CommandInputs = cmd.commandInputs

    _inp = inputs.addBoolValueInput('Bool 01', 'Boolean 01', True, '', False)
    _handlers.append(_inp)
    _inp = inputs.addGroupCommandInput('Group 1', 'Group 1')
    _handlers.append(_inp)
    grp_inputs = _inp.children
    _inp = grp_inputs.addBoolValueInput('Bool 11', 'Boolean 11', True, '', False)
    _handlers.append(_inp)
    _inp = grp_inputs.addBoolValueInput('Bool 12', 'Boolean 12', True, '', False)
    _handlers.append(_inp)
    _inp = inputs.addGroupCommandInput('Group 2', 'Group 2')
    _handlers.append(_inp)
    grp_inputs = _inp.children
    _inp = grp_inputs.addBoolValueInput('Bool 21', 'Boolean 21', True, '', False)
    _handlers.append(_inp)
    _inp = grp_inputs.addBoolValueInput('Bool 22', 'Boolean 22', True, '', False)
    _handlers.append(_inp)

    futil.add_handler(cmd.inputChanged, test_command_inputChanged)
    futil.add_handler(cmd.execute, command_execute)

def DumpInputs(ins: adsk.core.CommandInputs) -> None:
    for ci in ins:
        app.log(f"  id='{ci.id}'  type={ci.objectType}  {f'Val={ci.value}' if 'value' in dir(ci) else ''}")

def test_command_inputChanged(args: adsk.core.InputChangedEventArgs):
    app.log(f"[DumpInputs]                           InputChanged <{args.input.id}>")
    DumpInputs(args.inputs)
    app.log(f"[DumpInputs] <Full CommandInputs Tree> InputChanged <{args.input.id}>")
    DumpInputs(args.inputs.command.commandInputs)

 

 

Here you have some test results:

1.  Click on Boolean 01: access to all command inputs from the args.inputs ::

Jorge_Jaramillo_3-1756855603489.png

 

2. Click on Boolean 21: access only to command inputs in the group from the args.inputs, and full access from args.inputs.command.commandInputs ::

Jorge_Jaramillo_5-1756855692353.png

 

I hope this can help you.

 

Regards,

Jorge Jaramillo

 

Message 5 of 5

hans.billiet
Advocate
Advocate

Hello @Jorge_Jaramillo ,

 

That is EXACTLY what I was looking for. A way to reach all inputs from within the InputChangedEventHandler. You showed me the way how to do it!

 

Thank you very, very much.

 

Hans