2023年2月

大千世界,合久必分,分久必合,分合之道,利之所趋。

开发过程,积少成多,聚沙成塔,模块之理,必经之路。

本文继续WInform界面开发系列的介绍,主要针对性介绍Winform开发过程中,经常用到的模块:数据字典模块、参数配置模块、权限管理模块的模块化应用,以及各模块的分合之道。

1、数据字典模块

我们知道,一般程序基本上都涉及到了一个是数据参考用途的数据字典模块,不管是Web还是Winform的,这个模块基本上是必不可少的,Winform的界面效果大致如下所示:

本模块的数据字典支持无限级树形分层应用,可以添加大类,字典项目等数据, 系统集成只需要拷贝相关的字典表即可实现集成。

2、参数配置模块

在程序中,一般应用就是通过代码把相关的内容进行转义保存或者解析,以便呈现给用户更好的数据展示效果,这个模块比较通用,可分可合。

另外一个也比较常用的就是参数的配置管理模块,一般如果参数比较少,直接使用.NET的配置保存功能即可,如下图所示。

但这种方式只能保存比较少的内容,对于比较复杂的配置,一般很少采用这种模式存放程序的参数信息,这种方式存储的参数,如果不是放到一个独立的文件中,还存在一个不同步的现象。

其实我们还是可以把参数的配置功能作为一个独立的模块进行处理,我的程序就是经常这么干的,而且由于是相对比较独立,并在设计时候就支持参数的编辑及展示功能,因此效率大大提高,对使用用户来说,由于修改界面比较统一,而且参数的说明等很丰富,因此用户修改系统的配置参数友好性大大增强,界面效果如下所示:

使用用户看到的参数配置界面效果:

开发者在设计时刻的参数配置界面如下所示:


这些参数最终目的是为了程序的读取和调用,调用代码非常简单,而且由于是强类型的属性以及良好的提示功能,应该是比较方便的,示例代码如下所示:



代码


double
hours
=
span.Hours;
hours

=
(hours
<
SystemConfig.Default.KFPartAtLeaseHoure)
?

SystemConfig.Default.KFPartAtLeaseHoure : hours;

//
最小的钟点房计费



if
(span.Minutes
<

60

&&
span.Minutes
>=
SystemConfig.Default.KFPartHourMinutes)
{
hours

+=

1
;
}

else

if
(span.Minutes
>
SystemConfig.Default.KFPartHalfHourMinte
&&

span.Minutes

<
SystemConfig.Default.KFPartHourMinutes)
{
hours

+=

0.5
;
}

其中的
SystemConfig.Default.KFPartAtLeaseHoure就是参数的名称了,根据这个名称就可以读取和设置该参数的值。

该参数配置模块是一个Visual Studio的Addin插件,因此可以在设计时刻提供参数的添加、修改、删除等支持,非常方便,而且也能在发布后给用户界面提供修改,是一个独立的模块。


3、权限管理模块

最后说说第三个模块,权限管理模块,我们知道,一般对于进销存或者稍微复杂一点的系统,都需要最基本的权限控制模块,以便控制不同用户的访问功能,这个可以做成独立的应用程序进行管理,如下图所示:


程序是一个独立的程序,但是权限系统需要和业务应用系统结合一起才有价值,因此它们之间的结合,一个除了数据库的整合(把权限系统需要的表整合一起),还需要在系统的代码中进行整合(实现功能模块的登陆及权限控制等)。登陆验证如下所示:


try

{

string
loginName
=

this
.cmbzhanhao.Text.Trim();
User userBLL

=

new
User();

string
identity
=
userBLL.VerifyUser(loginName,
this
.tbPass.Text, Guid.NewGuid().ToString());

if
(
!
string
.IsNullOrEmpty(identity))
{
UserInfo info

=
userBLL.GetUserByName(loginName);


#region
获取用户的功能列表


Function functionBLL

=

new
Function();
List

<
FunctionInfo
>
list
=
functionBLL.GetFunctionsByUser(info.ID,
"
HOTELMS
"
);

if
(list
!=

null

&&
list.Count
>

0
)
{

foreach
(FunctionInfo functionInfo
in
list)
{

if
(
!
Portal.gc.FunctionDict.ContainsKey(functionInfo.ControlID))
{
Portal.gc.FunctionDict.Add(functionInfo.ControlID, functionInfo);
}
}
}


#endregion


bLogin

=

true
;
Portal.gc.LoginInfo

=
info;

this
.DialogResult
=
DialogResult.OK;
}

else

{
MessageUtil.ShowTips(

"
用户帐号密码不正确
"
);

this
.tbPass.Text
=

""
;
//
设置密码为空


}
}

catch
(Exception err)
{
MessageUtil.ShowError(err.Message);
}

用户功能权限认证如下所示:


#region
KTV包间管理


if
(Portal.gc.HasFunction(
"
KTV
"
))
{
OutlookBarBand myBasicBand

=

new
OutlookBarBand(
"
KTV包间管理
"
);
myBasicBand.SmallImageList

=

this
.imageList;
myBasicBand.LargeImageList

=

this
.imageList;

if
(Portal.gc.HasFunction(
"
KTV/View
"
))
{
myBasicBand.Items.Add(

new
OutlookBarItem(
"
KTV包间状态视图
"
,
0
));
}

if
(Portal.gc.HasFunction(
"
KTV/Setting
"
))
{
myBasicBand.Items.Add(

new
OutlookBarItem(
"
KTV包间设置
"
,
1
));
}

if
(Portal.gc.HasFunction(
"
KTV/Goods
"
))
{
myBasicBand.Items.Add(

new
OutlookBarItem(
"
KTV商品设置
"
,
3
));
}

if
(Portal.gc.HasFunction(
"
KTV/Waiter
"
))
{
myBasicBand.Items.Add(

new
OutlookBarItem(
"
KTV服务生管理
"
,
4
));
}

if
(Portal.gc.HasFunction(
"
KTV/BookIn
"
))
{
myBasicBand.Items.Add(

new
OutlookBarItem(
"
KTV预订管理
"
,
5
));
}

if
(Portal.gc.HasFunction(
"
KTV/OtherIncome
"
))
{
myBasicBand.Items.Add(

new
OutlookBarItem(
"
KTV其他款项登记
"
,
6
));
}
myBasicBand.Background

=
SystemColors.AppWorkspace;
myBasicBand.TextColor

=
Color.White;
outlookBar1.Bands.Add(myBasicBand);
}

#endregion

至此,三个常用而且比较独立模块介绍完毕,这几种场景你用了几个呢,如果觉得有价值,欢迎一起讨论切磋。

开发之道,顺手拈来;分合之需, 顺其自然;优码不语,润物无声;

在程序中有利用RichTextBox进行一些操作的需求,无意中在CodeProject中发现了一个封装比较完整的RichTextBox控件(
http://www.codeproject.com/KB/edit/csexrichtextbox.aspx
),控件封装的还是不错,测试界面效果如下:

总体来说,支持各种格式的定义以及图片的插入,效果还是相当不错,不过在实际中使用发现,用代码插入的中文内容会出现乱码。

解决方法一:

由于本身对RichTextBox的应用以及RTF格式不是很熟悉,搜索很久不得要领,没有找到好的解决方案,后来发现有一个比较取巧的方法,就是重新利用RichTextBox的机制来生成RTF文档内容,然后传入RTF格式内容给控件实现,代码如下所示:

RichTextBox rtb
=

new
RichTextBox();
rtb.Font

=
_font;
rtb.Text

=
_text;
AppendRtf(rtb.Rtf);

上面部分代码用来替换

public

void
InsertTextAsRtf(
string
_text, Font _font, RtfColor _textColor, RtfColor _backColor)

函数部分的this.SelectedRtf = _rtf.ToString(); 即可。

修改后完整的函数代码如下所示:


public

void
InsertTextAsRtf(
string
_text, Font _font, RtfColor _textColor, RtfColor _backColor) {
StringBuilder _rtf

=

new
StringBuilder();

//
Append the RTF header


_rtf.Append(RTF_HEADER);


//
Create the font table from the font passed in and append it to the

//
RTF string


_rtf.Append(GetFontTable(_font));


//
Create the color table from the colors passed in and append it to the

//
RTF string


_rtf.Append(GetColorTable(_textColor, _backColor));


//
Create the document area from the text to be added as RTF and append

//
it to the RTF string.


_rtf.Append(GetDocumentArea(_text, _font));


//
this.SelectedRtf = _rtf.ToString();



RichTextBox rtb

=

new
RichTextBox();
rtb.Font

=
_font;
rtb.Text

=
_text;
AppendRtf(rtb.Rtf);
}

步骤二:

上面那种方法应该是一种取巧的方法,后来学习了几篇文章关于RichTextBox格式介绍,发现还可以通过修改里面的一些编码格式实现中文的处理效果的。

RichTextBox格式相关的介绍文章如下所示:

RTF文件格式研究报告(
http://www.cnpopsoft.com/article.asp?id=11
)

关于RTF(富文本格式)的使用(
http://blog.sina.com.cn/s/blog_5d2a73550100bcth.html
)

使用C# 编程对RTF 文档进行操作(
http://www.webjx.com/htmldata/2007-10-22/1193038864.html
)

使用RichTextBox的一点心得(
http://blog.csdn.net/jiangxinyu/archive/2008/06/16/2552150.aspx
)

看了以上文章,你会发现,文章介绍的ExRichTextBox控件的编码头部内容以及字体内容是设置成了支持英语(美国)以及ASCII码,所以中文无法显示正常导致的乱码,如果修改了RichTextBox的编码头部内容和字体内容,应该就没有问题了。原来的RichTextBox头部内容如下:


/*
RTF HEADER

* ----------
*
* \rtf[N]        - For text to be considered to be RTF, it must be enclosed in this tag.
*                  rtf1 is used because the RichTextBox conforms to RTF Specification
*                  version 1.
* \ansi        - The character set.
* \ansicpg[N]    - Specifies that unicode characters might be embedded. ansicpg1252
*                  is the default used by Windows.
* \deff[N]        - The default font. \deff0 means the default font is the first font
*                  found.
* \deflang[N]    - The default language. \deflang1033 specifies US English.
*

*/


private

const

string
RTF_HEADER
=

@"
{\rtf1\ansi\ansicpg1252\deff0\deflang1033
"
;

如果修改下即可支持中文:

private

const

string
RTF_HEADER
=

@"
{\rtf1\ansi\ansicpg936\deff0\deflang1033\deflangfe2052
"
;

因为介绍内容如下:

1、文件基本属性:

{\rtf1 RTF版本\ansi字符集\ansicpg936简体中文\deff0默认字体0\deflang1033美国英语\deflangfe2052中国汉语

同样,把字体部分稍微调整下:

//
\fcharset specifies the character set of a font in the font table.

//
0 is for ANSI.


_fontTable.Append(
@"
\fcharset0
"
);

修改后变为以下代码即可:


//
\fcharset specifies the character set of a font in the font table.

//
0 is for ANSI.

//
_fontTable.Append(@"\fcharset0 ");


_fontTable.Append(
@"
\fcharset134
"
);

调整后测试效果如下图所示,同样可以实现和步骤一的效果:

内容打印

在以上基础上,我从别处获取了关于RichTextBox控件的打印功能代码,对该控件进行了功能扩充。打印效果如下图所示:

整合后的控件源码地址:
https://files.cnblogs.com/wuhuacong/ExRichTextBox.rar

深田之星绿苗帮电脑监控软件2010


鼠标单击可查看大图



文件大小

1,875 KB

更新时间

2010-08-07
下载地址
文件大小:2MB

在线帮助:



产品说明如下:

★软件功能
1) 当您不在家的时候,您是否担心孩子过多时间玩电脑游戏而耽误了学习...?
2) 互联网内容繁杂,您是否担心孩子上网浏览到不健康内容的网页...?
3) 孩子整天将自己关在房间中用电脑到深夜,也不知道到底是在学习还是在做其他什么…?
4) 您是否希望当您不在家时,您的电脑的被使用情况包括运行了什么软件,上了什么网,电脑具体操作了些什么...?

深田之星绿苗帮电脑监控软件专门定位于监护小孩的使用电脑及上网的行为习惯,让家长可以随时了解小孩成长过程,对其进行适当的引导及纠正!

本软件安装后以服务常驻在Windows内核,开机即自动运行,并进行后台图片监控,屏幕监控无任何可见界面;监控管理后台界面友好,可以配置各种监控参数,监控后台可以设置密码登陆。本软件具有较好的私密性、安全性等特点,对系统资源占用小,不影响系统正常运行。

深田之星绿苗帮电脑监控软件是一款定时记录电脑屏幕画面的软件,文件以默认以清晰高压缩的PNG图片进行保存,并按照每天进行归类,管理后台提供内置的图片管理及显示,并提供幻灯片模式对记录的图片进行回放,界面友好方便。

详细请查看在线帮助文档:
http://www.iqidi.com/HelpFile/GreenPlantHelp/Default.htm

★系统需求

深田之星绿苗帮电脑监控软件2010 使用C#语言开发 适运行在 Microsoft WindowsNT/2000/XP/2003 等平台,但必须安装有.Net2.0平台.
该软件利用了微软.NET Framework2.0 优秀的框架,因此在安装软件前,您需要花费一点时间来安装下面的组件(请您按照顺序安装即可):

(1)MicroSoft .NET Framework 2.0 官方下载地址:
http://www.microsoft.com/downloads/info.aspx?na=90&p=&SrcDisplayLang=zh-cn&SrcCategoryId=&SrcFamilyId=0856eacb-4362-4b0d-8edd-aab15c5e04f5&u=http%3a%2f%2fdownload.microsoft.com%2fdownload%2f5%2f6%2f7%2f567758a3-759e-473e-bf8f-52154438565a%2fdotnetfx.exe

(2)最后下载 深田之星绿苗帮电脑监控软件2010,进行安装即完成整个软件的安装。安装地址为:
http://www.iqidi.com/Download/GreenPlantHelpSetup.rar

★软件注册

深田之星绿苗帮电脑监控软件2010是一款共享软件,您可以自由发布和传播软件,未注册版本有些功能受限制,并且在使用
30
天后过期。在带给您方便的同时, 希望您能考虑注册这款软件。

注册用户可以获得我们的技术支持以及其他服务,并且可以免费升级到今后所有的新版本,请务必保留第一次注册时使用机器码及电子邮件,免费升级下一版本时,我们需求此信息。更重要的是:您的注册还将鼓励我们不断改进和完善这个软件,给大家提供更好的服务。

如果您在电脑报刊发表了介绍这个软件的文章,将刊物名称和期号告诉我,我们将免费为您提供注册码。.或者您有好的建议/意见/程序错误,请写信告诉我们,如果我们采纳,也将免费为您提供注册码。

深田之星绿苗帮电脑监控软件2010
该软件版权归伍华聪所有,作者保留该软件所有权利,未注册用户不得将该软件用于商业用途。

默认情况下,Windows服务基于安全考虑,是不允许任何服务程序和桌面进行交互的,也就是说,使用任何的Windows Form 的很多特性将会莫名奇妙的不起作用,如进行屏幕截图,或者使用System.Windows.Form.Timer对象也不行。由于Windows服务具有难以调试的特点,如果不注意这点,你反复检查自己的代码,都很难发现问题的所在的,我开始就是摸索了很久才发现,呵呵。

Windows服务的优点有:1. 能够自动运行。2. 不要求用户交互。3. 在后台运行。

一般情况下,Windows服务被用于耗费时间很多的进程中,例如备份数据库,数据同步等等。

本文介绍如何开启Windows服务桌面交互的设置,以便进行屏幕监控的功能开发,首先介绍一下屏幕监控的程序--绿苗帮电脑监控软件,它是随系统自动启动,在服务中定时进行屏幕截图的一个小软件,给家长提供监控小孩电脑使用情况使用的,系统管理界面如下所示 。



Windows服务默认情况下,不支持进行屏幕截图的,需要在安装程序的时候,把系统的参数修改一下才可以,下面是两种方式实现该功能的开启 :


private

void
serviceInstaller_AfterInstall(
object
sender, InstallEventArgs e)
{

base
.OnAfterInstall(e.SavedState);

ManagementObject wmiService

=

null
;
ManagementBaseObject InParam

=

null
;

try

{
wmiService

=

new
ManagementObject(
string
.Format(
"
Win32_Service.Name='{0}'
"
, Constants.ServiceName));
InParam

=
wmiService.GetMethodParameters(
"
Change
"
);
InParam[

"
DesktopInteract
"
]
=

true
;
wmiService.InvokeMethod(

"
Change
"
, InParam,
null
);
}

finally

{

if
(InParam
!=

null
)
InParam.Dispose();

if
(wmiService
!=

null
)
wmiService.Dispose();
}
}

另一种方式是通过注册表操作函数实现参数的修改:


protected

override

void
OnCommitted(System.Collections.IDictionary savedState)
{

base
.OnCommitted(savedState);


using
(RegistryKey ckey
=
Registry.LocalMachine.OpenSubKey(
@"
SYSTEM\CurrentControlSet\Services\
"

+
Constants.ServiceName,
true
))
{

if
(ckey
!=

null
)
{

if
(ckey.GetValue(
"
Type
"
)
!=

null
)
{
ckey.SetValue(

"
Type
"
, (((
int
)ckey.GetValue(
"
Type
"
))
|

256
));
}
}
}
}

最终达到的效果就是注册表的参数修改了,如下图所示:

在前面一篇随笔《
WinForm界面开发之模块化分合
》中,说到了参数配置管理模块内容,内容摘录如下:

====================================

参数配置模块

在程序中,一般应用就是通过代码把相关的内容进行转义保存或者解析,以便呈现给用户更好的数据展示效果,这个模块比较通用,可分可合。

另外一个也比较常用的就是参数的配置管理模块,一般如果参数比较少,直接使用.NET的配置保存功能即可,如下图所示。

但这种方式只能保存比较少的内容,对于比较复杂的配置,一般很少采用这种模式存放程序的参数信息,这种方式存储的参数,如果不是放到一个独立的文件中,还存在一个不同步的现象。

其实我们还是可以把参数的配置功能作为一个独立的模块进行处理,我的程序就是经常这么干的,而且由于是相对比较独立,并在设计时候就支持参数的编辑及展示功能,因此效率大大提高,对使用用户来说,由于修改界面比较统一,而且参数的说明等很丰富,因此用户修改系统的配置参数友好性大大增强,界面效果如下所示:

使用用户看到的参数配置界面效果:

=========================================================

该参数配置模块在设计时刻和运行时刻都提供管理界面,非常方便参数的定义以及管理,参数一旦定义后,就是强类型的属性类型,也非常方便在代码中进行引用或者修改。

而另一方面,Control.FirefoxDialog控件却提供了很好的参数管理界面效果,比前面说到的参数配置管理模块界面更加友好,FirefoxDialog控件的界面效果如下所示:

ControlFirefoxDialog.jpg

该控件的Codeproject地址是(
http://www.codeproject.com/KB/miscctrl/ControlFirefoxDialog.aspx?msg=1856449

因此我们可以结合两者的特点,对参数配置管理模块的功能进行整合,界面采用 FirefoxDialog控件显示,而参数的获取或者设置则采用前面介绍的强类型参数配置管理模块,这样可以很好的利用各自的特点,重新构造出一个优秀的参数配置管理功能模块。整合后在我的新软件,绿苗帮电脑监控软件中的配置管理界面,最终的界面效果如下所示:

这个FireFoxDialog的参数配置界面,其实是有一个主窗体界面和几个Page类组成的,如主窗体的加载界面代码如下:


public

partial

class
FrmSettings : Form
{

public
FrmSettings()
{
InitializeComponent();
}


private

void
FrmSettings_Load(
object
sender, EventArgs e)
{

this
.firefoxDialog1.ImageList
=

this
.imageList1;


this
.firefoxDialog1.AddPage(
"
参数设置
"
,
new
PageSetting());

this
.firefoxDialog1.AddPage(
"
程序控制
"
,
new
PageControl());

this
.firefoxDialog1.Init();
}
}

而对于每一个Page类,需要实现初始化控件以及“应用”的功能模块代码,如下面的实现所示:


public

override

void
OnInit()
{
SystemConfig.Default.Load();


#region
采集间隔时间


string
defaultInterval
=

"
00:00:30
"
;

string
intervalString
=
SystemConfig.Default.CaptureInterval;

if
(
string
.IsNullOrEmpty(intervalString))
{
intervalString

=
defaultInterval;
}


string
[] timeArray
=
intervalString.Split(
'
:
'
);

this
.txtHour.Value
=
Convert.ToInt32(timeArray[
0
]);

this
.txtMinute.Value
=
Convert.ToInt32(timeArray[
1
]);

this
.txtSecond.Value
=
Convert.ToInt32(timeArray[
2
]);

#endregion



if
(
!
string
.IsNullOrEmpty(SystemConfig.Default.ImageFormat))
{

this
.ddlImageFormat.Text
=
SystemConfig.Default.ImageFormat;
}

else

{

this
.ddlImageFormat.SelectedIndex
=

0
;
}


this
.txtExtension.Text
=
SystemConfig.Default.ImageFileExtension;

this
.txtSavePath.Text
=
SystemConfig.Default.PictureSavePath;
}


public

override

void
OnApply()
{

try

{

string
timeSpan
=

string
.Format(
"
{0:D2}:{1:D2}:{2:D2}
"
,
Convert.ToInt32(

this
.txtHour.Value), Convert.ToInt32(
this
.txtMinute.Value), Convert.ToInt32(
this
.txtSecond.Value));
SystemConfig.Default.CaptureInterval

=
timeSpan;
SystemConfig.Default.ImageFileExtension

=

this
.txtExtension.Text.Trim(
'
.
'
);
SystemConfig.Default.ImageFormat

=

this
.ddlImageFormat.Text;
SystemConfig.Default.PictureSavePath

=

this
.txtSavePath.Text;
SystemConfig.Default.Save();
}

catch
(Exception ex)
{
LogHelper.Error(ex);
MessageUtil.ShowError(ex.Message);
}
}

而其中代码中的“S
ystemConfig.Default”
调用的操作是强类型的,编写代码的时候,有相关的说明,非常易于操作,不会出现混淆意思的说明,比直接操作XML类或者其他封装XML调用的辅助类方便不少,而且也具有设计时刻对参数项的定义以及修改界面的支持。该代码
调用就是采用了原有的参数配置管理类,实现数据的获取或者修改的,这样把他们两者的特点都发挥出来,实现了更好的参数配置管理,根据具有可定制性和可扩展性。