Introduction
Control your .NET PC app with your
cell-phone. Without loading an app on the cellphone, you can control
your PC App from a cell-phone, android, iPhone, iPad—anything with
a browser by serving an HTML page with the controls and handling the input. On the way, this article shows most of the code needed to
write your own web server too.
Background
There are many ways for a cellphone to
control a PC app and I think this one is the easiest. I call it
“Ultralight” because it only does a few things...get button
presses and text input from the remote device. But if that's all you
need for your app, this is straightforward.
Instead of installing an app on the
remote control device (there are quite a few available), this control
works entirely from the PC side. It opens a “micro-web-server”
on the PC which can serve a simple HTML page to the remote device
showing the controls. When the remote browser submits user input
back to the PC, the control decodes it and raises an event.
Some advantages for this approach
It works with any device—iPhone,
iPad, Android, even another PC.
It is reasonably secure—serving
a web page is fairly safe.
There is no download/install
needed on the remote device.
You can customize to display just
the controls needed for your app. The UI on the remote device is
simply an HTML page and is easy to customize.
On the remote, you can create a
desktop short-cut which goes directly to the remote-control page so
it is as easy as starting a phone-app.
As a developer, you don't have to
download a bunch of tools and learn the java libraries for Android
development or buy a MAC for iPhone development.
Some limitations
You can't do drags or other
touch/mouse inputs (as written).
The user must key in a URL on the
remote device—the remote cannot search the network automatically
for your app.
As written, you cannot change the
controls displayed on the remote device because they are served to
the remote before the action is decoded by the UI event. As such,
the remote also has no way of behaving properly if the app has
stopped.
The program must have
administrator privileges if Windows Account Control is enabled.
The firewall may block access.
Although I have used this with Norton 360 with no issues on my
Vista laptop, on my Windows 7 machine, the app could be controlled from a
local browser but not from the remote until I edited the firewall
settings to allow World Wide Web (HTTP) services.
Using the code
Download, compile and try the demo
program: You can press one of the three buttons to change the
window's background color—just to demonstrate some functionality.
When the program is started, it also displays the URL you need to
enter on your remote device browser. For testing convenience, you
can try it out with a browser on your local machine first.
You should see a window like this:
Clicking the buttons will change the
window background on the PC App.
Then, on your remote device, open a
web-browser and enter in the URL shown in the app.
You should see screens like this:
Have fun!
To adapt the control to use in your
own app: Add the control to the window which will handle the
commands coming from the remote and add an event handler to handle
the control's RemoteInputReceived event. This handler is called
whenever input is received from the remote device. Add your own code
to the event handler—usually, the various remote control events will map to
controls which already exist in your window.
The demo event handler looks like this:
private void remoteControl11_RemoteControl(object sender, RoutedEventArgs e1)
{
var e = e1 as RemoteControl1.RemoteControlEventArgs;
if (e.pageParams.ContainsKey("red"))
buttonRed_Click(null, null);
if (e.pageParams.ContainsKey("blue"))
buttonBlue_Click(null, null);
if (e.pageParams.ContainsKey("green"))
buttonGreen_Click(null, null);
if (e.pageParams.ContainsKey("name") && e.pageParams["name"] != "")
textBlockHello.Text = "Hello " + e.pageParams["name"] + "!";
else
textBlockHello.Text = "";
}
Edit the HTML page (RemoteControl.htm)
to include the controls relevant to your app. In theory, you can
make the page as complex as you like but in practice, writing device-independent HTML means things should stay simple.
When the window loads (or is
initialized) call “StartServer” to start the remote
functionality. Call StopServer to end the remote functionality.
Under the Hood
The HttpListener is the key. In a
small amount of code, I could create a rudimentary web server which
can serve up an HTML page. Here's all the code needed to serve up a
page and get a response:
void BackgroundWorker_DoWork(object sender, System.ComponentModel.DoWorkEventArgs e)
{
listener = new HttpListener();
listener.Prefixes.Add("http://*:" + portNumber.ToString() + "/");
try
{
listener.Start();
TextReader tr = new StreamReader("RemoteControlPage.htm");
string pageString = tr.ReadToEnd();
while (!worker.CancellationPending)
{
HttpListenerContext context = listener.GetContext();
if (context.Request.HttpMethod == "POST")
{
var body = new StreamReader(context.Request.InputStream).ReadToEnd();
ParsePostParameters(body);
worker.ReportProgress(1);
}
byte[] buffer = System.Text.Encoding.UTF8.GetBytes(pageString);
context.Response.ContentLength64 = buffer.Length;
context.Response.OutputStream.Write(buffer, 0, buffer.Length);
context.Response.Close();
}
listener.Stop();
}
catch (Exception e1)
{
if (!cancelling)
MessageBox.Show("Remote Listener failed. " + e1.Message);
}
}
The HttpListener has a few
inconveniences for the developer:
It is a blocking function so it
needs to be put in its own thread. I put it in a BackGroundWorker
thread because it was easy. I use the ReportProgess functionality
of the BackgGroundWorker whenever a message is received from the
remote device, this way, the result ends up back in the UI thread
where it can be used to control the other actions.
The HttpListener leaves the
developer on your own to parse any parameters. I parse them into a
Dictionary (static) and pass it back to the UI thread in the event.
Determining the user input from the Dictionary is just a matter of
asking if it Contains a certain key or getting the value associated
with a key. The key is the “name” of the control in the HTML
and is case sensitive. Here is the input parser:
private static void ParsePostParameters(string body)
{
string[] stringParams = body.Split('&');
pageParams.Clear();
foreach (string s in stringParams)
{
int index = s.IndexOf('=');
if (index > -1)
{
string key = s.Substring(0, index);
string value = s.Substring(index + 1);
value = System.Uri.UnescapeDataString(value);
pageParams.Add(key, value);
}
}
}
<requestedExecutionLevel level="requireAdministrator" uiAccess="false" />
Points of Interest
Here's an interesting browser quirk:
Unlike IE, the android browser on my phone ignores port numbers
entered into its address bar. If I key in http://myPC:9999,
the browser actually goes to http://myPC:80
instead. That's the reason I use port 80 for the remote. Since I am
running a web server on my development machine, I have to shut it
down (Command Prompt: “net stop was”) whenever I want to run the
remote control program and restart it (Command Prompt: “net start
w3svc”) when I'm done.
Here's another browser quirk: If I
touch buttons on the web page, there is a noticeable delay before the
PC program responds. If I hold the button for a moment, the response
time is essentially instant when I release the button. I speculate
that there may be a Javascript way to eliminate the delay if it is a
problem for your app. I tried using 'onmousedown' on the buttons and
this worked when I tried it on IE but not on the Android.
Suggestions are welcome.
NOTE: this is the kind of little
app/control which is intended to be cross-platform is exceedingly difficult for a lone developer to test because many
external factors could make it not work. I would appreciate feedback
on the platforms/configurations where it works (or not) to help make
it more useful.
History
Initial submission 4/6/12
Fixed some typos 4/8/12