wpfXamlDemo

wpfXamlDemo is a Dyalog APL namespace that demonstrate the use of WPF Xaml with some utility functions. Please read the Dyalog tutorials for more detailed explanations. Part of the code was taken from CodeProject

WHAT IS XAML ?

XAML stands for Extensible Application Markup Language (and pronounced "Zammel"). It's a simple language based on XML to create and initialize .NET objects with hierarchical relations. All you can do in XAML can also be done in code. XAML is just another way to create and initialize objects. You can use WPF without using XAML. It's up to you if you want to declare it in XAML or write it in code. See UsingWPF_from_APLX for a more complete introduction. Here is an example of Xaml found in the attached namespace called sample1: Show sample1

   1 <Window
   2    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   3    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
   4    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
   5    Height="200"
   6    Width="250"
   7    Title="MainWindow"
   8    WindowStartupLocation="CenterScreen">
   9    <Grid>
  10       <Grid.RowDefinitions>
  11          <RowDefinition Height="0.5*"/>
  12          <RowDefinition Height="0.5*"/>
  13       </Grid.RowDefinitions>
  14       <TextBox
  15          x:Name="textBox1"
  16          Width="130"
  17          HorizontalAlignment="Center"
  18          VerticalAlignment="Center"
  19          Text="TextBox"
  20          TextAlignment="Center"/>
  21       <Button
  22          x:Name="button1"
  23          Height="40"
  24          Width="120"
  25          Grid.Row="1"
  26          HorizontalAlignment="Center"
  27          VerticalAlignment="Center"
  28          Content="Click Me !"/>
  29    </Grid>
  30 </Window>

FixSimpleXaml

If you install the attached namespace and execute the following 2 lines in your workspace:

      win ← FixSimpleXaml sample1
      win.Show

The following Window is displayed:
Sample1.png

FixSimpleXaml is a function used to execute the Xaml and return the root element as a .Net object. All the other elements that are named in the Xaml will be attached by their names to the root object automatically. For example the element TextBox that is named textBox1 (line 15) and the element Button that is named button1 (line 22) are attached automatically to the root element by the function FixSimpleXaml:

      win.textBox1.Text
Textbox
      win.button1
System.Windows.Controls.Button: Click Me !

That way you don't need to define a separate variable for each named element. If you install the User Command sfPropGrid you can see all the properties, methods and events of all the named objects by doing (click the combo of NOE to access all the named objects):

      ]noe win       ⍝ noe = .Net Object Explorer

In conclusion FixSimpleXaml is a quick function to use on simple Xaml that do not have events and are properly formed. In production code you may want to do something like this:

 :If ⎕NULL≡myObject ← FixSimpleXaml myXaml
    ⍝ Fixing the Xaml did not work. Show an error and exit.
     ⎕ ← 'Error Fixing Xaml'
     →0
 :Else
    ⍝ There is no error.
 :EndIf

FixXaml

For cases where there is events that need to be fixed and better error handling the function FixXaml is available. It is useful when using Xaml taken directly from Visual Studio. For example, with the Xaml code in sample2 that has an event on the button (Click="__Button_Click") at line 24, if you do the following: <<SeeSaw: execution failed [malformed parameters] (see also the log)>>", tohide="<<(Hide sample2)>>", bg="#FEE1A5", speed="Slow")>>

   1 <Window
   2    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   3    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
   4    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
   5    x:Class="WpfApplication3.MainWindow"
   6    Height="200"
   7    Width="250"
   8    Title="MainWindow"
   9    WindowStartupLocation="CenterScreen">
  10    <Grid>
  11       <Grid.RowDefinitions>
  12          <RowDefinition Height="0.5*"/>
  13          <RowDefinition Height="0.5*"/>
  14       </Grid.RowDefinitions>
  15       <TextBox
  16          x:Name="textBox1"
  17          Width="130"
  18          HorizontalAlignment="Center"
  19          VerticalAlignment="Center"
  20          Text="TextBox"
  21          TextAlignment="Center"/>
  22       <Button
  23          x:Name="button1"
  24          Click="__Button_Click"
  25          Height="40"
  26          Width="120"
  27          Grid.Row="1"
  28          HorizontalAlignment="Center"
  29          VerticalAlignment="Center"
  30          Content="Click Me !"/>
  31    </Grid>
  32 </Window>

      win ← FixXaml sample2
      win.Show

and then if you click on the button, the value of the TextBox will change. The value of the TextBox can be retrieved simply by doing:

      win.textBox1.Text
I Was Clicked !

The function __Button_Click is handling the event. The author has taken the convention of naming the callback functions with a double underscore prefix. The goal is to be able to take the Xaml directly from Visual Studio to APL. The single underscore '_' is a valid first character in Visual Studio and APL but is in conflict with the menu object that will accept an underscore as first character to signify a keyboard shortcut. The line 5 of sample2 (x:Class="WpfApplication3.MainWindow") that is necessary to Visual Studio is also removed by FixXaml. See the comments of the function for more information.

In production code you may want to trap any error by using the following code:

 :If ⎕NULL≡↑myObject ← FixXaml myXaml
    ⍝ Fixing the Xaml did not work. Show an error and exit.
     ⎕ ← 2⊃myObject
     →0
 :Else
    ⍝ There is no error.
 :EndIf

About ⎕USING

In general when using Xaml there is no need to define a ⎕USING before fixing it except when there is a 3rd party dll involve. For example the variable NewWindow is defined as:

<Window
   xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
</Window>

and if you do:

      win ← FixSimpleXaml NewWindow
      win.Show

a Window will appear. In procedural code the following is required for the same result:

      ⎕USING←'System.Windows,WPF/PresentationFramework.dll'
      win ← ⎕NEW Window
      win.Show

The first element of Xaml must contain the xmlns= and the xmlns:x= declarations. This is instructing the parser (in our case System.Windows.Markup.XamlReader in the function FixSimpleXaml and FixXaml) to load a series of .NET namespaces required to parse the Xaml. This is just a convention, there is no Web site that exist with those names.

3rd Party Dll

When using 3rd party dll, they must be added to the declaration in the first element of Xaml. There are 2 choices on how to do it:

   xmlns:myname="clr‑namespace:MyNamespace;assembly=MyDllName"  ⍝ Notice that there is no '.dll' after MyDllName
or
   xmlns:myname="http://schemas.somewebsite.com/xaml" 

The method on first line is the preferred choice. Here is an example with the Syncfusion PropertyGrid:

   xmlns:sf="clr-namespace:Syncfusion.Windows.PropertyGrid;assembly=Syncfusion.PropertyGrid.Wpf"  ⍝ Notice no .dll at the end

and the Xaml will look like this:

   <sf:PropertyGrid x:Name="PGrid"/>  ⍝ Notice the prefix 'sf' is the same on both lines (you choose the prefix).

but this is not enough, ⎕USING must be setup correctly before fixing the Xaml for 3rd party dll in order for the parser to find the assembly. Here is an example for the Syncfusion PropertyGrid (Syncfusion/4.5/ is the Dyalog sub-directory where are located the assemblies):

     ⎕USING ← 'Syncfusion.Windows.PropertyGrid,Syncfusion/4.5/Syncfusion.PropertyGrid.Wpf.dll'    ⍝ The .dll is required

or more general

     ⎕USING ← 'MyNamespace,FullPathOfAssembly/MyDllName.dll'          ⍝ If the dll is outside of the dyalog.exe folder
     ⎕USING ← 'MyNamespace,SubDirectoryOfDyalogFolder/MyDllName.dll'  ⍝ If the dll is in a sub-directory of dyalog.exe
     ⎕USING ← 'MyNamespace,MyDllName.dll'                             ⍝ If the dll is in the same directory as dyalog.exe

Another thing with 3rd party dll is that it may be required (in some cases) to call an element of the namespace so that the dll is loaded into the APL memory before fixing the Xaml. Here is an example of how to do it:

      :Trap 0
          {}PropertyGrid     ⍝ This will load the assembly into memory if ⎕USING is setup properly.
      :Else
          ⎕ ← 'Error loading the assembly Syncfusion.PropertyGrid.Wpf.dll'
          → 0
      :End

Fixing Images

In Xaml you declare an Image object that is on disk the following way:

<Image x:Name="MyImageName"
       Source="PathOfMyImage\MyImage.png"   ⍝ PathOfMyImage can be tricky to declare sometimes and is not discussed here.
       Width="24"
       Height="24"/>

When you want to keep the Image definition in the APL workspace (because it is easier that way to distribute the workspace or the namespace) one way of doing it is by keeping a Base64 definition of the Image. Base64 encoding is using a set of 64 visible characters to encode binary data. It is widely used on the internet to transmit images and in email for the same (definition of Base64 on Wikipedia). Here is the steps to use this technique with APL:<<BR>>

Step 1: Save all the images in the workspace with the function FileToBase64String

At design time, save the images in the workspace. The APL variables name must be the same name has the image name in the Xaml and with the suffix _b64 (for naming convention only).

      Paste_b64 ← FileToBase64String 'D:\Paste.png'
      Copy_b64  ← FileToBase64String 'D:\Copy.png'
      Cut_b64   ← FileToBase64String 'D:\Cut.png'     

The variable Copy_b64 looks like this in the attached namespace:

      Copy_b64
iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAMAAADXqc3KAAAAaVBMVEX///8AAAC2tra2tra2tra2trb////+/v62trb9/f309PT7+/vExMSurq6Hh4f4+Pj8/Pz5+fnMzMyenp6/zduoqKilpaXNzc2xsbHu7u6Li4vk5OTa2tro6Oj6+vrm5uaMjIympqby8vJPA9lJAAAABnRSTlMAAM8Q7zCjkYU+AAAAiklEQVR4XoXM2Q7CMAxE0bqAk+7s+/7/H4mJMGNXQdzXo5mCiKK2X01nlCpSkbXdspxk4VCLOHBvDvwbYPSWgxu/JQMn5rotMzA8L+d24wAFB+tvzEeFIGDr/0LlrjwEE2D+CxoZ4aoCLASQgau8mQAsb7hqFLp7L28mBSKKPJgS0AdcgO6xtQm8AN3LEZUq6MiXAAAAAElFTkSuQmCC

This format is perfect for scripted namespace compare to storing the image with the original file values that would have some non visual characters.

Step 2: Set the .Source of each Image with the function ImageFromBase64String

At run time, after obtaining the root object, set the .Source of each Image from the previously saved APL variable.

      win ← FixSimpleXaml sample3

      win.Paste_b64.Source ← ImageFromBase64String Paste_b64
      win.Copy_b64.Source  ← ImageFromBase64String Copy_b64
      win.Cut_b64.Source   ← ImageFromBase64String Cut_b64

If there is many images in the Xaml, the following lines of code can automate the process at run time:

    ⍝ Get the names of all the 'Image' object that has been fixed in the root object
      imgNames ← {((⊂'Image')≡¨(⍵.⍎¨(⍵.⎕NL-9)).GetType.Name)/(⍵.⎕NL-9)} win

    ⍝ Set the Source of all the 'Image'. Each 'Image' names must have an equivalent
    ⍝ Base64 variable ending with '_b64' in the APL workspace.
      win {(⍺.⍎⍵).Source←ImageFromBase64String⍎⍵}¨ imgNames

The icon and the cursor of the main window can be fixed manually by doing the following:

    ⍝ Fix manually the Icon of the main window.
     win.Icon ← ImageFromBase64String Settings_b64

    ⍝ Fix manually the Cursor of the main window.
     win.Cursor ← CursorFromBase64String HandCursor_b64

Routed Events

In WPF it is possible to set a single function that will receive all the Click events on the window (in this example it is __EventHandler) by doing:

  ⍝ Set Routed Events on the whole Window for ClickEvent when a MenuItem or a Button are clicked.
    ⎕USING←'System.Windows,WPF/PresentationCore.dll' 'System.Windows.Controls.Primitives,WPF/PresentationFramework.dll'
    win.AddHandler(Controls.MenuItem.ClickEvent)(⎕NEW RoutedEventHandler(⎕OR'__EventHandler'))
    win.AddHandler(ButtonBase.ClickEvent)(⎕NEW RoutedEventHandler(⎕OR'__EventHandler'))

This is useful because that way you don't need to define individual click event for each controls. The function __EventHandler will handle all the click events on the window Show EventHandler code (the double underscore prefix is not necessary but is kept for naming consistency)

 __EventHandler(sender event);name
⍝ Single Event Handler for the Window

⍝ Get name of the control that was clicked
 name←event.Source.Name

⍝ sender is the Root Object in this case because 
⍝ the routed event was attached to it.
 sender.lStatusBar.Content←'I Was Clicked: ',name

⍝ Select the code to be executed:
 :Select name
 :Case 'mnuCut'

 :Case 'mnuCopy'

 :Case 'mnuPaste'

 :Case 'mnuPrint'

 :Case 'mnuQuit'

 :Case 'mnuAbout'

 :Case 'btnButton1'

 :Case 'btnButton2'

 :Case 'btnButton3'

 :Else
     'Error: Unknow Name'
 :EndSelect

When all this is done the window can be shown:

      win.Show

The function DemoSample3 contains all the code related to sample3.

The User Command wpfXamlEditor is designed to edit the Xaml saved in the workspace and on disk.

How to install wpfXamlDemo in your workspace

  1. Download wpfXamlDemo.v1.0.txt

  2. Do a Select all (Ctrl+A) and a copy (Ctrl+C).
  3. In your workspace execute )ed ⍟ wpfXamlDemo 

  4. Paste (Ctrl+V) the text into the Dyalog editor
  5. Press Escape and ')save' your workspace

Version Information

Original author:

Pierre Gilbert

Responsible:

PierreGilbert

Email:

<apgil AT SPAMFREE videotron DOT ca>

CategoryDyalogWpfUtilities