Take the 2-minute tour ×
Mathematica Stack Exchange is a question and answer site for users of Mathematica. It's 100% free, no registration required.

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.
share|improve this question
    
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

3 Answers 3

up vote 4 down vote accepted

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)]]
share|improve this answer
    
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]
  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.

share|improve this answer
    
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
    
@bobthechemist. I hope I have addressed the concerns you raise in the above comment by the edit I have made. –  m_goldberg Aug 23 '13 at 2:13
    
Thanks for the clarification. –  bobthechemist Aug 23 '13 at 2:40

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}]
share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

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