1
$\begingroup$

I miss a Button-like interface element which would behave like Checkbox but evaluate expressions instead of changing value of a Dynamic variable. Specifically:

  1. The button should remember its state like Checkbox: when the (not pressed) button is clicked it should evaluate specified expression and keep pressed apppearance.

  2. When pressed button is clicked it should evaluate another expression and become visually not pressed.

  3. It would be very useful to have the state of the button dependent also on values of a some other shared variables (in order to have ability to change the state of the button via another interface elements like Checkboxes). But changing its state in this way shouldn't lead to evaluation of the corresponding expressions. It should influence only further behavior of the button.

How to approach this in an efficient way if number of such buttons (independent from each other and having different sets of expressions to evaluate) may be about 15?

$\endgroup$
  • $\begingroup$ Comments are not for extended discussion; this conversation has been moved to chat. $\endgroup$ – Kuba Apr 24 '18 at 5:28
1
$\begingroup$

OP really needs to provide a clear problem specification, since I still completely fail to understand what is meant by recent comment:

The problem with #3 is how to make state be dependent also from a set of other shared variables.

But here is an extendable version of the code I provided in my recent comment.

Clear[state, expr, toggler];
state[1] = False;
state[2] = False;
expr[1, False] := Print["The first checkbox was off"];
expr[2, False] := Print["The second checkbox was off"];
expr[1, True] := Print["The first checkbox was on"];
expr[2, True] := Print["The second checkbox was on"];
toggler[i : (1 | 2)] := 
  Button["Change state N" <> ToString[i], state[i] = ! state[i]];
Grid[
 Table[With[{i = i}, {Checkbox[
     Dynamic[state[i], (expr[i, state[i]]; state[i] = #) &]], 
    toggler[i]}], {i, 2}]]

Now each checkbox evaluates an expression that is dependent on the index of the checkbox and its current state: expr[i, state[i]] (so no need for an If statement any more). Each chekbox is also supplied with a button that toggles the state, but doesn't evaluate expressions.

Here's another example that replicates the behavior of the example provided by OP:

Clear[state, expr, groupExpr, group];
group = False;
state[1] = False;
state[2] = False;
groupExpr[ownState_] := (state[#] = ownState) & /@ Range[2];
expr[1, stateVec_] := (group = Or @@ stateVec);
expr[2, stateVec_] := (group = Or @@ stateVec);
Column[
 Prepend[
  Table[With[{i = i},
   Checkbox[Dynamic[state[i], (state[i] = #; expr[i, state /@ Range[2]]) &]]],
   {i, 2}],
  Checkbox[Dynamic[group, (group = #; groupExpr[group]) &]]
  ]
 ]

Most generally, I would write the following:

Clear[state, expr, groupExpr, group, n];
n = (* some n *);
group = False;
(state[#] = False) & /@ Range[n];
groupExpr[ownState_, 
   stateVec_] :=(* some code dependent on own state and other states *);
expr[i_, ownState_, 
   stateVec_] :=(* some code dependent on own state, index, and other \
states *);
Column[
 Prepend[
  Table[With[{i = i}, 
    Checkbox[
     Dynamic[state[
       i], (state[i] = #; 
        expr[i, state[i], state /@ Range[n]]) &]]], {i, n}],
  Checkbox[
   Dynamic[group, (group = #; groupExpr[group, state /@ Range[n]]) &]]
  ]
 ]

Definitions for expr[i,...] might need to be supplied explicitly for all possible values of i, depending on the complexity of the logic.

Finally, here's a generalization to two overlapping groups:

Clear[state, expr, groupExpr, groups, reverseGroups];

n = 6;
groups = {{1, 2, 3, 4}, {3, 4, 5, 6}};
reverseGroups = First /@ Position[groups, #] & /@ Range[n];

(group[#] = False) & /@ Range[Length@groups];
(state[#] = False) & /@ Range[n];

groupExpr[i_, ownState_] := (state[#] = ownState) & /@ groups[[i]];
expr[i_, stateVec_] := (group[#] = Or @@ stateVec[[groups[[#]]]]) & /@ reverseGroups[[i]];

Column[Join[
  Table[With[{i = i}, 
    Checkbox[Dynamic[group[i], (group[i] = #; groupExpr[i, group[i]]) &]]],
    {i, Length@groups}],
  Table[With[{i = i}, 
    Checkbox[Dynamic[state[i], (state[i] = #; expr[i, state /@ Range[n]]) &]]],
    {i, n}]
  ]]
$\endgroup$
  • $\begingroup$ I've updated the question. $\endgroup$ – Alexey Popkov Apr 24 '18 at 7:51
  • $\begingroup$ @Alexey would you like me to replicate your example in some sufficiently general way? $\endgroup$ – LLlAMnYP Apr 24 '18 at 7:58
  • $\begingroup$ Yes, it would be useful. $\endgroup$ – Alexey Popkov Apr 24 '18 at 8:03
  • $\begingroup$ @AlexeyPopkov does this work? $\endgroup$ – LLlAMnYP Apr 24 '18 at 8:23
  • $\begingroup$ Yes, thank you. I would be glad also for generalization for arbitrary number of groups (only elements from the group influence the group's Checkbox). $\endgroup$ – Alexey Popkov Apr 24 '18 at 8:44
0
$\begingroup$

In comments LLlAMnYP suggested to use second argument of Dynamic for evaluation of an expression. Here is a complete solution based on this idea:

n = 3; group[1] = True;
Checkbox[Dynamic[group[1], 
  group[1] = #; 
   If[group[1], Do[element[i] = True, {i, n}], 
    Do[element[i] = False, {i, n}]]; &]]
Table[With[{i = i}, element[i] = True; 
  Checkbox[Dynamic[element[i], 
    element[i] = #; 
     If[Or @@ (element /@ Range[n]), group[1] = True, 
      group[1] = False]; &]]], {i, n}]

This is a simple two-level selection interface: elements are splitted into groups (here is only one group) and we can include or exclude a group by checking or unchecking the group Checkboxes. We also can do this element-by-element and it will influence the state of the group checkbox (here group is counted as included if at least one element from the group is included).

$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.