.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<max
      :AndIf 4>+/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


CategoryDyalogExamplesDotNet

.Net GUI and Graphics (last edited 2011-04-09 17:11:44 by KaiJaeger)