Create Event Handlers for WPF frontends in Java

In one of the previous posts I showed how a WPF frontend can be controlled by a Java application. Furthermore, I demonstrated how proxies can be generated automatically that allows for more convenient way of programming the Java part. However, there is no way so far, to handle interaction of the user with the WPF frontend in the Java backend. This posts demonstrates the basics for such an implementation.

It uses the existing socket communication to send an event to the Java program whenever a relevant user interaction with the WPF frontend occurs. The java program interprets the received event and calls the corresponding handler that is selected depending on the type of event. Thus, to determine which event handler is to be called the type of event need to be set during modeling the user interface in XAML. To enable this, we will introduce a new attached property available to all controls.

Let’s start with introducing the attached property. Attached properties is a XAML concept that allows to set different child elements to specify unique values for a property that is actually defined in a parent element (take a look into the MSDN Library for a more detailled description of attached properties). In the remote-control example application, I used the NavigationService to navigate between the application’s screens. This implementation required the root element of all these screens to be Page. Thus, introducing the attached property for Page will make it available to all controls in every screen.

The code below subclasses Page and introduces the attached property “pressedEvent” of the type string.

namespace Example.Controls
{
	[Description("A page that attaches specific properties to all " + 
	"its controls")]
	public class PageEvent : Page
	{
		static PageEvent()
		{
			DefaultStyleKeyProperty.OverrideMetadata(
				typeof(PageEvent), 
				new FrameworkPropertyMetadata(typeof(PageEvent)));
		}

		[Description("The event that is sent when the control is pressed " +
		" or released."), Category("Common Properties")]
		public String PressedEvent
		{
			get
			{
				return (String)GetValue(PressedEventProperty);
			}
			set
			{
				SetValue(PressedEventProperty, value);
			}
		}

		public static readonly DependencyProperty PressedEventProperty =
	 		DependencyProperty.RegisterAttached(
				pressedEvent,
				typeof(String),
				typeof(PageEvent),
				new FrameworkPropertyMetadata(
					null, 
					FrameworkPropertyMetadataOptions.None
				)
			);

		public static void SetPressedEvent(UIElement element, String value)
		{
			element.SetValue(PressedEventProperty, value);
		}
		public static String GetPressedEvent(UIElement element)
		{
			return (String)element.GetValue(PressedEventProperty);
		}
	}
}

In order to make the property available we need to use this subclass instead of the regular Page as the top level element of the screens. Make sure to import the namespace of the subclass in your XAML and then adapt the top level element to be of the type PageEvent. The snippet below shows how this can be done. In the Image controls the new property is set to a value.

<Controls:PageEvent 
	x:Class="..."
	xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
	xmlns:Controls="clr-namespace:Example.Controls" 
	...>

	...
	<Image Controls:PageEvent.PressedEvent="Previous" .../>
	<Image Controls:PageEvent.PressedEvent="Next" .../>
	...
</Controls:PageEvent>

So far, we are able to model the type of event that is to be sent. However, the actual event handling is missing. In this example we will notify the java backend about pressing and releasing the left mouse button on controls. Note that this approach can also be applied to any other type of user interactions.

We will use a single handler for each type of interaction. This need to be called whenever the interaction occurs including any control. The most convenient way to do so is to register the event handlers in the top most element. In the remote-control example this is the NavigationWindow of the main screen.

<NavigationWindow
	xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
	xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
	xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
	xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
	...
	
	MouseLeftButtonDown="Window_MouseLeftButtonDown" 
	MouseLeftButtonUp="Window_MouseLeftButtonUp" />

	...
</NavigationWindow>

Both event handlers read the mouse position and delegate this to the function getPressedEventOfElementAt(Position) that returns the value of the property “pressedEvent” for the control at the given position. Finding the control at the mouse position is done the function HitTest of the class VisualTreeHelper. If this control does not set this property then its parental controls are tested. This can be useful, for instance, if a button is created by grouping multiple images in a grid. The pressed event need to be set for the grid only. The same event is fired no matter which one of its contained images is clicked. The property behaves as if it was inherited along this hierarchy of controls – the same behavior also applies for conventional event handlers.

Note that the given snippets assume that the function getActivePage() returns the page that is currently shown in the NavigationWindow.

private void Window_MouseLeftButtonDown(
	object sender, MouseButtonEventArgs e)
{
	// Get mouse position relative to the window's root
	Point p = e.GetPosition(this);
	if (getActivePage() != null)
	{
		String pressedEventName = getPressedEventOfElementAt(p);
		if (pressedEventName != null)
			// Handle event
			Console.WriteLine("Pressed: Event " + pressedEventName + "!");

		else
			Console.WriteLine("No element at " + p.X + "|" + p.Y);
	}
	else
		Console.WriteLine("No page is currently active");
}

private void Window_MouseLeftButtonUp(
	object sender, MouseButtonEventArgs e)
{
	// Get mouse position relative to the window's root
	Point p = e.GetPosition(this);
	if (getActivePage() != null)
	{
		String pressedEventName = getPressedEventOfElementAt(p);
		if (pressedEventName != null)
			// Handle event
			Console.WriteLine("Release: Event " + pressedEventName + "!");
			
		else
			Console.WriteLine("No element at " + p.X + "|" + p.Y);
	}
	else
		Console.WriteLine("No page is currently active");
}

/// <summary>
/// Returns the UI Element that is located at the given coordinates 
/// or null if no such was found
/// </summary>
/// <param name="p">The coordinates to test</param>
/// <returns></returns>
public UIElement getWidgetAt(Point p)
{
	if (getActivePage() == null)
		throw new Exception("getWidgetAt can only be called if a page is set");

	HitTestResult result = VisualTreeHelper.HitTest(getActivePage(), p);
	if (result != null && result.VisualHit != null && result.VisualHit is UIElement)
		return result.VisualHit as UIElement;

	else
		return null; // Nothing found
}

/// <summary>
/// Returns the value of the property "pressedEvent" of the element 
/// of the given coordinates or null if no such property was found 
/// or this property is not set
/// </summary>
/// <param name="p">The coordinates to test</param>
/// <returns>The value of the property "pressedEvent" of the element that 
/// was pressed or null</returns>
private String getPressedEventOfElementAt(Point p)
{
	UIElement uiElement = getWidgetAt(p);
	if (uiElement != null)
	{
		if (getActivePage() is PageEvent)
		{
			string pressedEvent;
			do
			{
				pressedEvent = PageEvent.GetPressedEvent(uiElement);
				DependencyObject obj = 
					VisualTreeHelper.GetParent(uiElement);
				if (obj != null && obj is UIElement)
					uiElement = obj as UIElement;
				else
					break; // Abort when the root is reached
					
			} while (pressedEvent == null || pressedEvent.Equals(""));

			return pressedEvent;
		}
	}
	return null;
}

There is some more work to do. This is not shown here because it is straightforward: Instead of printing the value of the pressedEvent-property to the console, use the already established socket channel to send the value to the Java peer. Depending on this value, the java program need to call the corresponding event handler.

Advertisements
  1. No trackbacks yet.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: