Disable Scroll Bouncing Effect of WebBrowser Control on Windows Phone 8

Just another story happens when developing WordsBaking.

First, the basis. If you don’t have any element in your web page that requires overflow style being auto or scroll, “-ms-touch-action: none;” under “html” selector should work well . Actually it works all the time in Internet Explorer, but in a WebBrowser control, if there’s something like a list for which you may need that style, the solution becomes tricky.

I spent tons of hours and tried tons of ways hoping figure out a solution, and luckily, found one.

That is the good news, but the bad news is, this solution doesn’t seem to be elegant (though it works perfectly so far).

The first thing that I found might be useful is that this bouncing effect happens only when your finger touches the elements that already have their contents at the top/bottom or both. So, the first solution I thought might work was to add a pointerdown (MSPointerDown) event listener on the element, and determine whether its content has reached the top or bottom. Unfortunately, it doesn’t work well.

Later I read about an article shows a solution on suppressing scrolling and zooming in WP7 WebBrowser control, I can’t say that it works (on WP8), but it helps.

Combining these two parts (and this is why I think it’s not elegant enough), here’s the solution:

C# Part

private void mainBrowser_Loaded(object sender, RoutedEventArgs e) {
    // here we are using a library named Linq to Visual Tree.
    // http://www.scottlogic.com/blog/2010/03/04/linq-to-visual-tree.html
    var border = mainBrowser.Descendants().Last() as Border;
    border.ManipulationDelta += border_ManipulationDelta;
    border.ManipulationCompleted += border_ManipulationCompleted;
}

void border_ManipulationCompleted(object sender, ManipulationCompletedEventArgs e) {
    mainBrowser.InvokeScript("onmanipulationcompleted");
}

void border_ManipulationDelta(object sender, ManipulationDeltaEventArgs e) {
    var status = mainBrowser.InvokeScript("onmanipulationdelta") as string;
    if (status == "top" || status == "both") {
        if (e.DeltaManipulation.Translation.Y > 0) {
            e.Handled = true;
        }
    }
    if (status == "bottom" || status == "both") {
        if (e.DeltaManipulation.Translation.Y < 0) {
            e.Handled = true;
        }
    }
}

JavaScript Part

window.manipulationTarget = null;

window.onmanipulationdelta = function () {
    if (!window.manipulationTarget) {
        return '';
    }

    var target = window.manipulationTarget;

    var top = target.scrollTop == 0;
    var bottom = target.scrollTop + target.clientHeight == target.scrollHeight;

    return top ? bottom ? 'both' : 'top': bottom ? 'bottom' : '';
};

window.onmanipulationcompleted = function () {
    window.manipulationTarget = null;
};

// and you'll need to make calls to every elements
// with overflow auto or scroll with this:
function preventBouncing(ele) {
    // using jQuery.
    ele.on('MSPointerDown pointerdown', function (e) {
        window.manipulationTarget = this;
    });
}

And good luck fellows~

C# Refresh System Tray Icons (Remove the dead ones)

I was writing a small tool to restart a related application when it seems to stop working. As I don’t want to spend much time on this thing, I use Process.Kill to do that part of job. But after that, I will need to clean the notification area and remove the dead system tray icons.

I made searches and found one working great on my computer running English Windows 7.

http://maruf-dotnetdeveloper.blogspot.com/2012/08/c-refreshing-system-tray-icon.html

However, when I copied it to the server, the code was just doing nothing. As I am not familiar with Windows API and its programming, it took me a long time to realize the problem. Though the window titles such as “User Promoted Notification Area” are not visible, it’s been translated on the non-English Windows OS.

So I tried Spy++ (which I am not familiar with, either), and found out the Chinese ones for those strings.

“User Promoted Notification Area” => “用户升级的通知区域”
“Overflow Notification Area” => “溢出通知区域”

I didn’t find “Notification Area” one, and guess it’s on Windows XP. But I think it should be translated into “通知区域”.

And, it now works like a charm. 😉

使用 Visual Studio 内建的 WebDev.WebHost.dll 搭建轻量级 .NET 服务器

最近做的 X-Wall, 偶尔需要和浏览器进行交互, 之前采取的办法是注册一个 xwall 协议, 然后浏览器中用 JavaScript 通过改变 location.href 的方法调用. 但是有两个缺点, 一个是第一次很多浏览器会有提示, 另一个是只能触发不能得到结果, 也就没法知道操作是否完成.

后来在网上搜索相关解决方案, 看到了这个 <重编译WebDev.WebServer,使其支持网络应用>.  觉得有戏, 就也反编译了下 VS 2012 自带的看了看. VS 2012 里有其实有两个, 一个是 WebDev.WebHost20.dll, 一个是 WebDev.WebHost40.dll, 因为统一用 .NET 3.5, 所以就选择了第一个. 不过与上面所引用的文章目的不同, 我是希望这个东西作为程序内建的微型服务器, 而不是作为一个单纯的程序, 所以只需要这个 DLL 就可以了. 不过也用上了文章作者添加的 InitHost 方法, 用于初始化.

在 Windows 7 下面没法直接看到这个文件, 可以打开资源管理器后直接在搜索栏搜索 WebDev.WebHost, 或者借助 WinRAR 在 Windows\assembly\ 目录下找到复制出来, 然后用 Reflector 反编译. 修改后在项目中添加引用, 调用方式:

// path 是 ASP.NET 程序目录, port 和 vpath 对应 URL 中的位置如下.
// localhost:[port]/[vpath]/
var server = new Microsoft.VisualStudio.WebHost.Server(port, vpath, path);
server.Start();

除了第一次访问还是略慢, 其他都蛮好. 希望有用~

让 Github 成为免费的软件发布/下载/更新的服务器

这年头, 自己动手丰衣足食, 不过真的做出来个什么好家伙, 要想分享给别人, 也得在网上找个地儿. 之前一直是拿博客的空间来放东西, 不过老实说 FTP 还真不够方便, 而且可能很多人还没有这么个空间, 囧.

后来知道有 Github 这个东西, 再后来发现 Github 有 raw (原始文件) 这个东西, 再后来发现 Github 还有 Github Pages 这个东西. 但 Github Pages 则是后话了.

raw 的路径是固定的, 并且非常规范, 同时有 SSL 加密. 比如 X-Wall 的安装文件我提供的下载地址就是 https://raw.github.com/vilic/x-wall/master/release/x-wall-setup.exe, 从前至后依次是 https://raw.github.com/用户/仓库/分支/目录. 人可以通过浏览器下载, 软件当然也可以通过 GET 拿到, 于是这里的下载和更新就解决啦.

顺便就提提 Visual Studio 提供的 ClickOnce 解决方案. ClickOnce 是个非常方便的东西, 特别是对于小工具来讲. 但也有很多局限性, 其中一些可以通过不那么优雅的方式解决 (比如自动启动, 因为如果直接把 exe 添加到启动目录, 是没法读到正确的设置文件的, 还有 Command Line 传递参数等问题), 另一些则比较… 比如每次升级之后, exe 文件的目录也变了 (其实相当于是新下载到了另一个文件夹), 防火墙/托盘图标之类的设置也就没了. 最近做的 X-Wall 也是因为这点最后放弃了 ClickOnce.

对于 ClickOnce 来说, 设置自动更新都是傻瓜化的, 这个蛮好, 所以图方便的话, 还是不错的选择. 而这里要说的是, 我们也可以将 ClickOnce 的应用部署到 Github 上. 不过多年前当作者第一次尝试这么做的时候, 结局是悲惨的. 因为 ClickOnce 有签名, 所以要保证提交的文件和签名是一致的. Git 默认安装提交的时候貌似会把 Windows 风格的换行变成 Linux 风格, 这个就是问题的原因了. 重新运行安装程序 (也许可以直接通过命令改, 但作者是懒人), 选择 “Commit as-is” 的选项.

之后, 把 ClickOnce 的应用发布到本地文件系统上, 也就是你的 Git 仓库所在的文件夹. 其实我们可以新建一个分支来专门放这些东西, 这个还是用 Github Pages 的时候学到的. 这个时候, ClickOnce 的自动更新设置中, 把地址设置成本地发布的这个文件夹提交到 Github 之后 raw 对应的目录即可. 最后提交代码, 大功告成.

最后再提一下 Github Pages. 当然你可以用来做个人/组织/项目的页面, 或许也可以用来做软件的下载/升级服务器. 之前说的 raw 上用户可以下载 exe 等等 binary 的文件, 软件也可以检查并下载更新, 唯有个缺点就是 MIME 类型有时候不那么尽如人意. 比如 js 也好, xml 也好, 都是 plain/text. 但用 Github Pages 理论上来讲就可以避免这个情况了. 总体来说, 如果你不嫌麻烦那么一点点, 并且不需要 SSL 加密连接的话, 用 Github Pages 做这个事情或许会更好.

另外 X-Wall 的主页也是架设在 Github 上的, 欢迎访问! http://x-wall.org/

Windows 7 开机以管理员身份启动应用程序

这个也是最近做的代理整合软件过程中遇到的问题, 因为要修改Internet Settings, 可能还有其他一些操作, 需要有管理员权限, 强制程序使用管理员权限可以添加一个manifest文件, 再将其中的requestedExecutionLevel, level改为”requireAdministrator”即可. 其实当时做到这里, 我以为就大功告成了, 但重启之后发现程序没有启动, 搜了搜, 才知道是自动启动的家伙获取不了管理员权限.

最后找到的解决方法有二, 一个是先启动一个不需要管理员权限的程序, 再延时打开需要管理员权限的程序, 不过效果不理想, 也不知道延时多久才能搞定. 并且, 每次开机都会请求权限, 用户用着也不爽. (话说, 关了UAC多好… 这儿整个都不用了.)

后来发现了Task Scheduler(任务计划)这个东西, 顿时觉得捡到了宝, 于是继续查找各种资料, 发现不仅能启动, 还能以管理员权限启动. 不过貌似只能用在Windows Vista/7 上(XP上貌似也有任务计划, 但可能接口不同或者没这么强大吧, 这个我没有细查, 但代码在XP上的确会进入catch), 所以XP上还要优雅降级, 添加普通的开机启动项, 反正XP又没有UAC, 是吧.

我使用的是C#, 同时需要添加COM引用, TaskScheduler 1.1 Type Library, 在system32目录下的taskschd.dll. 代码如下:

var dir = @”SOFTWARE\Microsoft\Windows\CurrentVersion\Run”;
try {
    var scheduler = new TaskSchedulerClass();
    scheduler.Connect(); //连接, 还有一些登录参数可选.
    var task = scheduler.NewTask(0); //官方文档上, 这个参数后面加了注释reserved.
    task.RegistrationInfo.Author = “Pacgen”;
    task.RegistrationInfo.Description = “Start Pacgen with Windows”;
    task.Settings.Enabled = true; //or false, 开关.

    //在启动的时候执行, 一开始只写了Logon, 不过发现开机的时候登录并没有触发.
    task.Triggers.Create(_TASK_TRIGGER_TYPE2.TASK_TRIGGER_BOOT);
    //注销后登录什么的.
    task.Triggers.Create(_TASK_TRIGGER_TYPE2.TASK_TRIGGER_LOGON);
    var action = task.Actions.Create(_TASK_ACTION_TYPE.TASK_ACTION_EXEC) as IExecAction;
    //上面的Triggers.Create也会像Actions.Create一样分别返回类型为IBootTrigger, ILogonTrigger的对象(自己as或者强制转换一下).
    //可以做更多设置.

    //这里就是设置为用户能达到的最高权限.
    task.Principal.RunLevel = _TASK_RUNLEVEL.TASK_RUNLEVEL_HIGHEST;
    action.Path = Application.ExecutablePath; //需要启动的程序路径.
    action.Arguments = “background”; //参数.

    var folder = scheduler.GetFolder(@”\”); //这里是Task的根文件夹, 还可以用folder.CreateFolder来创建自己的目录.

    //注册任务. 这里的TASK_LOGON_INTERACTIVE_TOKEN就是说使用用户当前的登录信息(如果已经登录).
    folder.RegisterTaskDefinition(“PacgenStartup”, task, (int)_TASK_CREATION.TASK_CREATE_OR_UPDATE, null, null, _TASK_LOGON_TYPE.TASK_LOGON_INTERACTIVE_TOKEN);

    //注册成功, 删除注册表内的启动项, 这里的SetRegister是我自己写的, 替换掉即可.
    //SetRegistry(dir, “Pacgen”, null);
}
catch {
    //注册失败, 添加开机启动项.
    //SetRegistry(dir, “Pacgen”, settings.AutoStart ? “\”” + Application.ExecutablePath + “\” background” : null);
}

现在程序就能悄无声息地以管理员权限启动了, 连请求用户都免了. 开心.

最后附上官方说明: http://msdn.microsoft.com/en-us/magazine/cc163350.aspx

通过注册表修改系统PAC代理设置

最近在写一个代理工具的整合, 不过不便发到博客上来, 就说说里面单纯的技术问题, 今天先写这一篇.

修改代理的办法貌似不少, 常用的貌似就注册表和Win API – InternetSetOption, 一开始我当然是想从相对正规的渠道, API来搞定, 花了相当大的力气, 才搜到相关内容, 并且修改成我需要的样子. 不过代码被我删了… 所以… 就不能分享了, 这里直接说说通过注册表改PAC设置的问题.

首先, 为什么我最后要用注册表而不是Win API? 原因是我没找到Win API修改Dial-up和VPN的PAC设置的方法, 所以也是无奈之举. 毕竟很多用户, 比如我自己, 就是通过Dial-up或者VPN联网的. 那, 下面开始主体内容. (我使用的是C#)

1. 注册表位置

CurrentUser, Software\Microsoft\Windows\CurrentVersion\Internet Settings\Connections

2. 值

值的名称就是连接的名称, 值是字节数据, 所以需要简单地分析下结构. (后来有个学长提醒说, C++什么的里面处理这种东西特别方便, 一个结构体搞定, 想想貌似是那个道理)

1) 最前面4字节应该是版本号, Win 7下是0x00000046, XP下貌似是3D, 记不清了…
2) 紧接着的4字节是修改次数, 每次修改都应该+1.
3) 接下来的4字节是flags, 其实貌似只有第一个字节用到了, 后面三个字节始终都是0, 这里开启PAC, 把这个值设为0x00000005就可以了, 其他开关什么的, 需要的话自己试试吧.
4) 接下来的4字节是保存了一个或多个(http, socks, ftp等)代理服务器及端口的字符串的长度,  当然, 再接下来的这个长度的字节就是这个字符串的数据了.
5) 接下来的4字节是保存了代理的例外服务器的字符串的长度,  再接下来的这个长度的字节也自然就是这个字符串的数据了.
6) 然后我们需要的东西终于来了, PAC文件的字符串长度, 4字节, 和PAC文件的字符串.
7) 因为我只需要在计数器(第5~8字节)上 +1, 并修改PAC文件即可, 所以后面的数据直接拷贝.

C#代码片段如下, 相关变量请自助:

var data = key.GetValue(name) as byte[];
var newData = new List<byte>();

var pos = 0;

//skip head, 4 bytes
newData.AddRange(data.Skip(pos).Take(4));
pos += 4;

//modify count
newData.AddRange(BitConverter.GetBytes(BitConverter.ToInt32(data, pos) + 1));
pos += 4;

//config flags
newData.AddRange(BitConverter.GetBytes(configFlags));
pos += 4;

//skip proxy setting
var skipCount = BitConverter.ToInt32(data, pos) + 4;
newData.AddRange(data.Skip(pos).Take(skipCount));
pos += skipCount;

//skip passby setting
skipCount = BitConverter.ToInt32(data, pos) + 4;
newData.AddRange(data.Skip(pos).Take(skipCount));
pos += skipCount;

//now, pac file
skipCount = BitConverter.ToInt32(data, pos) + 4;

var pathByteList = new List<byte>();
foreach (var chr in path) {
    var chrBytes = BitConverter.GetBytes(chr);
    chrBytes = chrBytes.Take(chrBytes.Length – 1).ToArray();
    pathByteList.AddRange(chrBytes);
}
newData.AddRange(BitConverter.GetBytes(pathByteList.Count));
newData.AddRange(pathByteList);
pos += skipCount;

//the rest
newData.AddRange(data.Skip(pos).ToArray());

var chrs = new char[newData.Count];
for (var i = 0; i < newData.Count; i++)
  chrs[i] = (char)newData[i];

//set the reg value
key.SetValue(name, newData.ToArray());

ASP.NET虚拟主机无写权限问题的解决

上周给Prever Start换了一个国内的虚拟主机,速度是快了不少,不过很快发现ASP.NET貌似没有写权限,导致很多功能没法用,今天Google了一下,很快找到了解决办法,现在共享下。

在Web.config文件的<system.web>节点下加入:
<identity impersonate=”true” />

或者是:
<identity impersonate=”true” userName=”YourAdminUsr” password=”YourAdminPwd” />