65.9K
CodeProject 正在变化。 阅读更多。
Home

用手机控制你的电脑应用程序

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.49/5 (19投票s)

2012年4月6日

CPOL

6分钟阅读

viewsIcon

86274

downloadIcon

3469

使用手机浏览器控制 PC 应用程序的超轻量级远程控制器

引言

用您的手机控制您的.NET PC应用。无需在手机上加载任何应用,您就可以从手机(Android、iPhone、iPad——任何有浏览器的设备)控制您的PC应用,只需提供一个带有控件的HTML页面并处理输入。此外,本文还将展示编写您自己的Web服务器所需的大部分代码。

背景

有很多种方法可以让手机控制PC应用,我认为这种方法是最简单的。我称之为“超轻量级”,因为它只做几件事……接收远程设备的按钮点击和文本输入。但如果您的应用只需要这些,那么这很简单。

与在远程控制设备上安装应用(市面上有很多可用的应用)不同,这个控件完全从PC端工作。它会在PC上打开一个“微型Web服务器”,该服务器可以向远程设备提供一个简单的HTML页面,显示控件。当远程浏览器将用户输入发送回PC时,控件会对其进行解码并引发一个事件。

这种方法的优点

  • 它适用于任何设备——iPhone、iPad、Android,甚至是另一台PC。

  • 它相对安全——提供网页内容是比较安全的。

  • 远程设备无需下载/安装任何东西。

  • 您可以自定义以仅显示您的应用所需的控件。远程设备的UI就是一个简单的HTML页面,易于自定义。

  • 在远程设备上,您可以创建一个桌面快捷方式,直接指向远程控制页面,就像启动手机应用一样方便。

  • 作为开发人员,您不必下载一堆工具来学习Android开发的Java库,也不必购买Mac来开发iPhone应用。

一些限制

  • 您无法进行拖动或其他触摸/鼠标输入(按当前代码编写)。

  • 用户必须在远程设备上手动输入URL——远程设备无法自动搜索网络上的应用程序。

  • 按当前代码编写,您无法更改远程设备上显示的控件,因为它们在UI事件解码操作发生之前就已经提供给远程设备了。因此,如果应用程序已停止,远程设备也无法正确响应。

  • 如果启用了Windows用户账户控制,程序必须拥有管理员权限。

  • 防火墙可能会阻止访问。虽然我在我的Vista笔记本上使用Norton 360时没有遇到任何问题,但在我的Windows 7机器上,该应用可以从*本地*浏览器访问,但无法从远程设备访问,直到我编辑防火墙设置以允许万维网(HTTP)服务。

使用代码

下载、编译并尝试演示程序:您可以按三个按钮之一来更改窗口的背景颜色——这只是为了演示一些功能。程序启动时,它还会显示您需要在远程设备浏览器中输入的URL。为了方便测试,您首先可以在本地计算机上的浏览器中进行尝试。

您应该会看到一个类似这样的窗口

点击按钮会改变PC应用窗口的背景。

然后,在您的远程设备上,打开一个Web浏览器,输入应用程序中显示的URL。

您应该会看到类似这样的屏幕

玩得开心!

要将此控件改编到您自己的应用程序中:将此控件添加到将处理来自远程命令的窗口中,并添加一个事件处理程序来处理控件的RemoteInputReceived事件。每当从远程设备接收到输入时,就会调用此处理程序。在事件处理程序中添加您自己的代码——通常,各种远程控制事件会映射到您窗口中已有的控件。

演示事件处理程序如下所示

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 = "";

}

编辑HTML页面(RemoteControl.htm)以包含您的应用程序相关的控件。理论上,您可以使页面尽可能复杂,但实际上,编写*设备无关*的HTML意味着它应该保持简单。

当窗口加载(或初始化)时,调用“StartServer”来启动远程功能。调用StopServer来结束远程功能。

幕后

HttpListener是关键。通过少量代码,我就可以创建一个简单的Web服务器来提供HTML页面。以下是提供页面和获取响应所需的所有代码

void BackgroundWorker_DoWork(object sender, System.ComponentModel.DoWorkEventArgs e)
{
    // Create a listener.
    listener = new HttpListener();
    // Add the prefixes.
    listener.Prefixes.Add("http://*:" + portNumber.ToString() + "/");
    try
    {
        listener.Start();
        //read in the html page to serve
        TextReader tr = new StreamReader("RemoteControlPage.htm");
        string pageString = tr.ReadToEnd();
        while (!worker.CancellationPending)
        {
            // Note: The GetContext method blocks while waiting for a request. 
            HttpListenerContext context = listener.GetContext();
            if (context.Request.HttpMethod == "POST")
            {
                //this is a POST, handle the input parameters
                var body = new StreamReader(context.Request.InputStream).ReadToEnd();
                ParsePostParameters(body);
                worker.ReportProgress(1); //raise the event
            }
            // Set up the response object to serve the page
            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);
    }
}

HttpListener对开发人员有一些不便之处

  • 它是一个阻塞函数,因此需要将其放在自己的线程中。我把它放在BackGroundWorker线程中,因为它很简单。我使用BackgGroundWorker的ReportProgess功能,每当从远程设备接收到消息时,这样结果就会回到UI线程,在那里可以用来控制其他操作。

  • HttpListener让开发人员需要自行解析任何参数。我将它们解析到一个Dictionary(静态)中,并将其传回UI线程的事件中。从Dictionary中确定用户输入,只需要检查它是否包含特定键,或获取与键关联的值。键是HTML中控件的“名称”,区分大小写。这是输入解析器

private static void ParsePostParameters(string body)
{
    //format:  name1=value1&name2=value2&name3=value3
    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); //removes all the 'secret' character encoding
            pageParams.Add(key, value);
        }
    }
}
  • 如果启用了用户账户控制(UAC),HttpListener需要管理员权限。为了解决这个问题,我在演示应用程序中添加了一个清单文件,并包含以下一行
<requestedExecutionLevel  level="requireAdministrator" uiAccess="false" />

关注点

这里有一个有趣的浏览器怪癖:与IE不同,我手机上的Android浏览器会忽略地址栏中输入的端口号。如果我输入http://myPC:9999,浏览器实际上会转到http://myPC:80。这就是为什么我使用端口80进行远程控制。由于我的开发机上运行着Web服务器,当我想要运行远程控制程序时,必须先将其关闭(命令提示符:“net stop was”),完成后再重新启动(命令提示符:“net start w3svc”)。

这是另一个浏览器怪癖:如果我点击网页上的按钮,PC程序响应之前会有明显的延迟。如果我按住按钮一会儿,在我释放按钮时,响应时间基本上是即时的。我推测,如果这对您的应用程序来说是个问题,可能存在一种JavaScript方法来消除延迟。我尝试在按钮上使用'onmousedown',在我尝试IE时是有效的,但在Android上却不行。欢迎提供建议。

注意:这种小型应用/控件旨在跨平台使用,但对于单人开发者来说,进行测试极其困难,因为许多外部因素可能导致其无法正常工作。我将非常感谢您在哪些平台/配置上测试成功(或失败)的反馈,以帮助它变得更有用。

历史

首次提交 2012年4月6日

修正了一些拼写错误 2012年4月8日

© . All rights reserved.