5
$\begingroup$

Context Added to End

In a previous question, I was looking for a way for an InputField to maintain the input structure of expressions so as to allow the user's input (say, in scientific notation) to be maintained. Here is the code I developed to solve that problem:

HoldBoxesInputField[Dynamic[var_], Dynamic[boxes_]] := 
  DynamicModule[{}, 
    If[boxes == Null, boxes = ToBoxes[var]]; 
    InputField[Dynamic[boxes, {Temporary, 
               Function[result, boxes = result; 
                                var = ToExpression[boxes]]}], 
               Boxes]];

Then in calling this function, I pass both the variable and the boxes as dynamic. This works well except in cases where I need have an intermediate value of the boxes that is not able to be interpreted as an expression. For example, I have the user inputting the names of various symbols, which I then add to a dynamically modified palette of PasteButton structures. So if, in one of these InputField boxes, I click one PasteButton, type +, and then try to click another PasteButton, because the expression is not complete and yet with the click I have navigated away from the InputField, ToExpression executes and returns an error. Here is an example with some context:

DynamicModule[{equations, eqboxes, constants, constboxes, numeq, numconst}, 
  constants[_] = c; 
  equations[_] = left == right; 
  eqboxes[_] = Null; 
  constboxes[_] = Null; 
  numeq = 1; 
  numconst = 1; 
  Column[{
    Panel[Column[Join[
      {Style["Constants", Bold]}, 
      {Dynamic[Grid[{"Constant " <> ToString[#] <> ": ", 
               HoldBoxesInputField[Dynamic[constants[#]], 
               Dynamic[constboxes[#]]]} & /@ Range[numconst]]]}, 
      {Button["Add Constant", ++numconst]}]]],
    Panel[Column[Join[
      {Style["Equations", Bold]}, 
      {Dynamic[Grid[{"Equation " <> ToString[#] <> ": ", 
               HoldBoxesInputField[Dynamic[equations[#]], 
               Dynamic[eqboxes[#]]]} & /@ Range[numeq]]]}, 
      {Button["Add Equation", ++numeq]}]]], 
    Panel[Dynamic[Column[Join[
      {Style["Pasting Palette", Bold]}, 
       Dynamic@PasteButton[ToString[constants[#]], constants[#]] & /@ 
               Range[numconst]]]]]}]]

enter image description here

So the fix, it seemed to me, was to use Check. Here is what I tried:

HoldBoxesInputField2[Dynamic[var_], Dynamic[boxes_]] := 
  DynamicModule[{oldval}, 
    If[boxes == Null, boxes = ToBoxes[var]]; 
    oldval = var; 
    InputField[Dynamic[boxes, {Temporary, Function[result, boxes = result; 
                               var = Quiet@Check[ToExpression[boxes], oldval]; 
                               If[var != oldval, oldval = var]]}], 
               Boxes]];

The idea is to hold on to the old value, and use that if converting to an expression fails, but then update the old value whenever the conversion to an expression succeeds. And that part seems to work. But in the process it broke two other things. Test code is the same as for the first case, just with HoldBoxesInputField2 in place of HoldBoxesInputField. In the first version, I could Tab from InputField to InputField. In the second version, if the palette of PasteButton structures changes, the Tab now fails. In addition, when I paste into an equation on the right-hand-side, the first paste moves the cursor to the beginning of the notebook and pastes there instead of into the field. Animation showing this behavior is below. So my questions: why does the new code change those behaviors, and what can I do about it? Thanks!

enter image description here

Edited to Add

A commenter asked for context. Here you go.

I am developing a package for chemists to use to do chemical kinetics. Most chemists are not particularly proficient in Mathematica, so I am developing a dynamic interface that lets them do the work without having to dive too deeply into the language. Some underpinnings of the project can be found on my website (http://kevinausman.net).

Regardless, here is the interface I am working on. Using other routines (from the website), they can enter chemical mechanisms in standard chemistry notation. On one tab they can select the elements of the mechanism they want to include in the kinetic modeling:

enter image description here

The system automatically turns the mechanism into a set of differential rate equations. But the measurable quantities are often not directly implied by these equations. So on the next tab I allow them to enter other time-dependent equations and parameters, including specifying whether parameters are fixed, variable, or calculated from other quantities, as shown here:

enter image description here

That is the screen that is causing me headaches. Following this tab, the user can then interactively adjust the model compared to experimental data, do fitting, and extract statistics from it. For example:

enter image description here

And:

enter image description here

So that is my context. It is a relatively massive interactive interface that needs to seamlessly handle input by someone who is only somewhat familiar with Mathematica, but who wants to take advantage of the incredible calculation capabilities of the package. I am organizing the package in a way that gives the user access to the features that are relevant to solving the particular types of problems that chemists need, but am hiding from them most of the coding.

The problem is arising in cases where the user is entering equations in the second tab that are not yet complete. I need Mathematica to interpret the results as soon as another tab is selected, because the equations are used in multiple places, but I also need them to be able to click on dynamically-generated paste buttons to enter their custom-defined symbols into the equations. In my example, if they have abs60[t]+ entered in the abs[t] field, and then click on abs70[t], an error is thrown, because by navigating away from the field to click on the paste button, Mathematica tries to interpret the contents of the field (which is incomplete). And yet if I try to catch the error using Check, the behavior goes really wonky, as I described in my original question, with pasting happening at the top of the notebook instead of in the field.

I hope that context helps.

$\endgroup$
  • $\begingroup$ My tip would be the same as in the linked question, don't do ToExpression unless you really want to use that expression. Don't do that in your controllers. Just keep your variables and equations in box form and convert them when you need them. You can Paste/NotebookWrite boxes anyway. p.s. feedback on my answer to your last question is appreciated: 204157 $\endgroup$ – Kuba Aug 26 '19 at 7:01
  • $\begingroup$ Unfortunately, I have a lot of coupled dynamics and they lead to very long calculations, so redoing the calculation when the field is updated is the only real option. $\endgroup$ – Kevin Ausman Aug 27 '19 at 23:05
  • $\begingroup$ @KevinAusman Could you give the motivation or an example of what are you trying to do with these functionalities? $\endgroup$ – M.R. Sep 4 '19 at 12:01
  • $\begingroup$ @M.R.: Context added to OP. Thank you for your consideration! $\endgroup$ – Kevin Ausman Sep 5 '19 at 3:52
3
+100
$\begingroup$

How about not committing to the assignment if the syntax is not valid:

HoldBoxesInputField[Dynamic[var_], Dynamic[boxes_]] := DynamicModule[{updateFunction},

  InputField[ Dynamic[boxes, {Temporary, updateFunction}], Boxes],

  Initialization :> (
    If[boxes == Null, boxes = ToBoxes[var]];

    SetAttributes[updateFunction, HoldRest];

    updateFunction[new_, boxVar_] := 
     With[{expr = Quiet@ToExpression@new},
      If[expr =!= $Failed, boxes = new; var = expr
      ]
     ]
   )
  ]

If you change 1 to 1+ and leave it, it will go back to 1.

|improve this answer|||||
$\endgroup$
  • $\begingroup$ Close! The problem with that version is that it reverts. I want it to maintain the boxes as changed (so that I can paste AFTER the 1+), but only assign the interpreted version when it converts. But your version was easily fixed to do that... moving boxes=new outside the If statement. Thank you! $\endgroup$ – Kevin Ausman Sep 6 '19 at 2:31
  • $\begingroup$ @KevinAusman I am glad it works for you. It could be confusing to have a box state not in sync with expression but whatever fits your needs. $\endgroup$ – Kuba Sep 6 '19 at 6:45
  • $\begingroup$ There is some error checking that can be done at a later point (as long as I remember to implement it). $\endgroup$ – Kevin Ausman Sep 7 '19 at 15:13

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.