Remote Control a WPF Frontend from Java

Windows Presentation Foundation is a powerful framework for building rich user interfaces that comes with great GUI builders. However, for creating the application logic WPF requires to use one of one of the .NET programming languages such as Visual Basic or C#. Java, also a great programming language for writing application logic, is not supported.

This post describes how to connect a an application written in Java to a WPF based user interface.

The solution uses socket communication, allowing to separate the user interface and its computation logic into different processes or programs. This can be necessary, if they are implemented in different programming languages. Furthermore, it allows to run frontend and backend on different machines or processors.

Our goal is to write the whole application logic in Java and use WPF for presentation purposes only. Therefore, the solution should require as little .net code as possible. We basically need to be able to “remote control” the WPF user interface from Java code. This requires the following features:

  • Show a specific view, navigate forward and backward in view history
  • Start or stop an animation
  • Set a control’s property

The following figure shows the basic structure of the solution presented here. The aforementioned functions are provided in the WPF Remote Control API. Please note, that they offer a one-way communication only which enables to use WPF for presentation purposes. To provide for a full interaction with the user you need to enable your Java application to react to user input. This requires to pass interaction events from the WPF application back to the Java code which can be done by socket communication, too.

Basic structure of solution

Basic structure of the WPF remote control

Java Server

We will need to implement a server component in Java and a corresponding client in a .net language, e.g. C#. The following class shows an implementation of the Java server. It provides for the functions of the WPF Remote Control API. Each one of them serializes all parameters and sends to the client using a socket.

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;

import javax.net.ServerSocketFactory;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class LocalSocketWpfAppWrapper {

	private static final Logger logger = LoggerFactory
			.getLogger(LocalSocketWpfAppWrapper.class);

	private static final String SEPARATOR_TOKEN = ";";
	private static final int DEFAULT_PORT = 1800;

	private final int port;

	private boolean initialized = false;
	private SocketServer server = null;
	private List writerList = null;

	
	

	
	private static char ESCAPE_SEQUENCE_START = '<'; 
	private static char ESCAPE_SEQUENCE_END = '>'; 
	private static final Map ESCAPE_SEQUENCES = new HashMap();
	private static final Map UNESCAPE_SEQUENCES = new HashMap();
	
	static {
		ESCAPE_SEQUENCES.put(new Character('<'), "lt");
		ESCAPE_SEQUENCES.put(new Character('>'), "gt");
		ESCAPE_SEQUENCES.put(new Character(';'), "semicolon");
		ESCAPE_SEQUENCES.put(new Character('\n'), "linebreak");

		// Add all the mapping in reverse order to the unescape map
		for (Iterator it = ESCAPE_SEQUENCES.keySet().iterator(); it.hasNext();  ) {
			Character key = (Character) it.next();
			String value = (String) ESCAPE_SEQUENCES.get(key);
			UNESCAPE_SEQUENCES.put(value, key);
		}
	}
	
	
	/**
	 * Escapes the given string for transmitt them over the socket
	 * @param s The string to escape
	 * @return The escaped string
	 */
	static String escape(String s) {
		StringBuffer sb = new StringBuffer();
		for (int i = 0; i<s.length(); i++) {
			char c = s.charAt(i);
			Character key = new Character(c);
			if (ESCAPE_SEQUENCES.containsKey(key)) {
				String substitution = (String)ESCAPE_SEQUENCES.get(key);

				sb.append(ESCAPE_SEQUENCE_START);
				sb.append(substitution);
				sb.append(ESCAPE_SEQUENCE_END);
			} else {
				sb.append(c);
			}
		}
		
		return sb.toString();
	}

	/**
	 * Unescapes the given string that was received over the socket
	 * @param s The escaped string
	 * @return The unescaped string
	 */
	static String unescape(String s) {
		StringBuffer sb = new StringBuffer();
		int i = 0;
		while (i < s.length()) {
			char c = s.charAt(i);
			i++;
			
			// Found start of escaped sequence
			if (c == ESCAPE_SEQUENCE_START) {
				// Find complete escape sequence
				StringBuffer sbEscapeSequence = new StringBuffer();
				boolean escapeSequenceComplete = false;
				while (i < s.length()) {
					c = s.charAt(i);
					if (c==ESCAPE_SEQUENCE_END) {
						escapeSequenceComplete = true;
						i++;
						break;
					} else {
						sbEscapeSequence.append(c);
					}
					i++;	
				}
				if (!escapeSequenceComplete) {
					throw new RuntimeException(
							MessageFormat.format("Incomplete escape sequence found in \"{0}\"", new Object[]{s}));
				}
				String escapeSequence = sbEscapeSequence.toString();
				
				// Unescape the character
				if (UNESCAPE_SEQUENCES.containsKey(escapeSequence)) {
					Character unescapedChar = (Character) UNESCAPE_SEQUENCES.get(escapeSequence);
					sb.append(unescapedChar.charValue());
				} else {
					throw new RuntimeException(
							MessageFormat.format("Unknown escape sequence \"{0}\" found in \"{1}\"", new Object[]{escapeSequence, s}));
				}
			} else {
				sb.append(c);
			}
		}
		
		return sb.toString();
	}
	
	public LocalSocketWpfAppWrapper(int port) {
		this.port = port;
		this.writerList = new ArrayList();
	}

	public synchronized boolean init() {
		if(isInitialized() == true) {
			return false;
		}

		logger.info("init WPF wrapper");

		server = new SocketServer();
		server.start();

		initialized = true;
		return true;
	}

	public boolean isInitialized() {
		return initialized;
	}

	public boolean close() {
		if(isInitialized() == false) {
			return false;
		}

		logger.info("close WPF wrapper");

		server.interrupt();
		server = null;

		for(QueuedSocketWriter w : writerList) {
			w.interrupt();
		}

		initialized = false;
		return true;
	}

	public boolean showView(String viewName) {
		return sendCommand("showView", viewName);
	}

	public boolean startAnimation(String animationName) {
		return sendCommand("startAnimation", animationName);
	}

	public boolean stopAnimation(String animationName) {
		return sendCommand("stopAnimation", animationName);
	}

	public boolean setBooleanValue(String pageName, String controlName,
		String propertyName, boolean value) {

		return sendCommand(
			"set",
			pageName, controlName, propertyName,
			"boolean", value);
	}

	public boolean setIntegerValue(String pageName, String controlName,
		String propertyName, int value) {

		return sendCommand(
			"set",
			pageName, controlName, propertyName,
			"integer", value);
	}

	public boolean setDoubleValue(String pageName, String controlName,
		String propertyName, double value) {

		return sendCommand(
			"set",
			pageName, controlName, propertyName,
			"double", value);
	}

	public boolean setStringValue(String pageName, String controlName,
		String propertyName, String value) {

		return sendCommand(
			"set",
			pageName, controlName, propertyName,
			"string", value);
	}

	public boolean goBack() {
		return sendCommand("back");
	}

	public boolean goForward() {
		return sendCommand("forward");
	}

	private boolean sendCommand(Object... commandParts) {
		StringBuilder sb = new StringBuilder();
		for (int i = 0; i < commandParts.length; i++) { 
			if (i > 0) {
				sb.append(SEPARATOR_TOKEN);
			}
			// Escape command part
			String escapedCommandPart = escape(commandParts[i].toString());
			sb.append(escapedCommandPart);
		}

		String message = sb.toString();
		boolean ret = false;
		for(QueuedSocketWriter w : writerList) {
			ret &= w.queueMessage(message);
		}

		return ret;
	}

	private class SocketServer extends Thread {...}
	private class QueuedSocketWriter extends Thread {...}
}

This implementation uses two helper classes. The first one is QueuedSocketWriter which is a writer that queues incoming messages and writes them asynchronously to the socket. It is required to prevent blocking the application logic by the socket communication.

private class QueuedSocketWriter extends Thread {
	PrintWriter socketWriter;
	Socket socket;
	Queue messageQueue = null;

	public QueuedSocketWriter(Socket socket) throws IOException {
		this.socket = socket;
		socketWriter = new PrintWriter(
			new OutputStreamWriter(socket.getOutputStream(), "Unicode"), true);
		messageQueue = new LinkedBlockingQueue();
	}

	@Override
	public void run() {
		logger.info("writer queue started");
		while (socket.isConnected()){
			if(this.isInterrupted()) {
				break;
			}

			synchronized (messageQueue) {
				if(!messageQueue.isEmpty()) {
					socketWriter.println(messageQueue.poll());
					continue;
				}

				try {
					messageQueue.wait(200);
				} catch (InterruptedException e) {
					break;
				}
			}

			if (socketWriter.checkError()) {
				logger.info("\nConnection lost. Server keeps running.");
				break;
			}
		}

		socketWriter.close();
		try {
			socket.close();
		} catch (IOException e) {
			logger.error("", e);
		}
		logger.info("writer queue ended");
	}

	public boolean queueMessage(String message) {
		if(!this.isAlive() || this.isInterrupted()) {
			return false;
		}

		synchronized (messageQueue) {
			messageQueue.add(message);
			messageQueue.notify();
		}

		return true;
	}
}

As both, the WPF and the Java applications, can be started independent of each other, we need to establish a connection as soon as they are both ready. The second helper class for the Java server is the SocketServer. It provides a thread that waits for clients connecting to the chosen port. After a client was discovered, a QueuedWriter for that client is created. This implementation also enables to connect multiple WPF clients to the same Java application.

private class SocketServer extends Thread {
	private ServerSocket serverSocket = null;

	@Override
	public void run() {
		logger.info("starting socket server - waiting for clients...");
		try {
			serverSocket = ServerSocketFactory.getDefault()
				.createServerSocket(port);

			while (true) {
				Socket socket = serverSocket.accept();
				logger.info("client accepted");
				QueuedSocketWriter writerThread =
					new QueuedSocketWriter(socket);
				writerThread.start();
				writerList.add(writerThread);
			}
		} catch (Exception e) {
			if(!isInterrupted()) {
				logger.error("Waiting for clients interrupted", e);
			}
		}
		logger.info("socket server thread ended");
	}

	@Override
	public void interrupt() {
		super.interrupt();
		if(serverSocket != null) {
			try {
				serverSocket.close();
			} catch (IOException e) {
				logger.error("error while closing ServerSocket", e);
			}
		}
	}
}

WPF client

The next step is to implement the WPF client.

To enable showing views by their name we will use the navigation service to load and display pages. We therefore assume a Page of the same name in the subfolder Pages. The function call showView("ExampleView") will display the page Pages/ExampleView.xaml. The navigation service requires to provide a main window that serves as a container to show the pages in. This is the XAML of this main window:

<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" 
	mc:Ignorable="d"

	x:Class="Example.MainWindow"
	x:Name="Window"
	Title="MainWindow"

	Left="0" Top="0" 
	Width="1280" Height="600" 
	WindowStyle="None" 
	ResizeMode="NoResize" 
	Background="#FF8D4747" 
	HorizontalAlignment="Left" VerticalAlignment="Top"
	
	Source="Pages/Startup.xaml" Closed="Window_Closed" />

The corresonding C# file of this main window implements the server component that establishes a socket connection and receives incoming commands. Each received command then need to be interpreted and executed. Just like in the Java server, the socket communication and the UI need to run in separate threads. However, WPF requires all modifications to the GUI to be performed in the GUI thread. Because of that, we pass each received command to the GUI thread for dispatching.

using System;
// Threading
using System.Threading;
using System.Windows.Threading;
// Socket-Communication
using System.Net.Sockets;
using System.IO;
// WPF
using System.Windows;
using System.Windows.Navigation;
using System.Windows.Media.Animation;
using System.Reflection;
using System.Windows.Controls;
using System.Globalization;
using System.Collections;

namespace Example
{
	/// <summary>
	/// The main window of the application.
	/// It can be remote controlled via socket.
	/// </summary>
	public partial class MainWindow : NavigationWindow
	{
		// Token to separate parts of the command
		public static char COMMAND_SEPARATOR_TOKEN = ';'; 
		
		// Hostname and port of the server
		public static String SERVER_HOSTNAME = "localhost"; 
		public static int SERVER_PORT = 1800;

		// Controls whether the application can reconnect to a server 
		// after the previous connection was lost
		public static bool MULTIPLE_CONNECTIONS_POSSIBLE = true;

		/// <summary>
		/// Constructor
		/// </summary>
		public MainWindow()
		{
			this.InitializeComponent();
			
			// Hide browser-like navigation bar
			this.ShowsNavigationUI = false;

			// Create thread for socket client
			socketClientThread = new Thread(new ThreadStart(socketClient));
			socketClientThread.Start();
		}

		Thread socketClientThread;

		// ***********************************************************
		// SOCKET COMMUNICATION
		// ***********************************************************

		/// <summary>
		/// This method runs the socket connection. Do not call this 
		/// methods directly but run it in a separate thread
		/// </summary>
		public void socketClient()
		{
			bool connectionWasOnceEstablished = false;
			while (
				MULTIPLE_CONNECTIONS_POSSIBLE || 
				!connectionWasOnceEstablished)
			{
				try
				{
					// Start TCP client for given socket and hostname
					TcpClient tc = new TcpClient(
						SERVER_HOSTNAME, 
						SERVER_PORT); 

					Console.WriteLine("Found server...");
					connectionWasOnceEstablished = true;

					NetworkStream ns = tc.GetStream();

					StreamReader sr = new StreamReader(ns, System.Text.Encoding.Unicode);

					try
					{
						// Listen to socket and dispatch received commands
						while (true)
						{
							String command = sr.ReadLine();
							if (command == null)
							{
								break; // Socket was closed
							}
							// Delegate command to UI thread for dispatching
							Dispatcher.BeginInvoke(
								DispatcherPriority.Normal,
								new Action<string>(dispatchCommand),
								command
							);
						}
					}
					catch (IOException)
					{
						// When an IOException occurs during reading, the 
						// server side will be down in most cases
						Console.WriteLine("Server connection lost!");
					}
					finally 
					{
						// Make sure to close all connections
						ns.Close();
						tc.Close();
					}
				}
				catch (SocketException)
				{
					// A timeout occcured during trying to establishing
					// a connection. Print the message and go on.
					Console.WriteLine("Waiting for server...");
				}
			}
			Console.WriteLine("Shutting down socket listener...");
		}

		/// <summary>
		/// The worker method, which actually dispatches the received
		/// command. Runs in the UI thread.
		/// param name="command" the command to dispatch
		/// </summary>
		public void dispatchCommand(String command)
		{
			try
			{
				if (command.Length > 0)
				{
					string[] parts = command.Split(COMMAND_SEPARATOR_TOKEN);
					for (int i = 0; i < parts.Length; i++)
					{
						parts[i] = unescape(parts[i]);
					}
					string commandType = parts[0];
					switch (commandType) {
						case "set":
							if (parts.Length == 6)
							{
								setValue(
									parts[1], 
									parts[2], 
									parts[3], 
									parts[4], 
									parts[5]);
								break;
							}
							else
								throw new Exception(
									"Invalid parameter count: " + command + 
									". Use \"set,[pageName],[controlName]," +
									"[propertyName],[datatype]," +
									"[datavalue]\"");
						case "startAnimation":
							if (parts.Length == 2)
							{
								startAnimation(parts[1]);
								break;
							}
							else
								throw new Exception(
									"Invalid parameter count: " + command + 
									". Use \"showAnmiation,"
									"[animationName]\"");
						case "stopAnimation":
							if (parts.Length == 2)
							{
								stopAnimation(parts[1]);
								break;
							}
							else
								throw new Exception(
									"Invalid parameter count: " + command + 
									". Use \"stopAnimation,"
									"[animationName]\"");
						case "showView":
							if (parts.Length == 2)
							{
								showView(parts[1]);
								break;
							}
							else
								throw new Exception(
									"Invalid parameter count: " + command + 
									". Use \"showView,[viewName]\"");
						case "back":
							if (parts.Length == 1)
							{
								goBack();
								break;
							}
							else
								throw new Exception(
									"Invalid parameter count: " + command + 
									". Use \"goBack\"");
						case "forward":
							if (parts.Length == 2)
							{
								goForward();
								break;
							}
							else
								throw new Exception(
									"Invalid parameter count: " + command + "." +
									"Use \"goForward\"");

						default:
							throw new Exception(
								"Unknown command: " + command + "." + 
								"Use one of the following: "
								"set, showAnimation, stopAnimation, " +
								"showView, goBack, goForward");
					}
				}
			}
			catch (Exception s)
			{
				// Print every exception to the console
				Console.WriteLine(s.Message);
			}
		}

		
        /// <summary>
        /// Sends the given answer to the server
        /// </summary>
        /// <param name="type">The type of the answer</param>
        /// <param name="parameters">A list of parameters</param>
        /// <returns>The value of the property "pressedEvent" of the element that was pressed or null</returns>
        private void sendAnswer(String type, params String[] parameters) {
            String command = escape(type);
            foreach (String parameter in parameters) {
                command += COMMAND_SEPARATOR_TOKEN + escape(parameter);
            }
            sw.WriteLine(command + "\r\n");
            sw.Flush();
        }


        private static char ESCAPE_SEQUENCE_START = '<';
        private static char ESCAPE_SEQUENCE_END = '>'; 
        private static readonly Dictionary<char, string> ESCAPE_SEQUENCES = new Dictionary<char, string>();
        private static readonly Dictionary<string, char> UNESCAPE_SEQUENCES = new Dictionary<string, char>();
        static MainWindow()
        {
            ESCAPE_SEQUENCES.Add('<', "lt");
            ESCAPE_SEQUENCES.Add('>', "gt");
            ESCAPE_SEQUENCES.Add(';', "semicolon");
            ESCAPE_SEQUENCES.Add('\n', "linebreak");

            // Add all the mapping in reverse order to the unescape map
            foreach (char key in ESCAPE_SEQUENCES.Keys)
            {
                String value = ESCAPE_SEQUENCES[key];
                UNESCAPE_SEQUENCES.Add(value, key);
            }
        }


        /// <summary>
        /// Escapes the given string before sending them to over the socket
        /// <param name="s">the string to escape</param>
        /// </summary>
        public string escape(string s)
        {
            string result = "";
            for (int i = 0; i < s.Length; i++)
            {
                char c = s[i];
                if (ESCAPE_SEQUENCES.ContainsKey(c))
                {
                    String substitution = ESCAPE_SEQUENCES[c];
                    result += ESCAPE_SEQUENCE_START + substitution + ESCAPE_SEQUENCE_END;
                }
                else
                {
                    result += c;
                }
            }

            return result;
        }
        /// <summary>
        /// Unescapes a string read from the socket
        /// <param name="s">the string to unescape</param>
        /// </summary>
        public string unescape(string s)
        {
            string result = "";
            int i = 0;
            while (i < s.Length)
            {
                char c = s[i];
                i++;

                // Found start of escaped sequence
                if (c == ESCAPE_SEQUENCE_START)
                {
                    // Find complete escape sequence
                    string escapeSequence = "";
                    bool escapeSequenceComplete = false;
                    while (i < s.Length)
                    {
                        c = s[i];
                        if (c == ESCAPE_SEQUENCE_END)
                        {
                            escapeSequenceComplete = true;
                            i++;
                            break;
                        }
                        else
                        {
                            escapeSequence += c;
                        }
                        i++;
                    }
                    if (!escapeSequenceComplete)
                    {
                        throw new Exception("Incomplete escape sequence found in \"" + s + "\"");
                    }

                    // Unescape the character
                    if (UNESCAPE_SEQUENCES.ContainsKey(escapeSequence))
                    {
                        char unescapedChar = UNESCAPE_SEQUENCES[escapeSequence];
                        result += unescapedChar;
                    }
                    else
                    {
                        throw new Exception("Unknown escape sequence \"" + escapeSequence + "\" found in \"" + s + "\"");
                    }
                }
                else
                {
                    result += c;
                }
            }
            return result;
        }

		// ***********************************************************
		// HIGH-LEVEL API TO REMOTE CONTROL THE APPLICATION
		// ***********************************************************

		Hashtable loadedPages = new Hashtable();

		private Page getPageByName(string pageName)
		{
			if (!loadedPages.ContainsKey(pageName))
			{
				Uri pageUri = new Uri(
					"Pages/" + pageName + ".xaml", 
					UriKind.Relative);
				Object o = Application.LoadComponent(pageUri);
				if (o is Page)
				{
					Page page = o as Page;
					loadedPages.Add(pageName, page);
					return page;
				}
				else
				{
					throw new Exception("Page not found!");
				}
			}
			else
			{
				return loadedPages[pageName] as Page;
			}
		}

		/// <summary>
		/// Sets the value of a property in a control of the currently 
		/// active page
		/// value param name="controlName" name of the control
		/// value param name="propertyName" name of the control's property
		/// value param name="type" datatype of the value
		/// value param name="serializedValue" serialized value
		/// </summary>
		public void setValue(string pageName, string controlName, string propertyName, string type, string serializedValue)
		{
			Object value = null;
			if (type.Equals("double")) {
				value = Double.Parse(
					serializedValue, NumberFormatInfo.InvariantInfo);
			} else if (type.Equals("integer")) {
				value = int.Parse(serializedValue);
			} else if (type.Equals("string")) {
				value = serializedValue;
			} else if (type.Equals("boolean")) {
				value = Boolean.Parse(serializedValue);
			} else {
				throw new Exception("Unknown datatype " + type); 
			}

			// Get specified page
			Page page = getPageByName(pageName);
			if (page != null)
			{
				object o = page.FindName(controlName);
				if (o != null)
				{
					if (o is UIElement)
					{
						UIElement control = o as UIElement;

						Type controlType = control.GetType();
						PropertyInfo prop = 
							controlType.GetProperty(propertyName);
						if (prop != null)
						{
							prop.SetValue(control, value, null);

						}
						else
						{
							throw new Exception(
								"Element " + controlName + " is of type " + 
								controlType.Name + 
								" that has no property " + propertyName);
						}

					}
					else
					{
						throw new Exception(
							"Element " + controlName + "is no UI element!");
					}
				}
				else
				{
					throw new Exception(
						"Element " + controlName + " not found");
				}
			}
			else
			{
				throw new Exception("Page " + pageName + " not found!");
			}
		}

		/// <summary>
		/// Navigates backwards
		/// </summary>
		public void goBack()
		{
			if (NavigationService.CanGoBack)
			{
				NavigationService.GoBack();
			}
			else
			{
				throw new Exception("Cannot go back!");
			}
		}

		/// <summary>
		/// Navigates forward
		/// </summary>
		public void goForward()
		{
			if (NavigationService.CanGoForward)
			{
				NavigationService.GoForward();
			}
			else
			{
				throw new Exception("Cannot go forward!");
			}
		}

		/// <summary>
		/// Shows a page with the given name
		/// value param name="name" the name of the page to show
		/// </summary>
		public void showView(string name)
		{
			Page page = getPageByName(name);
			if (getActivePage().Equals(page))
			{
				throw new Exception("Page " + name + " is already shown!");
			}
			else
			{
				NavigationService.Navigate(page);
				Console.WriteLine("Show View " + name);
			}
		}

		/// <summary>
		/// Starts an animation in the currently active page
		/// value param name="animationName" name of the animation to start
		/// </summary>
		public void startAnimation(string animationName)
		{
			Page activePage = getActivePage();
			if (activePage != null)
			{
				try
				{
					Storyboard animationStoryboard = 
						(Storyboard)activePage.FindResource(animationName);
					animationStoryboard.Begin(activePage, true);
				}
				catch (ResourceReferenceKeyNotFoundException e)
				{
					throw new Exception(
						"Animation " + animationName + " not found in " + 
						activePage.Name + "! (" + e.Message + ")");
				}
			}
			else
			{
				throw new Exception(
					"Cannot determine currently active page!");
			}

		}

		/// <summary>
		/// Stops an animation in the currently active page
		/// value param name="animationName" name of the animation to stop
		/// </summary>
		public void stopAnimation(string animationName)
		{
			Page activePage = getActivePage();
			if (activePage != null)
			{
				try
				{
					Storyboard animationStoryboard = 
						(Storyboard)activePage.FindResource(animationName);
					animationStoryboard.Stop(activePage);
				}
				catch (ResourceReferenceKeyNotFoundException e)
				{
					throw new Exception(
						"Animation " + animationName + " not found in " + 
						activePage.Name + "! (" + e.Message + ")");
				}
			}
			else
			{
				throw new Exception(
					"Cannot determine currently active page!");
			}
		}

		// ***********************************************************
		// HELPER FUNCTIONS
		// ***********************************************************

		/// <summary>
		/// Returns the currently active page
		/// </summary>
		private Page getActivePage()
		{
			Object x = NavigationService.Content;
			if (x is Page)
			{
				return x as Page;
			}
			else
			{
				return null;
			}
		}

		private void Window_Closed(object sender, EventArgs e)
		{
			if (socketClientThread != null && socketClientThread.IsAlive)
			{
				socketClientThread.Abort();
			}
			// Close the whole application if this window was closed
			Application.Current.Shutdown();
		}

	}
}

Example

This is an example of a page that can be remote controlled. It consist of the controls MySlider and MyCheckbox and provides the animation MyAnimation.

<Page x:Class="Example.MyView"
	xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
	xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
	xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
	xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
	mc:Ignorable="d" 
	  
	
	x:Name="ExamplePage"
	Title="Example"
	  
	Width="1280" Height="600"
	d:DesignHeight="1280" d:DesignWidth="480"
	Background="Black" HorizontalAlignment="Left" VerticalAlignment="Top">

	<Page.Resources>
		<Storyboard x:Key="MyAnimation">
			<DoubleAnimationUsingKeyFrames 
				Storyboard.TargetProperty=
					"(UIElement.RenderTransform).(TransformGroup.Children)[2].(RotateTransform.Angle)" 
				Storyboard.TargetName="MyImage">
				<EasingDoubleKeyFrame KeyTime="0:0:2" Value="180"/>
			</DoubleAnimationUsingKeyFrames>
			<DoubleAnimationUsingKeyFrames 
				Storyboard.TargetProperty=
					"(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleX)" 
				Storyboard.TargetName="MyImage">
				<EasingDoubleKeyFrame KeyTime="0:0:2" Value="0.75"/>
			</DoubleAnimationUsingKeyFrames>
			<DoubleAnimationUsingKeyFrames 
				Storyboard.TargetProperty=
					"(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleY)" 
				Storyboard.TargetName="MyImage">
				<EasingDoubleKeyFrame KeyTime="0:0:2" Value="0.75"/>
			</DoubleAnimationUsingKeyFrames>
		</Storyboard>
	</Page.Resources>

	<Grid x:Name="LayoutRoot">
		<Canvas 
			x:Name="RemoteControlCanvas" 
			ClipToBounds="True" 
			UseLayoutRounding="False" 
			HorizontalAlignment="Left" 
			VerticalAlignment="Top" 
			Height="478" Width="1280">
			
			<Image 
				x:Name="Background" 
				Canvas.Top="0" Canvas.Left="0" 
				Height="478" Width="1280"
				Source="Images\Background.png"/>
			<Image 
				x:Name="MyImage" 
				Canvas.Top="0" Canvas.Left="88" 
				Height="478" Width="478" 
				Source="Images\MyImage.png" 
				RenderTransformOrigin="0.5,0.5">
				<Image.RenderTransform>
					<TransformGroup>
						<ScaleTransform/>
						<SkewTransform/>
						<RotateTransform/>
						<TranslateTransform/>
					</TransformGroup>
				</Image.RenderTransform>
			</Image>
		</Canvas>

		<Slider 
			x:Name="MySlider" 
			Maximum="130" 
			Margin="470,0,0,48" 
			Width="100" Height="29" 
			HorizontalAlignment="Left" VerticalAlignment="Bottom"/>

		<CheckBox 
			x:Name="MyCheckbox" 
			Content="Airbag" 
			Margin="600,497,0,0" 
			Height="20" 
			VerticalAlignment="Top" HorizontalAlignment="Left" 
			Foreground="White"/>
	</Grid>
</Page>

The corresponding C# source is empty:

using System.Windows.Controls;

namespace Example
{
	public partial class MyView: Page
	{
		public MyView()
		{
			InitializeComponent();
		}
	}
}

This page can now be controlled from the Java application. This is a list of valid calls to the WPF Remote Control API:

  • Display the page: showView("MyView")
  • Start the animation: startAnimation("MyAnimation")
  • Write to control properties: setBooleanValue("MyView", "MyCheckbox", "IsChecked", true) and setDoubleValue("MyView", "MySlider", "Value", 100.0);
Advertisements
  1. I personally blog also and I’m creating a little something comparable to this
    excellent blog, “Remote Control a WPF Frontend from Java XTEXTerience”.
    Do you really mind in the event that I actuallyuse a bit of
    of your personal points? I appreciate it -Dusty

    • frank
    • April 22nd, 2013

    cool, thanks – you should look into apache thrift it will generate all the socket serialization code for you in java and .net

  1. July 3rd, 2011

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: