WPF XAML demo
This page gives a Dyalog APL demonstratration of WPF XAMPL with some utility functions, delivered in the form of a namespace.
Please read the Dyalog tutorials for a more detailed explanation. Part of the code was taken from CodeProject: A Beginner's Guide.
What is XAML?
XAML stands for Extensible Application Markup Language (and pronounced "Zammel"). It's a simple language based on wikipedia: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 whether you want to declare it in XAML or write it as code. There is a XAML Overview from Microsoft.
FixSimpleXaml
If you install the attached namespace and execute the following 2 lines in your workspace: <syntaxhighlight lang=apl>
win ← FixSimpleXaml sample1 win.Show
</source> The following window is displayed:
Template:Attachment:Sample1.png
<syntaxhighlight lang=apl inline>FixSimpleXaml</source> 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 to 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 <syntaxhighlight lang=apl inline>FixSimpleXaml</source>: <syntaxhighlight lang=apl>
win.textBox1.Text
Textbox
win.button1
System.Windows.Controls.Button: Click Me ! </source>
That way you don't need to define a separate variable for each named element. If you install the user command called <syntaxhighlight lang=apl inline>sfPropGrid</source> 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): <syntaxhighlight lang=apl>
]noe win ⍝ noe = .Net Object Explorer
</source> In conclusion <syntaxhighlight lang=apl inline>FixSimpleXaml</source> is a simple function to use on simple XAML that does not have events and is properly formed. In production code you may want to do something like this: <syntaxhighlight lang=apl>
: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
</source>
FixXaml
<syntaxhighlight lang=apl>
win ← FixXaml sample2 win.Show
</source> 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: <syntaxhighlight lang=apl>
win.textBox1.Text
I Was Clicked ! </source>
The function <syntaxhighlight lang=apl inline>__Button_Click</source> 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 the first character to define a keyboard shortcut.
The line 5 of sample2 (x:Class="!WpfApplication3.!MainWindow") that is required by Visual Studio is removed by <syntaxhighlight lang=apl inline>FixXaml</source>. See the comments in the function for more information.
In production code you may want to trap any error by using code like this: <syntaxhighlight lang=apl>
: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
</source>
About ⎕USING
In general, when using XAML there is no need to define a <syntaxhighlight lang=apl inline>⎕USING</source> before fixing it except when there is a 3rd party dll involved. For example, the variable <syntaxhighlight lang=apl inline>NewWindow</source> is defined as: <syntaxhighlight lang=apl> <Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
</Window> </source> and if you do: <syntaxhighlight lang=apl>
win ← FixSimpleXaml NewWindow win.Show
</source> a Window will appear. In procedural code, the following is required for the same result: <syntaxhighlight lang=apl>
⎕USING←'System.Windows,WPF/PresentationFramework.dll' win ← ⎕NEW Window win.Show
</source> 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 <syntaxhighlight lang=apl inline>FixSimpleXaml</source> and <syntaxhighlight lang=apl inline>FixXaml</source>) to load a series of .NET namespaces required to parse the XAML. This is just a convention, there is actually no such web site.
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:
<syntaxhighlight lang=apl>
xmlns:myname="clr‑namespace:MyNamespace;assembly=MyDllName" ⍝ Notice that there is no '.dll' after MyDllName
or
xmlns:myname="http://schemas.somewebsite.com/xaml" ⍝ A website name given by the 3rd party dll (may not exists)
</source> The method on the first line is recommended. Here is an example with the Syncfusion !PropertyGrid: <syntaxhighlight lang=apl>
xmlns:sf="clr-namespace:Syncfusion.Windows.PropertyGrid;assembly=Syncfusion.PropertyGrid.Wpf" ⍝ Notice no .dll at the end
</source> and the XAML will look like this: <syntaxhighlight lang=apl>
<sf:PropertyGrid x:Name="PGrid"/> ⍝ Notice the prefix 'sf' is the same on both lines (you choose the prefix).
</source> but this is not enough, <syntaxhighlight lang=apl inline>⎕USING</source> must be set up correctly before fixing the XAML for 3rd party dlls 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 the assemblies live): <syntaxhighlight lang=apl>
⎕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
</source> Another thing with the value of <syntaxhighlight lang=apl inline>⎕USING</source> for 3rd party dll is that it must be set in the __same namespace__ as where <syntaxhighlight lang=apl inline>FixXaml</source> or <syntaxhighlight lang=apl inline>FixSimpleXaml</source> is located (because <syntaxhighlight lang=apl inline>⎕USING</source> is Namespace scope). Alternatively, if you setup your <syntaxhighlight lang=apl inline>dyalog.exe.config</source> file that is in the same directory as the <syntaxhighlight lang=apl inline>dyalog.exe</source> file with a directive to look in the <syntaxhighlight lang=apl inline>Syncfusion/4.5</source> directory you will not need to set up <syntaxhighlight lang=apl inline>⎕USING</source> and you don't need to worry about loading it into memory.
Typically the file will look like this: <syntaxhighlight lang=apl>
<configuration> <startup useLegacyV2RuntimeActivationPolicy="true"> <supportedRuntime version="v4.0"> </supportedRuntime> </startup> <runtime> <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1"> <probing privatePath="MJHSoftware;Syncfusion/4.5"> </probing> </assemblyBinding> <loadFromRemoteSources enabled="true"/> </runtime> </configuration>
</source>
When using procedural code instead of XAML you may want to define a one time <syntaxhighlight lang=apl inline>⎕USING</source> like this: <syntaxhighlight lang=apl>
⎕USING←Using
</source>
<syntaxhighlight lang=apl>
use←Using ⍝ Full ⎕Using used by system.
use← 'System,System.dll' use,←⊂'System.Windows,WPF/PresentationFramework.dll' use,←⊂'System.Windows,WPF/PresentationCore.dll' use,←⊂'System.Windows.Input,WPF/PresentationCore.dll' use,←⊂'System.Windows.Shapes' use,←⊂'System.Windows.MessageBox' use,←⊂'Microsoft.Win32,WPF/PresentationFramework.dll' use,←⊂'System.Windows.Controls' use,←⊂'System.Windows.Markup' use,←⊂'System.Windows.Navigation' use,←⊂'System.Windows.Media' use,←⊂'System.Windows.Media.Imaging' use,←⊂'System.Windows.Documents' use,←⊂'System.Drawing.Bitmap' use,←⊂'System.Windows.Controls.Primitives' use,←⊂'System.Windows.Data' use,←⊂'System.Data,system.data.dll' use,←⊂'System.Xml,system.xml.dll' use,←⊂'System.Windows.Automation.Peers' use,←⊂'System.Windows.Automation.Provider,WPF/UIAutomationProvider.dll' use,←⊂'System.Windows,WPF/WindowsBase.dll' use,←⊂'System.ComponentModel,System.dll' use,←⊂'System.Windows.Media.Animation,WPF/PresentationFramework.dll' use,←⊂'System.Windows.Media.Animation,WPF/PresentationCore.dll' use,←⊂'System.Printing,WPF/ReachFramework.dll' use,←⊂'System.Windows.Threading' use,←⊂'System.Threading' use,←⊂'System.IO' use,←⊂'System.Windows.SystemParameters' use,←⊂'System.Collections' use,←⊂'System.Collections.Generic' use,←⊂'System.Collections.ObjectModel' use,←⊂'System.Objects' use,←⊂'System.Globalization'
</source>
To add a new definition to an existing <syntaxhighlight lang=apl inline>⎕USING</source> and to prevent duplicate entries the 'Union' operator is used like this: <syntaxhighlight lang=apl>
⎕USING∪←⊂'System.Windows.Controls,PresentationFramework.dll'
</source>
Fixing Images
In XAML you declare an Image object that is on disk the following way: <syntaxhighlight lang=apl> <Image x:Name="MyImageName"
Source="PathOfMyImage\MyImage.png" ⍝ PathOfMyImage can be tricky to declare sometimes and is not discussed here. Width="24" Height="24"/>
</source> 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, for example in emails for images and binary attachments.
Here are the steps to use this technique with APL:
Step 1: Save all the images in the workspace with the function FileToBase64String
At design time, save the images in the workspace. The APL variable name of any image must be the original name of the image name in the XAML with the added suffix _b64 (naming convention only). <syntaxhighlight lang=apl>
Paste_b64 ← FileToBase64String 'D:\Paste.png' Copy_b64 ← FileToBase64String 'D:\Copy.png' Cut_b64 ← FileToBase64String 'D:\Cut.png'
</source> The variable <syntaxhighlight lang=apl inline>Copy_b64</source> in the attached namespace looks like this: <syntaxhighlight lang=apl>
Copy_b64
iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAMAAADXqc3KAAAAaVBMVEX///8AAAC2tra2tra2tra2trb////+/v62trb9/f309PT7+/vExMSurq6Hh4f4+Pj8/Pz5+fnMzMyenp6/zduoqKilpaXNzc2xsbHu7u6Li4vk5OTa2tro6Oj6+vrm5uaMjIympqby8vJPA9lJAAAABnRSTlMAAM8Q7zCjkYU+AAAAiklEQVR4XoXM2Q7CMAxE0bqAk+7s+/7/H4mJMGNXQdzXo5mCiKK2X01nlCpSkbXdspxk4VCLOHBvDvwbYPSWgxu/JQMn5rotMzA8L+d24wAFB+tvzEeFIGDr/0LlrjwEE2D+CxoZ4aoCLASQgau8mQAsb7hqFLp7L28mBSKKPJgS0AdcgO6xtQm8AN3LEZUq6MiXAAAAAElFTkSuQmCC </source>
This format is perfect for scripted namespaces; compare this 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 <syntaxhighlight lang=apl inline>.Source</source> of each Image from the previously saved APL variable. <syntaxhighlight lang=apl>
win ← FixSimpleXaml sample3
win.Paste_b64.Source ← ImageFromBase64String Paste_b64 win.Copy_b64.Source ← ImageFromBase64String Copy_b64 win.Cut_b64.Source ← ImageFromBase64String Cut_b64
</source> If there are many images in the XAML, the following lines of code can automate the process at run time: <syntaxhighlight lang=apl>
⍝ 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
</source> The icon and the cursor of the main window can be fixed manually by doing the following: <syntaxhighlight lang=apl>
⍝ 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
</source>
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 <syntaxhighlight lang=apl inline>__EventHandler</source>) by doing: <syntaxhighlight lang=apl>
⍝ 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'))
</source> This is useful because that way you don't need to define individual click events for each control.
<syntaxhighlight lang=apl>
__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
</source>
When all this is done the window can be shown: <syntaxhighlight lang=apl>
win.Show
</source>
<syntaxhighlight lang=apl>
∇ DemoSample3;imgNames
⍝ Parse the xaml of the variable sample3 win←FixSimpleXaml sample3
⍝ Get the names of all the 'Image' object that has been fixed in rootObj imgNames←{((⊂'Image')≡¨(⍵.⍎¨(⍵.⎕NL-9)).GetType.Name)/(⍵.⎕NL-9)}win
⍝ Set the Source of all the 'Image'. Each 'Image' must have a Base64 variable of the same name. win{(⍺.⍎⍵).Source←ImageFromBase64String⍎⍵}¨imgNames
⍝ 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
⍝ 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'))
⍝ Show the window. win.Show ∇
</source>
The User Command | is designed to edit the XAML saved in the workspace and on disk.
Inserting WPF Controls into Traditional Dyalog Windows
You can insert a WPF control into an already existing application developed with <syntaxhighlight lang=apl inline>⎕WC</source> by using an ElementHost and a Dyalog's <syntaxhighlight lang=apl inline>NetControl</source>. Here is an example of how to insert a WPF Button: <syntaxhighlight lang=apl>
⎕USING∪←⊂'System.Windows.Forms.Integration,WPF/WindowsFormsIntegration.dll' ⍝ Location of 'ElementHost' ⎕USING∪←⊂'System.Windows.Controls,WPF/PresentationFramework.dll' ⍝ Location of WPF Button
'F' ⎕WC 'Form' ('Caption' 'My WPF Button')('Posn' (10 10))('Size'(10 20)) ⍝ Create a Form 'F.eh' ⎕WC 'NetControl' 'ElementHost' ('Posn'(10 10))('Size'(15 20)) ⍝ Add 'ElementHost'
⍝ Prepare the WPF Button, and make it the 'child' property of the 'ElementHost' control bn ← ⎕NEW Button bn.Content ← 'Click Me' bn.onClick ← '__Button_Click' F.eh.Child ← bn
</source> Instead of a Button, you could use some complex XAML developed with Visual Studio. You just need to fix the XAML with <syntaxhighlight lang=apl inline>FixXaml</source> and make it the child of the <syntaxhighlight lang=apl inline>ElementHost</source> element.
How to Access the UI Thread from Another Thread
Since Net 2.0 Microsoft does not allow writing on the UI thread from another thread for security and stability reasons. Consequently, if you are executing a long calculation on another thread and you want to show the results by accessing directly the UI thread it is not possible.
With a Delegate
Here is an example of how to use that function: <syntaxhighlight lang=apl>
⎕USING←'System.Windows,WPF/PresentationFramework.dll' win ← ⎕NEW Window win.Show win.Title ← 'MyTitle'
win DispatchDelegate& 'win.Title←MyDelegateTitle' ⍝ The .Title property is changed from another thread
</source> {{Collapse|If you have many lines that need to be executed in the UI thread the function <syntaxhighlight lang=apl inline>ScriptFollowsDispatchDelegate</source> can be used like this| <syntaxhighlight lang=apl>
ScriptFollowsDispatchDelegate obj;actions;delegate;dtlb;⎕IO;⎕ML;⎕USING
⍝ Function to write asynchronously to the UI thread from another thread. ⍝ The calling thread does not wait for the operation to finish. ⍝ Treat following commented lines in caller as a script. ⍝ Lines beginning with ⍝∇ are kept ⍝ Lines beginning with ⍝∇⍝ are stripped out (comments)
⍝ obj = UI Object that you want to write to from another thread ⍝ actions = Lines to execute in the UI thread.
(⎕IO ⎕ML)←1 3
:Trap 0 ⎕USING←'System,System.dll' 'System.Windows.Threading,WPF/WindowsBase.dll'
⍝ Create a function (⍙Delegate) with the 'action' to execute in the UI thread. ⍝ The function will be executed where 'DispatchDelegate' is called and not where ⍝ 'DispatchDelegate' is located. dtlb←{⍵{((∨\⍵)∧⌽∨\⌽⍵)/⍺}' '≠⍵} actions←{{'⍝'=↑⍵: ⋄ ' ',dtlb ⍵}¨2↓¨⍵/⍨∧\(⊂'⍝∇')≡¨2↑¨⍵}dtlb¨(1+2⊃⎕LC)↓⎕NR 2⊃⎕XSI delegate←(⎕IO⊃⎕RSI).⎕OR(⎕IO⊃⎕RSI).⎕FX(⊂'⍙Delegate'),actions,(⊂'⎕EX ⍙Delegate')
⍝ Get a 'Dispatcher' from the UI object. That way we are sure to have the right UI thread. ⍝ Use the method '.Invoke' instead of '.BeginInvoke' for synchronous call. {}obj.Dispatcher.BeginInvoke(DispatcherPriority.Normal(⎕NEW Action delegate))
:Else ⎕←'Error ScriptFollowsDispatchDelegate: ',actions
⍝ Returns the Last Error :If 90=⎕EN ⎕←'EXCEPTION: ',⎕EXCEPTION.Message :Else ⎕←(1⊃⎕DM),': ',(2⊃⎕DM) :EndIf :End
</source> }} <syntaxhighlight lang=apl> [3] [4] ⍝ ... long running process on another thread ... [5] [6] ScriptFollowsDispatchDelegate myObj [7] ⍝∇ line 1 to by executed in the UI thread [8] ⍝∇ line 2 to by executed in the UI thread [9] ⍝∇ line 3 to by executed in the UI thread [10] ⍝∇ line 4 to by executed in the UI thread </source> The author has taken the prefix '⍝∇' instead of '⍝' because in production code you will probably want to erase all the comments in your runtime WS because they are useless and it is helping to obfuscate the code while taking less space.
With a DispatcherTimer
If you have a repetitive task to be executed on the UI thread then you can use a DispatcherTimer like this: <syntaxhighlight lang=apl>
⎕USING∪←'System.Windows,WPF/WindowsBase.dll' 'System,mscorlib.dll' tm1_obj ← ⎕NEW Threading.DispatcherTimer(Threading.DispatcherPriority.Normal myObj.Dispatcher) tm1_obj.Interval ← TimeSpan.FromSeconds 1 tm1_obj.onTick ← '#.MyFunction' ⍝ Function to be executed every 1 second on the Dispatcher of the object 'myObj' tm1_obj.Start
</source> Note that since the <syntaxhighlight lang=apl inline>DispatcherTimer</source> is executed on the UI thread you cannot have a long callback because it will freeze the UI during its execution.
With a BackgroundWorker
The .NET framework provides a simple way to get started in threading with the BackgroundWorker component. This wraps much of the complexity and makes spawning a background thread relatively safe. It offers several features which include spawning a background thread, the ability to cancel the background process before it has completed, and the chance to report the progress back to your UI. The <syntaxhighlight lang=apl inline>BackgroundWorker</source> is an excellent tool when you want multithreading in your application with access to the UI thread, mainly because it's so easy to use.
An example is included in this namespace. It is inspired by this article. You start the example by doing: <syntaxhighlight lang=apl>
BackgroundWorkerSample& 1000
</source> A window will appear and if you click on the 'Start' button, the UI will be refreshed with the multiple of 42 between 1 and 1000 while simulating a long calculation. Check the comments of this function with the links for more details.
How to install wpfXamlDemo in your workspace
- Download attachment:wpfXamlDemo.v1.6.txt
- Do a Select all (Ctrl+A) and a copy (Ctrl+C).
- In your workspace execute <syntaxhighlight lang=apl inline>)ed ⍟ wpfXamlDemo </source>
- Paste (Ctrl+V) the text into the Dyalog editor
- Press Escape and ')save' your workspace
Optionally to de-script the namespace you can do: <syntaxhighlight lang=apl>
#.wpfXamlDemo←{('n' ⎕NS ⍵)⊢n←⎕NS }#.wpfXamlDemo
</source>