Contents Previous Next Index
13 Programming Interactive Elements
The Maple standard interface provides several tools for building interactive mathematical content. It includes a set of embedded components, which are configurable graphical controls, buttons, meters, and other interactive components that you can insert in a Maple document to analyze, manipulate, and visualize equations and Maple commands. For example, several task templates in Maple use embedded components to demonstrate mathematical concepts.
There are many ways to construct an application using embedded components. The easiest way involves drag and drop of components out of the palette to construct a wysiwyg interface. In addition, the entire interface can be described in code, and generated by a command. There are also higher-level commands that use embedded component interfaces for special purpose applications.
13.1 In This Chapter
Programming embedded components
Dynamically generated embedded component application
Canvas subpackage of DocumentTools
Maplets and Maplet Utilities
13.2 Programming Embedded Components
In the Maple standard interface, embedded components are available in the Components palette. These components include boxes, lists, gauges, and dials.
Adding Embedded Components to a Document
When you click an icon in the Components palette, the corresponding embedded component is inserted in your document at the current cursor location. For example, you can insert a button within a paragraph, or you can include a series of plot components, sliders, and check boxes that function together.
You can use a table to lay out the components in your document. To insert a table, from the Insert menu, select Table. You can then insert buttons, plots, gauges, math, text, and so on in each table cell.
Editing Component Properties
Embedded components can be programmed to accomplish specific tasks when mouse actions are performed. For example, button components and sliders can be programmed to display information when they are clicked or dragged, and a plot component can be programmed to display points when it is clicked. To program an embedded component to perform a task, you must edit the properties of the embedded component and, in most cases, provide the code needed to accomplish the task.
For example, if you want to provide code for a button component, you would right-click (Control-click for Macintosh) the button component that you inserted in your document and select Edit Click Action to display a window in which you can enter the code (see Figure 13.1). By default, this window contains sample code that you can start with.
Figure 13.1: Code Region for an Embedded Component
Each embedded component has different properties that you can edit. For more information about the properties for a specific component, refer to the EmbeddedComponents help page and browse to the page of the component that you want to use.
Tip: If you are working with multiple components in a document, you may find it easier to include the code for all of the component actions in one area, for example, the startup code region of your document. For more information, see the worksheet,documenting,startupcode help page.
Example: Creating a Random Plot Application
In the following example, we will add a Plot component and a Button to control it. Each time the button is pressed a new random plot will be displayed.
Key points in this example:
components are automatically given a name, like Plot0, or Button0; use this to reference the component with SetProperty, GetProperty and/or Do commands and connect components together
properties are available in the panel; click on a component to see its properties
Open a new Maple document.
Place your cursor on a blank spot in your document where you want the components to appear.
On the left side of the Maple window, open the Components palette and click the Plot component icon.
Press Enter to insert a new line.
From the Components palette, click on the Button component icon.
In your document, click on the button component to reveal the properties in the panel on the right side of the Maple window. Note you can also right-click (Control-click for Macintosh) on the button component in order to select the button but avoid activating the button action.
In the panel, change the Caption to "Random Plot".
Click the Edit Click Code.. button in the panel.
In the Button Action When Clicked window, enter the following code:
use DocumentTools in myplot := plot( randpoly(x) ); SetProperty( Plot0, 'value', myplot ); end use;
This code will be invoked when the button is pressed. It first generates a new plot of a random polynomial, assigned to the variable myplot. Then it sets the value property of the component named Plot0 to the generated plot.
Save and exit the Button Action When Clicked popup window.
Ensure that the plot component you inserted is named Plot0* by clicking on the plot and verifying that the Name property is set to Plot0. If it is not, either update the name, or update the button action code to use the alternate name.
Your application example is complete. Press the "Random Plot" button to see a new plot generated each time you click it.
Example: Creating a Tic-Tac-Toe Game
In the following example, a tic-tac-toe game will be created using embedded components in Maple. This example contains nine list boxes that are organized in a 3 by 3 table.
Use a table to arrange your component layout.
The %this argument can be used in action code to refer to the component whose action is being invoked.
Code can be written an a module or procedure in a common place such as the startup code editor. The action code can then be simplified to a one-line call to the appropriate method, simplifying maintenance of the code.
It is possible to associate code with each of the combo boxes individually. However, if you want to change this code in the future, you would need to change it in nine places. A simpler approach is to write a Maple module that contains the code to perform the action and call that code from each list box.
To insert a table, from the Insert menu, select Table...
In the Rows and Columns fields, enter 3, and click OK.
Place your cursor in the top-left cell of your table.
On the left side of the Maple window, open the Components palette and click the combo box component icon.
In your document, click on the combo box component that you inserted and observe the properties displayed on the right panel of the Maple Window. Note: If you do not see the panel, open it by clicking the double arrow near the top right corner of the Maple window.
Click the Edit Item List button in the panel
In the item list, double-click ComboBox, replace this value with a hyphen character (-), and press Enter.
Click the Add button, double-click the new field, enter X in this field, and press Enter. Repeat this step to add a list item for O.
To close the dialog box, click OK.
In the Name field, specify the name ComboBox0 for the component.
Tip: Make sure that all of the embedded components in your document have unique names.
To close the Properties dialog box, click OK.
Click Edit Select Code in the panel and replace the default code with TicTacToe:-Select( %this);
From the File menu, select Save Code.
From the File menu, select Close Code Editor.
In the document, select the combo box, and then copy and paste it into all of the remaining cells. If you plan to proceed to the next example, paste in order of rows top left to right then down to the next row and repeat.
Note: You can copy and paste embedded components within a document or from one document to another. A unique name is assigned to each pasted component--the number in the component name is incremented. Other details associated with the component, for example, properties and actions, are copied in their original form.
Below the table, enter the following module to perform the action.
TicTacToe := module() uses DT = DocumentTools; export Select := proc( what ) if DT:-GetProperty( what, 'value' ) <> "-" then DT:-SetProperty( what, 'enabled', false ); end if; end proc; end module;
The %this argument, which is passed as the value of the what parameter to the TicTacToe:-Select procedure, is set to the name of the component that generates the action. Therefore, the result is that if you select either the "X" or the "O" list element, the list box is dimmed and the user's selection cannot be changed.
For more information about modules, see Programming with Modules.
Alternatively, you can save this module in a Maple library archive, which is a separate file in which you can store Maple procedures, modules, and other data. For more information, refer to the repository help page.
Retrieving and Updating Component Properties
The examples above use commands from the DocumentTools package to retrieve information from components and update component properties. This package includes the following commands.
GetProperty: Retrieve information from a component.
SetProperty: Update a component.
Do: An alternate interface to both GetProperty and SetProperty. This command can be used to retrieve and update components.
For more information about the properties that can be retrieved and set for each component, refer to the EmbeddedComponents help page and browse to the help page of the component that you want to use.
Using the GetProperty Command to Retrieve Properties
You can specify two arguments for the DocumentTools:-GetProperty command: the name of the component and the property (or option) to be retrieved. Note: The value returned by the GetProperty command will either be a number or a string. For example, the command DocumentTools:-GetProperty( component_name, 'visible' ) returns a value of "true" or "false". To retrieve the corresponding Boolean value, the result must be processed by the parse command: parse( DocumentTools:-GetProperty( component_name, 'visible' ) ) returns a value of true or false. However, in many cases, this extra step is not necessary. For example, the comparison
if DocumentTools:-GetProperty( component_name, 'visible' ) = "true" then
will be faster than
if parse( DocumentTools:-GetProperty( component_name, 'visible' ) ) then
Using the SetProperty Command to Update Properties
You can specify the following arguments for the DocumentTools:-SetProperty command: the name of the component to update, the property to update, the new value for that property, and an optional parameter to indicate whether the update occurs immediately.
Code associated with a component can perform many different tasks. In particular, it can update other components. For example, you can create a plot component that returns certain values displayed in TextArea components or makes changes to other plots when the plot component is clicked.
When code is run as a result of a mouse event that updates other components, those updates occur after that code runs successfully. While this process is efficient, in some cases, you might want these updates to occur immediately. In such cases, you can use the optional refresh = true parameter, or simply refresh.
Using the Do Command to Retrieve and Update Component Properties
The DocumentTools:-Do command is a convenient interface to both the DocumentTools:-GetProperty and DocumentTools:-SetProperty commands. Components can be referenced as variables in expressions. For example, suppose that you have a math container component, two text area components, a button, and a plot component. You can enter a math expression in the variable x in the math container, numbers in each of the text areas and click the button, causing the expression to be plotted over the range specified by the numbers. Assuming the components that you inserted in your document are named MathContainer0, TextArea0, TextArea1, Button0, and Plot0, respectively, you can accomplish this task by using the single command DocumentTools:-Do( %Plot0 = plot( %MathContainer0, 'x' = %TextArea0 .. %TextArea1 ) ).
Consider the following points when deciding whether to use the DocumentTools:-Do command to retrieve or update components.
The embedded component type determines the default property retrieved or set by the Do command. For most components, the value property is the default property that is retrieved or set. This means that the Do command must query the GUI to determine which information to retrieve. The GetProperty and SetProperty commands avoid this step by requiring you to specify which property to retrieve. However, if you are working with a small number of components, this extra step will usually be insignificant.
The names of components appearing in the first argument to Do must be literal names prefixed by %. That is, you cannot use the Do command to access or update a component with a name that is determined programmatically.
Example: Extending the Tic-Tac-Toe Game
This example builds on the Tic-Tac-Toe game that was started earlier in this chapter. We will add a reset button so you can play again, and add some feedback when someone has completed the game.
Use of a module to organize code.
Stateless implementation; all state stored in component values so there is nothing extra to do when this is saved as part of a .mw and reopened later--you can continue playing where you left off
When relying on hard-coded component names, it is best to limit usage to one application per worksheet in order to avoid conflicts.
Continue from where the previous Tic-Tac-Toe example left off.
Enter a blank line in the Maple Window below the table of combo boxes
Place your cursor on the blank line and click the Button component from the palette.
In the right panel, change the button caption to "Reset"
Edit the button click-action In the right panel, change the button caption to "Reset"
click Edit Select Code in the panel and replace the default code with TicTacToe:-Reset();
Place your cursor next to the Reset button in your document, click on the Label component from the palette to insert a new label component.
In the right panel, clear the label's caption so it is empty
Below the table, replace the existing TicTacToe module with the following code:
TicTacToe := module() uses DT = DocumentTools; export Select := proc( what ) local val; if (val:=DT:-GetProperty( what, 'value' )) <> "-" then DT:-SetProperty( what, 'enabled', false ); end if; CheckForWinner(); end proc; local GetState := proc() local state := Array(1..3,1..3,fill="-"); for local N from 0 to 8 do state(N+1) := DT:-GetProperty(sprintf("ComboBox%d",N),'value'); end do; state; end proc; local CheckForWinner := proc() local i, r; local state := GetState(); for i from 1 to 8 do if i <= 3 then r := convert(state[i,..],set); elif i <= 6 then r := convert(state[..,i-3],set); elif i = 7 then r := {state[1,1],state[2,2],state[3,3]}; elif i = 8 then r := {state[1,3],state[2,2],state[3,1]}; end if; if numelems(r) = 1 and r[1] <> "-" then DT:-SetProperty("Label0",'caption',sprintf("%s is the winner",r[1])); break; end if; end do; end proc; export Reset := proc() for local N from 0 to 8 do DT:-SetProperty(sprintf("ComboBox%d",N),'value',"-"); DT:-SetProperty(sprintf("ComboBox%d",N),'enabled',true); end do; DT:-SetProperty("Label0",'caption',""); end proc; end module:
The Select method has been modified to call CheckForWinner, which gets the current state of all the combo boxes and checks for three-in-a-row. If found it updates the Label with a message saying who won.
GetState iterates over the nine combo box names, fetching their value properties and storing them in an Array. The array assignment uses Programmer Indexing to assign into a 2-D array with a single index. The returned state Array is a mirror of what is shown on the tic-tac-toe board.
CheckForWinner then examines the state for a row, column, or diagonal having three in a row. This is done by putting the three entries in a set. Since sets discard duplicates, if there is only one entry in the resulting set, then it means that all three inserted were the same. If this happens, and the set does not contain a dash, then update the Label's caption property using SetProperty to display a message indicating who won.
The Reset method in the TicTacToe module gets called when the user presses the button labeled Reset. This uses the SetProperty command to reset all the combo boxes to a dash value, and clears the label caption.
13.3 Programmatically Creating an Interface that Contains Embedded Components
In the previous section we showed how to create an interface by dragging components onto a Maple document. In this section, we will show how to write code that describes the components and their arrangement, and then inserts them dynamically into the interface.
Constructing a Portion of a Worksheet
The DocumentTools:-Components package contains constructors for each of the various embedded components. When called, these constructors return an XML string that describes the component in the same format as it would be saved in a .mw file. Related, the DocumentTools:-Layout package contains constructors for layout elements like tables, text, headings, groups, etc. These represent all of the static layout and input that can be manually added into a worksheet. Combining components and layout lets you construct pieces of a worksheet that can be inserted and embedded into the current document.
For example, behind the scenes, the DocumentTools:-Tabulate command uses Layout constructs in order to build a nicely formatted table that can be injected into the worksheet.
A := LinearAlgebra:-RandomMatrix(3, 4); header := <a | b | c | d>; DocumentTools:-Tabulate(<header, A>, width = 40, fillcolor = ((T, i, jj) -> `if`(i = 1, grey, white))):
Similarly the Grading:-Quiz command uses both Layout and Components constructs in order to build an interactive question.
Grading:-Quiz("Which of the following could be portions of the plot of sin(x)?", {1, 2}, proc() [plot(sin(x), x = 0 .. rand(2 .. 3)()), plot(sin(x), x = rand(2 .. 3)() .. 5), plot(2*sin(x), x = rand(2 .. 3)() .. rand(4 .. 7)())] end proc, 'style'='multipleselect', 'plotsize'=[400, 100]);
Example: Dynamically Creating a Random Plot Application
The Tabulate and Quiz examples show high-level commands with simple inputs. The building blocks of commands like these can be seen in the following example. We are going to recreate the Random Plot example from the previous section, but in code, rather than dragging and dropping components.
with(DocumentTools:-Components): with(DocumentTools:-Layout): with(DocumentTools): DocumentTools:-InsertContent( Worksheet( Group( Textfield('alignment'='left', Plot('identity'="Plot0",pixelwidth=800,pixelheight=200) )), Group( Textfield('alignment'='left', Button("New Random Plot", 'action'="DocumentTools:-SetProperty(\"Plot0\",'value',plot(randpoly(x)));") )) ) ):
The outermost InsertContent command is the one that injects the content-description into a worksheet. The inner commands are a nested description of the worksheet content to create. Effectively we create a mini-worksheet comprised of groups, textfields, a plot, and a button.
In order to avoid duplicate component names, the InsertContent command can rewrite the names, but it does this both in the component itself, and in the action code properties of every component. For example, if you copied the above example and ran it twice in the same worksheet, the second insertion would see "Plot2" in both the Plot component and the Button's action code. The example would work correctly independent of the first plot example inserted. Note that this is not the case for external code, say in a module, that is not referenced anywhere in the arguments to InsertContent. The next example touches on how to address this.
Example: Dynamically Creating a Tic-Tac-Toe Application
We have some experience with building a tic-tac-toe game from the previous section. This version of that example shares much of the same code, but introduces just about all of the concepts you need to know to create the most sophisticated application. Let's start by looking at the completed code.
with(DocumentTools:-Components): with(DocumentTools:-Layout):
TicTacToe := module() uses DT = DocumentTools; export ModuleApply := proc() local stateVar; local xml := Worksheet( Group(Textfield("Tic Tac Toe",'alignment'='left','style'='Heading2', State('stateVar','XO'=Array(1..3,1..3,fill="-"), 'xmap') )), Table( Column()$3, 'width'=30, (for local row from 1 to 3 do Row( for local col from 1 to 3 do local id := sprintf("ComboBox%d",(row-1)*3+col); Cell(Textfield( ComboBox(["-","X","O"],'identity'=id,'action'=sprintf("TicTacToe:-Select(%s,[%d,%d],%s);",id,row,col,stateVar)) )); end do ) end do) ), Group(Textfield('alignment'='left', Button("Reset",'action'=sprintf("TicTacToe:-Reset(%s);",stateVar)), Label("",'identity'="FeedbackLabel") )) ): stateVar:-xmap := DocumentTools:-InsertContent(xml,'state'=convert(stateVar,string),'output'='table'); NULL; end proc: export Select := proc( what, position, stateVar ) local val; if (val:=DT:-GetProperty( what, 'value' )) <> "-" then DT:-SetProperty( what, 'enabled', false ); end if; stateVar:-XO[op(position)] := val; CheckForWinner(stateVar); end proc; local CheckForWinner := proc(stateVar) local i, r; for i from 1 to 8 do if i <= 3 then r := convert(stateVar:-XO[i,..],set); elif i <= 6 then r := convert(stateVar:-XO[..,i-3],set); elif i = 7 then r := {stateVar:-XO[1,1],stateVar:-XO[2,2],stateVar:-XO[3,3]}; elif i = 8 then r := {stateVar:-XO[1,3],stateVar:-XO[2,2],stateVar:-XO[3,1]}; end if; if numelems(r) = 1 and r[1] <> "-" then DT:-SetProperty(stateVar:-xmap["FeedbackLabel"],'caption',sprintf("%s is the winner",r[1])); break; end if; end do; end proc; export Reset := proc(stateVar) for local N from 1 to 9 do local CB := stateVar:-xmap[sprintf("ComboBox%d",N)]; DT:-SetProperty(CB,'value',"-"); DT:-SetProperty(CB,'enabled',true); end do; DT:-SetProperty(stateVar:-xmap["FeedbackLabel"],'caption',""); state(..) := "-"; end proc; end module:
TicTacToe();
It is most natural to store the action handlers and application generator together in a module. This code makes use of the special export named ModuleApply, which gets called when the module is invoked like a procedure call.
To make the TicTacToe command available in any session, save the module to a .mla library. For details, see savelib and LibraryTools:-Save.
The ModuleApply procedure contains all of the code to build the interface. It constructs a Table with 3 rows and 3 columns. The Column()$3 command is short hand for repeating the Column() command three times. This denotes the number of columns, each of which can contain optional properties. Then the Row(Cell(...)) pattern follows. We use a for-loop in parentheses in order to create a sequence.
In this version of the tic-tac-toe implementation, the ComboBox action code calls TicTacToe:-Select with three parameters: the name/identity, the [row,column] coordinates of the X or O in the grid, and a state variable. In the hand-assembled version it would be tedious to open each ComboBox and add unique parameters to each action, but in generated code it is trivial. We could also easily extend this version to use a 4x4 grid or bigger.
The description of the interface, returned by Worksheet and all of its nested parameters, is then inserted into the document by the DocumentTools:-InsertContent command. When the content is inserted, some of the given identities may be renamed. For example, if we called TicTacToe() twice in the same worksheet, to avoid a conflict between two components named ComboBox0, the second one is renamed from ComboBox0 to ComboBox9. The combo-box select action code makes reference to ComboBox0, so that code is automatically updated to reflect the new name, ComboBox9. Other code, like that of the Reset procedure, is hidden in the TicTacToe module and cannot be rewritten. Therefore we need to make use of the output=table option to InsertContent, which causes a table of mappings of original name -> new name to be returned. In this code we save that table in a variable called stateVar:-xmap. In this scenario, calling stateVar:-xmap["ComboBox0"] will return the new name "ComboBox9", which we can use to reference the inserted component.
The other option we added to the InsertContent call is state=... This is used in conjunction with the State component, a hidden element that creates an object you can use to hold values. Using a State object takes care of issues relating to having multiple instances of an application in the same worksheet. It also takes care of restoring state when a worksheet is reopened after having been saved and closed. In this application, we have defined two properties in the state object, named XO and xmap. We assign stateVar:-XO to be a 3 x 3 Array, and use that to track X's and O's as they are selected. The stateVar:-xmap property is the name mapping table mentioned in the previous paragraph. The first argument to the State component is an unevaluated name that gets assigned to by-reference. Ensure the call to State(...) happens before any uses of this argument, named stateVar in our example. Pass it as an argument to your action handlers.
13.4 Programmatically Creating an Interface using the DocumentTools:-Canvas Package
The Canvas subpackage of DocumentTools is designed to work in both Maple and Maple Learn. Maple Learn is a web-browser based dynamic online environment designed specifically for teaching and learning math and solving math problems.
The term "canvas" brings to mind the cloth that artists paint on, but in this context the canvas is the medium that mathematics is presented on. A canvas can contain math, text, plots, and controls like buttons, check boxes and sliders.
Let's look at a first example to show the presentation of Math and plots.
Example: Text, Math and Plot
with(DocumentTools:-Canvas):
cv := NewCanvas([ "Sample Problem", "A rocket follows a path given by:", y=x-1/90*x^3, Text("If the horizontal velocity is given by %1",v[x]=x), "Find the magnitude and direction of the velocity when the rocket hits the ground (assume level terrain) if time is in minutes.", StaticPlot( plot([x-1/90*x^3,-2*x+6*sqrt(10)], color=[blue,red], linestyle=[solid,dash],view=[0..10,0..8]) ) ]):
ShowCanvas(cv);
The NewCanvas command takes a list of Canvas elements and generates an XML description that can be rendered by the ShowCanvas and ShareCanvas commands.
To mix text and math on a single line, use the Text element constructor with a %1 placeholder to be filled in by the math as a separate argument in the same call.
In Maple Learn, elements on a canvas can be placed in exact pixel positions. The rendering in Maple is more coarse-grained, giving an approximate position that better lends itself to one or two columns.
Example: Find Prime Numbers
A canvas can be edited to insert new expressions. These expressions are accessible via Script commands.
In this example the user is asked to enter prime numbers. When the button is clicked, the FindPrimes procedure will look through all of the entered math, and create a Script that adds a message next to the math indicating whether or not the input is indeed prime.
FindPrimes := proc( canvas ) local script := Script(); for local m in GetMath(canvas) do SetActive(script,m); if m:-math::integer and isprime(m:-math) then Annotate(script,"Good job, this is prime"); else Annotate(script,"This is not prime"); end if; end do; script:-ToString(script); end proc:
cv := NewCanvas(["Write Some Prime Numbers Anywhere", ScriptButton("Find Primes", FindPrimes, position = [500, 50])]):
When the Find Primes button is pressed the contents of the canvas will be serialized into an XML structure. This includes all text, existing math, newly entered math, the button, and all of the Maple code behind the button. The "canvas" will be passed as an argument to the FindPrimes() procedure, which can use GetElements to inspect values.
The button action procedure, FindPrimes, must create and return a Script. The script is generated by creating a Script object, and then calling any of the Script object methods such as SetActive, Annotate, and SetMath. This script is finally broken down into a series of XML instructions by calling ToString.
In the Maple environment, Script commands produce results immediately as they are invoked, but in the Maple Learn environment, no action is taken until the complete script is written and sent back to the MapleLearn interface, at which point the individual script commands are processed. This happens because the FindPrimes button action procedure is processed by a Maple engine in the cloud. This is separate from the returned XML instructions that will be processed in the client, no longer having a connection to Maple.
Example: Random Plot
Following the previous two sections, it is fitting to compare the way to create the "random plot" example, and contrast the programming styles.
UpdatePlot := proc( canvas ) local p := GetElements(canvas,custom="MyPlot")[1]; local sc := Script(); SetActive(sc,p); SetPlot(sc, plot(randpoly(x)) ); ToString(sc); end proc:
cv := NewCanvas([ "Random Plot", StaticPlot(custom="MyPlot"), ScriptButton("New Plot",UpdatePlot,position=[500,100]) ] ):
Constructing the NewCanvas is straightforward with the title as text, a plot, and a button. The button-click action code first uses GetElements to find the plot element with the custom attribute, "MyPlot". Noting that GetElements always returns an array, the [1] index at the end of the GetElements call resolves to the first element of the array. Then it constructs a Script object. Recorded in the script are two actions: (1) make the plot the active component, and (2) set a new plot at that location to plot(randpoly(x))
See DocumentTools:-Canvas for further information and examples, including a tutorial.
13.5 Programming Maplets
Maplets are another technology within Maple that allow you to build an interface from a program description. A key difference from the other options is that it does not use the same embedded components, and the interface always appears in a separate window. There are many examples of these interfaces as Tutors and Assistants under the Tools menu.
Maplets support UI elements including buttons, drop lists, text fields, and sliders. Some of the available UI elements are specific to Maple, for example, math fields and plot regions. For more information about these elements, refer to the Maplets,Elements help page.
For information on how to implement Maplet-based applications see examples,MapletsTutorial.
The Maplets,Utilities package, and Maplets,Examples package both contain useful pop-up style mini applications that are also beneficial to use along side component-based interfaces. These include a file browser, an error dialogue, an alert dialogue, and many more.
Download Help Document