[转载]flex 联机游戏开发 - 斗地主游戏:(一)核心逻辑

mikel阅读(1197)

[转载]flex 联机游戏开发 – 斗地主游戏:(一)核心逻辑 – 博弈居 – 开放的第三方应用游戏开发测试平台 – 博客园.

这是我希望开发的第一个纸牌类游戏,斗地主本身来说是一个有限用户的回合制游戏,核心逻辑与棋类游戏并无多大的不同,但是因为并无多大的行棋算法, 所以位置显得不那么的重要。所以我决定还是用开发四国军棋形成的flexchessapi来开发这个游戏,如果flexchessapi不能胜任这个工 作,我会考虑形成一个通用的flexcardapi来开发这个游戏。这样,我就能利用flexcardapi开发更多的游戏,比如诈金花等。

核心逻辑开发完成后效果如图所示

好了,现在我们开始设计一些纸牌类的一般逻辑思考,放在一个静态类如CardHelp.as中。。

1) 设计一幅牌

我们对一幅牌的每一张牌应该给定一个值,每个值包括花色与本身的大小,这样,我们就以一个3位数的值来代表一张牌,比如303代表红花3,414 代表黑桃A,这样,我们就能够设计出一幅牌的完整数组,不同的牌需要的完整数不同,比如扎金花是不要王,斗地主是要王的,现在,我们设计一幅牌。

/**
*斗地主带王的一幅完整的牌
* 在斗地主中,2是比A还大的牌,我们不能叫102,就叫115吧。
*/
public static const CARDS_WITH_JOKER:Array=[115,103,104,105,106,107,108,109,110,111,112,113,114,215,203,204,205,206,207,208,209,210,211,212,213,214,315,303,304,305,306,307,308,309,310,311,312,313,314,415,403,404,405,406,407,408,409,410,411,412,413,414,518,519];

/**
*扎金花不带王的一幅完整的牌
*/
public static const CARDS_NO_JOKER:Array=[102,103,104,105,106,107,108,109,110,111,112,113,114,202,203,204,205,206,207,208,209,210,211,212,213,214,302,303,304,305,306,307,308,309,310,311,312,313,314,402,403,404,405,406,407,408,409,410,411,412,413,414];

设计两个帮助方法

/**
*获得特定处于的花色
* @param card
* @return
*
*/
public static function getHuaShe(value:int):int{
return Math.floor(value/100);
}
/**
* 获得特定牌的原始值
* @param card
* @return
*
*/
public static function getPureValue(value:int):int{
return value%100;
}

2)牌有了,我们开始设计一个发牌的函数

2.1)发一张牌,发牌函数相对比较简单,直接从一幅牌的数组中取一张即可,注意的是,取完后应该将该牌删除,以免下次再发一样的牌。

/**
* 获得牌组中一张随机的牌值
* @param cards
* @return
*
*/
public static function getRadomCard(cards:ArrayCollection):int{
if (cards.length==0) return -1;
var key:int=Math.round(Math.random()*(cards.length-1));
return cards.removeItemAt(key) as int;

}

2.2)发一手牌,对斗地主来说,一手牌就是17张,最后的三张就是底牌,在发牌的时候,我们考虑的是给牌排序,这样,发完牌后就自动从大到小排列。

/**
* 获得一个随机的一手牌。
* @param cards 全部牌
* @param num 牌的数量
* @param sort 是否排序
* @return
*
*/
public static function getRadomCards(cards:ArrayCollection,num:int,sort:Boolean=true):ArrayCollection{
var temp:ArrayCollection=new ArrayCollection();
do{
var cardvalue:int=getRadomCard(cards);
trace(“生成牌:”+getDisplayName(cardvalue));
if (!sort)
temp.addItem(cardvalue);
else
{
var added:Boolean=false;
for (var i:int=0;i<temp.length;i++)
{

var value:int=temp.getItemAt(i) as int;
if (cardvalue%100>value%100)
{
temp.addItemAt(cardvalue,i);
added=true;
break;
}

}
if (!added)
{
temp.addItemAt(cardvalue,i);
}

}

num–;
}while(num>0);
trace(“排序后:”+temp.toString());
return temp;
}

3)进入斗地主的逻辑,我们现在设计一个通用类叫 DoudizhuHelp.as。

斗地主游戏中的牌型很多,算法也各有不同,但我总觉得网上一些通用的算法有点铺天盖地,所以,我决定自己来重新想一下算法,总的来说,无论你出什么 处于,三带二,炸,4带一等,你总是有个主牌的,从牌只认张数,大小是无所谓的,所以,我将主牌清点出来应该就只有 对子,3条,炸弹,双王,顺子这五类,所以,在发牌或者拿到底牌后,我们直接将所有的主牌类型清理出来即可。

现在,写一个清理主牌类型的函数。

/**
*获得当前牌的所有牌型
* @return
*
*/
public function initType():void{

var temp:ArrayCollection=new ArrayCollection();

for (var i:int=0;i<_cardAC.length-1;i++)
{

var card1:ICard=_cardAC.getItemAt(i) as ICard;
var card2:ICard=_cardAC.getItemAt(i+1) as ICard;

//添加至双王
if (card1.pureValue==18||card1.pureValue==19)
{
if (!shuangwangAC.contains(card1.pureValue))
shuangwangAC.addItem(card1.pureValue);
}
//相同部分,我们只需要判断一次
if (card1.pureValue==card2.pureValue)
{

//添加至炸弹
if (this.santiaoAC.contains(card1.pureValue))
{
if (!this.zhadanAC.contains(card1.pureValue))
this.zhadanAC.addItem(card1.pureValue);
}

//添加至三条
else if (this.duizhiAC.contains(card1.pureValue))
{
this.santiaoAC.addItem(card1.pureValue);
}
//添加至对子
else
{
this.duizhiAC.addItem(card1.pureValue);
}

}
//添加至顺子
else if (card1.pureValue==card2.pureValue+1&&card2.pureValue!=3)
{
//2不能加入顺子中
if (card1.pureValue==15) continue;

if (!temp.contains(card1.pureValue))
temp.addItem(card1.pureValue);
if (!temp.contains(card2.pureValue))
temp.addItem(card2.pureValue);
}
//位数不等或者已经到达最后
else
{
if (card2.pureValue==3) temp.addItem(card2.pureValue);
if (temp.length>=5)
{
this.sunzhiAC.addAll(temp);
}
temp=new ArrayCollection();

}

}
//如果没有双王,则清空双王集合
if (shuangwangAC.length<2)
{
shuangwangAC.removeAll();

}

trace(“双王..”+this.shuangwangAC.toString());
trace(“对子..”+this.duizhiAC.toString());
trace(“三条…”+this.santiaoAC.toString());
trace(“顺子…”+this.sunzhiAC.toString());
trace(“炸弹….”+this.zhadanAC.toString());

}

分析每一句的意义。函数应该比较好懂,暂时还没发现什么bug,不过我总觉得不放心:),高手们可以帮我找找bug,谢谢了。。。

现在,我试着分析了一手牌。

双王..
对子..15,9,6,4,3
三条…15,9
顺子…10,9,8,7,6,5,4,3
炸弹….

在这一个月里,我已经开了太多的头,四国军棋 写到了3,五子棋 写到了2,中国象棋 写到了 1,所以我决定在再完成一个扎金花的游戏逻辑分析后,下一步就开始埋头做联机游戏服务器网站 www.boyiju.com 的开发与联机游戏客户端api的开发,然后对所有已经完成逻辑的游戏进行统一更新,在第三方应用发展迅猛的今天,我的目标是建成一个所有程序员可以轻松进 行第三方游戏应用开发测试的公用开源平台,平台将一个完整的sns网站测试环境与以下的各类api,感兴趣的朋友可以随时关注项目开发进展情况,如果你极 端感兴趣,或者您仍旧怀着工作部分为了理想活着的大志。你也可以加我QQ 2 1 0 7 3 4 5 2 了解这个项目或者参与这个项目的开发,您不必担心我,你面对的只是一个除了有点忙,没什么坏习惯的开发者。

[转载]Windows下获取Android系统源码

mikel阅读(1264)

[转载]Windows下获取Android系统源码 – 杨平 – youhei – 博客园.

接手Android也有一年的时间了,由于刚开始学习时资料比较少,又大多都是断章取义的,所以对于Android我一直处于似懂非懂的状态下。今天终于感觉到该留点学习的经验吧,那就从这篇开始我的Android系列文章了。

Android使用Git管理源代码的,在Linux下我们可以使用ubuntu来获取系统源码,下面我就逐步介绍在Windows些如何获取Android系统源码了。

一。安装msysGit

在google的http://code.google.com/p/msysgit/中这样介绍到:

If you just want to use Git to do your version control in Windows, you will need to download Git for Windows, run the installer, and you are ready to start. Note: Git for Windows is a project run by volunteers, so if you want it to improve, volunteer!

If you want to download and install the complete source code of Git for Windows (including C compiler) to start hacking, you will need to install the full development system which is called msysGit.

注释:如果你需要使用Git工具在Windows下进行版本控制,你可以使用基于Windows的Git,这里提供了msysGit的安装下载。

我们选择full安装版本,http://code.google.com/p/msysgit/downloads/detail?name=msysGit-fullinstall-1.7.3.1-preview20101002.exe

下载msysGit安装包后安装,在安装过程中msysGit会自动的安装或编译基于Windows环境的Git。编译Git需要点时间,安装完后截图如下:

12

这就是Git的shell窗口,你可以在其中通过命名行操作下载得到Android的系统源码,下面就以Lunacher——桌面——为例下载源码。

二。下载Luancher源码

Android系统源码都在http://android.git.kernel.org/网址下,其中那个系统app都在platform/packages/apps结构下。

在刚才的Git的shell命名行窗口中输入下面的命名:

git clone git://android.git.kernel.org/platform/packages/apps/Luancher2.git

你就可以在你的msysGit安装目录的git下(~\msysgit\msysgit\git)看到Luancher工程文件夹了。

3456

然后打开~\msysgit\msysgit\git\Luancher2文件夹,就可以看到Android的Luancher系统源码工程了。

78

对于其他的app系统源码的获取方法和上述的Luancher相同,只是命名行后的参数不同。

git clone git://android.git.kernel.org/需下载源码的app所在的位置。

三。启动msysGit

有些朋友在关闭了msysGit命名行窗口后就找不到重新打开的链接了,不用着急,你只需要在你安装的msysGit路径下双击执行msys.bat文件即可。

所有的都大功告成了。

[转载]Android根据Button状态(normal,focused,pressed)显示不同背景图片

mikel阅读(1099)

[转载]Android根据Button状态(normal,focused,pressed)显示不同背景图片 – Android平台开发技术 – 博客园.

Android中Button 有focused, selected, pressed 等不同状态,通过配置一个XML格式的 drawable “selector” 即可实现”在不同状态下显示不同背景图片“的功能。

1. 在res/drawable目录下添加一个xml文件,用来描述Button在不同状态下对应的不同图片。我这里给该xml文件命名为btn_background.xml



<?xml version="1.0" encoding="UTF-8"?> <selector xmlns:android="http://schemas.android.com/apk/res/android"> <item android:state_pressed="true" android:drawable="@drawable/btn_pressed" /> <!-- pressed --> <item android:state_focused="true" android:drawable="@drawable/btn_normal" /> <!-- focused --> <item android:drawable="@drawable/btn_normal" /> <!-- default --> </selector>

2. 在res/layout目录下,对应的layout xml文件中,将Button的Android:background属性设置为btn_background即可。

<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent" > <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="@drawable/btn_background" /> </LinearLayout>

3.运行结果

默认状态(unselected)

点击状态(pressed)


参考资料:

Google Android开发文档:ImageButton

http://developer.android.com/reference/android/widget/ImageButton.html

我的Twitter @mainroadlee

[转载]Android编程获取手机型号,本机电话号码,sdk版本及firmware版本号(即系统版本号)

mikel阅读(1060)

[转载]Android编程获取手机型号,本机电话号码,sdk版本及firmware版本号(即系统版本号) – Android平台开发技术 – 博客园.

Android开发平台中,可通过TelephonyManager 获取本机号码。

TelephonyManager phoneMgr=(TelephonyManager)this.getSystemService(Context.TELEPHONY_SERVICE); txtPhoneNumber.setText(phoneMgr.getLine1Number()); //txtPhoneNumber是一个EditText 用于显示手机号

注:


Android的安全机制,在使用TelephonyManager时,必须在AndroidManifest.xml中添加<uses-
permission android:name=”READ_PHONE_STATE” /> 否则无法获得系统的许可。


手机型号 Build.MODEL

String MODEL The end-user-visible name for the end product.

sdk版本 Build.VERSION.SDK

String SDK This constant is deprecated. Use SDK_INT to easily get this as an integer.

及frimware版本号(系统版本号) Build.VERSION.RELEASE

String RELEASE The user-visible version string.


private void loadPhoneStatus() { TelephonyManager phoneMgr=(TelephonyManager)this.getSystemService(Context.TELEPHONY_SERVICE); txtPhoneModel.setText(Build.MODEL); //手机型号 txtPhoneNumber.setText(phoneMgr.getLine1Number());//本机电话号码 txtSdkVersion.setText(Build.VERSION.SDK);//SDK版本号 txtOsVersion.setText(Build.VERSION.RELEASE);//Firmware/OS 版本号 }

事实上,Build能向我们提供包括 硬件厂商,硬件编号,序列号等很多信息 调用方法也都同上,很简单。

String BOARD The name of the underlying board, like “goldfish”.
String BOOTLOADER The system bootloader version number.
String BRAND The brand (e.g., carrier) the software is customized for, if any.
String CPU_ABI The name of the instruction set (CPU type + ABI convention) of native code.
String CPU_ABI2 The name of the second instruction set (CPU type + ABI convention) of native code.
String DEVICE The name of the industrial design.
String DISPLAY A build ID string meant for displaying to the user
String FINGERPRINT A string that uniquely identifies this build.
String HARDWARE The name of the hardware (from the kernel command line or /proc).
String HOST
String ID Either a changelist number, or a label like “M4-rc20”.
String MANUFACTURER The manufacturer of the product/hardware.
String MODEL The end-user-visible name for the end product.
String PRODUCT The name of the overall product.
String RADIO The radio firmware version number.
String SERIAL A hardware serial number, if available.
String TAGS Comma-separated tags describing the build, like “unsigned,Debug”.
long TIME
String TYPE The type of build, like “user” or “eng”.
String UNKNOWN Value used for when a build property is unknown.
String USER

最近在做韩国一家公司的Android平台软件开发,我的手机号是韩国的啦。所以看到010打头的号码,别太惊讶..

我的twitter: @mainroadlee

手头做的项目涉及到Android平台中网络设置,数据库,http协议下的数据传输,支付系统,以及SNS API(twitter, facebook, me2day, etc), QR码扫描等模块。

如果大家感兴趣的话,我会在开发闲暇时间陆续将常用的一些模块的开发调用,写成独立,简洁的教程发上来。

[转载]android开发我的新浪微博客户端-用户授权页面功能篇(3.2) – 遇见未知的自己 – 博客园

mikel阅读(1035)

[转载]android开发我的新浪微博客户端-用户授权页面功能篇(3.2) – 遇见未知的自己 – 博客园.

==》

上一篇实现了用户授权页面的UI,如上图,接下来要做的就是在这个基础上完成功能部分真正实现用户的授权认证,这一篇是android开发我的新浪微博客户端-OAuth篇(2.1)的具体应用篇原理就不多解释了不懂的看OAuth篇即可。认证过程从点击开始按钮然后跳转到新浪的授权页面,接着用户在新浪的页面里输入自己的账户和密码确定后返回用户授权页面。首先给开始按钮添加点击事件代码,代码中主要是调用我们前面android开发我的新浪微博客户端-OAuth篇(2.1)完成的OAuth类的RequestAccessToken方法用来获取oauth_verifier,具体代码如下:

代码

ImageButton stratBtn=(ImageButton)diaView.findViewById(R.id.btn_start);
stratBtn.setOnClickListener(
new OnClickListener(){

@Override
public void onClick(View arg0) {
auth
=new OAuth();
auth.RequestAccessToken(AuthorizeActivity.
this, CallBackUrl);
}

});

上面的代码中重点来说明一下 RequestAccessToken方法的第二参数CallBackUrl,这个参数是用户在新浪的页面中输入账户密码 后完成认证后返回的地址,我这里是这样设置的CallBackUrl = “myapp://AuthorizeActivity”,在AndroidManifest.xml中配置给AuthorizeActivity添加如 下配置把myapp://AuthorizeActivity指向到AuthorizeActivity,这样当页面返回到 AuthorizeActivity中就可以获取到传过来的oauth_verifier参数。

代码

<intent-filter>
<action Android:name=”Android.intent.action.VIEW” />
<category android:name=”android.intent.category.DEFAULT” />
<category android:name=”android.intent.category.BROWSABLE” />
<data android:scheme=”myapp” android:host=”AuthorizeActivity” />
</intent-filter>

再AuthorizeActivity如果来接收返回的oauth_verifier参数呢?接下来在AuthorizeActivity添加如下方法:

@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
//在这里处理获取返回的oauth_verifier参数
}

关于onNewIntent的说明是这样的,onCreate是用来创建一个Activity也就是创建一个窗体,但一个Activty处于任务栈的顶 端,若再次调用startActivity去创建它,则不会再次创建。若你想利用已有的Acivity去处理别的Intent时,你就可以利用 onNewIntent来处理。在onNewIntent里面就会获得新的Intent,在这里AuthorizeActivity是属于已有的 Acivity,所以需要onNewIntent来处理接收返回的参数,获取oauth_verifier参数后OAuth还没有结束从android开发我的新浪微博客户端-OAuth篇(2.1)描述来看还需要进行根据这个参数继续向新浪微博请求获取User_id、Access Token和Access Secret,在这里我把这些操作全部写在了GetAccessToken方法中。在onNewIntent添加如下代码:

代码

UserInfo user= auth.GetAccessToken(intent);
if(user!=null){
DataHelper helper
=new DataHelper(this);
String uid
=user.getUserId();
if(helper.HaveUserInfo(uid))
{
helper.UpdateUserInfo(user);
Log.e(
UserInfo, update);
}
else
{
helper.SaveUserInfo(user);
Log.e(
UserInfo, add);
}
}

通过上面的代码完成了User_id、Access Token和Access Secret 获取并且保存到了SQLite库中,这样就完成了用户的OAuth认证,当需要调用新浪的api时只需要去SQLite库中找该用户的User_id、Access Token和Access Secret 即可。到这里本篇就结束了,请关注下一篇。

[转载]使用VS2010编译QOAuth支持微博通用认证OAuth实现SINA微博登陆 - 风雷云雪电 - 博客园

mikel阅读(1228)

[转载]微博是个大金矿,使用VS2010编译QOAuth支持微博通用认证OAuth实现SINA微博登陆 – 风雷云雪电 – 博客园.

随着Twitter的兴起和国内Sina和QQ等公司的追随,微博现在是如日中天,将传统的SNS给完全比拼下去,微博对于大家来说完全是个尚未完 全开采的大金矿,对于一直站在潮流最前端的程序员来说怎么能将这么好的机会错失呢。在这里我抛砖引玉,先介绍下如何在Qt平台上编译QAuth来支持现在 微博的通用认证OAuth

根据Twitter的API Wiki,基本的OAuth验证workflow如下:
1. 程序利用http://api.twitter.com/oauth/request_token来从twitter.com那里获取一个request token。
2. 然后程序引导用户到http://api.twitter.com/oauth/authorize页面。
3. 用户如果同意授权,twitter.com则会显示一个7位数字的PIN码。
4. 用户需要将PIN码复制,然后回到程序那里。
5. 之后程序要提示用户输入得到的PIN码。
6. 然后程序将PIN码作为参数oauth_verifier的值,接着调用http://api.twitter.com/oauth/access_token去核实PIN码,从而将request_token 换成access_token。
7. Twitter之后会返回一个access_token,程序就此token来生成之后的OAuth签名。

OAuth本身不是很复杂,不过如果有个库可以作为验证的helper的话,就可以省事很多了,由于最近一直在用Qt做开发,在上篇文章中我介绍了Qt并介绍了如何在VS2010中编译Qt环境,如果对Qt不熟悉的朋友可以点这里查看。顺理成章的是我希望在Qt框架下找到一个能支持OAuth的类库来简化开发,通过放狗进行搜索,搜到有个QOAuth的东西,是在Qt下的OAuth库,不过仔细一看,原来在使用QOauth之前还需要OpenssL,QCA和QCA的OpenssL插件,晕啊!

闲话就不说了,咱们进入正题

  • openssL

首先得到OpenssL的官网上下载OpenssL,http://www.openssl.org/,我下载的是openssl-1.0.0c.tar.gz,也是最新的,如果大家不想编译也可以到网上搜索别人编译好的库。

下载好后解压缩到硬盘中,我的路径是 H:\openssl-1.0.0c,由于OpenssL使用Perl生成makefile,所以需要先下载Perl,我下载的ActivePerl,下载好后进行安装。

Perl安装好后,运行Visual Studio Command Prompt (2010),不要运行普通的控制台。

键入命令 perl Configure VC-WIN32

image

如果输入错误会有明显提示,需要注意的是VC-W32必须是大写

等完成后键入命令 ms\do_ms

image

等完成后输入 nmake -f ms\ntdll.mak

image

编译成功后生成的lib,dll等库文件在out32dll中,头文件在include中,这些目录在以后的编译中需要加入到项目头文件目录和lib包含目录中。

  • 编译支持OpenssL的Qt

由于默认情况下Qt编译出来时不支持OpenssL的,需要重新编译下Qt,囧

基本步骤和我这篇文章 Qt简介以及如何配置Qt使用VS2010进行开发 中所描述的基本一致,唯一不同的在于configure的时候需要加上-openssl -I openssL头文件目录 -L openssLlib文件目录

如我的环境下是这样子滴

configure -platform win32-msvc2010 -openssl -I H:\openssl-1.0.0c\include -L H:\openssl-1.0.0c\out32dll

  • QCA

QCA需要到这个网站http://delta.affinix.com/qca/下载,同样的这个网站提供的QCA的OpenssL插件下载

QCA我下载的 qca-2.0.3.tar.bz2

QCA的OpenssL插件我下载的是 qca-ossl-2.0.0-beta3.tar.bz2

下载好后解压缩到硬盘,我的路径是 H:\qca-2.0.3,打开系统的环境变量,将QTDIR加入系统环境变量,如图所示

image

打开VS2010,Qt菜单(如果没有这个菜单,请查看我的上一篇文章),Open Qt Project File(.pro)…,选择QCA的安装目录下的qca.pro,稍等一会儿,自动将PRO项目转换成VS2010项目并加载了QCA下的所有项目

image

工程下有很多项目,我们只要编译qca项目即可,直接编译会出错,需要进行下设置

展开qca项目,在Generated Files下的Debug和release目录下找到qpipe.moc文件,右键属性,显示下图界面

image

在Command Line的最后面加上-DQ_OS_WIN,确定后,再编译,OK一切都安静了

  • QCA的Openssl插件

下载好后解压缩到硬盘,在Visual Studio Command Prompt (2010)中进入压缩目录,我的是在H:\qca-ossl-2.0.0-beta3

执行下面命令: configwin rd

image

新 建一个文本文件,输入WINLOCAL_PREFIX = C:/local,C:/Local可以是任何你想指定的目录,这个目录是作为零时文件的存放路径,将其保存到你的Qt目录下的 mkspecs\features目录中,文件名为winlocal.prf,注意不要保存成文本文件,我的保存路径是H:\Qt\2010.05\qt \mkspecs\features,供大家参考。

image

打开VS2010,Qt菜单(如果没有这个菜单,请查看我的上一篇文章),Open Qt Project File(.pro)…,选择QCA的OpenSSL插件的的安装目录下的qca-ossl.pro,稍等一会儿生成了QCA的OpenSSL插件的VS项目

将OpenssL的头文件路径和lib文件路径添加到项目中

在项目中搜索所有的EVP_md2,一共有4个,加上宏定义#ifndef OPENSSL_NO_MD2…#endif,如图示

image

编译之,在lib文件夹中获取劳动成果dll和lib文件两颗,拷贝进Qt的Plugins的crypto中,如果没有这个目录,手动建立目录。在以后编译的项目中需要将这个目录拷贝到exe文件的目录中即可。

  • QOAuth

从QOAuth的官方网站https://github.com/ayoy/qoauth下载QOAuth

在硬盘解压缩,我的目录是H:\ayoy-qoauth-18dbc19

通用的打开VS2010,Qt菜单(如果没有这个菜单,请查看我的上一篇文章),Open Qt Project File(.pro)…,选择QOAuth目录下的qoauth.pro,稍等片刻生成项目文件

在qoauth项目中的interface.h文件中点击右键,属性,如下图所示

image

将.\GeneratedFiles\$(ConfigurationName)\加入到include目录中

image

编译qoauth项目,成功后在lib文件中找到dll和lib

编译好之后得安装。具体做法是:
将oauth.prf复制到QTDIR\mkspecs\features里面,
将include和src复制到QTDIR\include里面,src里面只需有头文件即可
将生成的dll复制到QTDIR\lib,如果编译时候提示找不到这两个dll,可能是因为这是的dll名字包含版本号,去掉就ok了。。

用了QOAuth之后,编译出来的程序需要
+OpenSSL的链接库:(如果不带上OpenSSL的话,程序仍然可以启动,QCA会显示插件不支持的,然后会中止程序,算是个小陷阱)
-libeay32.dll,libssl32.dll,ssleay32.dll
+QCA的链接库:
-qcad2.dll
+QOAuth的链接库
-qoauth1.dll/qoauthd1.dll

[转载]优化OEA中的聚合SQL

mikel阅读(1090)

[转载]优化OEA中的聚合SQL – 哲学驱动设计 – 博客园.

之前写过几篇关于聚合对象SQL的文章,讲的是如果设计框架,使用一句SQL语句来加载整个聚合对象树中的所有数据。相关内容,参见:《性能优化总结(二):聚合SQL》、《性能优化总结(三):聚合SQL在GIX4中的应用》。 由于没有使用其它的ORM框架,当时项目组决定做聚合SQL,主要是为了减少SQL查询的次数,来提升部分模块的性能。现在看来,当时虽然达到了这个目 标,但是聚合SQL的API却不简单,使用极为不便。至今,项目组中的其它人也不会使用。所以,这次我们决定把聚合SQL的API使用再次进行封装,以达 到使用起来更简便的效果。

本文中的内容与前面几篇的内容、与OEA框架中的内容相关性比较大,有兴趣的朋友可以关注CodePlex中的项目:《OpenExpressApp

结果对比


优化前的代码,在前面的文章中已经有所展示。这里主要看一下优化过后的代码:

最简单的聚合SQL生成:

1 var sqlSimple = AggregateSQL.Instance.GenerateQuerySQL<PBS>(
2 option => option.LoadChildren(pbs => pbs.PBSBQItems),
3 pbsTypeId
4 );

这样就生成了如下SQL:

SELECT
pbs0.pid as PBS_pid, pbs0.pbstypeid as PBS_pbstypeid, pbs0.code as PBS_code, pbs0.name as PBS_name, pbs0.fullname as PBS_fullname, pbs0.description as PBS_description, pbs0.pbssubjectid as PBS_pbssubjectid, pbs0.orderno as PBS_orderno, pbs0.id as PBS_id,
pbsbqi1.pbsid as PBSBQItem_pbsid, pbsbqi1.code as PBSBQItem_code, pbsbqi1.name as PBSBQItem_name, pbsbqi1.unit as PBSBQItem_unit, pbsbqi1.bqdbid as PBSBQItem_bqdbid, pbsbqi1.id as PBSBQItem_id
FROM PBS AS pbs0
LEFT OUTER JOIN PBSBQItem AS pbsbqi1 ON pbsbqi1.PBSId = pbs0.Id
WHERE pbs0.PBSTypeId = ‘084a7db5-938a-4c7b-8d6a-612146ad87f9’
ORDER BY pbs0.Id, pbsbqi1.Id

该SQL用于加载聚合根对象PBSType下的所有PBS子对象,同时每个PBS的子对象PBSBQItems也都被同时查询出来。

再进一步,我们还可以直接使用聚合关系加载出对象,而不需要SQL,如:

1 var pbsList = AggregateSQL.Instance.LoadEntities<PBS>(
2 option => option.LoadChildren(pbs => pbs.PBSBQItems),
3 pbsTypeId
4 );

这样,API内部会生成聚合SQL,并进行聚合对象的加载。相对以前的模式,易用性提高了许多。这里,再给出一个目前支持的比较完整的API示例:

1 var projectPBSs = AggregateSQL.Instance.LoadEntities<ProjectPBS>(loadOptions =>
2 loadOptions.LoadChildren(pp => pp.ProjectPBSPropertyValues)
3 .Order<ProjectPBSPropertyValue>().By(v => v.PBSProperty.OrderNo)
4 .LoadFK(v => v.PBSProperty).LoadChildren(p => p.PBSPropertyOptionalValues),
5 criteria.ProjectId
6 );

表 示:加载ProjectPBS的对象列表时:同时加载它每一个ProjectPBS的子对象列表ProjectPBSPropertyValues,并把 ProjectPBSPropertyValues按照外键PBSProperty的OrderNo属性进行排序;同时,加载 ProjectPBSPropertyValue.PBSProperty、加载 PBSProperty.PBSPropertyOptionalValues。(其中,Order方法需要使用泛型方法指明类型是因为目前的实体列表都 是非泛型的,不能进行类型推断。)

总体设计


本次设计,主要是以提高模块的易用性为目的。

在原有的设计中,主要有两个步骤,生成聚合SQL 和 从大表中加载聚合对象。这两个过程是比较独立的。它们之间耦合的地方有两个。首先,是为表生成什么样的列名,生成SQL时按照这种列名的约定进行生成,加 载对象时则在大表中找对应列的数据。其次,它们还隐含耦合一些说明性的数据,这些数据指明了需要加载哪些子属性或者外键,什么样的加载关系,对应一个什么 样的聚合SQL,也就对应加载出来的对象。

也就是说,上述两个过程需要完整的封装起来,我们需要管理好这两个部分。而列名的生成在原来的模式中已经使用了“表名+列名”的格式进行了约定,所以现在 我们只需要把“描述如何加载的描述性数据”进行管理就可以了。有了这些数据,则可以在框架内部生成聚合SQL,在框架内部按照它们进行大表到聚合对象的加 载。以下,我将这些数据称为聚合对象的“加载选项”。

同时,考虑到聚合SQL生成的复杂性及使用的2/8原则,这次的聚合SQL自动生成和加载只处理比较简单的情况:只处理简单的链式的加载。例如:A对象作为Root的子对象,它还有子对象B、C,B有子对象D、E,D有外键引用对象F、F有子对象G,那么,只处理链式的加载意味着,最多可以在加载某个Root对象的A集合的同时,带上A.B、B.C、C.D、D.F、F.G。

image

如上图所示,在加载A.B的时候,不支持加载A.C;同理,加载B.D的时候,不支持加载B.E。其实在实际运用当中,这样的局限性在使用的时候并没有太大的问题,一是较多的使用场景不需要同时加载所有的子,二是可以分两条线加载对象后,再使用对象进行数据的融合。

核心数据结构 – 加载选项


上面已经说明了加载选项是整个聚合SQL加载的描述数据,描述如何生成SQL,描述如何加载对象。它其实也就是整个过程中的核心对象,由于时间有限(预计 只有一天时间完成整个设计及代码实现),而且这个对象并不会直接暴露在外面,所以这直接使用了最简单的链表类型来表示链式的加载选项。(老实说,这个设计 的扩展性并不好。)

01 /// <summary>
02 /// 聚合加载描述器。
03 ///
04 /// 目前只包含一些聚合加载选项“AggregateSQLItem”
05 /// </summary>
06 internal class AggregateDescriptor
07 {
08 private LinkedList<LoadOptionItem> _items = new LinkedList<LoadOptionItem>();
09
10 /// <summary>
11 /// 所有的AggregateSQLItem
12 /// </summary>
13 internal LinkedList<LoadOptionItem> Items
14 {
15 get
16 {
17 return _items;
18 }
19 }
20
21 /// <summary>
22 /// 直接加载的实体类型
23 /// </summary>
24 internal Type DirectlyQueryType
25 {
26 get
27 {
28 return this._items.First.Value.OwnerType;
29 }
30 }
31
32 /// <summary>
33 /// 追加一个聚合加载选项
34 /// </summary>
35 /// <param name="item"></param>
36 internal void AddItem(LoadOptionItem item)
37 {
38 this._items.AddLast(item);
39 }
40 }

而它包含的每一个元素 LoadOptionItem 则表示一个加载项,它主要包含一个属性的元数据,用于表示要级联加载的子对象集合属性或者外键引用对象属性。

01 /// <summary>
02 /// 生成聚合SQL的加载项中的某一项
03 /// </summary>
04 [DebuggerDisplay("{OwnerType.Name}.{PropertyEntityType.Name}")]
05 internal class LoadOptionItem
06 {
07 private Action<Entity, Entity> _fkSetter;
08
09 /// <summary>
10 /// 加载这个属性。
11 /// </summary>
12 internal IPropertyInfo PropertyInfo { get; private set; }
13
14 internal Func<Entity, object> OrderBy { get; set; }
15
16 /// <summary>
17 /// 指标这个属性是一般的实体
18 /// </summary>
19 internal AggregateLoadType LoadType
20 {
21 get
22 {
23 return this._fkSetter == null ? AggregateLoadType.Children : AggregateLoadType.ReferenceEntity;
24 }
25 }
26
27 //.......
28 }
29
30 /// <summary>
31 /// 属性的加载类型
32 /// </summary>
33 internal enum AggregateLoadType
34 {
35 /// <summary>
36 /// 加载子对象集合属性
37 /// </summary>
38 Children,
39
40 /// <summary>
41 /// 加载外键引用实体。
42 /// </summary>
43 ReferenceEntity
44 }

对象加载


按照上面的加载选项的链式设计,SQL生成其实就比较简单了:列名生成还是使用原有的方法,其它部分则只需要按照元数据进行链式生成就行了。花些时间就搞定了。

框架中对象的聚合加载的实现,和手写时一样,也是基于原有的ReadFromTable方法的,也不复杂,贴下代码,不再一一描述:

001 /// <summary>
002 /// 聚合实体的加载器
003 /// </summary>
004 internal class AggregateEntityLoader
005 {
006 private AggregateDescriptor _aggregateInfo;
007
008 internal AggregateEntityLoader(AggregateDescriptor aggregate)
009 {
010 if (aggregate == null) throw new ArgumentNullException("aggregate");
011 if (aggregate.Items.Count < 1) throw new InvalidOperationException("aggregate.Items.Count < 2 must be false.");
012
013 this._aggregateInfo = aggregate;
014 }
015
016 /// <summary>
017 /// 通过聚合SQL加载整个聚合对象列表。
018 /// </summary>
019 /// <param name="sql"></param>
020 /// <returns></returns>
021 internal EntityList Query(string sql)
022 {
023 IGTable dataTable = null;
024
025 IDbFactory dbFactory = this._aggregateInfo.Items.First.Value.OwnerRepository;
026 using (var db = dbFactory.CreateDb())
027 {
028 dataTable = db.QueryTable(sql);
029 }
030
031 //使用dataTable中的数据 和 AggregateDescriptor 中的描述信息,读取整个聚合列表。
032 var list = this.ReadFromTable(dataTable, this._aggregateInfo.Items.First);
033
034 return list;
035 }
036
037 /// <summary>
038 /// 根据 optionNode 中的描述信息,读取 table 中的数据组装为对象列表并返回。
039 ///
040 /// 如果 optionNode 中指定要加载更多的子/引用对象,则会递归调用自己实现聚合加载。
041 /// </summary>
042 /// <param name="table"></param>
043 /// <param name="optionNode"></param>
044 /// <returns></returns>
045 private EntityList ReadFromTable(IGTable table, LinkedListNode<LoadOptionItem> optionNode)
046 {
047 var option = optionNode.Value;
048 var newList = option.OwnerRepository.NewList();
049 newList.ReadFromTable(table, (row, subTable) =>
050 {
051 var entity = option.OwnerRepository.Convert(row);
052
053 EntityList listResult = null;
054
055 //是否还有后继需要加载的对象?如果是,则递归调用自己进行子对象的加载。
056 var nextNode = optionNode.Next;
057 if (nextNode != null)
058 {
059 listResult = this.ReadFromTable(subTable, nextNode);
060 }
061 else
062 {
063 listResult = this.ReadFromTable(subTable, option.PropertyEntityRepository);
064 }
065
066 //是否需要排序?
067 if (listResult.Count > 1 && option.OrderBy != null)
068 {
069 listResult = option.PropertyEntityRepository.NewListOrderBy(listResult, option.OrderBy);
070 }
071
072 //当前对象是加载类型的子对象还是引用的外键
073 if (option.LoadType == AggregateLoadType.Children)
074 {
075 listResult.SetParentEntity(entity);
076 entity.LoadCSLAProperty(option.CslaPropertyInfo, listResult);
077 }
078 else
079 {
080 if (listResult.Count > 0)
081 {
082 option.SetReferenceEntity(entity, listResult[0]);
083 }
084 }
085
086 return entity;
087 });
088
089 return newList;
090 }
091
092 /// <summary>
093 /// 简单地从table中加载指定的实体列表。
094 /// </summary>
095 /// <param name="table"></param>
096 /// <param name="repository"></param>
097 /// <returns></returns>
098 private EntityList ReadFromTable(IGTable table, EntityRepository repository)
099 {
100 var newList = repository.NewList();
101
102 newList.ReadFromTable(table, (row, subTable) => repository.Convert(row));
103
104 return newList;
105 }
106 }

美化的API


基于以上的基础,我们需要一个流畅的API来定义加载选项。这一点对于一个框架设计人员来说,往往很重要,只有流畅、易用的API才能对得起你的客户:框架使用者。以下我只把给出几个为达到流畅API而特别设计的类。其中,用到了《小技巧 – 简化你的泛型API》中提到的设计原则。

001 /// <summary>
002 /// 存储了加载选项项
003 /// </summary>
004 public abstract class LoadOptionSelector
005 {
006 internal LoadOptionSelector(AggregateDescriptor descriptor)
007 {
008 _descriptor = descriptor;
009 }
010
011 private AggregateDescriptor _descriptor;
012
013 internal AggregateDescriptor InnerDescriptor
014 {
015 get
016 {
017 return _descriptor;
018 }
019 }
020 }
021
022 /// <summary>
023 /// 属性选择器
024 /// </summary>
025 /// <typeparam name="TEntity"></typeparam>
026 public class PropertySelector<TEntity> : LoadOptionSelector
027 where TEntity : Entity
028 {
029 internal PropertySelector(AggregateDescriptor descriptor) : base(descriptor) { }
030
031 /// <summary>
032 /// 需要同时加载外键
033 /// </summary>
034 /// <typeparam name="TFKEntity"></typeparam>
035 /// <param name="fkEntityExp">
036 /// 需要加载的外键实体属性表达式
037 /// </param>
038 /// <returns></returns>
039 public PropertySelector<TFKEntity> LoadFK<TFKEntity>(Expression<Func<TEntity, TFKEntity>> fkEntityExp)
040 where TFKEntity : Entity
041 {
042 var entityPropertyName = GetPropertyName(fkEntityExp);
043 var propertyName = entityPropertyName + "Id";
044
045 IEntityInfo entityInfo = ApplicationModel.GetBusinessObjectInfo(typeof(TEntity));
046 var propertyInfo = entityInfo.BOPropertyInfos.FirstOrDefault(p => p.Name == propertyName);
047
048 //构造一个临时代理方法,实现:TEntity.EntityProperty = TFKEntity
049 var pE = System.Linq.Expressions.Expression.Parameter(typeof(TEntity), "e");
050 var pEFK = System.Linq.Expressions.Expression.Parameter(typeof(TFKEntity), "efk");
051 var propertyExp = System.Linq.Expressions.Expression.Property(pE, entityPropertyName);
052 var body = System.Linq.Expressions.Expression.Assign(propertyExp, pEFK);
053 var result = System.Linq.Expressions.Expression.Lambda<Action<TEntity, TFKEntity>>(body, pE, pEFK);
054 var fkSetter = result.Compile();
055
056 var option = new LoadOptionItem(propertyInfo, (e, eFK) => fkSetter(e as TEntity, eFK as TFKEntity));
057
058 //避免循环
059 if (this.InnerDescriptor.Items.Any(i => i.OwnerType == option.PropertyEntityType))
060 {
061 throw new InvalidOperationException("有循环的实体设置。");
062 }
063
064 this.InnerDescriptor.AddItem(option);
065
066 return new PropertySelector<TFKEntity>(this.InnerDescriptor);
067 }
068
069 /// <summary>
070 /// 需要同时加载孩子
071 /// </summary>
072 /// <typeparam name="TChildren"></typeparam>
073 /// <param name="propExp">
074 /// 需要加载的孩子属性表达式
075 /// </param>
076 /// <returns></returns>
077 public ChildrenSelector LoadChildren<TChildren>(Expression<Func<TEntity, TChildren>> propExp)
078 where TChildren : EntityList
079 {
080 var propertyName = GetPropertyName(propExp);
081 IEntityInfo entityInfo = ApplicationModel.GetBusinessObjectInfo(typeof(TEntity));
082 var propertyInfo = entityInfo.BOsPropertyInfos.FirstOrDefault(p => p.Name == propertyName);
083
084 this.InnerDescriptor.AddItem(new LoadOptionItem(propertyInfo));
085
086 return new ChildrenSelector(this.InnerDescriptor);
087 }
088
089 private static string GetPropertyName<TProperty>(Expression<Func<TEntity, TProperty>> propExp)
090 {
091 var member = propExp.Body as MemberExpression;
092 var property = member.Member as PropertyInfo;
093 if (property == null) throw new ArgumentNullException("property");
094 var propertyName = property.Name;
095
096 return propertyName;
097 }
098 }
099
100 /// <summary>
101 /// 孩子选择器
102 /// </summary>
103 /// <typeparam name="TEntity"></typeparam>
104 public class ChildrenSelector : LoadOptionSelector
105 {
106 internal ChildrenSelector(AggregateDescriptor descriptor) : base(descriptor) { }
107
108 public OrderByLoadOption<TEntity> Order<TEntity>()
109 where TEntity : Entity
110 {
111 return new OrderByLoadOption<TEntity>(this.InnerDescriptor);
112 }
113
114 /// <summary>
115 /// 把孩子集合转换为实体对象,需要继续加载它的子对象
116 /// </summary>
117 /// <typeparam name="TEntity"></typeparam>
118 /// <returns></returns>
119 public PropertySelector<TEntity> Continue<TEntity>()
120 where TEntity : Entity
121 {
122 return new PropertySelector<TEntity>(this.InnerDescriptor);
123 }
124 }
125
126 public class OrderByLoadOption<TEntity> : LoadOptionSelector
127 where TEntity : Entity
128 {
129 internal OrderByLoadOption(AggregateDescriptor descriptor) : base(descriptor) { }
130
131 public PropertySelector<TEntity> By<TKey>(Func<TEntity, TKey> keySelector)
132 {
133 this.InnerDescriptor.Items.Last.Value
134 .OrderBy = e => keySelector(e as TEntity);
135
136 return new PropertySelector<TEntity>(this.InnerDescriptor);
137 }
138 }

小结


本次重构由于只处理“链式的加载选项”,所以实现并不复杂。同时,由于把Repository都临时存放在了LoadOptionItem中,使得Repository的获取不再浪费,印证了:“一个重构后良好结构的程序,性能很有可能会有所提升。”

[转载].NET控件Designer架构设计

mikel阅读(1014)

[转载].NET控件Designer架构设计 – 葡萄城控件技术团队博客 – 博客园.

总体结构

Designer设计

Designer总体上由三大部分组成:View,ViewModel和Model,这个结构借鉴了流行的MVVM模式。这三部分的职责分工是:

View

负责把ViewModel以图形的方式展现出来,它主要在处理画法。View适合用xaml来表达,对于某些复杂的layout,仍然会需要写一些 code,但这些code不涉及业务逻辑。和MVVM的区别是,我们只是在简单输入的情况下,采用了Behavior模式,对于复杂的输入,由于判断用户 的意图需要参考许多其它信息,可能要用到很多Service,或者查阅很多的状态信息,这些代码写在View端不合适,我们就直接把事件发给了 ViewModel,由ViewModel去处理。View和平台相关,不同平台(WPF、SL,WP7)的xaml可能不同,代码也不同。

ViewModel

主要负责逻辑的处理,接收Event和Command,判断用户意图,改变数据,并反馈给View。ViewModel既有数据,又能响应事件,而 且是一棵树,所以它本质上就是一个view,只不过是一个抽象的View,它把琐碎的画法丢给了真正的View,只关心那些和逻辑有关的数据。 ViewModel和View有一定的对应关系,但它的结点比View要少得多,因此比直接在View上进行逻辑处理要简单得多。由于ViewModel 的数据和操作都是针对抽象的概念进行的,因此它和平台无关。为了方便对ViewModel中的逻辑操作进行管理,我们引入了Service和 Feature的概念,Service是向其它模块提供支持的内部模块,是系统的基础,所有的Service构成了系统的骨架。Feature是实现系统 外部功能的模块,Feature之间没有依赖关系,它们只依赖于Service。系统中的Service不多,而且只关注最重要的逻辑,代码量不大,所以 Service都是经过精心设计和良好的测试,具有很强的稳定性。feature是系统的皮肉,它直接暴露给用户,关注很多细节,代码量大,容易变化,由 于feature和feature之间没有依赖性,所以这种变化不会对其它模块造成影响,利于渐进式的开发。

Model

是数据,按照业界大师的说法,Model是纯粹的数据。但我很怀疑这个说法,如果Model是纯粹的数据,那它就没有存在的必要,因为 ViewModel上也有数据,何必要把数据存两份呢,同步起来还挺麻烦。我的理解是,Model上是有逻辑的,只是这些逻辑是属于另一个领域的范围了。 比如Designer的Model,就是Runtime的control,这些Control是有逻辑的,但它们的逻辑已经和Designtime没有任 何关系。ViewModel和Model的关系是,ViewModel操纵Model,但同时要监测Model的变化,和Model同步。如果我们清楚除 了ViewModel外,不会有其它的模块去修改Model(这种情况对于一些简单的Designer是正常的),那么ViewModel和Model的 关系可以更简单一些,只有ViewModel改变Model,上图中ViewModel和Model之间的箭头就只需要保留左边那一个(图中的箭头表示数 据传递)。

从上面的介绍,我们可以看出,View和Model在DesignTime下都是比较简单的,复杂度主要在ViewModel,我们需要进一步对它阐述。

ViewModel层的结构

Designer设计1
我 们前面提到,Designer主体结构分成三大部分:View,ViewModel,Model,这里的概念是一个宏观概念,代表它所在的那一层里的所有 结构。我们现在讨论ViewModel这一层,它里面除了一个ViewModel树,还有一些Service和许多的Feature。我们知道,图形软件 的功能,不外乎就是处理用户的鼠标键盘输入,然后改变数据,最后以可视化的方式反馈给用户,因此,我们只要分析清楚我们的软件是如何来应对这样一个输入输 出过程就可以了。

我们看上图,红色虚线内的结构都是在处理输入,红色虚线外的部分在处理输出(展现),可见对于Designer,输入非常复杂,输出比较简单。我们 先分析简单的输出:ViewModel时刻监视着Model的变化,一旦发现Model发生了变化,就改变自己的Property以同步,注意这里的 Model不一定实现了INotifyPropertyChanged接口,因此这种同步可能不能借用绑定。但ViewModel一定是 DependencyObject,或者实现了INotifyPropertyChanged接口,所以当ViewModel的属性变化后,View通过 绑定会让展现和数据保持一致,输出过程就完成了。

对于输入,我们需要针对不同的情况进行考虑,基本上,我们可以把输入分成两大类:简单输入和复杂输入。

什么是简单输入?

就是整个输入处理过程很简单,牵涉的模块很少,Command有明确的接受对象。常见的Adorner上的行为,大部分都是如此。举一个具体的例 子,有一个Button,当它被选中的时候,会出现一个Adorner,上面有一个Slider,调整这个Slider,Button的透明度会随着变 化。要处理这个Slider对Model的改变,最简单的做法就是把Slider双向绑定到对应的Adorner ViewModel的某个属性,即使不能用双向绑定,也可以通过Behavior模式调用对应ViewModel的Command。整个过程只涉及到一个 Adorner View,一个Adorner ViewModel和一个Button Control,和系统的其它部分没有什么关系,这类输入行为用双向绑定或者Behavior模式处理最合适。

什么是复杂输入呢?

就是整个输入处理过程涉及到的模块比较多,受很多系统状态的影响,输入没有明确的接收对象,充满了变化。这类行为在Designer中也很多。举一 个Multirow Template Designer的例子,一个CellView上收到一个MouseLeftButtonDown事件,View应该怎么处理呢?它会调用 ViewModel的什么Command呢?CellView需要先判断用户的意图,但这个判断比较有难度。用户有可能是想选中这个Cell,如果是这样 需要执行Selection Command,但是如果这个时候Designer处于Tab Order模式,那就不允许选择,可能是用户想改变Tab order的值。也有可能是用户刚才选择了一个ToolboxItem,现在是想创建一个Cell,还有可能是用户想移动Cell,要进行这些判断,必须 要借助其它Service和查询系统中某些状态,如果判断出来是选择,还得检查这个时候的键盘状态,检查目前是否支持扩展选择,在扩展选择模式下,按 Control键和Shift键的行为不一样。我们还得检查当前Cell是否已经被选中,如果已经被选中,就需要反选,这需要我们查询Selection Service。如果我们发现Cell是可以移动的,那么MouseLeftButtonDown的处理又得注意了,如果Cell没有被选择,要先选中 Cell,如果Cell已经被选中了,不能立即反选,要看用户是否后续有移动的操作,反选必须放到MouseLeftButtonUp中进行。还要考虑 到,今后可能需要增加新的Feature,比如增加一个移动画布的功能,用户先在Toolbar上单击了一个手型Icon的Command,然后再在 CellView上单击了一下,这个时候以前的判断都无效,因为用户现在是要移动整个画布,那么我们很可能得去修改以前的CellView的Code。总 之,View在处理某些事件的时候,需要知道的东西太多,只靠ViewModel提供的Property远远不够,ViewModel层必须把整个结构 (所有的Service和各种状态)完全暴露给View层,这样显然不符合我们模块划分的思路。因此,对于这类复杂输入,我们让View什么都不处理,而 是把事件转发给ViewModel层去处理。

除了某些事件处理很复杂,某些Command的处理也比较麻烦,比如菜单上的cut,copy,paste,delete等,这些Command没 有明确的接收对象,最终由谁来处理需要根据系统当时的各种状态决定。为了解决这类Command,我们必须设计一个Command的路由机制,让那些关心 这个Command的feature能够按照一定的优先级来处理这个Command.

为了处理上述的复杂输入,我们学习wpf designer,设计了一个比较复杂的机制。我们设计了一个叫Tool的类,它有一个Task集合,按照一定的优先级把Command交给每个Task 处理。Task从哪儿来呢?Task属于Feature,当一个Feature认为它需要监听某些Command时,它会把自己的Task添加到Tool 的Tasks中。事实上Task并没有直接处理Command,Task内部有一个CommandBinding集合,它负责处理Command。 Task的Commandbinding在执行代码时,修改ViewModel的属性,或者执行一个ViewModel的Command。

对于View层转发给ViewModel层的Event,在处理中会被先翻译成Command,然后按照前面的Command流程处理。在这个过程中,需要经历下面两个步骤:

第一步

View接到鼠标键盘事件后,会调用InputService的一个函数PerformInput,把事件转发给InputService。 InputService会对这个事件进行预处理,然后再转发出去。预处理解决两个问题:1.把针对View的事件,转换成针对ViewModel的事 件。因为ViewModel就是一个抽象的View,如果把事件转换成了针对ViewModel的事件(就是把事件的参数Sender转换成对应的 ViewModel,EventArgs转变成适合于ViewModel的EventArgs),我们就可以按照以前熟悉的windows事件处理思路来 处理ViewModel的事件,把View彻底屏蔽掉。2.添加或改变一些事件,以方便后续的处理。Designer有一些频率特别高的操作,比如 Drag,系统的默认事件比较弱,或者没有对应的事件,如果我们在这儿进行一些强化,后面的处理就会减少很多麻烦。

第二步

InputService对事件进行完预处理后,会把事件交给Tool。Tool不但可以对Command派发,还能对Event进行派发,因为 Task中除了有CommandBinding,还有InputBinding,InputBinding用于处理事件。事件被处理完后,会生成一个 Command,这个过程就是把事件翻译成Command的过程。翻译成的Command,会发给Tool处理,绕这个圈是为了和前面的Command处 理流程保持一致。

在和大家的讨论中,觉得输入处理的流程太复杂,尤其是我开始的时候,为了减少ViewModel层的信息入口,不建议View去直接改变 ViewModel,所有事件都转发给ViewModel层来处理。大家发现,如果那样做,即使做一个很简单的输入,都要绕很大一个圈子,非常麻烦。因 此,对于简单的输入处理,我们认为应该用双向绑定或者Behavior模式,直接修改ViewModel,只对于那种比较复杂的输入,才把事件转发给 ViewModel。这样一来,这个图变得似乎更复杂了,但我经过仔细考虑,觉得不能删减,因为Designer有些输入处理的流程确实非常复杂,过于简单的结构会导致后面写feature的时候需要考虑更多的问题。

另外说一下Tool,大家不大适应这个结构。因为按照我们以前的思路,即使事件交给ViewModel层处理,经过预处理 后,InputService也应该直接把事件派发给对应的ViewModel,即使要路由,也可以学Wpf的路由机制,那样大家都比较熟悉。但我认为, 那样的设计会让大量的逻辑写到ViewModel中,和ViewModel绑得比较死,这样会有两个大的缺点:

复杂输入处理

逻辑往往跨越多个ViewModel,本来是一个完整的逻辑,不得不分片写在不同的ViewModel中,依靠全局变量或者Service来协调。 比如我们在Winform Designer中,就设计了一个DragService,用得非常频繁,原因就是在Drag中,不同的View需要协作来完成一些任务,它们只能通过 DragService来协调。但现在这种机制下,就不需要DragService了。

对原有的行为进行修改很困难

一个典型场景就是,在某种状态下,需要禁止掉某些原有的行为。在Winform Designer下,我们只能有两种处理方式:一,修改原来的Code,增加判断条件,这种方式很容易搞出来新的Bug。二,在原来的View上盖上一个 透明的View,把事件劫持掉,这种方式属于比较变态的方式,系统中如果用多了,会让后面的人很难理解原有的设计。微软的Winform Designer在处于这种情况时有一个经典的变态处理,它需要放一个Runtime的Control在Designer上,但不想让它的行为在 Designer中起作用,或者在某些情况下有选择的让它起作用,它用了hook技术,劫持windows消息,如果有需要,可以选择性的放过去一些消 息。Visual Studio中这类东西用得比较多,导致即使你按正常的方式放一个Control在Visual sdudio中,它有时工作也不正常,因为它的某些消息被hook劫持掉了。wpf中提供了Preview message,在某些情况下能够简化这类问题的处理,但我相信它的灵活性还是远远不如Tool这种把消息集中起来处理的方式,因为这种机制把逻辑彻底从 ViewModel中剥离出来了,谈不上需要改变哪一个ViewModel的行为,因为ViewModel没有控制行为的代码,所有行为都属于外面的 Feature,只要Feature发生变化,对应的ViewModel的“行为”自然就发生变化。

当然,Tool这种把所有消息集中处理的方式也有缺点,就是模块间的干扰非常严重,就相当于编程语言中的全局变量,方便了使用,但带来了干扰。因此 我们推荐复杂的输入用这种方式,简单的输入用Behavior模式,直接修改ViewModel,或者通过DelegateCommand,把View的 事件直接转发给ViewModel处理,绕过这个机制。但是,我们一定要意识到,绕过这个机制会带来的问题,就是后面要改变原有的行为是不行的,因为消息 的传输过程中没有留下改变的控制点,只能去修改原有的View和ViewModel的代码。在designer中,这类简单输入方式主要应该用于 Adorner,因为Adorner一般都是临时使用一下,输入简单,即使后面发现需要改变它的行为,不得已可以换一个AdornerModel和 AdornerView,也不会对系统造成多大影响。但如果你要把Multirow Template designer中SectionViewModel和SectionView,或者CellViewModel和CellView换了,那影响就大了去 了。所以我们今后在选择哪种输入处理方式时,一定要充分考虑到后面变化的需要。

View和ViewModel的对应关系

Designer设计2
讨 论中大家觉得View和ViewModel的对应关系比较复杂,所以这儿单独花一节来谈谈它们的关系。举一个大家熟悉的MultiRow的例子,现在假设 有一个SectionViewModel,它的Chilren中有两个Cell,分别是CellViewModel1和CellViewModel2,现 在我们看这个ViewModel Tree如何展现,事件如何传递,HitTest是如何实现的。

先看一下我们会怎样来设计View,为了便于用Xaml表达,我们一般会用UserControl来表达View,虽然CustomControl 也能用Xaml,但它的xaml一般要写到Resource中,所以我们一般不用。我们现在有两个类:SectionViewModel和 CellViewModel,因此,对应的我们会设计两个UserControl,分别叫做SectionView和CellView。这儿我要说明的 是,由于CellView很简单,做产品的时候也许不会单独为它用一个UserControl,而是在Section的Xaml里直接表达了,甚至 MultiRow的整个Template都用一个UserControl描述。在这里为了方便阐述概念,我们把两个View看成是两个独立的 UserControl。

怎样来设计SectionView呢?我们会在UserControl中放一个ItemsControl,把它的ItemsSource邦定到 datacontext的Chilren属性上,然后把ItemsPanel设置成Canvas,在ItemTemplate中指定用CellView来 展现CellViewModel,当然,我们也可以用隐式DataTemplate来表达。

CellView呢?我们就在UserControl中放一个Border,把Border的Background绑定到DataContext的Background就可以了。

当外部某个对象把SectionView加载到VisualTree上时,它会负责把SectionView的DataContext指向 SectionViewModel(这个对象很可能也是一个DataTemplate),通过绑定,所有的CellViewModel都会有对应的 CellView,最后的VisualTree会如图中所示。

我们看到,VisualTree的Visual结点明显多于ViewModel的结点,那么它们的对应关系是如何的呢?有两条原则:
1.一个ViewModel有且只有一个Visual和它对应,我们可以把这个Visual叫做这个ViewModel的View。一个Visual对应一个或零个ViewModel。
2.如果ViewModel A是ViewModel B的祖先,那么对应的Visual A也应该是Visual B的祖先,如果ViewModel A不是ViewModel B的祖先,那么对应的Visual A也不应该是Visual B的祖先。

按照我们的设计,ViewModel和Visual对应关系如上图,红色结点的Visual就是ViewModel对应的View。那么这个对应关系是怎么记录的,因为今后的很多逻辑会依赖这个数据。
首 先,我们会在设计的时候认定ViewModel和Visual的对应关系,如上图,我们认为SectionViewModel对应 SectionView(UsrControl),CellViewModel对应CellView(UserControl),所以我们会在这两个 UserControl的Xaml中设置一个附加属性ViewProperties.ViewModel,把它绑定到DataContext上,这样就让 View指向了ViewModel,在附加属性ViewProperties.ViewModel的PropertyChanged事件里,我们会创建一 个IViewModel对象赋给对应的ViewModel的View属性,IViewModel会抓着真正的Visual。这样ViewModel和 View的双向对应关系就建立起来了。

如何解决HitTest?

View层会实现一个IViewService,里面有一个函数:IEnumerable<IView> FindViews(Point p),其它对象可以调用这个函数来拿到HitTest的IView,再通过IView拿到ViewModel(我想这一步可以简化到直接返回 ViewModel,目前是返回的IView)。View层是如何查找View的呢?它会调用VisualTreeHelper的HitTest,找到 Hit的Visual,然后遍历父Visual,找到某个有对应ViewModel的Visual,那么这个Visual就是Hit的View了。

解决了HitTest,InputService对事件的预处理就简单了,它拿到一个Mouse事件参数后,会得到Mouse的坐标,然后调用 IViewService的FindView函数,就可以知道这个Mouse事件是针对哪一个ViewModel,然后把Sender和 EventArgs都转换成适合于ViewModel的,再转发出去就可以了。

与PropertyGrid交互

会有一个专门的Service来负责与PropertyGrid交互,展现在PropertyGrid上的对象是ViewModel创建的一个对 象,因此受ViewModel控制,ViewModel可以决定是把自己交给PropertyGrid,或者设计另一个类型,融合Model的 Property和Design Time的Property。

序列化

序列化由专门的Service来完成,Service中登记有不同类型的ViewModel的Serializer,默认的Serializer会 调用Runtime的序列化方法直接把RuntimeControl序列化成文本,这个序列化设计学习Winform designer的序列化架构。

Undo/Redo

从我们上面的设计看,所有的输入都要经过ViewModel,所以在ViewModel上做Undo/Redo。系统中有一个 UndoService,当一个ViewModel的Property被改变时,会通知UndoService,UndoService会把改变前的值记 录下来。值得注意的是,不是所有的ViewModel的属性都需要Undo,这点具体设计时根据需要判断,一般来说,Runtime的Property都 需要,非Runtime的Property可能有部分需要。我考虑过根据Undo和序列化的要求,在ViewModel和runtime control之间再隔离出来一个层次,比如叫ModelItem,这样结构上更清楚一些。但多一个层次开发的时候会多不少工作,觉得不划算,目前暂定由 ViewModel兼任这个职责。

架构如何应对未来的变化

目前的架构是针对复杂Designer设计的架构,如果未来的Designer比较简单,这个架构是不是有点高射炮打蚊子呢?我的想法万一未来的Designer比较简单,这个架构可以从下面三个地方去简化:
1. 砍掉输入的无关事件和无关Feature.目前的架构添加了一些事件,如Drag,实现了一些和这些事件有关的核心Feature,如果未来不需要,可以 砍掉。因为按现在的架构,Feature是独立的,彼此互不影响,把这类Feature删掉即可。事件也一样,删减事件不影响整体流程。
2. 如果仍然觉得复杂,可以把Tool,Task等概念删掉,增加Tool,task的概念,是为了让输入集中处理,拥有更强的灵活性。如果把这些概念删除 掉,InputService直接把事件派发给对应的ViewModel就可以了,这就相当于winform的事件机制,由ViewModel直接处理事 件。这一步把输入大大的简化了。
3.如果上面简化还不够,把InputService也干掉,由View利用Behavior处理输入,然后调用ViewModel的Command,这就变成了经典的MVVM模式,到这一步应该化到最简了。

转载深入理解ASP.NET MVC(9)

mikel阅读(1072)

转载深入理解ASP.NET MVC(9) – P_Chou Go deep and Keep learning – 博客园.

系列目录

异步处理请求的意义

大伙儿都知道,ASP.NET通过线程池处理请求,对于每个请求从线程池中请求一个可用的线程来处理请求,当请求处理完毕之后,线程资源将被归还到 线程池。然而,线程池中的线程是互斥资源,当网站在同一时刻的请求量达到一定数量的话,必然会导致这种资源不够耗尽,新来的请求只能等待有新的线程归还才 能被处理。当然这不是最糟糕的,通常每个请求只需要很短的时间就可以了,新的请求不会等待太长的时间,但是,如果处理请求需要花费较长的时间呢?比如一次 耗时的数据库查询、一次外部web service请求这类的IO操作。注意这里特指的IO操作,指的是不会占用ASP.NET线程池线程的,甚至不占用本机CPU资源的操作。正因为如此, 异步处理请求在这种情况下尤其适用。当异步处理请求时,占用的线程会在耗时的IO操作开始前,将线程归还给线程池,直到IO操作完成后,再从线程池中请求 一个线程,并恢复当时的HttpContext,处理IO操作的结果。这样就不会占用宝贵的线程资源了。

MVC中的异步Controller机制

MVC支持异步地处理请求。可以通过下面的三种方式:

  1. 实现一个自定义的RouteHandler,并为GetHttpHandler方法返回一个实现IHttpAsynHandler的对象。
  2. 创建一个自定义的基类Controller,并实现IAsyncController,IAsyncController是IController的异步版本。
  3. MVC内置了一个AsyncController,它实现了上述的IAsyncController,通过简单的继承AsyncController,即可实现异步。

下面仅对第三种方法作简单介绍。假设某个Action需要调用一个web service,并处理结果后返回:

01 public ContentResult GetPhotoByTag(string tag)
02 {
03 ...
04 using (var response = WebRequest.Create(url).GetResponse())
05 {
06 // Parse the response as XML
07 var xmlDoc = XDocument.Load(XmlReader.Create(response.GetResponseStream()));
08 ...
09 }
10 ...
11 }

显然,如果这个web request消耗2s,那么这个请求将hold这个线程至少2s。这种同步的处理方式显然不合理,想要异步处理,只需按如下步骤进行:

1、替换基类Controller为AsyncController

2、创建两个配对的Action:ActionNameAsync和ActionNameCompletedActionNameAsync方法必须返回void,在内部启动一个耗时的IO操作前,需要使用AsyncManager.OutstandingOperations.Increment()向MVC框架“注册启动”,在IO方法返回后,可以在AsyncManager.Parameters字典中保存希望传给ActionNameCompleted方法的参数。最后调用AsyncManager.OutstandingOperations.Decrement()通知MVC框架操作完成,此时,MVC框架会自动调用ActionNameCompleted。ActionNameCompleted需要向通常的Action一样,返回一个ActionResult。因此上面的代码需要改写成如下这样:

01 public void GetPhotoByTagAsync(string tag)
02 {
03 //向MVC中注册启动
04 AsyncManager.OutstandingOperations.Increment();
05 ...
06 WebRequest request = WebRequest.Create(url);
07 //启动一个异步的web request
08 request.BeginGetResponse(asyncResult =>
09 {
10 using (WebResponse response = request.EndGetResponse(asyncResult))
11 {
12 var xml = XDocument.Load(XmlReader.Create(response.GetResponseStream()));
13 ...
14 //将结果photoUrls,保存在AsyncManager.Parameters中
15 AsyncManager.Parameters["photoUrls"] = photoUrls;
16 //通知MVC框架操作完成 ,准备调用Completed
17 AsyncManager.OutstandingOperations.Decrement();
18 }
19 }, null);
20 }
21 //像通常的Action一样,这里的参数photoUrls将在AsyncManager.Parameters中匹配
22 public ContentResult GetPhotoByTagCompleted(IEnumerable<string> photoUrls)
23 {
24 return Content(string.Format("<img src='{0}'/>", photoUrls.First()));
25 }

当然,可以设置异步操作的超时时间:

1 [AsyncTimeout(10000)] // 10000 milliseconds equals 10 seconds
2 public void GetPhotoByTagAsync(string tag) { ... }

上面的代码如果超时了,将抛出TimeoutException异常,我们可以用希望的方式处理它。

当使用类似BeginGetResponse这类的异步方法,并提供回调函数参数时,你无法控制回调函数调用在哪个线程上。大多数情况下,甚至不在ASP.NET的工作线程上。所以回调函数无法关联原始的HttpContext对象。

幸好,AsyncManager提供了一个Sync()方法,它会将一个委托在ASP.NET的工作线程上启动,并关联原始的HttpContext对象。而且它保证线程安全:

01 BeginAsyncOperation(asyncResult => {
02 var result = EndAsyncOperation(asyncResult);
03 // Can't always access System.Web.HttpContext.Current from here...
04 Action doSomethingWithHttpContext = () => {
05 // ... but can always access it from this delegate
06 };
07 if (asyncResult.CompletedSynchronously) // Already on an ASP.NET thread
08 doSomethingWithHttpContext();
09 else // Must switch to an ASP.NET thread
10 AsyncManager.Sync(doSomethingWithHttpContext);
11 AsyncManager.OutstandingOperations.Decrement();
12 }, null);

以上内容只是书中的内容摘录。对异步处理请求,我也没有深入研究,等以后用到了再回过来研究吧。

ps:最近比较忙,没有什么时间关注在MVC上,见谅。

劳动果实,转载请注明出处:http://www.cnblogs.com/P_Chou/archive/2011/01/07/details-asp-net-mvc-09.html

[转载]android开发我的新浪微博客户端-用户授权页面UI篇(3.1)

mikel阅读(881)

[转载]android开发我的新浪微博客户端-用户授权页面UI篇(3.1) – 遇见未知的自己 – 博客园.

上一篇讲了讲OAuth授权认证的事情,大概的介绍了OAuth的原理,并且完成了一个OAuth.java的类库,提供了几个OAuth认证必要的方 法,本篇开始具体讲本项目的用户授权功能,用户授权页面是当用户第一次使用本软件的时候自动从载入页面跳转过来的显示的页面,涉及OAuth认证相关都是 在上一篇的OAuth.java的类基础上开发。用户授权页面分为UI篇和功能篇两篇,本篇先来讲讲UI的实现,这次就不贴PS的效果图了直接贴实现后的 功能截图如下:

看上面的图,其实这个页面的UI实现不复杂,首先是背景部分的实现这个参考 android开发我的新浪微博客户端-载入页面UI篇(1.1),重点来讲讲这个半透明的弹出对话框窗口是如何实现的,首先新建名为AuthorizeActivity.java 的Activity,并且在AndroidManifest.xml文件中添加这个Activity,这样这个Activity才能被使用,接下来为这个 Activity新建名为authorize.xml的Layout,这个Layout很简单只负责logo小图标显示,背景部分和透明窗口都是有代码来 实现,所以非常简单参考 android开发我的新浪微博客户端-载入页面UI篇(1.1)

完成Layout建立后在AuthorizeActivity的onCreate方法添加如下代码,设置authorize.xml为AuthorizeActivity的页面Layout:

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.authorize);
…….
}

接下来是本文的重点部分,半透明弹窗用Dialog控件进行实现,首先为这个半透明弹窗新建一个名为dialog.xml的Layout,这个Layout主要是对4个元素进行布局,如图所示分别为i小图标信息提示中间文字开始按钮,首先用LinearLayout对i小图标信息提示进行水平布局,中间文字以一个TextView跟在下面,对于开始按钮是用RelativeLayout进行底部对齐显示。具体代码如下:

代码

<?xml version=”1.0″ encoding=”utf-8″?>
<LinearLayout
xmlns:Android=”http://schemas.Android.com/apk/res/android”
android:layout_width
=”wrap_content”
android:layout_height
=”wrap_content”
android:orientation
=”vertical”
android:padding
=”10dip”>
<LinearLayout
android:layout_width=”wrap_content”
android:layout_height
=”wrap_content”
android:orientation
=”horizontal”>
<ImageView
android:layout_width=”wrap_content”
android:layout_height
=”wrap_content”
android:src
=”@drawable/info_icon”>
</ImageView>
<TextView
android:layout_width=”wrap_content”
android:layout_height
=”wrap_content”
android:text
=”信息提示”
android:textSize
=”13px”
android:textColor
=”#219ac6″
android:layout_marginLeft
=”5dip”>
</TextView>
</LinearLayout>
<TextView
android:id=”@+id/text_info”
android:layout_marginTop
=”6px”
android:layout_width
=”200px”
android:layout_height
=”wrap_content”
android:textColor
=”#686767″
android:textSize
=”14px”
android:text
=”第一次使用需要输入您的新浪微博账号和密码进行登录授权”>
</TextView>
<RelativeLayout
android:layout_width=”fill_parent”
android:layout_height
=”40px”>
<LinearLayout
android:layout_width=”wrap_content”
android:layout_height
=”wrap_content”
android:orientation
=”horizontal”
android:layout_centerHorizontal
=”true”
android:layout_alignParentBottom
=”true”>
<ImageButton
android:id=”@+id/btn_start”
android:layout_width
=”80px”
android:layout_height
=”31px”
android:src
=”@drawable/btn_start_selector”>
</ImageButton>
<ImageButton
android:id=”@+id/btn_cancel”
android:layout_width
=”80px”
android:layout_height
=”31px”
android:layout_marginLeft
=”8px”
android:src
=”@drawable/btn_cancel_selector”>
</ImageButton>
</LinearLayout>
</RelativeLayout>

</LinearLayout>

完成了半透明弹窗的Layout定义接下来我们要做的就是为它写一个自定义样式来实现我们想要的显示效果,首先我们需准备一个圆角的半透明png图片名 为dia_bg.png并且添加到drawable中,接下来再res/values文件夹新建名为 dialogStyle.xml的 resources样式文件,具体代码如下:

代码

<?xml version=”1.0″ encoding=”utf-8″?>
<resources>
<style name=”dialog” parent=”@android:style/Theme.Dialog”>
<item name=”android:windowFrame”>@null</item>
<item name=”android:windowIsFloating”>true</item>
<item name=”android:windowIsTranslucent”>false</item>
<item name=”android:windowNoTitle”>true</item>
<item name=”android:windowBackground”>@drawable/dia_bg</item>
<item name=”android:backgroundDimEnabled”>false</item>
</style>
</resources>

这个样式文件的说明如下

parent=”@android:style/Theme.Dialog” :在系统Dialog样式基础上,相当于继承系统样式

<item name=”android:windowFrame”>@null</item> :Dialog的windowFrame框为无

<item name=”android:windowIsFloating”>true</item>:是否浮现在activity之上

<item name=”android:windowIsTranslucent”>false</item>:是否半透明
<item name=”android:windowNoTitle”>true</item>:是否显示title
<item name=”android:windowBackground”>@drawable/dia_bg</item>:设置dialog的背景

<item name=”android:backgroundDimEnabled”>false</item>: 背景是否模糊显示

接下来写java代码把这个半透明弹窗显示出来,在AuthorizeActivity的onCreate方法添加如下代码:

……
View diaView
=View.inflate(this, R.layout.dialog, null);
dialog
=new Dialog(AuthorizeActivity.this,R.style.dialog);
dialog.setContentView(diaView);
dialog.show();
……

最后运行查看效果,到这里我们的任务已经完成了。请关注下一篇功能篇。