How handle events of WPF windows with methods of APL classes

Using (or providing) Microsoft.NET Classes

How handle events of WPF windows with methods of APL classes

Postby uwejanza on Fri Sep 03, 2010 3:31 pm

For the application I am working on I would like to use WPF. So I can design my window with a graphical design tool which delivers XAML code.

My application will need several windows with specific data processing in the background. So I think it would be nice to have a kind of toolkit to handle the vanilla window housekeeping. And I'd like to use an APL class that does this basic stuff.

The idea

For each of the application windows I could then write an application window class that inherits from the vanilla window class. The application window class instances would keep the local data they need inside instance fields.
Event handlers could be very specific. I would implement them as PUBLIC INSTANCE methods, because each of these methods finds it's way to the local data of the class instance it resides in.
If I needed several concurrent copies of one application window, I could create several instances of that application window class.

The experiment

Let Def be a text variable containing the following XAML definition of a very simple window:
Code: Select all
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="Hello WPF!"
Height="100" Width="150">
<StackPanel>
<Button Height="23" Name="buttonOK" IsDefault="True" >OK</Button>
<Button Height="23" Name="buttonCancel" IsCancel="True" >Cancel</Button>
</StackPanel>
</Window>


Define a very simple class WPFWindow to handle WPF stuff. The example given here is extremely simplified. Please note that I am attempting to use WPF 4. So there are two :USING declarations in the class that point to the standard dotNet Framework 4 installation directory inside the Windows installation directory.
Code: Select all
:Class WPFWindow

:using System,System.dll
:using System.IO,mscorlib.dll
:using System.Xml,system.xml.dll
:using System.Windows, C:\Windows\Microsoft.NET\Framework\v4.0.30319\WPF\PresentationFramework.dll
:using System.Windows, C:\Windows\Microsoft.NET\Framework\v4.0.30319\WPF\PresentationCore.dll
:using System.Windows.Controls
:using System.Windows.Markup
:using System.Windows.Navigation

    :Field Public Instance window
    ⍝ Field Private Instance Data   ⍝ not implemented for the sake of simplicity

    ⎕IO ⎕ML←1 3

    ∇ Make(Xaml)
    ⍝a Xaml: C1: XAML Definition of the window
      :Access Public Instance
      :Implements Constructor
      window←XamlReader.Load ⎕NEW XmlTextReader(⎕NEW StringReader(⊂Xaml))
      ⎕DF'Instance of #.WPFWindow'
    ∇

    ∇ MyCallbackMethod event
      :Access Public Instance
      ∘
    ⍝
    ∇

:EndClass


Of course at load time of my interpreter I need a dyalog.exe.manifest file to enable dotNet4.
<?xml version ="1.0"?>
<configuration>
<runtime>
<NetFx40_LegacySecurityPolicy enabled="true"/>
</runtime>
<startup useLegacyV2RuntimeActivationPolicy="true">
<supportedRuntime version="v4.0.30319" />
</startup>
</configuration>


In my little test WS there are also two functions. One is supposed to be used as an example event handler.

Code: Select all
∇MyCallbackFunction[⎕]∇
   MyCallbackFunction event
   ∘
  ⍝


The other one is for running the test:
Code: Select all
∇{r}←Test;ctl;inst
   inst←⎕NEW #.WPFWindow(,⊂Def)
  ⍝ try to define a callback function
   ctl←inst.window.FindName⊂'buttonOK'
  ⍝ ctl.onClick←'#.MyCallbackFunction'    ⍝ USE OLD STYLE FUNCTION: IT WORKS
   ctl.onClick←'inst.MyCallbackMethod'    ⍝ PUBLIC INSTANCE METHOD: THIS ONE FAILS
   r←inst.window.ShowDialog
  ⍝


As you can see, first I create an instance of my WPFWindow class. Then I define the method MyCallbackMethod of that class instance to be the event handler for the Click event of the OK button.

Run Test and push the OK button!

Alas!


Unfortunately, whenever I click the OK button I get an exception with messages like the following ("bei" is German for "at")

System.MissingMethodException: Method not found inst.MyCallbackMethod
( bei SyncCall(sDyalogInterfaces* sdyalog, Int32 idx, Int32 idx_class, Int32 thunks, String Name, String PropName, Object args, Type RsltType)
bei ToDyalog.Call(String Name, String PropName, Int32 target, Object args, Type RsltType)
bei DyalogDelegate_System_Windows_RoutedEventHandler.Invoke(Object sender, RoutedEventArgs e)
bei System.Windows.RoutedEventHandlerInfo.InvokeHandler(Object target, RoutedEventArgs routedEventArgs)
bei System.Windows.EventRoute.InvokeHandlersImpl(Object source, RoutedEventArgs args, Boolean reRaised)
bei System.Windows.UIElement.RaiseEventImpl(DependencyObject sender, RoutedEventArgs args)
bei System.Windows.UIElement.RaiseEvent(RoutedEventArgs e)
bei System.Windows.Controls.Primitives.ButtonBase.OnClick()
bei System.Windows.Controls.Button.OnClick()
bei System.Windows.Controls.Primitives.ButtonBase.OnMouseLeftButtonUp(MouseButtonEventArgs e)
bei System.Windows.UIElement.OnMouseLeftButtonUpThunk(Object sender, MouseButtonEventArgs e)
bei System.Windows.Input.MouseButtonEventArgs.InvokeEventHandler(Delegate genericHandler, Object genericTarget)
bei System.Windows.RoutedEventArgs.InvokeHandler(Delegate handler, Object target)
bei System.Windows.RoutedEventHandlerInfo.InvokeHandler(Object target, RoutedEventArgs routedEventArgs)
bei System.Windows.EventRoute.InvokeHandlersImpl(Object source, RoutedEventArgs args, Boolean reRaised)
bei System.Windows.UIElement.ReRaiseEventAs(DependencyObject sender, RoutedEventArgs args, RoutedEvent newEvent)
bei System.Windows.UIElement.OnMouseUpThunk(Object sender, MouseButtonEventArgs e)
bei System.Windows.Input.MouseButtonEventArgs.InvokeEventHandler(Delegate genericHandler, Object genericTarget)
bei System.Windows.RoutedEventArgs.InvokeHandler(Delegate handler, Object target)
bei System.Windows.RoutedEventHandlerInfo.InvokeHandler(Object target, RoutedEventArgs routedEventArgs)
bei System.Windows.EventRoute.InvokeHandlersImpl(Object source, RoutedEventArgs args, Boolean reRaised)
bei System.Windows.UIElement.RaiseEventImpl(DependencyObject sender, RoutedEventArgs args)
bei System.Windows.UIElement.RaiseTrustedEvent(RoutedEventArgs args)
bei System.Windows.UIElement.RaiseEvent(RoutedEventArgs args, Boolean trusted)
bei System.Windows.Input.InputManager.ProcessStagingArea()
bei System.Windows.Input.InputManager.ProcessInput(InputEventArgs input)
bei System.Windows.Input.InputProviderSite.ReportInput(InputReport inputReport)
bei System.Windows.Interop.HwndMouseInputProvider.ReportInput(IntPtr hwnd, InputMode mode, Int32 timestamp, RawMouseActions actions, Int32 x, Int32 y, Int32 wheel)
bei System.Windows.Interop.HwndMouseInputProvider.FilterMessage(IntPtr hwnd, WindowMessage msg, IntPtr wParam, IntPtr lParam, Boolean& handled)
bei System.Windows.Interop.HwndSource.InputFilterMessage(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam, Boolean& handled)
bei MS.Win32.HwndWrapper.WndProc(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam, Boolean& handled)
bei MS.Win32.HwndSubclass.DispatcherCallbackOperation(Object o)
bei System.Windows.Threading.ExceptionWrapper.InternalRealCall(Delegate callback, Object args, Int32 numArgs)
bei MS.Internal.Threading.ExceptionFilterHelper.TryCatchWhen(Object source, Delegate method, Object args, Int32 numArgs, Delegate catchHandler)
bei System.Windows.Threading.Dispatcher.InvokeImpl(DispatcherPriority priority, TimeSpan timeout, Delegate method, Object args, Int32 numArgs)
bei MS.Win32.HwndSubclass.SubclassWndProc(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam)
bei MS.Win32.UnsafeNativeMethods.DispatchMessage(MSG& msg)
bei System.Windows.Threading.Dispatcher.PushFrameImpl(DispatcherFrame frame)
bei System.Windows.Threading.Dispatcher.PushFrame(DispatcherFrame frame)
bei System.Windows.Window.ShowHelper(Object booleanBox)
bei System.Windows.Window.Show()
bei System.Windows.Window.ShowDialog())



I tried several ways to pass the location of MyCallbackMethod to the interface. None worked. Only when I attach the ordinary function MyCallbackFunction as an event handler, the callback works. But an ordinary function is in no way related to the data in my class instance. So this is not helpful.

What am I doing wrong? Is there a way to attach instance methods as event handlers to controls?

Uwe Janza
uwejanza
 
Posts: 19
Joined: Tue Mar 09, 2010 2:01 pm
Location: Nürnberg, Germany

Re: How handle events of WPF windows with methods of APL classes

Postby RossHale on Sat Sep 04, 2010 7:45 pm

Hi Uwe,

One possible approach to callbacks from .NET and WPF to Dyalog APL
is to post the callback to a plain vanilla APL function in the workspace
or in a namespace. Using a single such function per APL defined class,
each class instance can set callback events on .NET (or WPF) objects
designating this function for callback.
Each class instance also maintains a PUBLIC INSTANCE senders list of event objects.
The external to the class callback function simply calls a class PUBLIC SHARED
callback method which uses ⎕INSTANCES on the class to reference
the senders list in each current class instance.
If the current callback sender object is in the senders list for an instance,
the callback method for that instance is invoked/called.
**** Mostly event bearing objects are created at the instance level and are unique to an instance.
**** Consideration of WPF routed event handling is open concern.
**** Possibility of no current instance with sender in senders list;
**** perhaps mis-matched lifetimes of event object and instance.

TThis approach sketch handles one callback method per class instance
which can be coded to discriminate by sender object.
If there is need for multiple callback events for a particular sender object,
then perhaps the EventArgs parameter of the callback can be used to select
the appropriate action or an overload by difference in specific EventArgs might be useful.

Code: Select all
MyCallbackFunction(sender event)
 :Signature MyCallbackFunction Object sender, EventArgs event
 WPFWindow.CallbackMethod(sender event)
  ⍝
  ⍝ One Callback function per class.
  ⍝ It calls a single shared Callback method for the class
  ⍝ that in turn inspects the current instances of the class
  ⍝ for an occurrence of the sender object in a senders list
  ⍝ kept in each instance.


Code: Select all
{r}←Test;ctl;inst;inst2
 ⍝ inst←⎕NEW #.WPFWindow(,⊂Def)
 inst←⎕NEW #.WPFWindow((⊂Def),⊂'First Try')
  ⍝ try to define a callback function
 ctl←inst.window.FindName⊂'buttonOK'
  ⍝
 inst.senders∪←ctl                           ⍝ post object to instance senders callback list
 ctl.onClick←'#.MyCallbackFunction'          ⍝ USE OLD STYLE FUNCTION: IT WORKS
  ⍝
  ⍝ ctl.onClick←'inst.MyCallbackMethod'      ⍝ PUBLIC INSTANCE METHOD: THIS ONE FAILS
  ⍝ ctl.onClick←'#.WPFWindow.CallbackMethod' ⍝ PUBLIC SHARED METHOD:   ALSO FAILS
 r←inst.window.ShowDialog
  ⍝
  ⍝ after cancel for First Try, try again to test distinct instances ..
  ⍝
 inst2←⎕NEW #.WPFWindow((⊂Def),⊂'Second Try')
  ⍝ try to define a callback function
 ctl←inst2.window.FindName⊂'buttonOK'
  ⍝
 inst2.senders∪←ctl                          ⍝ post object to instance senders callback list
 ctl.onClick←'#.MyCallbackFunction'          ⍝ USE OLD STYLE FUNCTION: IT WORKS
 r←inst2.window.ShowDialog
 ⍝


Code: Select all

:Class WPFWindow

:using System,System.dll
:using System.IO,mscorlib.dll
:using System.Xml,system.xml.dll
:using System.Windows, C:\Windows\Microsoft.NET\Framework\v4.0.30319\WPF\PresentationFramework.dll
:using System.Windows, C:\Windows\Microsoft.NET\Framework\v4.0.30319\WPF\PresentationCore.dll
:using System.Windows.Controls
:using System.Windows.Markup
:using System.Windows.Navigation
:using System.Collections.Specialized,System.dll


    :Field Public Instance senders←⍬  ⍝ posted list of event callback objects

    :Field Public Instance mark←⍬     ⍝ testing mark to distinguish class instances

    :Field Public Instance window
    ⍝ Field Private Instance Data   ⍝ not implemented for the sake of simplicity

    ⎕IO ⎕ML←1 3

    ∇ Make(Xaml tag)
    ⍝a Xaml: C1: XAML Definition of the window
      :Access Public Instance
      :Implements Constructor
      window←XamlReader.Load ⎕NEW XmlTextReader(⎕NEW StringReader(⊂Xaml))
      mark←tag
      ⎕DF'Instance of #.WPFWindow'
     
    ∇

    ∇ MyCallbackMethod(sender event)
      :Access Public Instance
      :Signature MyCallbackMethod Object sender, EventArgs event
      ⍝ Probably discriminate by sender object as to action ...
      ⍝ If more than one callback event posted on same action,
      ⍝     possibly distinguish by details of event type
      ⍝     or overload "MyCallbackMethod" by Signature for EventArgs type variation
      ⍝
      ⎕←'*** MyCallbackMethod for mark: 'mark
    ⍝
    ∇

    ∇ CallbackMethod(sender event);z
      :Access Public Shared
      :Signature CallbackMethod Object sender, EventArgs event
      z←sender{⍬⍴(⍺∊¨⍵.senders)/⍵}⎕INSTANCES WPFWindow ⍝ find instance(s) posting callback for sender object
      z.MyCallbackMethod(sender event)  ⍝ instance callback
      ⍝⍝
      ⍝⍝:Trap 0 1000
      ⍝⍝    z.MyCallbackMethod(sender event)
      ⍝⍝:Else
      ⍝⍝    '*** Probably missing instance of WPFWindow ***'
      ⍝⍝:EndTrap
      ⍝⍝
    ⍝
    ∇

:EndClass
Last edited by RossHale on Mon Sep 06, 2010 7:33 am, edited 4 times in total.
User avatar
RossHale
 
Posts: 31
Joined: Thu Jun 18, 2009 9:08 pm
Location: Bellevue, Washington, USA

Re: How handle events of WPF windows with methods of APL classes

Postby RossHale on Mon Sep 06, 2010 7:25 am

After a little think, it seems simpler to set up one external proxy event handler
for each event handler (method) in the class instance.

The recipe would be to assign the external proxy event handler to the event of a particular object
and also put the object in the senders list for the current class instance at this time.
Whenever the event is fired, the proxy event handler can examine the current class instances
and determine which instance senders list contains the event object sender.
The so determined class instance event handler would be called.

This seems fairly simple to put in practice, especially if the proxy event handlers
are named like the instance event handlers and gathered in a namespace obviously
associated with the class. I found they could be copied/imported/fixed into the namespace
that Dyalog APL envelops around the shared singular class object and used as proxy callbacks.
This is kind of a stunt as I am not sure how you maintain them unless you use a utility function
to create your proxy event handlers (if need be) from a template.

Code: Select all
   (ctlobject 'Click') sethandler 'ClickHandler'


Where sethandler would add ctlobject to the class instance senders list;
check the existence of a proxy event handler named in part by the argument 'ClickHandler'
and if need be establish a proxy event handler so named to ultimately call the proper instance 'ClickHandler';
finally sethandler would determine if 'Click' corresponded to an event of ctlobject
and accomplish ...
Code: Select all

      ctlobject.Click←⊂prefix,'ClickHandler' ⍝ where prefix might be or '#.Proxy.' or '#.WPFWindow.'



If the object is not unique to a class instance, the question as to which instance should be selected.
Keeping a single list of event object senders is fine unless the object is used for events in more than one class instance.
If one instance sets a Tick event and another instance sets a Click event for the same object,
then ambiguity is the result. A separate object senders list could be set up for eavent handler chain or
the senders list could be augmented with (object event) where event is is like 'Click' not an EventArgs.
This would allow a proxy ebent handler to find the instances that matched both the event object sender
and desired event by name like 'Click'.
This would reduce difficult to handle cases to those with the same object and and same event like 'Click'
being set by different class instances. You could arrange this proxy scheme to call both instances (seems proper).
If the instance handlers complete needed event action (perhaps setting the EventArgs Handled property),
then the first fired instance event handler might do all that is needed and the second one does no harm
by seeing the effects of its preceding comrade (doppelganger).
Since .NET/WPF objects are passed by reference, an EventArgs object passed to one instance and marked Handled
will have Handled marked and visible to the second instance.

Ross
User avatar
RossHale
 
Posts: 31
Joined: Thu Jun 18, 2009 9:08 pm
Location: Bellevue, Washington, USA

Re: How handle events of WPF windows with methods of APL classes

Postby Dick Bowman on Mon Sep 06, 2010 10:12 am

Diverting slightly - is there a way to pass an argument to a WPF callback?

In APL/W "native" applications I'd do this a lot, even though the syntax seemed a bit flaky...

aform.btnCancel.onSelect←'CloseForm'args

where <args> would pop up as the left argument to the <CloseForm> function.

I think I'm sharing a goal of Uwe by trying to keep my application code relatively clean. Something that troubled me in the early days of Windows applications was that we seemed to be splattering globals around much more than in the "olden days" - and I'm seeing this in spades with WPF.

Tried various possibilities, but all I get working is...

aform.btnCancel.onClick←'foo'

and a <foo> that's monadic with generic control/event as right argument.

Maybe I'm missing something too obvious.

What seems most attractive would be to be able to write

aform.btnCancel.onClick←myargs 'foo' (or 'myargs foo').

Yes, I know the argument is going to be evaluated at that point - but its been useful in the past and I'm sad to see it go.
Visit http://apl.dickbowman.com to read more from Dick Bowman
User avatar
Dick Bowman
 
Posts: 235
Joined: Thu Jun 18, 2009 4:55 pm

Re: How handle events of WPF windows with methods of APL classes

Postby MikeHughes on Tue Sep 07, 2010 7:07 am

The way the Command mechanism works in WPF is

obj.Command←'Action'
obj.CommandParameter←'Arg'

you could try

obj.(onClick ClickParameters)←'foo' myargs

foo could then check for obj.ClickParameters

Not as clean as an argument but almost there
User avatar
MikeHughes
 
Posts: 86
Joined: Thu Nov 26, 2009 9:03 am
Location: Market Harborough, Leicestershire, UK

Re: How handle events of WPF windows with methods of APL classes

Postby uwejanza on Tue Sep 07, 2010 4:01 pm

To keep things straightforward I choose the proxy function approach. I am unhappy with the necessity to create proxies at all, but I don't seem to have any choice that I'd prefer.

I do not want to implement a mechanism that upon arrival of an event searches lists of WPF objects to find the right APL object instance to play with. This is a job for the interpreter, not for the APL programmer. So as a first approach I'll create one proxy function for each relevant combination of (WPFWindow instance / control / event), hopeing there will not be too many of these combinations. For this purpose I have an event proxy factory function as a PUBLIC SHARED method in my WPFWindow class. Given the complete name of the WPFWindow instance, this method will fix a proxy function into the namespace that surrounds the WPFWindow instance, as Ross pointed out in his second posting. The proxy factory returns the proxy's full path name, which I'll need later.

Great idea, Ross! So things that logically belong together are physically grouped together. The proxies will behave like I had originally expected my PUBLIC INSTANCE event handling methods to behave. And each of them will automatically vanish from the workspace upon destruction of the instance that hosts it.

Dick guessed right about my intention to keep the workspace as clear as possible of classic APL globals. This applies to variables as well as to dynamically fixed functions.

To establish the connection between a WPF object's event and a proxy function, I seem to have to make the assignment outside of the WPFWindow class and give the proxy's absolute namespace path. Otherwise the namespace path to the proxy function will be misinterpreted at the moment the event fires. This means that a tool to connect a list of event proxies to a list of controls in a WPF window can not be a method of my WPFWindow class but must be a plain APL function.

Uwe
uwejanza
 
Posts: 19
Joined: Tue Mar 09, 2010 2:01 pm
Location: Nürnberg, Germany

Re: How handle events of WPF windows with methods of APL classes

Postby gkurt on Thu Oct 14, 2010 2:46 pm

Unfortunately I cannot get Dyalog 12.1 working with either of the methods.
It says :

System.MissingMethodException: Method not found

and additionally I get [MyClassName].methodName

It seems like it is looking for the function in the class somehow but not suceeding.

Is there any open issues with Version 12.1 and classes and WPF callbacks?

Regards

/Gökhan
gkurt
 
Posts: 1
Joined: Thu Oct 14, 2010 8:11 am

Re: How handle events of WPF windows with methods of APL classes

Postby uwejanza on Thu Oct 14, 2010 5:19 pm

Gökhan,

it seems like you are running into the same trouble I did.

Currently, you can not use methods directly to handle your WPF callbacks. What you need are classical APL functions that connect APL methods with WPF windows. These function can be located everywhere - except in a class or an instance of class. But it is allowed that you fix such a function into a class instance's surrounding namespace by means of
      ⎕FX
.
You can not easily distinguish between these two locations. The call syntax looks the same. You must check the thing's name class by means of
      ⎕NC
to find out about the difference. Even the location from where you fix the function matters. It also must not lie inside a class, but in a classical function.

These classical ("plain vanilla") APL functions may contain calls to class/instance methods. As they serve the purpose to connect APL methods with WPF windows Ross called them "proxy" functions.

Uwe
uwejanza
 
Posts: 19
Joined: Tue Mar 09, 2010 2:01 pm
Location: Nürnberg, Germany


Return to Microsoft.NET

Who is online

Users browsing this forum: No registered users and 1 guest