基于 WPF + Modern UI 的 公司OA小助手 开发总结

前言:

距离上一篇博客,整整一个月的时间了。人不能懒下来,必须有个阶段性的总结,算是对我这个阶段的一个反思。人只有在总结的过程中才会发现自己的不足。

公司每天都要在OA系统上上班点击签到,下班点击签退,每天都要写工作日志。有的时候头脑不清醒或者忙过头了(别说你们没有过),就会忘记签到或者签退,有时候甚至忘记写工作日志。这会直接导致扣人工啊有木有,所以我才有了这个想法。首先声明,开发这个东西并不是博主对工作不认真不负责任,也并不是偷懒。相反,第一,可以避免因工作过忙忘记签到扣工资;第二,在开发的过程中你学到的东西是快速的,有趣的,让自己受益的。对于每个公司来说,OA系统都是他们的公司机密,所以博主并不会贴源码,只在这里阐述一个开发流程与思想,让你感觉到做一个自己觉得有趣的产品,思想的火花是多么不可思议。

一. 用到的模板与技术

1. WPF

相比传统的WinForm,WPF真是太强大了,无论在UI还是在多线程的处理上,以及一些其它的改进,都预示着WinForm将被WPF取代(这只是理论上,事实上,因为很多产品都是多年前开发的,用的是WinForm,如果要整个框架移植到WPF将是一件痛苦的事,反正产品没功能上的问题,这个移植就显得没必要了。所以目前,很多公司依然使用着WinForm的技术,开发者都在这个基础上对产品缝缝补补,更没有机会接触WPF了,就算是会这门技术的人,也找不到这个职位。比如我的公司就是)。本软件全面采用WPF技术,使用XAML布局以及做一些增强用户体验的动画。

2. Modern UI

Modern UI 是基于WPF的一个开源项目,托管在 code plex 上。你可以参考以下方法把 Modern UI 的模板添加到你的 Visual Studio 上:

  • 在Visual Studio 2012中,打开扩展管理器(工具 > 扩展和更新
  • 选择在线 > Visual Studio库和搜索“ 现代UI “
  • 选择现代为WPF的UI模板,然后单击“ 下载“,下载并安装。

关于 Modern UI 的介绍和使用,请参考http://mui.codeplex.com/,博主不再累赘。

3. 多线程

任何一个涉及到下载数据的程序都应该使用多线程编程,我们另开一个线程去下载数据的话,界面就不会有“假死”现象,用户体验显著提升。在WPF中,如果要在子线程中获取或者设置界面UI的值也是很简单的事情,这个WPF都为我们处理好了,很方便使用。

4. Lambda表达式

Lambda表达式是一个匿名方法,你不必再为只使用一次的方法独立写成一个函数(比如委托)。在WPF中在子线程里获取界面控件的值的时候就使用了Lambda表达式。Lambda表达式是委托的实现方法。

5. MVVM设计模式

MVVM 是 Model-View-ViewModel 的缩写,看字面你就能想到是什么意思吧。使用它的好处是,如果绑定的数据上下文改变了,会自动通知UI做出相应的更改。这也是比WinForm进步的地方。对于开发人员来说相当方便。当然MVVM不只这些内容。

6. XML配置文件的操作

因为要保存用户名密码、是否开启自动签到动能、自动签到的时间等等数据,就用到了App.config,实际上这是一个XML文件。

7. 系统托盘的处理

很多程序都有这个功能,主要是为了让程序在后台继续运行,以便时间到了就自动签到或者签退。

8. 开机自启

早上一来开机就自动启动,然后自动签到,会很爽吧,都完全不用自己动手。主要是写入注册表操作。

9. 模拟浏览器请求(重点)

使用HttpWatch来抓包,使用HttpWebRequest和HttpWebResponse来模拟浏览器的行为,要理解HTTP请求协议,当然在asp.net下还要理解asp.net网站与普通网站的差异(asp.net的原理)。asp.net的网站,使用服务器控件开发的话,页面上会有一大堆“垃圾代码”,用来保存页面状态,控件状态等等信息,这些信息在发送post报文的时候也需要发送过去,而且它的值的长度很长。

10. 正则表达式

软件里面大量使用了正则表达式从服务器返回的html页面来获取我们需要的数据,比如我的工作日志列表,签到记录等等。关于正则表达式无非两个类,Match/MatchCollection、Regex。有兴趣的自己去了解一下正则表达式的使用,即便是做前端开发的,也需要掌握js下的正则表达式来做客户端的表单验证。

二. 核心代码HtmlHelper

public static class HtmlHelper
{
private static string cookieHeader = string.Empty; /// <summary>
/// 添加日志
/// </summary>
/// <param name="strUrl">请求的url</param>
/// <param name="param">参数</param>
/// <returns></returns>
public static string PostData(string strUrl, string param)
{
string strResult = "";
HttpWebRequest myHttpWebRequest = (HttpWebRequest)WebRequest.Create(strUrl);
myHttpWebRequest.AllowAutoRedirect = true;
myHttpWebRequest.KeepAlive = true;
myHttpWebRequest.Accept = "image/gif, image/x-xbitmap, image/jpeg, imagepeg, applicationnd.ms-excel, application/msword, application/x-shockwave-flash, */*";
myHttpWebRequest.Timeout = 10000;
myHttpWebRequest.UserAgent = "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; Maxthon; .NET CLR 2.0.50727)";
myHttpWebRequest.ContentType = "application/x-www-form-urlencoded";
myHttpWebRequest.Method = "POST";
myHttpWebRequest.Headers.Add("cookie:" + cookieHeader); Stream MyRequestStrearm = myHttpWebRequest.GetRequestStream();
StreamWriter MyStreamWriter = new StreamWriter(MyRequestStrearm, Encoding.ASCII);
//把数据写入HttpWebRequest的Request流
MyStreamWriter.Write(param);
//关闭打开对象
MyStreamWriter.Close();
MyRequestStrearm.Close(); HttpWebResponse response = null;
System.IO.StreamReader sr = null;
response = (HttpWebResponse)myHttpWebRequest.GetResponse(); sr = new System.IO.StreamReader(response.GetResponseStream(), Encoding.GetEncoding("utf-8")); // //utf-8
strResult = sr.ReadToEnd();
return strResult;
} /// <summary>
/// 功能描述:模拟登录页面,提交登录数据进行登录,并记录Header中的cookie
/// </summary>
/// <param name="strURL">登录数据提交的页面地址</param>
/// <param name="strArgs">用户登录数据</param>
/// <returns></returns>
public static string PostLogin(string strURL, string strArgs)
{
string strResult = "";
HttpWebRequest myHttpWebRequest = (HttpWebRequest)WebRequest.Create(strURL);
myHttpWebRequest.AllowAutoRedirect = true;
myHttpWebRequest.KeepAlive = true;
myHttpWebRequest.Accept = "image/gif, image/x-xbitmap, image/jpeg, imagepeg, applicationnd.ms-excel, application/msword, application/x-shockwave-flash, */*"; myHttpWebRequest.UserAgent = "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; Maxthon; .NET CLR 2.0.50727)";
myHttpWebRequest.ContentType = "application/x-www-form-urlencoded";
myHttpWebRequest.Method = "POST";
myHttpWebRequest.Headers.Add("cookie:" + cookieHeader); CookieContainer myCookieContainer = new CookieContainer();
myHttpWebRequest.CookieContainer = myCookieContainer; Stream MyRequestStrearm = myHttpWebRequest.GetRequestStream();
StreamWriter MyStreamWriter = new StreamWriter(MyRequestStrearm, Encoding.ASCII);
//把数据写入HttpWebRequest的Request流
MyStreamWriter.Write(strArgs);
//关闭打开对象
MyStreamWriter.Close();
MyRequestStrearm.Close(); HttpWebResponse response = null;
System.IO.StreamReader sr = null;
response = (HttpWebResponse)myHttpWebRequest.GetResponse(); cookieHeader = myHttpWebRequest.CookieContainer.GetCookieHeader(new Uri(strURL)); sr = new System.IO.StreamReader(response.GetResponseStream(), Encoding.GetEncoding("utf-8")); // //utf-8
strResult = sr.ReadToEnd();
return strResult;
} /**/
/// <summary>
/// 功能描述:在PostLogin成功登录后记录下Headers中的cookie,然后获取此网站上其他页面的内容
/// </summary>
/// <param name="strURL">获取网站的某页面的地址</param>
/// <param name="strReferer">引用的地址</param>
/// <returns>返回页面内容</returns>
public static string GetPage(string strURL)
{
string strResult = "";
HttpWebRequest myHttpWebRequest = (HttpWebRequest)WebRequest.Create(strURL);
myHttpWebRequest.ContentType = "textml";
myHttpWebRequest.Method = "GET";
myHttpWebRequest.Headers.Add("cookie:" + cookieHeader); HttpWebResponse response = null;
System.IO.StreamReader sr = null;
response = (HttpWebResponse)myHttpWebRequest.GetResponse();
sr = new System.IO.StreamReader(response.GetResponseStream(), Encoding.GetEncoding("utf-8")); // //utf-8
strResult = sr.ReadToEnd();
return strResult;
} /// <summary>
/// 获取隐藏控件的value
/// </summary>
/// <param name="loginHtml">HTML页面代码字符串</param>
/// <param name="regex">要获取的值的正则表达式</param>
/// <param name="replaceLeft">左边要删除的字符串</param>
/// <param name="replaceRight">右边要删除的字符串</param>
/// <returns></returns>
public static string GetHiddenValue(string loginHtml, string regex, string replaceLeft, string replaceRight)
{
string viewState = string.Empty;
Match match = new Regex(regex).Match(loginHtml);
if (match.Success)
{
viewState = match.Value.Replace(replaceLeft, "");
viewState = viewState.Replace(replaceRight, "");
}
return viewState;
}

吐槽一下自己,这个类其实可以优化,比如PostLogin和PostData其实可以合并,GetHiddenValue也可以写得更好,只是工作这边还比较忙,刚刚接手了一个任务,是把项目的结构全面改版,使得每一个功能就是一个小项目,这样管理起来方便很多,所以没有时间进行优化,做好了自己能用就用着先,还是工作比较重要。

很明显,里面有4个方法,每个方法的作用以及调用方式都有很详细的注释,我就不重复了。至于更多的代码我就不贴了,毕竟涉及到商业机密的问题。讲讲原理吧。

三. 原理

首先,我们知道http是无状态连接,每次浏览器向服务器端发送请求,服务器返回数据之后就断开了,你下一次请求的时候服务器并不知道你是否已经登录,那么asp.net下服务器怎么知道你的登陆状态呢?当你登陆之后,服务器会给浏览器发送一个cookie,用来标识你的登录状态,下一次请求的时候浏览器会把这个cookie一同发给服务器,服务器接收到之后验证你发过来的cookie数据,然后就知道你是否已经登陆过。如果没登陆,就不让你请求别的页面数据。我们可以使用HttpWebRequest和HttpWebResponse类来模拟浏览器的请求。

登陆之后,我们要写工作日志,就要把日志内容拼成要提交的报文,然后post到服务器,这就是一个post请求过程。还有一种请求叫做get请求,这种请求是不提交报文的,直接发请求,然后服务器就会返回一个html页面,然后我们就可以利用正则表达式来获取我们需要的数据了。

这里有一个值得注意的地方,因为我们的程序要一个挂在电脑上,以便它可以到时间后自动签退或者签到,但是服务器为你保存的登录状态是有时间段的,如果过了一段时间你没有请求操作,服务器认为你已经断开连接,不再为你保持登录状态(我们公司的OA系统似乎是半个小时),所以我们进行一个请求之前要判断一下登录状态是否还保持着,如果断开了就重新登录一下再进行请求。怎么判断登录状态呢?我们公司的OA系统会弹出一个提示框提示身份验证过期,而这个提示框当然是在html页面上的,我们只需要请求一下主页,看它返回的html页面中是否包含身份验证过期这个信息就行了(别说你不知道html页面其实就是一个字符串)。

四. 晒图

1.登陆(其实只是保存了用户名密码,并没有真正登陆,到需要进行登陆操作的时候才登陆,比如提交日志、签到、签退、获取日志列表、登录信息等等,当然并不是每次这些操作都登陆,只要登录状态还保持着,就不需要重新登陆了)

2.主页

3.添加工作日志

4.个性化(模板自带)

5.用户信息(用于登陆)

6.系统托盘

7.签到成功提示

五. 后话        

在这个浮躁的世界里,我们不可以浮躁。人要有梦想,不然跟条咸鱼有什么分别。虽然我在追求自己喜欢的生活方式上有着各种阻碍,但是我还是认为,以自己喜欢的方式生活才是最开心的。人就一辈子,只要能承担责任,还有什么必要跟自己过不去呢?昨天我朋友从凤凰古城给我寄来一张明信片,我感慨万千,于是回复了一句:祝前程似锦,山明水秀,兄弟情谊,万古长青。切记,莫屈服,要以自己喜欢的方式生活。

也献给你们,亲爱的园友。

 
 
分类: XAML
标签: WPFModern UI

OA小助手的更多相关文章

  1. (转)基于 WPF + Modern UI 的 公司OA小助手 开发总结

    原文地址:http://www.cnblogs.com/rainlam163/p/3365181.html 前言: 距离上一篇博客,整整一个月的时间了.人不能懒下来,必须有个阶段性的总结,算是对我这个 ...

  2. 基于 WPF + Modern UI 的 公司OA小助手 开发总结

    前言: 距离上一篇博客,整整一个月的时间了.人不能懒下来,必须有个阶段性的总结,算是对我这个阶段的一个反思.人只有在总结的过程中才会发现自己的不足. 公司每天都要在OA系统上上班点击签到,下班点击签退 ...

  3. 发布代码小助手V2.1发布了——Code2HTML工具

    设计起源: 新浪博客似乎没有插入代码的功能,所以不得不用打空格的方法格式化代码.而且没法显示行号. 描述: 发布代码小助手用python和Tkinter开发,可以在任何常见操作系统上运行.主要用于在不 ...

  4. 书签小助手V1.1发布了

    更新信息: 1.修改了部分BUG;2.添加了一些不错的网站:3.重新设计了添加书签和编辑书签的界面. 安装说明: 类Ubuntu系统: 1.安装Python3解释器和Python3-tk sudo a ...

  5. 环境监测小助手V1.1的Windows版

    环境监测小助手V1.1——可以实时查看空气质量和城市排名 一款跨平台空气质量监测软件 数据来源互联网,请联网使用. 暂不支持效果预览. 下载地址:http://files.cnblogs.com/py ...

  6. Windows版词汇小助手V3.0发布了

    欢迎使用词汇小助手 作者:IT小小龙 电子邮箱:long_python@126.com 个人博客:http://blog.sina.com.cn/buduanqs 一款跨平台词汇查询记忆学习软件. 已 ...

  7. 词汇小助手V3.0发布了——不只是一个查单词的软件

    欢迎使用词汇小助手 作者:IT小小龙 电子邮箱:long_python@126.com 个人博客:http://blog.sina.com.cn/buduanqs 一款跨平台词汇查询记忆学习软件. 已 ...

  8. 生日小助手V4.0——迁移到Python3

    生日小助手V4.0——迁移到Python3 生日小助手V4.0只支持Linux系统,依赖命令行软件lunar Ubuntu系统安装方法:1.安装lunarsudo apt-get install lu ...

  9. 通达OA 小飞鱼工作流在线培训教程文件夹及意见征集

    最近通达OA技术交流群有不少朋友反映说表单设计这块 改动样式的问题,这块须要html和css的改动.本来最近正好要在工作流这块准备做一个系列的课程,都是基础的设置主要是给刚接触工作流的朋友用的,大家有 ...

随机推荐

  1. git常用命令,git版本控制和Xcode结合使用,用Xcode提交到github,github客户端使用

    1.git常用命令 查看命令: 1.git --help 查看git所有命令 2.git clone -help 查看git clone命令的细节 3.git config -l   查看当前所有配置 ...

  2. Paper.js - Paper.js

    Paper.js - Paper.js   Paper.js is an open source vector graphics scripting framework that runs on to ...

  3. bootstraptable表格基本

    function tableint(){   $("#tableFromData").bootstrapTable({    url:BASE_URL+"/do/fron ...

  4. 转:C# Process.Start()方法详解

    http://blog.csdn.net/czw2010/article/details/7896264 System.Diagnostics.Process.Start(); 能做什么呢?它主要有以 ...

  5. Jenkins部分插件介绍

    1.Join Plugin 功能介绍:这是一个触发job的插件,亮点在于它触发job的条件是等待当前job的所有下游job都完成才会发生. 例:假如A同时触发B1和B2两个下游job,然后配置这个插件 ...

  6. ionic3搭建笔记及编译成apk

    一.安装node.js 二.安装Ionic2 npm install -g ionic (安装最新版本) ionic -v //查看版本号(是否安装成功) npm uninstall -g ionic ...

  7. BZOJ1497[NOI2006]最大获利——最大权闭合子图

    题目描述 新的技术正冲击着手机通讯市场,对于各大运营商来说,这既是机遇,更是挑战.THU集团旗下的CS&T通讯公司在新一代通讯技术血战的前夜,需要做太多的准备工作,仅就站址选择一项,就需要完成 ...

  8. Linux Platform驱动模型(三) _platform+cdev

    平台总线是一种实现设备信息与驱动方法相分离的方法,利用这种方法,我们可以写出一个更像样一点的字符设备驱动,即使用cdev作为接口,平台总线作为分离方式: xjkeydrv_init():模块加载函数 ...

  9. C#-VS SQLServer数据库编程-摘

    ado.net 通用类对象.在本地内存暂存数据 托管类对象.让本地通用类对象连接数据库,让本地通用类对象和数据库同步 连接数据库 new connection(connectstring) comma ...

  10. jQuery使用scrollTop获取div标签的滚动条已滚动高度(jQuery版本1.6+时,用prop()方法代替attr()方法)

    $("#content").append('<div>' + data.msg + '</div>'+.'<br>');$('#content' ...