2023年2月

在较早时期,我写过一篇文章《
结合Control.FirefoxDialog控件,构造优秀的参数配置管理模块
》,
介绍过在我的Winform框架基础上集成的参数配置模块功能,但是参数模块的配置管理感觉还不够灵活,于是一直在寻找一个较好的替代者,用来结合FireFoxDialog界面一并展现,期间仔细研读过好几篇Codeproject网站上的关于配置管理的文章,但是总是觉得不够灵活或者简便。本文主要针对结合FireFoxDialog参数配置界面组件和SettingsProvider.net技术,实现较为美观、灵活的Winform程序参数配置管理。

在CodeProject上有两三篇文章介绍配置文件的很不错,下面给出链接大家分享下:

http://www.codeproject.com/Articles/25829/User-Settings-Applied

http://www.codeproject.com/Articles/475498/Easier-NET-settings

这两个都很不错,不过感觉不太满足我的简单、高效的需求,偶然之间在GitHub上发现一个《
SettingsProvider.net
》,做的非常不错,而且感觉扩展性也做的很好,因此就把它和FireFoxDialog界面一同整合,在框架内部实现参数管理功能。

1、SettingsProvider.net功能介绍

这个是在GitHub上的一款参数配置组件,能够基于普通配置文件、ProgramData目录文件、独立存储区文件等方式的配置文件存储,它主要是基于Json格式进行的配置保存,因此我们还可以把它存储到数据库,虽然官方没有提供例子,但是我们很容易通过扩展实现这个功能,后面我介绍我对其数据库参数保存扩展类。

它还可以通过Atrribute标识进行管理配置文件,可以把参数设置为加密、默认值、修改名称等,如下是它的一个配置参数的类的例子。

public classMySettings
{
[DefaultValue(
"Jake")]
[DisplayName(
"Your Name")]public string Name { get; set; }

[DefaultValue(
true)]
[Description(
"Should Some App Remember your name?")]public bool RememberMe { get;set; }public List<Guid> Favourites { get;set; }

[Key(
"OriginalName")]public string Renamed { get; set; }

[ProtectedString]
public string Encrypted { get; set; }
}

读取操作如下所示:

var settingsProvider = new SettingsProvider(); //By default uses IsolatedStorage for storage
var mySettings = settingsProvider.GetSettings<MySettings>();
Assert.True(mySettings.RememberMe);

保存操作如下所示:

var settingsProvider = new SettingsProvider(); //By default uses IsolatedStorage for storage
var mySettings = new MySettings { Name = "Mr Ginnivan"};
settingsProvider.Save(mySettings);

参数元数据获取操作代码如下:

var settingsProvider = newSettingsProvider();foreach (var setting in settingsProvider.ReadSettingMetadata<MySettings>())
{
Console.WriteLine(
"{0} ({1}) - {2}", setting.DisplayName, setting.Description, setting.DefaultValue);
}

2、参数配置功能界面截图

基于上述的SettingsProvider.net的组件,我们可以结合我前面介绍的FireFoxDialog界面效果,实现较好的参数配置管理功能,如下界面所示。

我们可以分别把不同的参数放到不同的存储介质里面去,如一些常用的,可以配置到本地目录文件里面去,一些和个人信息相关的内容,我们可以把它放到数据库里面去,这样可以在各个客户端使用都不需要重新配置,非常方便。

上面的例子,我针对性介绍两个,一个是基于本地文件参数存储,一个是基于数据库文件的参数存储。

3、基于SettingsProvider.net的整合操作的实现

介绍了基于SettingsProvider.net的功能,以及最终整合的效果,我们来看看它具体是如何整合实现不同文件类型数据的保存的。

首先,我们在设计模式下,拖动好对应的界面,由于FireFoxDialog界面本来就是用来做参数设置的,因此他们里面有一些控件就不一一介绍了,具体可以参考对应的文章(http://www.codeproject.com/KB/miscctrl/ControlFirefoxDialog.aspx?msg=1856449)。

我们看看上述效果界面的设计界面和后台代码。

设计界面就是如下所示,拖动一个参数配置用户控件到窗体上,设置好对应的内容就可以了。

后台代码如下所示。

    public partial classFrmSettings : BaseForm
{
publicFrmSettings()
{
InitializeComponent();
}
private void FrmSettings_Load(objectsender, EventArgs e)
{
this.firefoxDialog1.ImageList = this.imageList1;this.firefoxDialog1.AddPage("报表设置", new PageReport());//基于本地文件的参数存储 this.firefoxDialog1.AddPage("邮箱设置", new PageEmail());//基于数据库的参数存储//下面是陪衬的 this.firefoxDialog1.AddPage("短信设置", newPageEmail());this.firefoxDialog1.AddPage("声音设置", newPageEmail());this.firefoxDialog1.AddPage("系统设置", newPageEmail());this.firefoxDialog1.AddPage("备份设置", newPageEmail());this.firefoxDialog1.AddPage("其他设置", newPageEmail());this.firefoxDialog1.Init();
}
}

这里最有代表性的是PageReport和PageEmail两个组件对象。

1)报表模块配置管理

基于报表参数和邮件参数,我们可以定义一个参数对象类,用来方便保存和获取数据的承载对象。

报表参数对象类如下(只做了简单的一个报表路径处理):

    /// <summary>
    ///报表设置/// </summary>
    public classReportParameter
{
/// <summary> ///派车单报表文件/// </summary> [DefaultValue("WHC.CarDispatch.CarSendBill2.rdlc")]public string CarSendReportFile { get; set; }
}

对应的设计界面如下所示,用来提供一个报表文件的参数配置,很简单了。

我们来看看后台的组件,对参数是如何保存和显示的。

首先需要初始化相应的对象,我们这里使用了在程序运行目录下,创建一个文件用来保存这些设置。

    public partial classPageReport : PropertyPage
{
privateSettingsProvider settings;privateISettingsStorage store;publicPageReport()
{
InitializeComponent();
if(!this.DesignMode)
{
//PortableStorage: 在运行程序目录创建一个setting的文件记录参数数据 store = newPortableStorage();
settings
= newSettingsProvider(store);
}
}

注意:PortableStorage是在运行程序目录创建一个setting的文件记录参数数据,运行后,最终会在目录下生成一个类似“ReportParameter.settings”的文件,它的内容格式如下所示。

[{"Key":"CarSendReportFile","Value":"\"WHC.CarDispatch.CarSendBill2.rdlc\""}]

我们看看报表组件的参数是如何初始化的:

        public override voidOnInit()
{
ReportParameter parameter
= settings.GetSettings<ReportParameter>();if (parameter != null)
{
EnableOtherReport(
false);string reportFile =parameter.CarSendReportFile;if (reportFile == "WHC.CarDispatch.CarSendBill2.rdlc")
{
this.radReport.SelectedIndex = 0;
}
else if (reportFile == "WHC.CarDispatch.CarSendBill.rdlc")
{
this.radReport.SelectedIndex = 1;
}
else{
EnableOtherReport(
true);this.radReport.SelectedIndex = 2;this.txtOtherReport.Text =reportFile;
}
}
}

通过参数的存储对象处理,这样我们就可以通过ReportParameter 进行数据获取了。

保存参数的时候,同样也是先获取到一个参数对象,并设置它的值后,然后进行保存就可以了,具体代码如下所示。

        public override boolOnApply()
{
bool result = false;try{
ReportParameter parameter
= settings.GetSettings<ReportParameter>();if (parameter != null)
{
int otherType = 2;//2代表其他类型 if (this.radReport.SelectedIndex <otherType)
{
parameter.CarSendReportFile
= this.radReport.Properties.Items[this.radReport.SelectedIndex].Value.ToString();
}
else{
parameter.CarSendReportFile
= this.txtOtherReport.Text;
}
settings.SaveSettings
<ReportParameter>(parameter);
}
result
= true;
}
catch(Exception ex)
{
LogTextHelper.Error(ex);
MessageDxUtil.ShowError(ex.Message);
}
returnresult;
}

2)邮件参数配置管理

邮件参数对象类如下:

    /// <summary>
    ///邮箱设置/// </summary>
    public classEmailParameter
{
/// <summary> ///邮件账号/// </summary> //[DefaultValue("wuhuacong@163.com")] public string Email { get; set; }/// <summary> ///POP3服务器/// </summary> [DefaultValue("pop.163.com")]public string Pop3Server { get; set; }/// <summary> ///POP3端口/// </summary> [DefaultValue(110)]public int Pop3Port { get; set; }/// <summary> ///SMTP服务器/// </summary> [DefaultValue("smtp.163.com")]public string SmtpServer { get; set; }/// <summary> ///SMTP端口/// </summary> [DefaultValue(25)]public int SmtpPort { get; set; }/// <summary> ///登陆账号/// </summary> public string LoginId { get; set; }/// <summary> ///登陆密码/// </summary> [ProtectedString]public string Password { get; set; }/// <summary> ///使用SSL加密/// </summary> [DefaultValue(false)]public bool UseSSL { get; set; }
}

参数显示和保存的界面设计如下所示。

界面的初始化,同样和上面的差不多,不过这里使用了数据库的存储类DatabaseStorage,内容将保存在数据库里面,而且我们通过用户的标识Create进行路径的针对性处理,可以使每个用户的配置文件不同。

    public partial classPageEmail : PropertyPage
{
privateSettingsProvider settings;privateISettingsStorage store;publicPageEmail()
{
InitializeComponent();
if (!this.DesignMode)
{
//DatabaseStorage:在数据库里面,以指定用户标识保存参数数据 string creator =Portal.gc.LoginUserInfo.Name;
store
= newDatabaseStorage(creator);
settings
= newSettingsProvider(store);
}
}

参数的加载代码如下所示,也就是把数据获取后,显示在界面上即可。

        public override voidOnInit()
{
EmailParameter parameter
= settings.GetSettings<EmailParameter>();if (parameter != null)
{
this.txtEmail.Text =parameter.Email;this.txtLoginId.Text =parameter.LoginId;this.txtPassword.Text =parameter.Password;this.txtPassword.Tag =parameter.Password;this.txtPop3Port.Value =parameter.Pop3Port;this.txtPop3Server.Text =parameter.Pop3Server;this.txtSmtpPort.Value =parameter.SmtpPort;this.txtSmtpServer.Text =parameter.SmtpServer;this.txtUseSSL.Checked =parameter.UseSSL;
}
}

数据的保存操作也很简单,和前面的操作差不多,如下所示。

                EmailParameter parameter = settings.GetSettings<EmailParameter>();if (parameter != null)
{
parameter.Email
= this.txtEmail.Text;
parameter.LoginId
= this.txtLoginId.Text;
parameter.Password
= this.txtPassword.Text;
parameter.Pop3Port
= Convert.ToInt32(this.txtPop3Port.Value);
parameter.Pop3Server
= this.txtPop3Server.Text;
parameter.SmtpPort
= Convert.ToInt32(this.txtSmtpPort.Value);
parameter.SmtpServer
= this.txtSmtpServer.Text;
parameter.UseSSL
= this.txtUseSSL.Checked;

settings.SaveSettings
<EmailParameter>(parameter);
}

最终,我们在数据库表里面,可以看到对应记录已经保存起来了,并且用户密码部分也进行了加密处理。

这样,我们整合两者的特点,就可以实现比较不错的参数配置界面的显示和后台存储处理了,针对性的,使用不同的存储介质,以满足不同的需要。

我们开发程序的时候,经常会碰到一些报表,如果是Winform的报表,一般可以采用DevExpress控件组的XtraReport,或者微软的RDLC报表,当然还有一些其他的,在此不再赘述。由于本人在Winform开发中经常使用到一些报表,有时候使用XtraReport报表,有时候也使用RDLC报表,因此本篇主要介绍这两种不同类型报表的使用,以及对比它们的差异性,以便我们更好的掌握了解。

1、XtraReport报表的使用

1)报表界面展示

在我的派车管理系统里面,我需要根据数据库的记录,把相关的派车历史信息做成一个报表统计的模块,需要列出报表的明细(派车明细)和统计值(记录数、公里数),这种类型的报表是很多情况下需要碰到的。

我需要实现一个基于XtraReport报表技术实现的功能界面,如下面图示所示。

这里面涉及的字段,包括字符型、日期型,数值型、枚举类型等,还有统计值、打印时间(参数),因此也算一个比较完整的报表展示了。

2)创建新报表

为了实现这个报表模块,我们先使用VS的工具创建一个报表组件对象。

在使用前,我们需要设置好报表组件的各个显示模块,报表报表上下空间部分(TopMargin/BottomMargin),这两个部分用来调整上下的边线的;

然后每个报表有头部内容和底部内容的区分,ReportHeader一般用来放置表格头部字段显示的,明细的内容包括展示列表的明细信息,大致调整如下所示。

3)设计报表控件和数据源管理

在VS左边的工具箱里面有响应的XtraReport控件,拖动到报表界面里面就可以进行绘制设计了。

为了实现表格统计和展示,我们需要拖动两个表格到报表界面里面去,一个是报表头字段,一个用来显示报表明细的列表内容,两个单元格的宽度设置一致,并且上下空间要挨着,否则显示出来就有空间,不好看。

表格拖动过去后,可以使用右键添加相应的单元格,然后统一设置他们的背景颜色,高度宽度,边框等属性即可。

表格头我们可以填写相关的文字替代,但是报表的明细信息,我们需要绑定相应的字段内容,因此需要增加一个数据源用来绑定到报表上去。

在报表属性里面,找到DataSource进行配置,如下所示。

拖动相关的表格,然后设计其界面,并绑定明细单元格的内容为[ABC]这样的格式,就标识绑定了一个ABC的字段属性了。如下所示是绑定了一个HandNo的字段显示。

最终设计的报表界面如下所示。

4)特殊字段的处理

在报表的设计过程中,我们一般会碰到各种不同的字段,它不是简单的文本信息,可能是日期,也可以是统计值,计算值等。

打印时间,只需要增加一个XRPageInfo,然后设置里面的值就可以了,这个有点类似RDLC的参数值。

从上面我们可以看到有很多项目可以选的,除了有时间,还有记录数、总记录数、用户名、总数等等。

记录数的统计设计如下所示,主要是通过千制的方式显示,调用Count函数处理。

同样,总公里数,是统计整个报表里面记录的公里数操作,调用SUM函数处理,它的设计界面如下。

设计好Xtra报表后,我们就可以程序里面,直接调用进行加载处理即可,代码如下所示。

        private voidPrintReport()
{
string where =GetConditionSql();
List
<CarApplyInfo> list = BLLFactory<CarApply>.Instance.Find(where);

FrmHistoryReport report
= newFrmHistoryReport();
report.DataSource
=list;

ReportPrintTool tool
= newReportPrintTool(report);
tool.ShowPreview();
}

我们主要获取数据源,并绑定数据源到对应的报表组件上,然后调用ReportPrintTool 进行展现即可,代码非常简洁,只是设计过程麻烦一些。

2、RDLC报表的使用

在我《
会员管理系统的设计和开发(2)-- RDLC报表的设计及动态加载
》里面,有对RDLC报表设计做了一些介绍,RDLC是微软的报表格式,它是一个相对比较独立的文件,是一个基于XML描述性的文件,除了可以用在Winform上外,也可以用在Web上,通用性也非常不错。

基于对比的考虑,我也为同样的模块设计了一个一样的报表,RDLC报表最终展示界面如下所示。

和前面XtraReport的报表界面做一个对比。

两者的展现效果都差不多,只是界面效果有一些差异而已。

1)RDLC的设计效果

由于RDLC它是一个独立的XML文件,它的描述全部在一个XML里面,因此我们设计的时候,只是对XML文件进行处理,VS给我们提供了一个设计器及进行设计而已。

在RDLC的设计视图里面,我们可以看到报表数据里面,包括一些预定义的数据,以及我们添加进去的数据源。

当然报表设计少不了相关的控件,因此在工具栏里面,可以看到可以用的报表控件元素,如下所示。

2)报表特殊元素的处理

RDLC报表,也有一些如打印时间,分页数据等,是需要特殊的处理了,打印时间是使用报表数据里面的【内置字段】项目,如下所示。

对于记录总数和统计值,我们可以通过自定义的格式内容进行处理,如下所示。

统计总数的操作设计如下,调用SUM函数即可,如果忘记是那个,可以在下面类别里面进行查询,里面有很多运算符、常见函数等内容可以使用。

另外,对于一些特殊的显示格式,我们需要正确设置,如下是日期的格式设置。

3)
报表的调用处理

由于报表的展现模块,我已经放到了一个独立的通用模块里面,因此对于报表的加载,只需要简单的设置属性,然后调用即可,如下所示。

        private voidPrintRDLCReport()
{
string where =GetConditionSql();
List
<CarApplyInfo> list = BLLFactory<CarApply>.Instance.Find(where);foreach (CarApplyInfo info inlist)
{
info.Data1
= info.Status.ToString();//转换枚举类型描述 }

ReportViewerDialog dlg
= newReportViewerDialog();
dlg.Text
= "历史记录报表";
dlg.DataSourceDict.Add(
"CarApplyInfo", list);
dlg.ReportName
= "WHC.CarDispatch.HistoryReport";
dlg.IsPrintLayout
= true;

dlg.ShowDialog();
}

上面主要也是构建数据源,指定报表路径和布局,然后绑定报表展示模块窗口即可。

下面附上几个系统功能截图,供参考学习。

1)主界面列表,基于GridControl的展示,并增加统计栏目。

2)历史记录报表展示(XtraReport报表)

3)两种不同格式的派车单报表展示(RDLC报表)。

3、XtraReport报表和RDLC报的差异

这两者的报表都能实现相似的效果,不过XtraReport是把它集成到DLL里面,类似编译的窗体文件一样了;而RDLC报表本身是一个独立的文件,我们发布的时候,要把它带上,但是又有较好的通用性。

对于XtraReport的字段内容展示,默认就是调用了ToString()的函数处理,因此类似日期、枚举类型,都不需要怎么样设置,就可以正常显示,但是RDLC报表设计里面,我们需要为每种字段设置好对应的格式,枚举类型好像解析还是会转换为整形,因此需要在数据源级别进行转换,否则显示不出来。

对于自定义函数方面,XtraReport提供了较好的设计界面,能够很大程度减少出错的机会;RDLC则提供了弹性化的操作,提供了很多函数进行参考,需要自己添加表达式,不过在编译的时候,能够检查出现的错误。

如果需要动态配置、加载或者开发一些新的报表展示,可能相对来说,RDLC更方便,可以增量发布一些报表,然后在后台配置即可展示,不需要改动源码编译。

在我们开发一些项目的时候,一般需要一些外围的设备进行数据处理,如ID/IC读卡器获取卡号、激光条码扫描枪、USB摄像头、USB方式的小票据打印机(POS打印机)、USB来电录音盒、普通打印机等一系列附属设备。借助这些设备,可以使我们的业务流程更严谨,输入数据更方便,或者能够一些特殊的数据等功能。本文主要介绍其中的ID读卡器(IC读卡器)快速读取卡号,以及实用激光条码枪的条码扫描录入功能,后面的一些硬件设备的处理,后续文章在继续介绍。

1、设备介绍

前面介绍的设备,在很多场合上都可能用到,如我的会员管理系统里面,就需要用到下面的设备处理。

本文主要针对性了解ID读卡器和条码枪的设备数据处理,这两种设备虽然不同,但是它们相似的地方就是都支持在光标处录入数据的,就有点类似我们的键盘快速录入一样,当然激光条码枪也支持很多种方式的事件处理操作,这是后话。

2、ID读卡器数据读取界面和条码扫描枪读取界面的分析介绍

在我的会员管理系统里面,录入卡号一般是通过ID读卡器获取的,在界面上设置一个可以弹出录入的文本框,也方便手工录入卡号,如下面的界面功能所示。

当然,有时候,我们可能不需要提供手工录入,那么就不能通过光标录入方式获取扫描的内容,因为我们把输入框设置为只读的了,所以这种情况,就就应该通过事件来获取设备的输入内容。

在条码枪处理读取条形码或者二维码的时候,我们一般都是和商品相关的地方使用条形码,二维码也可以使用,条形码可能一般带有数据供阅读,二维码则没有,但是都可以通过设备读取出来到文本框里面,一般如果录入,就停放光标在文本框就可以了,如商品的信息的录入。在我们需要输入条码的地方点一下,然后操作条码枪录入条码即可,这种不需要额外的代码处理。

但是对于一些我们需要快速录入商品信息的界面,如客户消费界面,那么就需要对条码的事件进行处理了。

例如下面的界面,在消费确认前的产品录入,我们都是通过条码枪的快速扫描产品进行录入的,这时候条码枪就代替了手工的录入,我们可以每次扫描一次,就在列表里面自动增加一个对应商品的记录,非常方便的了。

3、通用的读卡操作和条码扫描枪操作实现

在前面小节介绍了一些利用ID读卡器录入数据和使用条码枪的场景,对于如果是在可输入文本框里面获得内容,不用任何编码,如果是在只读界面或者窗体上获得设备的数据,那么就可以通过事件进行处理了,那么读卡器和扫描枪的事件应该如何处理的呢。

我的做法,是统一在我的Winform开发框架的界面层基类模块里面,增加一些硬件相关的处理类和界面,这样在各个框架派生出来的项目就可以很方便使用了。

其中Device里面的CardReader就是IC、ID读卡器获取操作的处理,一般来说,这些卡都是以00开始的,所以我们的处理类,通过一个Time来控制连续获取数据的处理就可以了,主要就是监听KeyUp事件。

以CardReader为例,它的完整代码如下所示。

    /// <summary>
    ///读卡器封装类/// </summary>
    public classCardReader
{
privateControl _hostCtrl;private string_cardCode;privateTimer _timer;private const int CARD_CODE_LEN = 10;private const string CARD_CODE_START = "00";/// <summary> ///读卡器读到一张卡的事件/// </summary> public eventCardReadEventHandler CardRead;/// <summary> ///默认读卡器(挂在主窗体上,会被主窗体初始化,在模块里用肯定是安全的)/// </summary> public static CardReader Default { get; set; }/// <summary> ///构造器/// </summary> /// <param name="hostCtrl">接受键盘事件的宿主控件</param> publicCardReader(Control hostCtrl)
{
_hostCtrl
=hostCtrl;if (_hostCtrl isForm)
{
(_hostCtrl
as Form).KeyPreview = true;
}
_hostCtrl.KeyUp
+= newKeyEventHandler(hostCtrl_KeyUp);
_cardCode
= "";
_timer
= newTimer();
_timer.Interval
= 20;
_timer.Tick
+= newEventHandler(timer_Tick);
_timer.Start();
}
/// <summary> ///判断是否卡号/// </summary> /// <param name="code"></param> /// <returns></returns> public static bool IsCardCode(stringcode)
{
return code.Length == CARD_CODE_LEN &&code.StartsWith(CARD_CODE_START);
}
/// <summary> ///定时器到期的事件/// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void timer_Tick(objectsender, EventArgs e)
{
//达到一定的位数才开始判断 if (_cardCode.Length >=CARD_CODE_LEN)
{
_cardCode
= _cardCode.Trim((char)13);if(IsCardCode(_cardCode))
{
_timer.Stop();
OnCardRead(_cardCode);
}
}
_cardCode
= "";
_timer.Start();
}
/// <summary> ///监听按键弹起的事件/// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void hostCtrl_KeyUp(objectsender, KeyEventArgs e)
{
_timer.Stop();
_cardCode
= _cardCode + (char)e.KeyValue;
_timer.Start();
}
private void OnCardRead(stringscanCode)
{
if (CardRead != null)
{
CardRead(scanCode);
}
}

}
/// <summary> ///读卡器读到一张卡的事件处理委托/// </summary> /// <param name="cardCode"></param> public delegate void CardReadEventHandler(string cardCode);

CardReader封装类,

的使用操作如下所示。我们通过事件就可以获取到完整的输入内容,然后进行数据的绑定或处理即可,代码如下所示。

    public partial classFrmProcessConsumption : BaseDock
{
/// <summary> ///会员信息/// </summary> private MemberInfo memberInfo { get; set; }/// <summary> ///读卡器接口/// </summary> privateCardReader cardReader;publicFrmProcessConsumption()
{
InitializeComponent();

................................

cardReader
= new CardReader(this);
cardReader.CardRead
+= newCardReadEventHandler(cardReader_CardRead);
}
void cardReader_CardRead(stringcardCode)
{
this.txtMember_CardNo.Text =cardCode;

BindMemberData();
}

然后我们为了方便使用,还可以定义一个统一的处理读卡器和扫描枪的接收数据的小窗口。

这个弹出的小窗口用来处理读卡器,扫描枪等信息的录入就可以了,当然上述的如CardReader/USBScanner还是可以独立使用,如我们在一个只读控件或者窗口里面,一样可以监听到对应的设备数据读取操作,但设备有数据读取完成过后,就会触发相应的事件了。

下面代码就是上面设备信息读取的代码

    /// <summary>
    ///读卡器、USB条码扫描器、串口条码扫描器数据读取及显示窗体/// </summary>
    public partial classDeviceReaderDialog : BaseForm
{
privateCardReader _cardReader;privateUSBScanner _usbScanner;public DeviceReaderDialog(DeviceType type =DeviceType.Card)
{
InitializeComponent();
//能手填 this.Readonly = false;if (type ==DeviceType.Card)
{
this._cardReader = new CardReader(this);this._cardReader.CardRead += newCardReadEventHandler(_cardReader_CardRead);
}
else if (type ==DeviceType.UsbScanner)
{
this._usbScanner = new USBScanner(this);this._usbScanner.ScannerRead += newScannerReadEventHandler(Scanner_ScannerRead);
}
}void Scanner_ScannerRead(stringscanCode)
{
this.txtCode.Text =scanCode;
DialogResult
=DialogResult.OK;
}
void _cardReader_CardRead(stringcardCode)
{
this.txtCode.Text =cardCode;
DialogResult
=DialogResult.OK;
}
public stringCode
{
get { returntxtCode.Text; }
}
public boolReadonly
{
get { returntxtCode.Properties.ReadOnly; }set{
txtCode.Properties.ReadOnly
=value;this.btnOK.Enabled = !value;this.btnOK.Visible = !value;
}
}
private void DeviceReaderDialog_Load(objectsender, EventArgs e)
{
if (!this.Readonly)
{
this.KeyDown += newKeyEventHandler(DeviceReaderDialog_KeyDown);
}
}
void DeviceReaderDialog_KeyDown(objectsender, KeyEventArgs e)
{
if (e.KeyCode ==Keys.Enter)
{
this.DialogResult =DialogResult.OK;
}
}
}

在我的博客里面,很多Winform程序里面都用到了分页处理,这样可以不管是在直接访问数据库的场景还是使用网络方式访问WCF服务获取数据,都能获得较好的效率,因此WInform程序里面的分页控件的使用是很好的一个解决方式,它能够快速获取数据,并将可能减少我们增加太多的代码逻辑,实现简单、高效、统一的理念。本篇主要介绍如何在分页的GridControl里面如何实现数据的统计功能。

一般情况下,分页控件呈现分为两种方式,一种需要有分页码的真正分页处理,一种是不需要分页处理,但是列表和功能基本保持一致的界面,其实这两种情况都是对列表控件的进一步封装,以方便使用的目的。

有分页底栏的控件界面:

无需分页的,保持一致界面的控件效果。

这两种方式,都是对GridControl控件的封装,而且他们的使用步骤和属性基本上保持一致。

使用代码如下所示。

            this.winGridView1.BestFitColumnWith = false;this.winGridView1.AppendedMenu = this.contextMenuStrip1;this.winGridView1.gridView1.DataSourceChanged += newEventHandler(gridView1_DataSourceChanged);this.winGridView1.OnRefresh += newEventHandler(winGridView1_OnRefresh);this.winGridView1.gridView1.CustomColumnDisplayText += newDevExpress.XtraGrid.Views.Base.CustomColumnDisplayTextEventHandler(gridView1_CustomColumnDisplayText);this.winGridView1.gridView1.RowCellStyle += new DevExpress.XtraGrid.Views.Grid.RowCellStyleEventHandler(gridView1_RowCellStyle);

那么我们如何在分页控件的基础上增加统计功能呢,由于分页控件的字段是根据配置自动创建的,我们需要在创建后,数据绑定的时候,把它增加上去,这样我们就可以实现数据的统计处理了,我们最终希望的统计效果是在底部统计栏目里面增加一些特殊的值的统计处理,界面如下所示,这个是我Winform开发框架里面,对于库存查询统计的一个案例,供参考。

上面的统计实现代码如下所示。

        /// <summary>
        ///常见汇总信息/// </summary>
        private voidCreateSummary()
{
GridView gridView1
= this.winGridView1.gridView1;if (gridView1 != null && gridView1.Columns.Count > 0)
{
gridView1.GroupSummary.Clear();

gridView1.OptionsView.ColumnAutoWidth
= false;
gridView1.OptionsView.GroupFooterShowMode
=DevExpress.XtraGrid.Views.Grid.GroupFooterShowMode.VisibleAlways;
gridView1.OptionsView.ShowFooter
= true;
gridView1.OptionsView.ShowGroupedColumns
= true;
gridView1.OptionsView.ShowGroupPanel
= false;

DevExpress.XtraGrid.Columns.GridColumn IDColumn
= gridView1.Columns["ID"];
IDColumn.Summary.AddRange(
newDevExpress.XtraGrid.GridSummaryItem[] {new DevExpress.XtraGrid.GridColumnSummaryItem(DevExpress.Data.SummaryItemType.Count, "ID", "记录数:{0}")});

DevExpress.XtraGrid.Columns.GridColumn StockQuantityColumn
= gridView1.Columns["STOCKQUANTITY"];
StockQuantityColumn.Summary.AddRange(
newDevExpress.XtraGrid.GridSummaryItem[] {new DevExpress.XtraGrid.GridColumnSummaryItem(DevExpress.Data.SummaryItemType.Sum, "STOCKQUANTITY", "库存数量:{0}")});

DevExpress.XtraGrid.Columns.GridColumn StockAmountColumn
= gridView1.Columns["STOCKAMOUNT"];
StockAmountColumn.Summary.AddRange(
newDevExpress.XtraGrid.GridSummaryItem[] {new DevExpress.XtraGrid.GridColumnSummaryItem(DevExpress.Data.SummaryItemType.Sum, "STOCKAMOUNT", "库存金额:{0:C2}")});
}
}

这个函数,我们可以在数据变化的时候,重新调用函数进行统计设置即可,如下代码所示。

        private void gridView1_DataSourceChanged(objectsender, EventArgs e)
{
if (this.winGridView1.gridView1.Columns.Count > 0 && this.winGridView1.gridView1.RowCount > 0)
{
//统一设置100宽度 foreach (DevExpress.XtraGrid.Columns.GridColumn column in this.winGridView1.gridView1.Columns)
{
column.Width
= 100;
}
//可特殊设置特别的宽度 SetGridColumWidth("Note", 200);
SetGridColumWidth(
"ItemNo", 120);
SetGridColumWidth(
"ItemBigType", 120);
SetGridColumWidth(
"WareHouse", 120);

SetGridColumWidth(
"ID", 100);
SetGridColumWidth(
"StockQuantity", 120);
SetGridColumWidth(
"StockAmount", 160);//ID,StockQuantity,Unit,Price SetGridColumWidth("Unit", 80);
SetGridColumWidth(
"Price", 80);
}

CreateSummary();
}

以上就是基于DevExpress样式的Winform分页控件的基础上实现的统计数据效果,希望对你使用有帮助。

我们在开发一些Winform程序的时候,除了常规的显示普通数据外,有的时候需要显示一些人员肖像或者一些车辆等物体的图片,一般这些内容较小,所以以二进制存储在数据库是一个不错的方案。但由于它们虽然很常用,设计数据库保存的逻辑又会使得整个控件的封装显得麻烦很多。本文介绍的肖像显示保存控件,通过事件的封装处理,让数据的保存不在依赖于数据库存储模块,实现更加通用的特性。

1、肖像显示保存控件的需求

我们在一些程序了里面,可能需要显示一些人员头像,车辆图片,物件图片等,这些图片可以从电脑上选取,也可以拍照,当然最重要的是,方便使用,并能存储到数据库里面,这个就是我们一般的需求了。

1)人员肖像显示效果:

2)车辆图片展示效果

控件的工具条上,提供了编辑图片,保存图片,恢复默认图片,拍照等功能,当然我们希望控件和整个界面一起,实现图片数据的方便加载和保存操作。

2、肖像显示保存控件的设计

前面我们看了一些常用的场景,对我们设计这个通用性的肖像显示保存控件有很好的指导意义,我们只需要把图片显示部分作为一个控件,然后公布一些事件给外部实现,从而实现我们需要的数据加载、数据保存等操作,至于其他的功能,我们可以集成到里面去。

我们设计一个用户自定义控件,上面放一个图片显示框,然后增加一些按钮在上面,并设置好工具栏的图片显示,效果如下所示。

由于我们需要给外部处理数据的绑定(需要从数据库加载)和数据的保存(保存到数据库里面),因此抛出两个委托代理定义。

    /// <summary>
    ///保存图片的处理代理定义/// </summary>
    /// <param name="id">记录ID</param>
    /// <param name="imageBytes">图片数据</param>
    /// <param name="imageType">图片类型</param>
    /// <returns></returns>
    public delegate bool SavePortraitHandler(string id, byte[] imageBytes, stringimageType);/// <summary>
    ///绑定图片数据的代理定义/// </summary>
    /// <param name="id">记录ID</param>
    /// <param name="imageType">图片类型</param>
    /// <returns></returns>
    public delegate byte[] BindPortraitHandler(string id, string imageType);

然后我们在用户控件里面增加一些属性和事件定义,部分代码如下所示。

    /// <summary>
    ///图片数据显示和采集控件/// </summary>
    public partial classPortraitControl : XtraUserControl
{
#region 事件及属性定义 /// <summary> ///保存图片操作的事件/// </summary> public eventSavePortraitHandler OnSavePortrait;/// <summary> ///绑定图片数据的事件/// </summary> public eventBindPortraitHandler OnBindPortait;/// <summary> ///记录ID/// </summary> public string ID { get; set; }/// <summary> ///图片是否被修改过/// </summary> public bool ImageIsDirty { get; set; }

然后我们需要实现的就是,公布两个公共方法,分别是数据的绑定,绑定到界面控件的操作函数,我们主要注意的是,这里调用了事件
OnBindPortait
,以实现数据库的解耦,代码如下所示。

        /// <summary>
        ///绑定图片的操作,触发绑定事件处理/// </summary>
        /// <param name="id">记录ID</param>
        public void BindPicture(stringid)
{
try{this.ID = id; //设置控件的ID值 #region 更新图片显示操作 if (OnBindPortait == null)
{
MessageDxUtil.ShowTips(
"控件未指定OnBindPortait事件处理");return;
}
byte[] imageBytes = OnBindPortait(id, ImageType);if (imageBytes != null)
{
this.picPortrait.Image =ImageHelper.ImageFromBytes(imageBytes);
}
else{
ResetDefaultImage(ImageType);
}
#endregion}catch(Exception ex)
{
MessageDxUtil.ShowError(ex.Message);
LogTextHelper.Error(ex);
}
}

数据库保存的实现,和上面思路差不多,也是实现事件的处理即可,通过调用事件
OnSavePortrait
,实现数据库的解耦。

        /// <summary>
        ///保存图片到服务器/// </summary>
        public bool SavePicture(stringid)
{
this.ID = id;//设置控件的ID值 if (string.IsNullOrEmpty(id))
{
MessageDxUtil.ShowTips(
"记录ID未指定,无法保存,请先保存数据!");return false;
}
if (OnSavePortrait == null)
{
MessageDxUtil.ShowTips(
"控件未指定OnSavePortrait处理事件!");return false;
}
if (picPortrait.Image != null)
{
try{byte[] imageBytes = ImageHelper.ImageToBytes(this.picPortrait.Image);bool sucess = false;if (OnSavePortrait != null)
{
sucess
= OnSavePortrait(this.ID, imageBytes, ImageType);
}
returnsucess;

}
catch(Exception ex)
{
MessageDxUtil.ShowError(ex.Message);
LogTextHelper.Error(ex);
}
}
return false;
}

3、肖像显示保存控件的使用

完成以上控件的设计和处理后,编译后,模块将增加一个用户控件,这样我们就可以在需要的界面模块里面,把这个控件拖动到设计界面里面去了,设计效果和下图类似。

这个时候,肖像显示保存控件就作为一个整体进行使用了。

由于我们设计控件的时候,我们把它和外部数据库存储和加载进行了隔离,因此这里需要进行整合,通过事件进行整合处理。调用代码如下所示。

    public partial classFrmEditDriver : BaseEditForm
{
publicFrmEditDriver()
{
InitializeComponent();
this.portraitControl1.ImageType = "个人肖像";this.portraitControl1.OnBindPortait += new BindPortraitHandler(portraitControl1_OnBindPortait);
this.portraitControl1.OnSavePortrait += new
SavePortraitHandler(portraitControl1_OnSavePortrait);
}

然后实现其中自动增加的事件函数即可,主要就是调用业务类实现数据的存储和加载处理逻辑,代码如下所示。

        bool portraitControl1_OnSavePortrait(string id, byte[] imageBytes, stringimageType)
{
return BLLFactory<Driver>.Instance.UpdateImageBytes(id, imageBytes, imageType);
}
byte[] portraitControl1_OnBindPortait(string id, stringimageType)
{
return BLLFactory<Driver>.Instance.GetImageBytes(id, imageType);
}

在数据的显示函数里面,我们主动调用控件的函数实现界面数据的绑定显示。

        /// <summary>
        ///数据显示的函数/// </summary>
        public override voidDisplayData()
{
InitDictItem();
//数据字典加载(公用) if (!string.IsNullOrEmpty(ID))
{
#region 显示信息DriverInfo info= BLLFactory<Driver>.Instance.FindByID(ID);if (info != null)
{
tempInfo
= info;//重新给临时对象赋值,使之指向存在的记录对象 txtHandNo.Text=info.HandNo;
txtName.Text
=info.Name;
txtMobile.Text
=info.Mobile;
txtDept.Text
=info.Dept;
txtStartDriveDate.SetDateTime(info.StartDriveDate);
txtSex.Text
=info.Sex;
txtBirthday.SetDateTime(info.Birthday);
txtNote.Text
=info.Note;
}
#endregion }else{ }//绑定图片 this.portraitControl1.BindPicture(tempInfo.ID);
}

而在保存按钮实现数据保存的时候,我们也可以调用控件自身的保存操作函数,实现数据的存储。

        /// <summary>
        ///新增状态下的数据保存/// </summary>
        /// <returns></returns>
        public override boolSaveAddNew()
{
DriverInfo info
= tempInfo;//必须使用存在的局部变量,因为部分信息可能被附件使用 SetInfo(info);try{#region 新增数据 bool succeed = BLLFactory<Driver>.Instance.Insert(info);if(succeed)
{
//可添加其他关联操作 this.portraitControl1.SavePicture(tempInfo.ID);return true;
}
#endregion}catch(Exception ex)
{
LogTextHelper.Error(ex);
MessageDxUtil.ShowError(ex.Message);
}
return false;
}

通过和数据库操作实现解耦,我们可以对这个控件进行更方便的重用,而代码也很简单,这样也就实现了我们统一化、简单化、可复用性的目标了。