<> = .Net GUI and Graphics = GUI and Graphics can be used in APL through .Net. The following code was prepared with V12 of Dyalog. == Introduction == This page shows one way to create forms, form items, menus and how to display graphics. It uses the Mandelbrot Set as an example. == Implementation == Two classes were written, again, to present examples. It could have been done differently and more efficiently. Everything has been kept simple. The first class creates the main form which allows the user to modify the parameters of the drawing and start the drawing. In theory there could be menu items to tweak the process; some have been defined but not implemented. The second class creates an input form for the drawing limits. If you define these classes in a workspace you will need to first 'export to memory' before using them. This allows .Net classes to be used by creating the metadata so that the .Net stuff has all the "connections" into the workspace. It would be too time consuming to do it automatically when you exit a function or class so it is done only once. It also has to be done after a )LOAD because it does an initial )CLEAR before which cleans up all connections. == Classes == Here are the classes: === Class drawForm === {{{ :class drawForm: Form ⍝ Version 1.00 :USING System.Windows.Forms,system.windows.forms.dll :USING System.Drawing,system.drawing.dll :USING ,System.dll ⍝ This class will create a form to display any graphic. ⍝ The actual implementation generates a Mandelbrot set based pattern. ⍝ It was created from a C# example ⎕io←0 ⋄ ⎕ml←1 bmp←0 Title←'APL/.Net Mandelbrot set' ∇ make0 ⍝ niladic ctor :Access public :Implements constructor InitializeComponent ¯2.1 ¯1.2 1.3 1.2 12 ∇ ∇ make1 values ⍝ monadic ctor :Access public :Signature make1 System.Double[] ⍝ this needed for .Net :Implements constructor InitializeComponent values ∇ ∇ InitializeComponent arg;t;components;mainMenu1;SubHelp ⍝ Prepare the instance 'parameters must be 5 doubles'⎕SIGNAL(5∊⍴arg)↓5 ⍝ Use a container for the menus components←⎕NEW System.ComponentModel.Container mainMenu1←⎕NEW MainMenu(components) (mnuFractal SubMenus mnuHelp SubHelp)←⎕NEW¨¨⍬ 6 ⍬ 2⍴¨MenuItem SuspendLayout ⍝ Main menu mainMenu1.MenuItems.AddRange⊂,mnuFractal mnuHelp ⍝ 1st level mnuFractal.MenuItems.AddRange⊂SubMenus mnuFractal.TabIndex←0 mnuFractal.Text←'&',t←'Fractal' mnuFractal.onSelect←'m',t mnuHelp.MenuItems.AddRange⊂SubHelp mnuHelp.Text←'&',t←'Help' ⍝ Sub menus t←~∘'&'¨SubMenus.Text←{1↓¨(⍵∊1↑⍵)⊂⍵}'/S&ettings/&Start/&Pause/&Continue/S&top/E&xit' SubMenus.onClick←'m',¨t ⍝ m... fns SubHelp.Text←t←'About' 'How' SubHelp.onClick←'m',¨t SubMenus.TabIndex←⍳6 AutoScaleBaseSize←⎕NEW System.Drawing.Size(5,13) BackColor←Color.White ClientSize←⎕NEW System.Drawing.Size(744,489) Menu←mainMenu1 Name←'MainForm' Text←Title onPaint←'Form_Paint' ResumeLayout(0) Visible←1 Thread←⍴running←paused←0 ⍝ Create a settings form too Opt←⎕NEW frmOptions,⊂⊂arg ⍝ not visible but there ∇ ⍝ The onClick fns ∇ mFractal ⍝ Enable/disable menu commands based on program state. SubMenus.Enabled←1 1 0 0 0 1 ⍝ Set, Start, Exit :If (running) ⋄ SubMenus.Enabled←0∧0 0 1 1 1 1 ⋄ :EndIf ⍝ Pause, Cont, Stop, Exit :If (paused) ⋄ SubMenus.Enabled←0 0 0 1 1 1 ⋄ :EndIf ⍝ Cont, Stop, Exit ∇ ∇ mSettings ⍝ The settings command. {}Opt.ShowDialog ⍬ ⍝ this returns a result (which button was pressed) which we discard ∇ ∇ mStart(s e) ⍝ Start calculations in a separate thread running←1 ⍝ Start in a new thread Thread←MakeFractal&0 Text←Title,' (Calculating...)' ∇ ∇ mPause(s e) ⍝ Pause drawing paused←1 Text←Title,' (Paused)' ∇ ∇ mContinue(s e) ⍝ Resume calculations paused←0 ⍝ Change form caption. Text←Title,' (Calculating...)' ∇ ∇ mStop(s e) ⍝ Stop the process ⎕TKILL Thread Thread←⍴running←paused←0 Text←Title ∇ ∇ mExit(s e) ⎕TKILL Thread ⍝ in case still running Opt.Dispose Application.Exit ⍬ Dispose ⍬ ∇ ∇ mAbout {}MessageBox.Show('MandelBrot Set drawing',⎕TC[2],' DanB 2009')'About this' ∇ ∇ mHow;m;c m←'Use Settings for define the limits of the drawing',c←⎕TC[2] m,←'Use Start to start the drawing. You can Stop before finished',c m,←'or Pause the drawing thread.' {}MessageBox.Show m'Instructions' ∇ ⍝ Other fns ∇ z←max finditer pt;c;c2 c←pt ⋄ z←0 :While z+/c2×c2←pt+{(-/⍵×⍵)(2××/⍵)}c c←c2 ⋄ z+←1 :EndWhile ∇ ∇ MakeFractal dum;sz;p;iter;tlc;∆xy;∆ri;n;y;x;t;len;st;yi;np;gScreen;gBitmap ⍝ This is the fn that performs the calculations and displays the graphics gScreen←CreateGraphics ⍝ Make the form non-sizable. FormBorderStyle←FormBorderStyle.FixedSingle ⍝ Get the form's client size. sz←ClientSize.(Width Height) ⍝ The shadow bitmap - same size as the current form's client area. bmp←⎕NEW Bitmap sz ⍝ Graphics object for the bitmap. gBitmap←Graphics.FromImage(bmp) ⍝ The array of color pens np←¯1+⍴colorPens←Pens.(Black Red Gold Green Fuchsia Yellow Blue Gray Orange Aquamarine Azure BlueViolet Brown Coral Cornsilk Crimson Cyan DarkBlue DarkGoldenrod DarkGray DarkGreen DarkMagenta DarkOrange DarkOrchid Firebrick FloralWhite ForestGreen Gainsboro GreenYellow Honeydew HotPink IndianRed Indigo Ivory Khaki Lavender) ⍝ Black first ⍝ Get the parameters from the options form. p←Opt.Params ∆ri←-1⊃⊃-/(tlc n)←↓2 2⍴p ⋄ iter←np⌊¯1↑p ⍝ Get the pixel size of the client area ⍝ and determine the deltas - the amount that the Real and Imaginary values will be incremented. ∆xy←∆ri÷sz[1] yi←⌽⍳sz[1] :For x :In ⍳sz[0] ⍝ Try to hasten the pace by gathering same color patterns →running↓done ⍝ are we still on? :While paused ⋄ ⎕DL 1 ⋄ :EndWhile ⍝ have we been told to wait? n←iter finditer∘(tlc∘+)¨∆xy×x,¨yi st←¯1↓0,+\len←∊⍴¨{⊂⍨1,1↓⍵≠¯1⌽⍵}n :For i :In ⍳⍴st ⋄ y←st[i] p←colorPens[n[y]] gScreen.DrawLine t←(p,x,y,x,y+len[i]) ⍝ draw on the screen gBitmap.DrawLine t ⍝ draw on 'bmp' :EndFor :EndFor done:Text←Title ⍝ finished: restore title bar. ⍝ NOTE: this is not considered good style to set a parent's properties from a child task ⍝ Make the form sizable again. FormBorderStyle←FormBorderStyle.Sizable running←0 ∇ ∇ Form_Paint(s e) :If bmp≢0 ⋄ e.Graphics.DrawImage(bmp 0 0) ⋄ :EndIf ∇ :endclass }}} === Class frmOptions === The Settings form: {{{ :class frmOptions: Form :USING System.Windows.Forms,system.windows.forms.dll :USING ,system.dll :USING System.Drawing,system.drawing.dll ⍝ This class displays a form to input numbers. ⍝ The first 4 numbers represent the bottom left and top right corner ⍝ coordinates of a plane, the fifth number is an integer >0. ⍝ There is little validation performed on the input and none ⍝ on the instance parameters. ⎕io←1 :field public Params ∇ Init values :Access public :Implements constructor ⍝ Because .Net needs to know what it is dealing with (it is 'strongly typed') the ⍝ following statement is needed; it tells .Net what is expected, a list of doubles as arg: :Signature ctor System.Double[] Params←values ⍝ we expect 5 numbers ⍝ Set up form to input parameters labels←⎕NEW¨5⍴Label ⍝ Labels. All 5 labels are treated together labels.Location←{⎕NEW Point ⍵}¨(32,24)(40,56)(0,88)(40,120)(48 152) labels.Name←'label'∘,¨'12345' labels.TabIndex←0 labels.Size←{⎕NEW System.Drawing.Size(⍵ 24)}¨128 120 160 120 104 labels.Text←'Bottom left corner, Real part:' 'Imaginary part:' 'Top right corner, Real part:' 'Imaginary part:' 'Iterations:' labels.TextAlign←ContentAlignment.MiddleRight ⍝ Buttons (btnOK btnCL)←⎕NEW¨2⍴Button ⍝ The OK button; it is done one property at a time (cleaner): btnOK.Location←⎕NEW Point(272,136) btnOK.Name←'btnOK' btnOK.Size←⎕NEW System.Drawing.Size(88,32) btnOK.Text←'OK' btnOK.onClick←'bOKK' ⍝ For the Cancel button we do it all at once (hard to read): btnCL.(Location Name Size Text onClick)←(⎕NEW Point(272,88))'btnCL'(⎕NEW System.Drawing.Size(88,32))'Cancel' 'bCancelK' ⍝ Text fields params←⎕NEW¨5⍴TextBox params.Name←'txtTLReal' 'txtTLImag' 'txtBRReal' 'txtBRImag' 'txtIterations' params.Text←⍕¨Params params.Location←{⎕NEW Point(176 ⍵)}¨24 56 88 120 152 params.Size←⎕NEW System.Drawing.Size(30,20) params.TabIndex←⍳5 btnOK.TabIndex←6 btnCL.TabIndex←7 SuspendLayout ⍝ Form definition AutoScaleBaseSize←⎕NEW System.Drawing.Size(5,13) ClientSize←⎕NEW System.Drawing.Size(376,197) Controls.Add¨labels,params,btnOK btnCL FormBorderStyle←FormBorderStyle.FixedDialog Name←'frmOptions' Text←'Mandelbrot Parameters' ResumeLayout 0 PerformLayout ⍬ ∇ ∇ clean :Implements destructor Dispose ⍬ ∇ ∇ bOKK;n;⎕ML;in;b ⍝ User selects OK. Save values. ⎕ML←1 ⍝ accept - for ¯ :If ∧/b←(,1)∘≡¨1⊃¨n←{b←'-'=v←⍵ ⋄ (b/v)←'¯' ⋄ ⎕VFI v}¨params.Text :AndIf ¯1∧.=×-⌿2 2⍴in←∊2⊃¨n ⍝ 2nd set must be > than the 1st one (¯1↑in)⌈←1 params.Text←⍕¨Params←in Hide :Else {}MessageBox.Show⊂'Invalid number',(1<+/~b)/'s' :EndIf ∇ ∇ bCancelK ⍝ User selected Cancel. Restore values. params.Text←⍕¨Params ⍝ reset values Hide ∇ :endclass }}} == Testing == To test the class do {{{ ⎕NEW drawForm }}} Because the .Net framework does not close the form when it "goes out of scope" you don't need to assign the instance but you need to call the Close method to close it (it's exactly the same as in C#). Check that the various (enabled) menus work. Author: DanBaronet ---- CategoryDotNet - CategoryDyalogDotNet - CategoryDyalogExamplesDotNet