3
$\begingroup$

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@
     ActionMenu[question, 
      MapThread[#1 :> (x = #2) &, {answers, Range@Length@answers}]],
    PaneSelector[
     MapThread[#1 -> #2 &, {Range@Length@answers, answers}], 
     Dynamic[x]]
    }]
  ]

The code is executed as below and gives the button:

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

Mathematica graphics

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.
$\endgroup$
4
  • $\begingroup$ By multiple choice you mean that one is able to change the answer given earlier? $\endgroup$
    – Kuba
    Commented Aug 20, 2013 at 17:05
  • $\begingroup$ 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. $\endgroup$
    – C. E.
    Commented Aug 20, 2013 at 17:08
  • $\begingroup$ Dynamic before ActionMenu is useless ? $\endgroup$
    – andre314
    Commented Aug 20, 2013 at 18:11
  • $\begingroup$ @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. $\endgroup$ Commented Aug 20, 2013 at 18:34

3 Answers 3

5
$\begingroup$

You can use DynamicWrapper[] :

 mchoice[question_, answers_, correct_, var_] := DynamicModule[{x},
  DynamicWrapper[
    Column[{
     ActionMenu[question, MapThread[#1 :> (x = #2) &, {answers, Range@Length@answers}]], 
     PaneSelector[MapThread[#1 -> #2 &, {Range@Length@answers, answers}], Dynamic[x]]
     }],
   var = (x == correct)]]
$\endgroup$
1
  • $\begingroup$ DynamicWrapper seems to do the trick. $\endgroup$ Commented Aug 20, 2013 at 18:37
4
$\begingroup$

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]
  mchoice[var_Symbol, question_, answers_, correct_] := 
  DynamicModule[{x$$x}, 
     DynamicWrapper[
       Column[{
         ActionMenu[
           question, 
           MapThread[#1 :> (x$$x = #2) &, {answers, Range @ Length @ answers}]], 
         PaneSelector[
           MapThread[#1 -> #2 &, 
           {Range @ Length @ answers, answers}], 
           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.

$\endgroup$
3
  • $\begingroup$ 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. $\endgroup$ Commented Aug 21, 2013 at 14:01
  • $\begingroup$ @bobthechemist. I hope I have addressed the concerns you raise in the above comment by the edit I have made. $\endgroup$
    – m_goldberg
    Commented Aug 23, 2013 at 2:13
  • $\begingroup$ Thanks for the clarification. $\endgroup$ Commented Aug 23, 2013 at 2:40
1
$\begingroup$

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}]
$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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