2023年2月

在“
随风飘散
” 博客里面,介绍了一个不错的DataGridView数据窗口控件《
DataGridView数据窗口控件开发方法及其源码提供下载
》,这种控件在有些场合下,还是非常直观的。因为,在一般要求客户录入数据的地方,一般有两种途径,其一是通过弹出一个新的窗口,在里面列出各种需要输入的要素,然后保存的,如下图所示;

其二就是直接在DataGridView中直接输入。这两种方式各有优劣,本文介绍采用该控件实现第二种模式的数据数据。如下图所示

这种方式,直接通过在DataGridView中下拉列表或者文本框中输入内容,每列的数据可以联动或者做限制,实现用户数据的约束及规范化。

控件只要接受了DataTable的DataSource之后,会根据列的HeadText内容,显示表格的标题及内容,应用还是比较直观方便的。


private

void
BindGridViewData(DataTable dt, DataTable dtNoRelation)
{
organTable

=
dt;
noRelationTable

=
dtNoRelation;

DataGridView dataGridView1

=

new
DataGridView();

this
.groupBox1.Controls.Clear();

this
.groupBox1.Controls.Add(dataGridView1);
dataGridView1.Dock

=
DockStyle.Fill;
dataGridView1.CellValueChanged

+=
new
DataGridViewCellEventHandler(organDataGridView_CellValueChanged);
dataGridView1.UserDeletedRow

+=

new
DataGridViewRowEventHandler(organDataGridView_UserDeletedRow);

dataGridView1.AutoGenerateColumns

=

false
;
dataGridView1.Rows.Clear();
dataGridView1.Columns.Clear();

DataGridViewDataWindowColumn col1

=

new
DataGridViewDataWindowColumn();
col1.HeaderText

=

"
机构代码
"
;
col1.Name

=

"
机构代码
"
;


//
下拉列表的数据


col1.DataSource
=
GetDataTable(dtNoRelation);
dataGridView1.Columns.Add(col1);

DataGridViewTextBoxColumn col2

=

new
DataGridViewTextBoxColumn();
col2.HeaderText

=

"
机构名称
"
;
col2.Name

=

"
机构名称
"
;
col2.Width

=

300
;
col2.ReadOnly

=

true
;
dataGridView1.Columns.Add(col2);


if
(dt
!=

null
)
{

foreach
(DataRow dr
in
dt.Rows)
{

string
value
=
dr[
0
].ToString();
DataGridViewRow row

=

new
DataGridViewRow();
DataGridViewDataWindowCell cell

=

new
DataGridViewDataWindowCell();
cell.Value

=
value;
row.Cells.Add(cell);
cell.DropDownHeight

=

400
;
cell.DropDownWidth

=

300
;

DataGridViewTextBoxCell cell2

=

new
DataGridViewTextBoxCell();
cell2.Value

=
dr[
1
].ToString();
row.Cells.Add(cell2);
dataGridView1.Rows.Add(row);
}
}
}

由于列之间的数据输入等相关的影响需要处理,因此控件的处理方式是通过委托函数进行处理,如上面的部分代码中就是处理这些事件的。

dataGridView1.CellValueChanged
+=
new
DataGridViewCellEventHandler(organDataGridView_CellValueChanged);
dataGridView1.UserDeletedRow

+=

new
DataGridViewRowEventHandler(organDataGridView_UserDeletedRow);

由于本例子是通过输入内容后,及时更新数据库及控件的显示,因此需要对该事件进行处理,处理代码如下所示。


private

void
organDataGridView_CellValueChanged(
object
sender, DataGridViewCellEventArgs e)
{
DataGridView organDataGridView

=
sender
as
DataGridView;

if
(
!
organDataGridView.IsCurrentCellInEditMode)

return
;


#region
显示关联机构名称


if
(e.RowIndex
>

-
1
)
{

if
(organDataGridView.CurrentCell.Value
==
System.DBNull.Value)
{

return
;
}


if
(e.ColumnIndex
==

0
)
{
DataGridViewCell cell

=
organDataGridView.Rows[e.RowIndex].Cells[
"
机构代码
"
];

if
(cell.Value
==

null
)

return
;

string
organCode
=
cell.Value.ToString();

string
organName
=
GetOrganName(organTable, organCode);

if
(
string
.IsNullOrEmpty(organName))
{
organName

=
GetOrganName(noRelationTable, organCode);
}
organDataGridView.Rows[e.RowIndex].Cells[

"
机构名称
"
].Value
=
organName;
}
}

#endregion



if
(
this
.treeView1.SelectedNode
!=

null
)
{

string
gjOrganCode
=

this
.treeView1.SelectedNode.Tag.ToString();

if
(
!
string
.IsNullOrEmpty(gjOrganCode))
{

string
yctOrganCode
=
organDataGridView.CurrentCell.Value.ToString();

OrganCodeMapDAL organMapDal

=

new
OrganCodeMapDAL();
organMapDal.UpdateOrganMapData(gjOrganCode, yctOrganCode);
}
}
}


private

void
organDataGridView_UserDeletedRow(
object
sender, DataGridViewRowEventArgs e)
{
DataGridView organDataGridView

=
sender
as
DataGridView;

string
organCode
=
e.Row.Cells[
0
].Value.ToString();
OrganCodeMapDAL organMapDal

=

new
OrganCodeMapDAL();
organMapDal.DeleteOrganMapData(organCode);
}

另外,该控件还提供了一个用于对话框窗体中的复杂下拉列表的数据显示方式,控件支持内容的过滤检索,非常方便实用,如下所示

该控件的使用代码如下所示:


private

void
BindData()
{

//
设置DataWindow属性



this
.dataWindow1.PopupGridAutoSize
=

false
;

this
.dataWindow1.DropDownHeight
=

300
;

this
.dataWindow1.DropDownWidth
=

240
;

this
.dataWindow1.FormattingEnabled
=

true
;

this
.dataWindow1.sDisplayField
=

"
车辆编码,车牌号码
"
;

this
.dataWindow1.sDisplayMember
=

"
车辆编码
"
;

this
.dataWindow1.sValueMember
=

"
车辆编码
"
;

this
.dataWindow1.SeparatorChar
=

"
|
"
;

BusDAL busDal

=

new
BusDAL();
DataTable dt

=
busDal.GetYCTBusTable(
this
.txtOrganName.Tag.ToString());

this
.dataWindow1.DataSource
=
dt;

this
.dataWindow1.AfterSelector
+=

new
EventHandler(dataWindow1_AfterSelector);


//
必须在DataSource绑定之后设置该属性



this
.dataWindow1.RowFilterVisible
=

true
;
}


//
选择完下拉表格后执行的事件



private

void
dataWindow1_AfterSelector(
object
sender, EventArgs e)
{
}

有一次,一个用户埋怨,说我输入的电子邮件内容是正常的,为什么不允许输入,保存不成功,老是提示格式有问题。

又有一次,另外一个用户埋怨,为输入的数字式正确的,可以跳出数字输入控件后,内容有变为0了,怎么回事呢?

诸如此类的问题,你可能自己也会碰到,究其原因,就是在桌面程序中,当你在文本框或者数字输入框中输入内容的时候,输入法莫名奇妙的由半角变为全角,你没有发现,继续填写或者保存,就会出现问题。

如果我们不注意这些细节,用户输入的内容部经过验证,他们在数据查询检索的时候,就会找不到内容,或者出现莫名其妙的错误,这是我们不愿意看到的。

其实解决这个问题,做好这个细节问题的处理预防,是很简单的事情,下面我来介绍一个辅助类,调用一下他的函数就可以了,如下所示。


///

<summary>


///
输入法帮助,全角 转换为半角

///

</summary>



public

class
ImeHelper
{

#region
声明一些API函数

[DllImport(

"
imm32.dll
"
)]

public

static

extern
IntPtr ImmGetContext(IntPtr hwnd);
[DllImport(

"
imm32.dll
"
)]

public

static

extern

bool
ImmGetOpenStatus(IntPtr himc);
[DllImport(

"
imm32.dll
"
)]

public

static

extern

bool
ImmSetOpenStatus(IntPtr himc,
bool
b);
[DllImport(

"
imm32.dll
"
)]

public

static

extern

bool
ImmGetConversionStatus(IntPtr himc,
ref

int
lpdw,
ref

int
lpdw2);
[DllImport(

"
imm32.dll
"
)]

public

static

extern

int
ImmSimulateHotKey(IntPtr hwnd,
int
lngHotkey);

public

const

int
IME_CMODE_FULLSHAPE
=

0x8
;

public

const

int
IME_CHOTKEY_SHAPE_TOGGLE
=

0x11
;

#endregion



///

<summary>


///
重载SetIme,传入Form

///

</summary>


///

<param name="frm"></param>



public

static

void
SetIme(Form frm)
{
frm.Paint

+=

new
PaintEventHandler(frm_Paint);
ChangeAllControl(frm);
}


///

<summary>


///
重载SetIme,传入Control

///

</summary>


///

<param name="ctl"></param>



public

static

void
SetIme(Control ctl)
{
ChangeAllControl(ctl);
}


///

<summary>


///
重载SetIme,传入对象句柄

///

</summary>


///

<param name="Handel"></param>



public

static

void
SetIme(IntPtr Handel)
{
ChangeControlIme(Handel);
}


private

static

void
ChangeAllControl(Control ctl)
{

//
在控件的的Enter事件中触发来调整输入法状态


ctl.Enter
+=

new
EventHandler(ctl_Enter);


//
遍历子控件,使每个控件都用上Enter的委托处理



foreach
(Control ctlChild
in
ctl.Controls)
{
ChangeAllControl(ctlChild);
}
}


static

void
frm_Paint(
object
sender, PaintEventArgs e)
{

/**/


/*
有人问为什么使用Pain事件,而不用Load事件或Activated事件,是基于下列考虑:
* 、在您的Form中,有些控件可能是运行时动态添加的
* 、在您的Form中,使用到了非.NET的OCX控件
* 、Form调用子Form的时候,Activated事件根本不会触发

*/

ChangeControlIme(sender);
}


///

<summary>


///
控件的Enter处理程序

///

</summary>


///

<param name="sender"></param>


///

<param name="e"></param>



static

void
ctl_Enter(
object
sender, EventArgs e)
{
ChangeControlIme(sender);
}


private

static

void
ChangeControlIme(
object
sender)
{
Control ctl

=
(Control)sender;
ChangeControlIme(ctl.Handle);
}


///

<summary>


///
下面这个函数才是真正检查输入法的全角半角状态

///

</summary>


///

<param name="h"></param>



private

static

void
ChangeControlIme(IntPtr h)
{
IntPtr HIme

=
ImmGetContext(h);

if
(ImmGetOpenStatus(HIme))
//
如果输入法处于打开状态


{

int
iMode
=

0
;

int
iSentence
=

0
;

bool
bSuccess
=
ImmGetConversionStatus(HIme,
ref
iMode,
ref
iSentence);
//
检索输入法信息



if
(bSuccess)
{

if
((iMode
&
IME_CMODE_FULLSHAPE)
>

0
)
//
如果是全角


{
ImmSimulateHotKey(h, IME_CHOTKEY_SHAPE_TOGGLE);

//
转换成半角


}
}
}
}

}

很久没写博客了,最近没有什么惊天地、泣鬼神的大作,就从小处着眼,总结一些开发过程中的一些心得和见解吧。

众所周知,互联网改变生活,现在是验证码大行其道的年代,基本上主要涉及用户信息注册、登录、提交数据等,都来一个验证码来限制下,QQ在这方面可真是运用到了极致,因此做QQ的开发,必须了解如何处理验证码的问题。一般验证码是通过一个带参数的URL获取到一个数字字符图片,由于网络原因,一般返回比较缓慢,所以如果要提高用户体验和程序的响应速度,就必须采用改良一点的方法来进行处理。

我们先从一个正常的操作讲起,逐步改进,看看效果及使用情况,如一般的处理就是把它封装在一个独立的函数中,在触发事件的地方调用该函数,如下所示。


public

void
GetNewImage()
{
HttpHelper httpHelper

=

new
HttpHelper();

using
(Stream s
=
httpHelper.GetStream(
"
http://ptlogin2.qq.com/getimage?aid=3000801&0.43878429697395826
"
, Portal.gc.cookieQun))
{

if
(s
==

null
)
{
MessageUtil.ShowWarning(

"
获取登陆码错误,请检查您的网络!
"
);

return
;
}
pictureBox1.Image

=
Image.FromStream(s);
}
}

这种方式方式是一般的处理方式,有时候界面会因为获取网络数据而有停顿,除非你的网络非常好,所以可以把它放到一个独立的线程中进行处理,通过hreadPool.QueueUserWorkItem来进行线程处理,如下代码所示:


public

void
GetNewImage()
{
ThreadPool.QueueUserWorkItem(GetNewImageThread,

null
);
}


private

void
GetNewImageThread(
object
obj)
{

using
(Stream s
=
httpHelper.GetStream(
"
https://mail.qq.com/cgi-bin/getverifyimage?aid=23000101&f=html&ck=1&31997
"
, EmailCookie))
{

if
(s
==

null
)
{
SetTips(

"
获取登陆码错误,请检查您的网络!
"
);

return
;
}


this
.pictureBox1.Invoke(
new
MethodInvoker(
delegate
()
{

this
.pictureBox1.Image
=
Image.FromStream(s);
}));
}
}

这样已经比较好的处理界面停顿等不友好的问题了,不过有时候,因为使用了Invoke函数,可能会出现一些线程间的异常,如你关闭了窗口,而进行还在运行,就会出现一些问题,但是还是相对比较好的解决途径。

偶尔一天,看到一个比较好的后台线程封装类QueuedBackgroundWorker,使用效果还比较不错,虽然代码多了一些,不过处理起来效果还真的不错,还支持取消线程操作等功能,整个后台线程以及获取验证码的操作代码如下所示,整个代码运行效果不错,而且不会出现线程间的绑定错误。


private
QueuedBackgroundWorker worker;



public
Form1()
{
InitializeComponent();

worker

=

new
QueuedBackgroundWorker();
worker.IsBackground

=

true
;
worker.Threads

=

1
;
worker.ProcessingMode

=
ProcessingMode.FIFO;
worker.DoWork

+=

new
QueuedWorkerDoWorkEventHandler(worker_DoWork);
worker.RunWorkerCompleted

+=

new
RunQueuedWorkerCompletedEventHandler(worker_RunWorkerCompleted);
}



private

void
Form1_FormClosing(
object
sender, FormClosingEventArgs e)
{
worker.DoWork

-=
worker_DoWork;
worker.RunWorkerCompleted

-=
worker_RunWorkerCompleted;
worker.Dispose();
}


private

void
GetVerifyImage()
{
worker.RunWorkerAsync(

"
GetImage
"
);
}


void
worker_DoWork(
object
sender, QueuedWorkerDoWorkEventArgs e)
{

string
newverifyUrl
=

"
http://captcha.qq.com/getimage?aid=1007901&0.5723910723542236
"
;

string
referer
=

"
http://reg.qq.com
"
;

cookieReg

=

new
CookieContainer();
HttpHelper httpHelper

=

new
HttpHelper();

using
(Stream s
=
httpHelper.GetStream(newverifyUrl, cookieReg, referer))
{

if
(s
==

null
)
{
MessageUtil.ShowWarning(

"
获取登陆码错误,请检查您的网络!
"
);

return
;
}
e.Result

=
Image.FromStream(s);
}
}


void
worker_RunWorkerCompleted(
object
sender, QueuedWorkerCompletedEventArgs e)
{
Image image

=
e.Result
as
Image;

if
(image
!=

null
)
{

this
.pictureBox1.Image
=
image;
}
}

线程封装类的源码地址提供给大家下载:
https://files.cnblogs.com/wuhuacong/QueuedBackgroundWorker.rar

其实用C#内置的后台线程也是可以处理这个问题的,只是这个封装的后台线程,提供较好的一些封装及处理,可以应付更为复杂的情况,而对于要等线程返回值、用户可取消的操作,更是方便。系统这些对你有帮助!

为了有效阻止恶意用户的攻击,一般登录都会采用验证码方式方式处理登录,类似QQ的很多产品的验证码处理,但在一些OA系统中,系统通过非对称加密方式来处理登录的密码信息,登录页面每次提供对密码进行加密的公钥是不同的,因此如果要模拟登录,就需要先获取公钥,然后根据公钥把输入的密码加密,然后通过POST提交给服务器进行验证登录。由于公钥是页面刷新变化的,而加密是通过Javascript脚本进行加密,如下面的登录页面源码所示。

<
meta
http-equiv
="Content-Type"
content
="text/html; charset=gb2312"
>


<
link
rel
="stylesheet"
type
="text/css"
href
="/templates/2008/index.css"
>


<
link
rel
="shortcut icon"
href
="/images/tongda.ico"
>


<
script
src
="/inc/js/rsa/jsbn.js"
></
script
><
script
src
="/inc/js/rsa/prng4.js"
></
script
><
script
src
="/inc/js/rsa/rng.js"
></
script
><
script
src
="/inc/js/rsa/rsa.js"
></
script
>


<
script
type
="text/javascript"
>


function
CheckForm()
{

var
rsa
=

new
RSAKey();
rsa.setPublic(

"
97e256ec6147b7aadc46a353b5c5d707a895b402d114290c0c24a28919507569
"
,
"
10001
"
);

try
{
document.form1.PASSWORD.value

=
rsa.encrypt(document.form1.PASSWORD.value);
}

catch
(ex){

return

false
;
}

return

true
;
}

</
script
>


</
head
>



<
body
onload
="javascript:document.form1.PASSWORD.focus();"
>


<
br
>


<
br
>


<
br
>


<
br
>



<
div
align
="center"
>


<
form
name
="form1"
method
="post"
action
="logincheck.php"
autocomplete
="off"
onsubmit
="return CheckForm();"
>



<
table
cellspacing
="0"
cellpadding
="0"
align
="center"
>


<
tr
class
="img_field"
>


<
td
align
="center"
><
img
src
="/attachment/2090997160/index_1.jpg"
width
="651"
height
="241"
></
td
>


</
tr
>


<
tr
height
="37"
class
="login_field"
>


<
td
align
="center"
>


<
b
>
用户名
</
b
>

<
input
type
="text"
class
="text"
name
="UNAME"
size
="15"
onmouseover
="this.focus()"
onfocus
="this.select()"
value
=""
>


<
b
>
密码
</
b
>

<
input
type
="password"
class
="text"
name
="PASSWORD"
onmouseover
="this.focus()"
onfocus
="this.select()"
size
="15"
value
=""
>


<
select
name
="UI"
>


<
option
value
="0"
>
标准界面
</
option
></
select
>


&nbsp;
<
input
type
="submit"
name
="submit"
class
="submit"
value
="登 录"
>


</
td
>


</
tr
>


</
table
>


<
br
>



</
form
>

为了模拟登录,我们需要先获取页面的公钥信息,然后通过在C#中运行Javascript脚本,传递公钥和明文密码,然后获取脚本加密的结果,再提交给服务器处理。

如下面代码所示,获取公钥就是分析HTML源码,通过正则表达式匹配即可获取到,相对比较简单,但是要获取Javascript脚本的运行结果,就需要花点功夫了。


///
inc/js/rsa/jsbn.js /inc/js/rsa/prng4.js /inc/js/rsa/rng.js /inc/js/rsa/rsa.js



string
scriptUrl
=

"
http://www.abc.cn:8080/inc/js/rsa/jsbn.js
"
;

string
scriptUrl2
=

"
http://www.abc.cn:8080/inc/js/rsa/prng4.js
"
;

string
scriptUrl3
=

"
http://www.abc.cn:8080/inc/js/rsa/rng.js
"
;

string
scriptUrl4
=

"
http://www.abc.cn:8080/inc/js/rsa/rsa.js
"
;

string
referen
=

"
http://www.abc.cn:8080
"
;
HttpHelper helper

=

new
HttpHelper();
helper.Encoding

=
Encoding.Default;


string
mainContent
=
helper.GetHtml(referen, cookie, referen);


string
regex
=

"
rsa.setPublic\\(\
"
(
?<
publicKey
>
.
*?
)\
"
,\\s*\
"
(
?<
encrypt
>
.
*?
)\
"
\\);
"
;
Regex re

=

new
Regex(regex, RegexOptions.IgnoreCase
|
RegexOptions.Singleline
|
RegexOptions.IgnorePatternWhitespace);
Match mc

=
re.Match(mainContent);

if
(mc.Success)
{

string
publicKey
=
mc.Groups[
"
publicKey
"
].Value;

string
encrypt
=
mc.Groups[
"
encrypt
"
].Value;

string
pass
=
config.AppConfigGet(
"
ContactPassword
"
);


string
source
=

""
;
//
"var appName = \"Microsoft Internet Explorer\"; " + Environment.NewLine;


source
+=
helper.GetHtml(scriptUrl);
source

+=
helper.GetHtml(scriptUrl2);
source

+=
helper.GetHtml(scriptUrl3);
source

+=
helper.GetHtml(scriptUrl4);

//
source = source.Replace("navigator.", "");


source
=
getJS(source);

encryptPass

=
scriptEngine.Eval(
"
getRSAKey()
"
,
source

+


"
\r\nfunction getRSAKey(){\r\nvar RSA = new RSAKey();\r\nRSA.setPublic(\
""
+


publicKey
+

"
\
"
,\
""

+
encrypt
+

"
\
"
);\r\nvar Res
=
RSA.encrypt(
'
" + pass + "
'
);\r\nreturn Res;\r\n}
"
).ToString();




}

#endregion

上面的运行Javascript脚本,需要先把用到的脚本全部下载,把内容组合起来,然后添加一个虚拟的函数,运行得到返回结果接口,虚拟的函数一定要写正确,否则出来很多错误,得不到结果。

上面的代码有
source
=
getJS(source);这一句,是为了避免脚本调用navigator.appName来处理浏览器类型和版本的判断,有两种方式可以跳过这个处理,一个增加一个appName的变量,如

var appName =**
这样,然后统一替换
navigator.
的字符,使得脚本判别浏览器代码失效;二是通过正则表达式替换掉响应的判断代码即可


private

string
getJS(
string
strJS)

{

if
(
!
Regex.IsMatch(strJS,
@"
if\(j_lm \&\& \(navigator.appName == ""Microsoft Internet Explorer""\)\) {.+?dbits = 28;.+?}
"
, RegexOptions.Singleline)
||


!
Regex.IsMatch(strJS,
@"
if\(navigator.appName == ""Netscape"" && navigator.appVersion < ""5"" && window.crypto\) {.+?}
"
, RegexOptions.Singleline))
{

return

string
.Empty;
}

strJS

=
Regex.Replace(strJS,

@"
if\(j_lm \&\& \(navigator.appName == ""Microsoft Internet Explorer""\)\) {.+?dbits = 28;.+?}
"
,

"
BigInteger.prototype.am = am2;\r\ndbits = 30\r\n
"
, RegexOptions.Singleline);
strJS

=
Regex.Replace(strJS,

@"
if\(navigator.appName == ""Netscape"" && navigator.appVersion < ""5"" && window.crypto\) {.+?}
"
,

string
.Empty, RegexOptions.Singleline);

return
strJS;
}

得到处理过的密码密文 ,一般通过POST方式提交登录页面,即可完成系统的登录了,然后继续可以通过HttpRequest方式获取系统各种页面的信息了(如联系人等),如下面所示。


string
referen
=

"
http://www.abc.cn:8080/
"
;


string
loginUrl
=

"
http://www.abc.cn:8080/logincheck.php
"
;

string
login
=

"
test
"
;

string
loginPostData
=

string
.Format(
"
UNAME={1}&PASSWORD={0}&UI=0&submit={2}
"
, encryptPass, login,
"
%B5%C7+%C2%BC
"
);


string
conctactUrl
=

"
http://www.abc.cn:8080/general/ipanel/user/search.php
"
;

string
itemRegex
=

"
<tr\\s*class=\
"
TableLine\\d\
"
>\\s*(.*?)\\s*</tr>
"
;

string
memberRegex
=

"
<td.*?>\\s*(.*?)\\s*</td>
"
;

List

<
ContactInfo
>
contactList
=

new
List
<
ContactInfo
>
();
HttpHelper helper

=

new
HttpHelper();
helper.Encoding

=
Encoding.Default;


string
result
=
helper.GetHtml(loginUrl, cookie, loginPostData,
true
,
""
, loginUrl);

最后程序处理登录后,自动获取联系人的界面效果如下所示:

除了上面的操作方式,还有一种途径是通过WebBrowser控件实现数据的自动提交,WebBrowser控件处理脚本的运行更加方便,但缺点是这个控件相对较慢,首先我介绍一下这种方式,在按钮触发中调用控件的Navigate函数,打开相应的登录链接地址。

webBrowser1.Navigate(
"
http://www.abc.cn:8080
"
);

接着在浏览器控件的页面完成函数处理中对数据进行处理,处理的思路就是调用脚本对输入的内容进行加密,然后再触发提交按钮即可完成页面的登录,记录登录的信息,然后再去获取相关的页面内容信息,不过这种控件处理相对没那么大的弹性处理,不过可以作为一些功能的补充使用。


private

int
numtries
=

0
;

private

void
webBrowser1_DocumentCompleted(
object
sender, WebBrowserDocumentCompletedEventArgs e)
{

if
(webBrowser1.Document.GetElementById(
"
UNAME
"
)
!=

null
)
{
webBrowser1.Document.GetElementById(

"
UNAME
"
).SetAttribute(
"
value
"
,
"
陈建才
"
);
webBrowser1.Document.GetElementById(

"
PASSWORD
"
).SetAttribute(
"
value
"
,
"
voowoo770916
"
);


if
(numtries
<

2
)
{
IHTMLWindow2 login

=
(mshtml.IHTMLWindow2)webBrowser1.Document.Window.DomWindow;

//
login.execScript("document.forms[0].submit();", "javascript");


login.execScript(
"
CheckForm();
"
,
"
javascript
"
);

string
value
=
webBrowser1.Document.GetElementById(
"
PASSWORD
"
).GetAttribute(
"
value
"
);
encryptPass

=
value;

GetContact();
numtries

++
;
}
}

if
(webBrowser1.Document.GetElementById(
"
userName
"
)
==

null
)
{
numtries

=

0
;


//
string cookieString = webBrowser1.Document.Cookie;


//
CookieCollection cc = new CookieCollection();

//
CookieManger.SetCKAppendToCC(cc, cookieString, "
http://www.abc.cn
:8080");

//
cookie.Add(cc);



}
}

通过浏览器接口,我们可以实现页面内容在不可以见的浏览器控件中呈现,然后获取相应的页面对象或者页面源码进行分析,可以得到更加丰富的数据,模拟浏览器的实际操作和获得真实的显示结果。

一直以来,做.NET共享小软件的界面一般采用IrisSkin这个比较不错的皮肤控件来美化界面效果,方便易用,界面效果也还可以。如下面我做的QQ搜通天的界面效果如下:
http://www.iqidi.com/Download/qqcollector1.png
(不贴图了,省得说广告嫌疑)。该控件开发应用比较方便,拖动控件到主界面,然后初始化地方添加一下代码即可:


string
skinFile
=
ConfigurationManager.AppSettings[
"
SkinFile
"
];

this
.skinEngine1.SkinFile
=
Path.Combine(Application.StartupPath, skinFile);

主界面初始化中添加以上其他地方代码后,所有该程序的相关窗口都被美化成统一风格。

时代变迁,技术进化,界面效果变化无穷,现在最流行的界面应该是Windows2007风格,Ribbon风格的界面效果了,国外很多大型的.NET控件商,亦步亦趋,紧跟潮流,也推出不同版本的界面控件,支持这种新潮的界面效果。

我这里主要介绍一下DotNetBar这个控件,这个控件使用也非常简便,更加重要的是,这个控件提供各种各样的界面控件,支持整体的Ribbon以及更多更复杂的界面效果。我们从该控件的官网效果中可以窥见一斑(
http://www.devcomponents.com/dotnetbar/screenshots.aspx
),其中的效果非常酷,和Office2007的效果几乎没有什么差异。

本人闲暇之余用该控件做了一些界面测试效果,如下所示,这个应该是我下个版本QQ搜通天的界面效果了,呵呵:

软件包含几部分内容,为了练手,我做了一个布局相对比较饱满的程序,我通过这个小程序:短信发送程序,来介绍一下界面各部分的内容,以便使我们创建布局更加合理,更加美观方便的节目效果。


下面介绍的几点内容,是基于上面图形效果的编号来逐一分解,介绍各部分框架的意图以及效果:
1、Office2007中,这部分是一个综合的菜单界面,我上面的小程序,只是保留按钮,没有设置菜单,类似页面主页连接的功能,可以快速返回到主界面中。

2、Office界面样式下拉选择框,可以选择不同的界面效果,在这里还可以放置一些快速的按钮,如Office2007的保存、新建、撤销等功能。

3、 Ribbon样式可折叠的TabItem控件,集中放置一些功能相对类似的按钮或者其他界面元素,这里我放置了一些功能按钮,附上一些图片,就显得比较好看了。

4、 是技术支持网站的连接地址,也可以放一些帮助快速入口按钮。

5、 5、 6 模块就是主界面显示的区域了,这里因为功能界面不多,为了方便操作我使用了一个TabControl控件,把窗体内容控件一股脑的放到一起,这样方便用户的切换。

6、 6其实是主体内容控件的,我在设计的时候,把每一部分内容都设计为一个用户控件,这样可以利用TableControl控件,动态统一把需要的用户控件加载到界面中来。

7、 用来显示公司的Logon、操作提示信息、日期时间、登录用户等相关信息的状态栏。

通过以上7个模块,我们把软件的界面分解出来,各司其职,这样布局相对比较稳定,容纳的东西可以比较多,应该来说,还是一个不错的界面效果布局,这些有赖于DotNetbar控件来帮助我们设计出专业、符合现代潮流的界面效果。

其实在很多情况下,我们也可能采用基于单文档这种方式的展示效果, 这样用户看到的东西不至于混乱,注意力相对集中,这样的效果设计,在一些功能模块和其他部分相对没有太多直接关系的时候,就可以采用,如下面我们改变一下展示效果。

上面的两种图形可以通过界面元素的组装,来实现不同的效果布局显示,这样可以达到比较好的显示效果。下面贴出一些功能代码,以飨读者:

1、 改变界面样式的代码:


private

void
ChangeStyle(
object
sender, EventArgs e)
{

string
style
=
cbStyleManager.Text;

if
(
"
Office2007Blue
"
.Equals(style))
{
styleManager.ManagerStyle

=
eStyle.Office2007Blue;
}

else

if
(
"
Office2007Silver
"
.Equals(style))
{
styleManager.ManagerStyle

=
eStyle.Office2007Silver;
}

else

if
(
"
Office2007Black
"
.Equals(style))
{
styleManager.ManagerStyle

=
eStyle.Office2007Black;
}

else

if
(
"
Office2007VistaGlass
"
.Equals(style))
{
styleManager.ManagerStyle

=
eStyle.Office2007VistaGlass;
}

else

if
(
"
Office2010Silver
"
.Equals(style))
{
styleManager.ManagerStyle

=
eStyle.Office2010Silver;
}

else

if
(
"
Windows7Blue
"
.Equals(style))
{
styleManager.ManagerStyle

=
eStyle.Windows7Blue;
}

config.AppConfigSet(

"
UIStyle
"
, style);
}

2、 把用户控件附加到界面上的代码:



代码


public

void
SetDetailPanel(UserControl uc)
{

if
(uc
==

null
)
{

throw

new
ArgumentNullException(
"
uc
"
,
@"
用户控件uc不能为空
"
);
}

uc.Dock

=
DockStyle.Fill;
ribbonDetailPanel.Controls.Clear();
ribbonDetailPanel.Controls.Add(uc);
}

var tabMessage
=

new
CtrlMessageTab();
SetDetailPanel(tabMessage);

3、 使用TabControl控件,我们可以在代码中动态组装需要的窗体控件,如下所示:



代码

ctrlMessage
=

new
CtrlMainContent();
ctrlMessage.Dock

=
DockStyle.Fill;

this
.panelMessage.Controls.Add(ctrlMessage);

var ctrlTemplate

=

new
CtrlDictionary();
ctrlTemplate.Dock

=
DockStyle.Fill;
ctrlTemplate.SetText

=

new
SetTextDelegate(SetMessage);

this
.panelTemplate.Controls.Add(ctrlTemplate);

var ctrlLocal

=

new
CtrlLocalHistroy();
ctrlLocal.Dock

=
DockStyle.Fill;

this
.panelLocal.Controls.Add(ctrlLocal);

var ctrlAll

=

new
CtrlAllHistory();
ctrlAll.Dock

=
DockStyle.Fill;

this
.panelAllHistory.Controls.Add(ctrlAll);

DotNetBar提供了很多有用的控件,我们可以查看一下这里就知道了:
http://www.devcomponents.com/dotnetbar/

给我一个支点,可以撬动地球, 给我一个好的控件,可以装扮好一个杰出的软件。