Configuration Revisited

An earlier paper described an approach to editing, storing and using application configuration values, a new version is under development which migrates the user interface from APL/W native GUI controls to Microsoft's WPF (Windows Presentation Foundation).  This is work-in-progress, forming a part of preparing the Dogon Research APL and WPF Tutorial.  These are early days for the author's use of WPF, so there may be various issues - but the code does seem to function.

Briefly, the underlying design of this approach to application configuration is that configuration values are held as components in an APL component file, each consisting of four elements:

Configuration review and editing is done using a form which is generated in code from these values (only this part is changed by the move to WPF, reading and writing is unchanged).  Code is (as before) held in a script file which is presented and described in fragments below (contact the author if you want a complete copy)...

Overall Architecture

Everything is under the control of the <Configure> function, which is called with

∇ Configure(form app file clist help);⎕IO;⎕ML;formc;config;rc
 ⍝ Changing configuration
      ⎕IO ⎕ML←0 3
      config←file ReadConfig,clist
      formc←#.WPF.LoadXaml ConfigXaml config
      formc←config SetObjects formc
      formc←config SetCallbacks formc
      formc←(⍴config)SetHeights formc
      formc.Icon←form.Icon
      rc←formc.ShowDialog
    ∇

Structure here is one that's presently seeming to serve its purpose displaying and using WPF forms, after getting the required configuration items...

Building the XAML

While mainstream/conventional WPF applications hold their form definitions on file, we can't do this here (well, maybe we could - but this seems easier) and so we build the XAML piece-by-piece before loading it ready for use.

Our form is going to have two parts

   ∇ z←ConfigXaml w;⎕IO;⎕ML;item;row
⍝ Form definition XAML
      ⎕IO ⎕ML←0 3
      z←ConfigXamlPreamble⍴w
      :For item row :InEach w(⍳⍴w)
          z,←ConfigXamlItem item row
      :EndFor
      z,←ConfigXamlPostamble ⍬
    ∇

Preamble

Nothing very interesting here, just setting up the form and a grid to hold item-related controls.

   ∇ z←ConfigXamlPreamble w;⎕IO;⎕ML
    ⍝ First part of configuration form XAML
      ⎕IO ⎕ML←0 3
      z←'<Window '
      z,←'    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"'
      z,←'    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"'
      z,←'    Title="Configuration" Width="500" MinWidth="500" Background="#FFF0F0F0">'
      z,←'    <Grid>'
      z,←'        <StackPanel HorizontalAlignment="Stretch" Margin="0" VerticalAlignment="Top" Name="stkControls" >'
      z,←'            <Grid>'
      z,←'                <Grid.RowDefinitions>'
      z,←∊w⍴⊂'                    <RowDefinition />'
      z,←'                </Grid.RowDefinitions>'
      z,←'                <Grid.ColumnDefinitions>'
      z,←'                    <ColumnDefinition MinWidth="200" />'
      z,←'                    <ColumnDefinition MinWidth="200"/>'
      z,←'                    <ColumnDefinition MinWidth="50"/>'
      z,←'                </Grid.ColumnDefinitions>'
    ∇

Per-item controls

This is the complicated bit of form creation, there's a row for each configuration item, and the contents of the row are determined by the item type.  At the time of writing <Folder> is the only type handled (others are going to be migrated as and when, there's a bit of placeholder code here).  Template is that each row will contain a label and an appropriate control to hold the value and allow the user to change it - some types have an additional control.  The "value" control is given a standardised name.

   ∇ z←ConfigXamlItem(item row);⎕IO;⎕ML;value;var;desc;type;name
    ⍝ Create definition for a row of the configuration form
      ⎕IO ⎕ML←0 3
      value var desc type←item
      name←{∊¯1↑(~⍵∊'.')⊂⍵}var
      z←'                <Label Content="',(desc,': '),'" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Grid.Row="',(⍕row),'" HorizontalContentAlignment="Right" />'
      :Select type
      :Case 'Char'
          z,←ConfigXamlChar row name value
      :Case 'Folder'
          z,←ConfigXamlFolder row name value
      :Case 'Select'
          z,←ConfigXamlSelect row name value
      :Else
          ∘ ⍝ Not yet defined
      :EndSelect
    ∇

Type-dependent code was fairly robotic in the earlier incarnation, so the prognosis for hole-filling is optimistic...

Char

(not yet completed)
    ∇ z←ConfigXamlChar(row name value);⎕IO;⎕ML
    ⍝ Create definition for char row of configuration form
      ⎕IO ⎕ML←0 3
      ∘ ⍝ Modify line below (to be done) ...
      z←'                <TextBox Grid.Column="1" Grid.Row="0" HorizontalAlignment="Stretch" Name="ComboBox1" VerticalAlignment="Top" />'
    ∇

Folder

For folders, we need a TextBox for the folder name, and a Button which will allow the user to select a different folder.

    ∇ z←ConfigXamlFolder(row name value);⎕IO;⎕ML
    ⍝ Create definition for folder row of configuration form
      ⎕IO ⎕ML←0 3
      z←'                 <TextBox Text="',value,'" Grid.Column="1" Grid.Row="',(⍕row),'" HorizontalAlignment="Stretch" Name="ctl',name,'" VerticalAlignment="Top" />'
      z,←'                <Button Content="..." Grid.Column="2" Grid.Row="',(⍕row),'" HorizontalAlignment="Stretch" Name="btn',name,'" VerticalAlignment="Top"/>'
    ∇

Select

(not yet completed)

    ∇ z←ConfigXamlSelect(row name value);⎕IO;⎕ML
    ⍝ Create definition for select row of configuration form
      ⎕IO ⎕ML←0 3
      ∘ ⍝ Modify line below (to be done) ...
      z←'                <ComboBox Grid.Column="1" Grid.Row="1" HorizontalAlignment="Stretch" Name="ComboBox2" VerticalAlignment="Top" />'
    ∇

Postamble

Having got this far, we just need to add the OK/Cancel buttons and close up the XAML...

∇ z←ConfigXamlPostamble w;⎕IO;⎕ML
    ⍝ Tail of configuration form XAML
      ⎕IO ⎕ML←0 3
      z←'            </Grid>'
      z,←'        </StackPanel>'
      z,←'        <StackPanel HorizontalAlignment="Stretch" Margin="0" VerticalAlignment="Bottom" Orientation="Horizontal" Name="stkButtons">'
      z,←'            <Button Content="OK" Name="btnOK" HorizontalAlignment="Left" MinWidth="100" Margin="5" IsEnabled="False"></Button>'
      z,←'            <Button Content="Cancel" Name="btnCancel" MinWidth="100" HorizontalAlignment="Left" Margin="5" />'
      z,←'        </StackPanel>'
      z,←'    </Grid>'
      z,←'</Window>'
    ∇


Make the APL Objects

I suspect that a slicker approach will reveal itself over time, but this does for now...

    ∇ z←a SetObjects w;⎕IO;⎕ML;name;type
⍝ Set the configuration form objects
      ⎕IO ⎕ML←0 3
      z←w
      z.stkControls←z.FindName⊂'stkControls'
      z.stkButtons←z.FindName⊂'stkButtons'
      z.btnCancel←z.FindName⊂'btnCancel'
      z.btnOK←z.FindName⊂'btnOK'
      :For name type :In (⊂0 1 0 1)/¨a
          name←{∊¯1↑(~⍵∊'.')⊂⍵}name
          ⍎'z.ctl',name,'←z.FindName ⊂''ctl'',name'
          :If 'Folder'≡type
              ⍎'z.btn',name,'←z.FindName ⊂''btn'',name'
          :EndIf
      :EndFor
    ∇

Nothing complex going on and a fairly obvious (?) special-case for the Folder type.

Define the Callbacks

Since APL is a bit "decoupled" from XAML compared to things like VB we have this explicit task (which is again fairly mndane)...


  ∇ z←a SetCallbacks w;⎕IO;⎕ML;var;name;type
⍝ Set callbacks for the configuration form
      ⎕IO ⎕ML←0 3
      z←w
      :For var type :In (⊂0 1 0 1)/¨a
          name←{∊¯1↑(~⍵∊'.')⊂⍵}var
          ⍎'z.ctl',name,'.onTextChanged ←''ConfigChange'''
          :If type≡'Folder'
              ⍎'z.btn',name,'.onClick←''ConfigBrowseFolder'''
          :EndIf
      :EndFor
      z.btnCancel.onClick←'ConfigCancel'
      z.btnOK.onClick←'ConfigOK'
    ∇


Jiggerypokery

Just sorting out some heights so that the form looks presentable (we've also separatly borrowed an icon from the parent form).

   ∇ z←a SetHeights w;⎕IO;⎕ML
    ⍝ Set panel and form heights
      ⎕IO ⎕ML←0 3
      z←w
      z.stkControls.(Height MinHeight)←25×a
      z.stkButtons.(Height MinHeight)←30
      z.(Height MinHeight)←100⌈+/z.(stkControls stkButtons).Height
    ∇


Our User Springs into Action

Something Changes

We need to notice that they've changed something, remind them and let them save...

   ∇ z←ConfigChange w;⎕IO;⎕ML;⎕USING
 ⍝ Callback for changes on configuration form
      ⎕IO ⎕ML←0 3
      ⎕USING←#.WPF.Using
      (↑w).Background←Brushes.Yellow
      formc.btnOK.IsEnabled←1
    ∇


A difference from native APL/W controls here, the Change event of WPF fires whenever any change is made to control contents (APL/W waits until it seems the user has moved on to other things) - maybe I need a different triggering event.

Browsing for Folders

Because we're living independently from APL/W's controls, we need to look for folders differently (#.UTIL.DIALOGUE does the business) and leverage our simple-minded control naming to get the value where it needs to go...

   ∇ ConfigBrowseFolder w;⎕IO;⎕ML;ctl;btn;folder;new
 ⍝ Browse for a configuration folder
      ⎕IO ⎕ML←0 3
      btn←(↑w).Name
      ctl←'ctl',3↓btn
      folder←⍎'formc.',ctl,'.Text'
      new←#.UTIL.DIALOGUE.SelectFolder folder
      :If ×⍴new
          ⍎'formc.',ctl,'.Text←''',new,''''
      :EndIf
    ∇

Saving the New Configuration

Nothing complex, save the values and close the form.  Saving is just a sequential grab from each value control, appropriate translation and write everything back to file (and no fancy look-see to only save what's changed, life's too short).

   ∇ ConfigOK w;⎕IO;⎕ML
 ⍝ Callback for configuration OK button
      ⎕IO ⎕ML←0 3
      ConfigSave w
      formc.Close
    ∇

∇ ConfigSave w;⎕IO;⎕ML;item;no;value;var;desc;type;cname
    ⍝ Collect configuration values from form and save to file
      :For (item no) :InEach config(⍳⍴config)
          value var desc type←item
          cname←'ctl',{∊¯1↑(~⍵∊'.')⊂⍵}var
          :Select type
          :Case 'Folder'
              value←⍎'formc.',cname,'.Text'
              ⍎var,'←value'
          :Else
              ∘ ⍝ Type not yet recoded
          :EndSelect
          (no 0⊃config)←value
      :EndFor
      file WriteConfig(⊂¨config),¨clist
    ∇

Cancelling

Needing to make sure that they're not cancelling by oversight (#.UTIL.DIALOGUE contains code for using Common Dialogues - seems WPF is too grand to bother itself with such trivia)...

   ∇ ConfigCancel w;⎕IO;⎕ML;⎕USING
 ⍝ Callback for configuration cancel
      ⎕IO ⎕ML←0 3
      ⎕USING←#.WPF.Using
      :If formc.btnOK.IsEnabled
      :AndIf #.UTIL.DIALOGUE.YesNo'Save configuration' 'Do you want to save your configuration changes'
          ConfigSave w
      :EndIf
      formc.Close
    ∇

Summing Up

Seems to work, optimistic that extension to other types of configuration item should be fairly mechanical (they're all working that way in the previous edition).

Some of the code may prove an embarrassment in coming months and years as experience with WPF/XAML grows.

Feeling a little miffed that I'm spending time migrating code that's working perfectly adequately as an insurance against Dyalog pulling the plug on native GUI controls (given their respective track records I'd have greater expectation that Microsoft will pull the plug on WPF).  But also viewing it as a useful learning experience against the day of being able to generate richer GUIs with WPF.

Feeling a little uncomfortable with reliance on globals - this rather mirrors feelings in the early days of using the APL/W native GUI; either this is going to become less of an issue or else I'm going to find ways to clean the code up - which is how my earlier native code went.

Page updated 25 August 2010; Copyright © Dogon Research 2010