之前用业余时间开发了一个电话本小程序(http://www.cnblogs.com/GhostZCH/archive/2011/11/25/2263705.html),开发之后有一些待改善的问题,放假回家又用了一些时间开发了 2.0,改善了其中的一些问题。
安装包下载:https://files.cnblogs.com/GhostZCH/PhoneII_Setup.rar
一、程序的基本信息:
开发工具: VS2010;
大 小 :安装包700k;
有效代码:2200行左右(有一部分是用自制工具生成的http://www.cnblogs.com/GhostZCH/archive/2012/01/13/2321336.html);
架 构 :.NET 4.0 Client Prifile;
主要技术:XML 读写,WPF 界面制作,MVC结构的探索,架构探索;
开发时间:不祥,有空就弄几下;
二、总体结果:
开发总体上算是完成了,bug多多,但是可以用,在1.0的开发中提出了7条改进设想完成了5条,剩下异常处理和导入导出没有完成。在开发中基本摆脱了WPF默认窗口格式约束,边框、标题栏、状态栏都是自己制作,也就可以使用更多的特效,当然了拖动等行为也要自己处理。大体上对MVC结构的应用程序有了了解。定义一套自己的代码格式并应用自制代码生成工具生成代码,算是一个思维上的突破。对诸如:反射、WPF控件继承、文件读写、只启动一个实例等技术点有了一些应用,稍有了解。
三、界面截图:
1. 左面是安装后的桌面图标,右面是安装包。
2.鼠标离开窗口时的登录界面,边框消失,标题栏和状态栏消失,界面成为半透明。
3. 鼠标进入登录窗口后,显示半透明的标题栏和状态栏,主要区域不透明,显示浅蓝色边框。
4. 鼠标进入标题栏或状态栏,标题栏和状态栏的透明度减弱,文字变清晰。
5. 鼠标停留按钮变色(这个是从网上抄来的)。
6. 注册界面
7. 鼠标离去的主界面,无边框、状态栏、标题栏,主要区域半透明。左上角显示用户名,右上角菜单可用作用户操作(设置,重登陆,退出)。左半部分是联系人分组,右半部分显示每个分组下的联系人列表,通过点击左边的分组名可以切换右边的列表信息。
8. 鼠标进入窗口后的样子
9. 另一种按钮。
10. 分组编辑或新增。
11. 联系人编辑和新增界面
12. 鼠标离开后的联系人界面
13. 在任务栏中的状态,可显示出用户名。
四、代码结构:
1. 总体由四个解决方案组成
2. 主项目包含存储数据用的文件,这次用到的界面和控制器。这里的界面不是窗口而是一些控件,这些控件继承自PhoneIIDrawEngine的BaseControl,在使用时由Controller决定把哪一个控件加入主窗口中显示。这个程序执行过程中主窗口不关闭,只是在窗口中切换控件,切换动作控制器完成。
3. 绘画引擎(我也没想到更好的名字只好这样叫了)包含通用界面元素,事实上是一个用户控件库。整个程序中所有的窗口都是WinBase,所有的界面控件都继承自BaseControl,WinBase中可以加载一个BaseControl控件,并根据控件自动调整大小位置标题和状态。
4. 数据模型包含所有的业务实体,访问数据完成业务逻辑,为controller提供服务。DataTool是通用工具,为每一个业务实体建立一个文件夹(子命名空间),并用工具自动生成5个类。两个抽象类不允许开发人员修改,开发人员可在其他三个类中添加代码完成自定义业务逻辑。
5. 安装项目,生成安装包,不解释了。
五、数据模型部分的结构:
1.理论上的结构,数据模型为控制器提供服务,不予界面发生关系。我才用了先分层(V,C,M),然后分模块(为每个业务实体建立一个模块),模块内再分层(业务逻辑,数据访问)的结构。每个实体5个类,四个处在业务逻辑层,对外看到 Info 和 Factory 两个类。Info 包含实体本身的属性和行为(不访问数据),Fatory 则是业务逻辑的主要部分通过DataTool间接地访问数据。将 1.0 中单一的业务逻辑类分割成多个模块方便维护,也避免了一个类太长。自定义个DataTool必须实现IDataTool(因为被AbstarctFactory使用完成多数常用功能),理论上在系统工具中的DBBaseTool(未完工)与XMLBaseTool 已经实现了接口,只用继承二者之一就可以了,并在自定义的DataTool 中添加 其他方法 如:
1 namespace PhoneIIModles.User
2 {
3 /// <summary>
4 /// 自定义实体数据访问工具类 必须实现 IDataTool<T> 根据需要选择继承 XMLBaseTool 或 DBBaseTool 也可不继承
5 /// 可以添加自定义方法,此类由 factory 使用 在 factory 的 Getdatatool() 方法中设置
6 /// </summary>
7 public class UserDataTool:XMLBaseTool<UserInfo>, IDataTool<UserInfo>
8 {
9 const string PATH = @"DATA/00000000-0000-0000-0000-000000000000.usr";
10
11 public UserDataTool(): base(PATH, new ToObject<UserInfo>((element) => { return UserInfo.XElementToUserInfo(element); }), new ToElement<UserInfo>((info) => { return UserInfo.UserToXElement(info); }))
12 {
13 }
14
15 public void AddFiles(UserInfo info)
16 {
17 // 添加 组 文件
18 XMLBaseTool<UserInfo>.AddFile("GroupList", info.GetGroupFilePath());
19 //添加 记录 文件
20 XMLBaseTool<UserInfo>.AddFile("RrecordList", info.GetRecordFilePath());
21 // 添加默认组
22 new GroupFactory(info.Id).Add(new GroupInfo(Guid.Empty,"我的好友",0));
23 }
24
25 public void DeleteFiles(UserInfo info)
26 {
27 // 删除 组 文件
28 File.Delete(info.GetGroupFilePath());
29 // 删除记录 文件
30 File.Delete(info.GetRecordFilePath());
31 }
32 }
33 }
这是个示意图
这是VS自动生成的类图
六、MVC的使用及思考
从一开始就纠结于应用程序中的MVC结构是一种什么样的实现方法,做完了都还有很多疑惑,这里只是我的一些方法和思考,全当抛砖引玉了。
我认定数据模型和界面是不发生关系的,但是如果只是向制作网站的微软MVC一样由界面发请求,得到控制器返回的界面也有一些问题。比如:在注册用户时发现用户明为空这样的简单错误不需要向后台的控制器发出请求经过真个繁杂的过程,只需要在前台校验一下就好了,尤其是远程程序,省去的访问服务器的麻烦。又如,在用户保存数据时需要后台校验,这时需要的不是一个界面而是一个信息,有时只是进行一个简单的操作不需要返回,标准的MVC就会遇到问题。还有一个问题就是弹出子窗口的时机和窗口内容也不方便由控制器来完成。
我个人认为,即使是在MVC中,界面还是可以处理一定的业务逻辑的,只要这个逻辑不需要数据模型的参与就可以放在界面中完成,例如,输入为空的校验,弹出子窗口,弹出提示框等,这样即方便实现有减少的对控制器的依赖(对于远程系统可以减少RPC调用)。
这样一来界面对控制器的使用就只剩先两种:1.请求更换界面,如登录完成。 2。请求后台执行某系操作但不更换界面,如获得一些校验信息或修改一些数据。
分而治之:
首先,对于请求更换界面的。在这个程序中,主窗口只有一个,是一个WinBase 在程序启动时打开,窗口关闭程序结束。不同的界面只是在WinBase中加载不同界面
1先定义一个代理 public delegate void Request(string methodName, object[] parameters);
2制作一个控件 BaseControl 包含这样一个事件 public virtual event Request Request;
3程序所有的界面都要继承 BaseControl 并覆盖这个事件
public partial class CtrlLogin : BaseControl
{
// 必须覆盖父类的事件
public override event Request Request;
}
4 在控制器重定义一个 以控件为返回类型的方法
public BaseControl RegistStart()
{
return new CtrlRegist();
}
5 在需要的时候激发这个事件就可以了
public partial class CtrlLogin : BaseControl
{
// 必须覆盖父类的事件
public override event Request Request;
private void BtnRegist_Click(object sender, RoutedEventArgs e)
{
Request("RegistStart",null); // 激发事件
}
}
6 这样那个名为 CtrlRegist 的控件就替换原有控件(CtrlLogin )显示在窗口中了
7 这个原理吗?呵呵,我来解释一下,控件事件激发后被窗口捕获处理后又激发了窗口的事件(WinBase中已经处理好好了),窗口的事件被App捕获,通过反射获得“请求”对应的控制器的方法,并执行。
private void winmain_Request(string methodName, object[] parameters)
{
// 非关闭请求交给控制器处理 (注:由页面发出请求,使用反射找到控制器的对应方法)
Type type = _controller.GetType();// _controller 是控制器的实例
MethodInfo method = type.GetMethod(methodName); // 通过 “请求”中的 methodName 找到方法
BaseControl ctrl = method.Invoke(_controller, parameters) as BaseControl; // 执行
// 添加控件到窗口
_winMain.AddContent(ctrl);
}
然后,对于第二种情况,就简单多了。只要在控制器中定义一些静态方法就好了。
public static bool AddUser(UserInfo info,out string msg)
{
return new UserFactory().AddUser(info, out msg);
}
这样,控制器中就只有两类方法,一类是返回界面控件的方法类似于 MVC 中的 Action ,另一类就是静态方法。总之在一定程度上打破了标准MVC的结构,让界面处理一部分逻辑让控制器多了一些静态方法使得编程更轻松容易(还记得被微软那个MVC逼到连保存成功这样的信息都要返回整个页面,刷新网页)。