# Making a multiple choice button

I've written the following code to create a multiple choice button that takes an arbitrary number of answer options and will assign a variable true/false depending on whether or not the correct choice is selected:

mchoice[question_, answers_, correct_, var_] :=
DynamicModule[{x},
Column[{Dynamic[var = (x == correct);], (*problem line *)
Dynamic@
PaneSelector[
Dynamic[x]]
}]
]


The code is executed as below and gives the button:

Clear[avar]
mchoice["argon", {"solid", "liquid", "gas"}, 3, avar]


The correctness of the selection is stored in avar which I can evaluate at the end of the notebook to "grade" the quiz, if you will. The problem is the presence of Null above the button. With the code as written, I cannot seem to create a button that both updates the value of avar and doesn't print the answer. For example, if I substitute Dynamic[var = (x == correct)] for the problem line, then the student will know if the chosen answer is correct. If I use Dynamic[var = (x == correct)]; then the result is visually correct but avar will not update when the student selects an answer. I can get the desired result by using Dynamic[var = (x == correct);] /. {Null ->""} but that feels like I'm cheating (yes, I'm getting the desired result, but no, I'm not using Dynamic correctly).

I'm interested in either of the following:

1. A better solution to my (* problem line *)
2. Alternative methods for creating a multiple choice button.
-
By multiple choice you mean that one is able to change the answer given earlier? – Kuba Aug 20 '13 at 17:05
Apropos alternative methods, isn't the standard representation for multiple choice forms in interface design radio buttons... such as RadioButtonBar, it would certainly make things simpler for you as well, code-wise. – Pickett Aug 20 '13 at 17:08
Dynamic before ActionMenu is useless ? – andre Aug 20 '13 at 18:11
@anon yes. For this particular application, I am trying to simplify the type of input methods and keep the display clean. Radio buttons with all the options take up a lot of screen real estate. – bobthechemist Aug 20 '13 at 18:34

You can use DynamicWrapper[] :

 mchoice[question_, answers_, correct_, var_] := DynamicModule[{x},
DynamicWrapper[
Column[{
}],
var = (x == correct)]]

-
DynamicWrapper seems to do the trick. – bobthechemist Aug 20 '13 at 18:37

This is not an answer but an extended comment on andre's answer.

I up-voted the answer because it's basically a good one. However, I want to point out two problems with the answer as posted.

1. The following doesn't work because y has a value when mchoice is called.

y = 42; Dynamic @ y
mchoice["argon", {"solid", "liquid", "gas"}, 3, y]

2. The following doesn't work because x is shadowed by the local x of the DynamicModule.

Clear@x; Dynamic @ x
mchoice["argon", {"solid", "liquid", "gas"}, 3, x]


I offer the following modification:

SetAttributes[mchoice, HoldFirst]
DynamicModule[{x$$x}, DynamicWrapper[ Column[{ ActionMenu[ question, MapThread[#1 :> (x$$x = #2) &, {answers, Range @ Length @ answers}]],
PaneSelector[
Dynamic @ x$$x]}], Unevaluated @ var = (x$$x == correct)]]

This isn't foolproof -- the identifier x$$x can't be passed in as var -- but it's safer than andre's version. Also, I have changed the order of the formal arguments, which is not desirable. But it is the best I can come up with. ### Edit The reason I changed the order of the formal arguments was to be able to use the HoldFirst attribute. Unique could be used rather than a 'funny' local variable, but I personally find it a bit awkward. It would go like this: Unique[]  3 Dynamic @ 3 mchoice[3, "argon", {"solid", "liquid", "gas"}, 3]  The shadowing of global x by the dynamic module's local is peculiar. It appears to happen only because x is dynamic in the module. Consider a = 42; Dynamic @ a SetAttributes[f, HoldFirst] f[u_] := (DynamicModule[{a}, a = RandomInteger[100]; Unevaluated@u = a];) f[a]  which works; however, f[u_] := (DynamicModule[{a}, a = RandomInteger[100]; Unevaluated @ u = Dynamic @ a];) f[a]  fails in the same way mchoice["argon", {"solid", "liquid", "gas"}, 3, x] fails. It appears that wrapping the variable in question with Dynamic globalizes it. - Thanks for paying such close attention. I had encountered your point 1, which I viewed as an inconvenience with an easy fix of clearing var before passing it to mchoice. If I understand your 2nd point correctly, the variable x$$x needs to have a name that, in theory, would never be used outside of mchoice. Is this a case for Unique[]? I'm also interested in why the argument order change was needed as that's not clear to me. – bobthechemist Aug 21 '13 at 14:01
If I understand correctly fix to your code would be to put checking procedure into ActionMenu:
 MapThread[#1 :> (x = #2; var = (x == correct);) &, {answers, Range@Length@answers}]