2023年2月

开发C#相关的项目有很多年了,一直没有接触串口的开发,近期由于工作的需要,需要了解熟悉对硬件串口的开发,通过对串口的深入了解,串口也不再是什么神秘的东西,利用SerailPort组件,对串口的各种操作也非常的方便,由于本人总是喜欢把一些常用的东西封装成可供重复利用的类库,因此,阅百家代码,提炼总结优化,把对串口的操作封装成一个公用的类库,应付日常的串口编程开发,也算是工作的一个阶段性总结吧。

先上图,了解串口的一些基本的东西,并逐步介绍相关的知识。

微软在 .NET FrameWork2.0中对串口通讯进行了封装,我们可以在.net2.0及以上版本开发时直接使用SerialPort类对串口进行读写操作。
SerialPort类的属性主要包括:
1)串口名称(PortName)
2)波特率(BaudRate)
3)数据位 DataBits
4)停止位 StopBits
5)奇偶校验 Parity
SerialPort类的事件主要包括:
DataReceived:用于异步接收串口数据事件

ErrorReceived:错误处理事件

SerialPort类的方法主要包括:

Open();Close();Read();Write()、DiscardInBuffer()、DiscardOutBuffer()等

从上面的测试例子图中,我们可以看到,一般对串口的操作,需要提供串口号、波特率、数据位、停止位、奇偶校验的参数,用来构造一个串口操作类,以便实现具体的串口操作,而这些参数有的是系统内置的枚举参数,我们可以通过遍历枚举对象来绑定下来列表(如停止位、奇偶校验);但有些参数却不是系统内置的枚举类型,例如波特率、数据位等,而且对这些参数操作也是串口开发经常用到的,因此,第一步,我对这些参数的绑定做了一个简单的封装。

1、先构造波特率、数据位这两个枚举对象,方便实际操作。


///

<summary>


///
串口数据位列表(5,6,7,8)

///

</summary>



public

enum
SerialPortDatabits :
int

{
FiveBits

=

5
,
SixBits

=

6
,
SeventBits

=

7
,
EightBits

=

8

}


///

<summary>


///
串口波特率列表。

///
75,110,150,300,600,1200,2400,4800,9600,14400,19200,28800,38400,56000,57600,

///
115200,128000,230400,256000

///

</summary>



public

enum
SerialPortBaudRates :
int

{
BaudRate_75

=

75
,
BaudRate_110

=

110
,
BaudRate_150

=

150
,
BaudRate_300

=

300
,
BaudRate_600

=

600
,
BaudRate_1200

=

1200
,
BaudRate_2400

=

2400
,
BaudRate_4800

=

4800
,
BaudRate_9600

=

9600
,
BaudRate_14400

=

14400
,
BaudRate_19200

=

19200
,
BaudRate_28800

=

28800
,
BaudRate_38400

=

38400
,
BaudRate_56000

=

56000
,
BaudRate_57600

=

57600
,
BaudRate_115200

=

115200
,
BaudRate_128000

=

128000
,
BaudRate_230400

=

230400
,
BaudRate_256000

=

256000

}

2、对常用的参数下拉列表绑定做一个封装。


///

<summary>


///
设置串口号

///

</summary>


///

<param name="obj"></param>



public

static

void
SetPortNameValues(ComboBox obj)
{
obj.Items.Clear();

foreach
(
string
str
in
SerialPort.GetPortNames())
{
obj.Items.Add(str);
}
}


///

<summary>


///
设置波特率

///

</summary>



public

static

void
SetBauRateValues(ComboBox obj)
{
obj.Items.Clear();

foreach
(SerialPortBaudRates rate
in
Enum.GetValues(
typeof
(SerialPortBaudRates)))
{
obj.Items.Add(((

int
)rate).ToString());
}
}


///

<summary>


///
设置数据位

///

</summary>



public

static

void
SetDataBitsValues(ComboBox obj)
{
obj.Items.Clear();

foreach
(SerialPortDatabits databit
in
Enum.GetValues(
typeof
(SerialPortDatabits)))
{
obj.Items.Add(((

int
)databit).ToString());
}
}


///

<summary>


///
设置校验位列表

///

</summary>



public

static

void
SetParityValues(ComboBox obj)
{
obj.Items.Clear();

foreach
(
string
str
in
Enum.GetNames(
typeof
(Parity)))
{
obj.Items.Add(str);
}
}


///

<summary>


///
设置停止位

///

</summary>



public

static

void
SetStopBitValues(ComboBox obj)
{
obj.Items.Clear();

foreach
(
string
str
in
Enum.GetNames(
typeof
(StopBits)))
{
obj.Items.Add(str);
}
}

这样我们在窗体界面代码中,绑定相关参数的数据源就很方便了,如下所示。


private

void
Form1_Load(
object
sender, EventArgs e)
{
BindData();
}


private

void
BindData()
{

//
绑定端口号


SerialPortUtil.SetPortNameValues(txtPort);
txtPort.SelectedIndex

=

0
;


//
波特率


SerialPortUtil.SetBauRateValues(txtBaudRate);
txtBaudRate.SelectedText

=

"
57600
"
;


//
数据位


SerialPortUtil.SetDataBitsValues(txtDataBits);

this
.txtDataBits.SelectedText
=

"
8
"
;


//
校验位


SerialPortUtil.SetParityValues(txtParity);

this
.txtParity.SelectedIndex
=

0
;


//
停止位


SerialPortUtil.SetStopBitValues(txtStopBit);

this
.txtStopBit.SelectedIndex
=

1
;


this
.btnSend.Enabled
=
isOpened;
}

3、 为了方便构造封装的窗口类,提供了两个不同类型参数的串口辅助类构造函数,一个可以使用枚举参数,一个使用字符串参数(最终转换为枚举参数对象),如下所示。使用枚举对象,不需要记住不同参数应该填写那些值,只需要从枚举中选择即可,方便又直观。


///

<summary>


///
参数构造函数(使用枚举参数构造)

///

</summary>


///

<param name="baud">
波特率
</param>


///

<param name="par">
奇偶校验位
</param>


///

<param name="sBits">
停止位
</param>


///

<param name="dBits">
数据位
</param>


///

<param name="name">
串口号
</param>



public
SerialPortUtil(
string
name, SerialPortBaudRates baud, Parity par, SerialPortDatabits dBits, StopBits sBits)
{
_portName

=
name;
_baudRate

=
baud;
_parity

=
par;
_dataBits

=
dBits;
_stopBits

=
sBits;

comPort.DataReceived

+=

new
SerialDataReceivedEventHandler(comPort_DataReceived);
comPort.ErrorReceived

+=

new
SerialErrorReceivedEventHandler(comPort_ErrorReceived);
}


///

<summary>


///
参数构造函数(使用字符串参数构造)

///

</summary>


///

<param name="baud">
波特率
</param>


///

<param name="par">
奇偶校验位
</param>


///

<param name="sBits">
停止位
</param>


///

<param name="dBits">
数据位
</param>


///

<param name="name">
串口号
</param>



public
SerialPortUtil(
string
name,
string
baud,
string
par,
string
dBits,
string
sBits)
{
_portName

=
name;
_baudRate

=
(SerialPortBaudRates)Enum.Parse(
typeof
(SerialPortBaudRates), baud);
_parity

=
(Parity)Enum.Parse(
typeof
(Parity), par);
_dataBits

=
(SerialPortDatabits)Enum.Parse(
typeof
(SerialPortDatabits), dBits);
_stopBits

=
(StopBits)Enum.Parse(
typeof
(StopBits), sBits);

comPort.DataReceived

+=

new
SerialDataReceivedEventHandler(comPort_DataReceived);
comPort.ErrorReceived

+=

new
SerialErrorReceivedEventHandler(comPort_ErrorReceived);
}

构造函数做好了,就很方便在实际的窗体界面函数中构造串口实例了,如下使用代码所示:


private

void
btnConnect_Click(
object
sender, EventArgs e)
{

try

{

if
(serial
==

null
)
{

try

{

string
portname
=

this
.txtPort.Text;
SerialPortBaudRates rate

=
(SerialPortBaudRates)Enum.Parse(
typeof
(SerialPortBaudRates),
this
.txtBaudRate.Text);
//
int.Parse(this.txtBaudRate.Text);


SerialPortDatabits databit
=
(SerialPortDatabits)
int
.Parse(
this
.txtDataBits.Text);
Parity party

=
(Parity)Enum.Parse(
typeof
(Parity),
this
.txtParity.Text);
StopBits stopbit

=
(StopBits)Enum.Parse(
typeof
(StopBits),
this
.txtStopBit.Text);


//
使用枚举参数构造

//
serial = new SerialPortUtil(portname, rate, party, databit, stopbit);


//
使用字符串参数构造


serial
=

new
SerialPortUtil(portname,
this
.txtBaudRate.Text,
this
.txtParity.Text,
this
.txtDataBits.Text,
this
.txtStopBit.Text);
serial.DataReceived

+=

new
DataReceivedEventHandler(serial_DataReceived);

}

catch
(Exception ex)
{
MessageBox.Show(ex.Message);
serial

=

null
;

return
;
}
}


if
(
!
isOpened)
{
serial.OpenPort();
btnConnect.Text

=

"
断开
"
;
}

else

{
serial.ClosePort();
serial

=

null
;

btnConnect.Text

=

"
连接
"
;
}

isOpened

=

!
isOpened;

this
.btnSend.Enabled
=
isOpened;

this
.lblTips.Text
=
isOpened
?

"
已连接
"
:
"
未连接
"
;
}

catch
(Exception ex)
{

this
.lblTips.Text
=
ex.Message;
MessageBox.Show(ex.Message);
}
}

4、对串口数据的发送以及串口的一些基本操作进行简单封装,方便辅助类对串口进行相关操作。


///

<summary>


///
端口是否已经打开

///

</summary>



public

bool
IsOpen
{

get

{

return
comPort.IsOpen;
}
}


///

<summary>


///
打开端口

///

</summary>


///

<returns></returns>



public

void
OpenPort()
{

if
(comPort.IsOpen) comPort.Close();

comPort.PortName

=
_portName;
comPort.BaudRate

=
(
int
)_baudRate;
comPort.Parity

=
_parity;
comPort.DataBits

=
(
int
)_dataBits;
comPort.StopBits

=
_stopBits;

comPort.Open();
}


///

<summary>


///
关闭端口

///

</summary>



public

void
ClosePort()
{

if
(comPort.IsOpen) comPort.Close();
}


///

<summary>


///
丢弃来自串行驱动程序的接收和发送缓冲区的数据

///

</summary>



public

void
DiscardBuffer()
{
comPort.DiscardInBuffer();
comPort.DiscardOutBuffer();
}


///

<summary>


///
写入数据

///

</summary>


///

<param name="msg"></param>



public

void
WriteData(
string
msg)
{

if
(
!
(comPort.IsOpen)) comPort.Open();

comPort.Write(msg);
}


///

<summary>


///
写入数据

///

</summary>


///

<param name="msg">
写入端口的字节数组
</param>



public

void
WriteData(
byte
[] msg)
{

if
(
!
(comPort.IsOpen)) comPort.Open();

comPort.Write(msg,

0
, msg.Length);
}


///

<summary>


///
写入数据

///

</summary>


///

<param name="msg">
包含要写入端口的字节数组
</param>


///

<param name="offset">
参数从0字节开始的字节偏移量
</param>


///

<param name="count">
要写入的字节数
</param>



public

void
WriteData(
byte
[] msg,
int
offset,
int
count)
{

if
(
!
(comPort.IsOpen)) comPort.Open();

comPort.Write(msg, offset, count);
}

6、接收数据的还原

这样基本上就对串口封装的差不多了,不过还有一个重要的操作就是对串口的数据进行接收,并进行处理。由于串口获取数据不是一次性完整的获取的,可能会被拆分为好几段,因此,如何还原接收到的数据也就是一个值得注意的问题,这个最好能根据协议来确定,如我的协议基本上是以“~”符号开始,以“#”符号结束,因此我对协议数据的还原,就有可依据的准则。

1)首先要构造一个数据处理的代理,和一个数据处理的事件参数类,如下所示。


public

class
DataReceivedEventArgs : EventArgs
{

public

string
DataReceived;

public
DataReceivedEventArgs(
string
m_DataReceived)
{

this
.DataReceived
=
m_DataReceived;
}
}



public

delegate

void
DataReceivedEventHandler(DataReceivedEventArgs e);

2)然后构造一个数据接收和错误处理的事件,如下所示


///

<summary>


///
完整协议的记录处理事件

///

</summary>



public

event
DataReceivedEventHandler DataReceived;


public

event
SerialErrorReceivedEventHandler Error;

3)在构造函数添加相关的事件处理,如下所示


///

<summary>


///
参数构造函数(使用枚举参数构造)

///

</summary>


///

<param name="baud">
波特率
</param>


///

<param name="par">
奇偶校验位
</param>


///

<param name="sBits">
停止位
</param>


///

<param name="dBits">
数据位
</param>


///

<param name="name">
串口号
</param>



public
SerialPortUtil(
string
name, SerialPortBaudRates baud, Parity par, SerialPortDatabits dBits, StopBits sBits)
{
_portName

=
name;
_baudRate

=
baud;
_parity

=
par;
_dataBits

=
dBits;
_stopBits

=
sBits;

comPort.DataReceived

+=

new
SerialDataReceivedEventHandler(comPort_DataReceived);
comPort.ErrorReceived

+=

new
SerialErrorReceivedEventHandler(comPort_ErrorReceived);
}

4)实现对数据的接收和错误的处理

/// <summary>
/// 结束符比特
/// </summary>
public byte EndByte = 0x23;//string End = "#";



///

<summary>


///
数据接收处理

///

</summary>



void
comPort_DataReceived(
object
sender, SerialDataReceivedEventArgs e)
{

//
禁止接收事件时直接退出



if
(ReceiveEventFlag)
return
;


#region
根据结束字节来判断是否全部获取完成

List

<
byte
>
_byteData
=

new
List
<
byte
>
();

bool
found
=

false
;
//
是否检测到结束符号



while
(comPort.BytesToRead
>

0

||

!
found)
{

byte
[] readBuffer
=

new

byte
[comPort.ReadBufferSize
+

1
];

int
count
=
comPort.Read(readBuffer,
0
, comPort.ReadBufferSize);

for
(
int
i
=

0
; i
<
count; i
++
)
{
_byteData.Add(readBuffer[i]);


if
(readBuffer[i]
==
EndByte)
{
found

=

true
;
}
}
}

#endregion



//
字符转换



string
readString
=
System.Text.Encoding.Default.GetString(_byteData.ToArray(),
0
, _byteData.Count);


//
触发整条记录的处理



if
(DataReceived
!=

null
)
{
DataReceived(

new
DataReceivedEventArgs(readString));
}
}


///

<summary>


///
错误处理函数

///

</summary>



void
comPort_ErrorReceived(
object
sender, SerialErrorReceivedEventArgs e)
{

if
(Error
!=

null
)
{
Error(sender, e);
}
}

在数据的接收还原中,我们用到了

EndByte的变量,这个变量是协议数据的结束字符,如果检测到有这个字符的,就表明收到了一条完整的协议,可以把收到的字节数组组装成文本字符串,然后交给委托事件进行处理即可。

在外部的宿主程序中,当有数据收到的时候,辅助类会通知其对数据进行处理,如我们在宿主程序中绑定处理代码如下所示。


void
serial_DataReceived(DataReceivedEventArgs e)
{

this
.txtReceived.Invoke(
new
MethodInvoker(
delegate

{

this
.txtReceived.AppendText(e.DataReceived
+
Environment.NewLine);
}));
}

这样,一旦收到一条完整的协议,界面上就会在文本框中增加一行数据,如前面的图所示

最后呈上该串口辅助类库提供下载,欢迎大家提供改善意见,多多交流。

https://files.cnblogs.com/wuhuacong/SerialPortUtil.rar

俗话说,一个好汉十个帮,众人拾柴火焰高等都说明一个道理,有更多的资源,更丰富的积累,都是助你走向成功,走向顶峰的推动力。
继续前面的随笔系列,介绍公用类库的详细使用,既上篇介绍了数据库相关的操作内容后,本篇主要介绍文件相关的处理辅助类。本篇介绍的辅助类包括CSV操作辅助类,非VBA操作的Excel辅助类、常用文件操作辅助类、常用目录操作辅助类、打开及保存文件对话框操作辅助类、INI文件操作辅助类、监视文件变化的类、独立存储操作辅助类等文件相关的辅助类。

1、CSV文件和DataTable对象转换辅助类 CSVHelper

实现效果

1)本辅助类主要是用来方便实现CSV文件和DataTable对象的相互转换。

2)逗号分隔型取值格式(英文全称为Comma Separated Values,简称CSV),是一种纯文本格式,用来存储数据。在CSV中,数据的字段由逗号分开,程序通过读取文件重新创建正确的字段,方法是每次遇到逗号时开始新一段数据。CSV除了可以用记事本等文本工具打开外,还可以用Excel打开,其效果和Excel很类似,因此二维表格数据一般也可以导出成CSV格式的文件。由于CSV文件可以使用Excel打开并操作,但导出CSV文件不需要客户端安装Excel软件,因此非常方便易用。

实现代码

1)辅助类提供的方法接口如下所示:


///

<summary>


///

CSV转换成DataTable(OleDb数据库访问方式)

///

</summary>


///

<param name="csvPath">
csv文件路径
</param>


///

<returns></returns>



public

static
DataTable CSVToDataTableByOledb(
string
csvPath)


///

<summary>


///
CSV转换成DataTable(文件流方式)

///

</summary>


///

<param name="csvPath">
csv文件路径
</param>


///

<returns></returns>



public

static
DataTable CSVToDataTableByStreamReader(
string
csvPath)


///

<summary>


///
DataTable 生成 CSV

///

</summary>


///

<param name="dt">
DataTable
</param>


///

<param name="csvPath">
csv文件路径
</param>



public

static

void
DataTableToCSV(DataTable dt,
string
csvPath)

2) 辅助类CSVHelper的使用例子代码如下所示

string
access
=

@"
C:\Orderwater.mdb
"
;
OleDbHelper helper

=

new
OleDbHelper(access);

string
sql
=

string
.Format(
"
Select * from All_Customer
"
);
DataTable dt

=
helper.ExecuteDataSet(sql).Tables[
0
];


//
导出到CSV文件


string
fileName
=
Path.Combine(Application.StartupPath,
"
customer.csv
"
);
CSVHelper.DataTableToCSV(dt, fileName);


//
从CSV文件导入到DataTable


DataTable dtNew
=
CSVHelper.CSVToDataTableByOledb(fileName);

this
.dataGridView1.DataSource
=
dtNew.DefaultView;

2、 Excel操作辅助类(无需VBA引用) ExcelHelper

实现效果

1)本辅助类主要是用来方便实现对Excel的相关操作,不需要调用Office的VBA相关类。
该导出操作是基于XML文件和OleDB格式的,因此导出Excel文件不需要客户端安装Excel软件,因此非常方便易用

2) 辅助类可以列出Excel的所有表、列出指定表的所有列、从Excel转换为DataSet对象集合、把DataSet转换保存为Excel文件等操作。

实现代码

1) 辅助类提供的方法接口如下所示:

#region
获取Excel连接字符串



///

<summary>


///
返回Excel 连接字符串   [IMEX=1]

///

</summary>


///

<param name="excelPath">
Excel文件 绝对路径
</param>


///

<param name="header">
是否把第一行作为列名
</param>


///

<param name="eType">
Excel 版本
</param>


///

<returns></returns>



public

static

string
GetExcelConnectstring(
string
excelPath,
bool
header, ExcelType eType)


///

<summary>


///
返回Excel 连接字符串

///

</summary>


///

<param name="excelPath">
Excel文件 绝对路径
</param>


///

<param name="header">
是否把第一行作为列名
</param>


///

<param name="eType">
Excel 版本
</param>


///

<param name="imex">
IMEX模式
</param>


///

<returns></returns>



public

static

string
GetExcelConnectstring(
string
excelPath,
bool
header, ExcelType eType, IMEXType imex)


#endregion




#region
获取Excel工作表名



///

<summary>


///
返回Excel工作表名

///

</summary>


///

<param name="excelPath">
Excel文件 绝对路径
</param>


///

<param name="eType">
Excel 版本
</param>


///

<returns></returns>



public

static
List
<
string
>
GetExcelTablesName(
string
excelPath, ExcelType eType)


///

<summary>


///
返回Excel工作表名

///

</summary>


///

<param name="connectstring">
excel连接字符串
</param>


///

<returns></returns>



public

static
List
<
string
>
GetExcelTablesName(
string
connectstring)


///

<summary>


///
返回Excel工作表名

///

</summary>


///

<param name="connection">
excel连接
</param>


///

<returns></returns>



public

static
List
<
string
>
GetExcelTablesName(OleDbConnection connection)


///

<summary>


///
返回Excel第一个工作表表名

///

</summary>


///

<param name="excelPath">
Excel文件 绝对路径
</param>


///

<param name="eType">
Excel 版本
</param>


///

<returns></returns>



public

static

string
GetExcelFirstTableName(
string
excelPath, ExcelType eType)


///

<summary>


///
返回Excel第一个工作表表名

///

</summary>


///

<param name="connectstring">
excel连接字符串
</param>


///

<returns></returns>



public

static

string
GetExcelFirstTableName(
string
connectstring)


///

<summary>


///
返回Excel第一个工作表表名

///

</summary>


///

<param name="connection">
excel连接
</param>


///

<returns></returns>



public

static

string
GetExcelFirstTableName(OleDbConnection connection)


///

<summary>


///
获取Excel文件中指定工作表的列

///

</summary>


///

<param name="excelPath">
Excel文件 绝对路径
</param>


///

<param name="table">
名称 excel table  例如:Sheet1$
</param>


///

<returns></returns>



public

static
List
<
string
>
GetColumnsList(
string
excelPath, ExcelType eType,
string
table)


#endregion




#region
EXCEL导入DataSet



///

<summary>


///
EXCEL导入DataSet

///

</summary>


///

<param name="excelPath">
Excel文件 绝对路径
</param>


///

<param name="table">
名称 excel table  例如:Sheet1$
</param>


///

<param name="header">
是否把第一行作为列名
</param>


///

<param name="eType">
Excel 版本
</param>


///

<returns>
返回Excel相应工作表中的数据 DataSet   [table不存在时返回空的DataSet]
</returns>



public

static
DataSet ExcelToDataSet(
string
excelPath,
string
table,
bool
header, ExcelType eType)


///

<summary>


///
判断工作表名是否存在

///

</summary>


///

<param name="connection">
excel连接
</param>


///

<param name="table">
名称 excel table  例如:Sheet1$
</param>


///

<returns></returns>



private

static

bool
isExistExcelTableName(OleDbConnection connection,
string
table)


///

<summary>


///
EXCEL导入DataSet

///

</summary>


///

<param name="connectstring">
excel连接字符串
</param>


///

<param name="table">
名称 excel table  例如:Sheet1$
</param>


///

<returns>
返回Excel相应工作表中的数据 DataSet   [table不存在时返回空的DataSet]
</returns>



public

static
DataSet ExcelToDataSet(
string
connectstring,
string
table)


///

<summary>


///
EXCEL所有工作表导入DataSet

///

</summary>


///

<param name="excelPath">
Excel文件 绝对路径
</param>


///

<param name="header">
是否把第一行作为列名
</param>


///

<param name="eType">
Excel 版本
</param>


///

<returns>
返回Excel第一个工作表中的数据 DataSet
</returns>



public

static
DataSet ExcelToDataSet(
string
excelPath,
bool
header, ExcelType eType)


///

<summary>


///
EXCEL所有工作表导入DataSet

///

</summary>


///

<param name="connectstring">
excel连接字符串
</param>


///

<returns>
返回Excel第一个工作表中的数据 DataSet
</returns>



public

static
DataSet ExcelToDataSet(
string
connectstring)


#endregion




///

<summary>


///
把一个数据集中的数据导出到Excel文件中(XML格式操作)

///

</summary>


///

<param name="source">
DataSet数据
</param>


///

<param name="fileName">
保存的Excel文件名
</param>



public

static

void
DataSetToExcel(DataSet source,
string
fileName)


///

<summary>


///
将DataTable到处为Excel(OleDb 方式操作)

///

</summary>


///

<param name="dataTable">

</param>


///

<param name="fileName">
导出默认文件名
</param>


public

static

void
DataSetToExcel(DataTable dataTable,
string
fileName)

2)辅助类ExcelHeper的使用例子代码如下所示

string
filePath
=

@"
C:\test.xls
"
;

//
获取第一个表名


string
sheetname
=
ExcelHelper.GetExcelFirstTableName(filePath, ExcelHelper.ExcelType.Excel2003);
//
Sheet1$


//
列出所有表名称


List
<
string
>
tableList
=
ExcelHelper.GetExcelTablesName(filePath, ExcelHelper.ExcelType.Excel2003);


//
从Excel转换为DataSet对象集合


DataSet ds
=
ExcelHelper.ExcelToDataSet(filePath,
true
, ExcelHelper.ExcelType.Excel2003);


//
列出指定表的列名称


List
<
string
>
columnList
=
ExcelHelper.GetColumnsList(filePath, ExcelHelper.ExcelType.Excel2003,
"
Sheet1$
"
);


//
绑定数据显示


this
.dataGridView1.DataSource
=
ds.Tables[
0
].DefaultView;


//
导出DataSet到Excel文件中


filePath
=
FileDialogHelper.SaveExcel();

ExcelHelper.DataSetToExcel(ds, filePath);
Process.Start(filePath);

3、 常用文件操作辅助类 FileUtil

实现效果

1)本辅助类主要是用来方便实现文件相关的操作,包括Stream、byte[] 和 文件之间的转换、获取文件编码、获取文件长度、创建文件、删除文件、移动文件、读取文件、读取文件属性、设置文件属性等功能。

2) 辅助类主要提供File、FileInfo、FileStream、MemoryStream、Stream、StreamReader、Encode等类的封装,提供文件相关的操作功能。

实现代码

1) 由于辅助类接口函数比较多,在此列出部分接口,辅助类提供的方法部分接口如下所示:

///

<summary>


/

//
向文本文件中写入内容
///

</summary>


///

<param name="filePath">
文件的绝对路径
</param>


///

<param name="content">
写入的内容
</param>



public

static

void
WriteText(
string
filePath,
string
content)


///

<summary>


///
向文本文件的尾部追加内容

///

</summary>


///

<param name="filePath">
文件的绝对路径
</param>


///

<param name="content">
写入的内容
</param>



public

static

void
AppendText(
string
filePath,
string
content)


///

<summary>


///
将源文件的内容复制到目标文件中

///

</summary>


///

<param name="sourceFilePath">
源文件的绝对路径
</param>


///

<param name="destFilePath">
目标文件的绝对路径
</param>



public

static

void
Copy(
string
sourceFilePath,
string
destFilePath)


///

<summary>


///
将文件移动到指定目录

///

</summary>


///

<param name="sourceFilePath">
需要移动的源文件的绝对路径
</param>


///

<param name="descDirectoryPath">
移动到的目录的绝对路径
</param>



public

static

void
Move(
string
sourceFilePath,
string
descDirectoryPath)


///

<summary>


///
检测指定文件是否存在,如果存在则返回true。

///

</summary>


///

<param name="filePath">
文件的绝对路径
</param>



public

static

bool
IsExistFile(
string
filePath)


///

<summary>


///
创建一个文件。

///

</summary>


///

<param name="filePath">
文件的绝对路径
</param>



public

static

void
CreateFile(
string
filePath)


///

<summary>


///
创建一个文件,并将字节流写入文件。

///

</summary>


///

<param name="filePath">
文件的绝对路径
</param>


///

<param name="buffer">
二进制流数据
</param>



public

static

void
CreateFile(
string
filePath,
byte
[] buffer)


#region
XML文件操作


///

<summary>


///
从XML文件转换为Object对象类型.

///

</summary>


///

<param name="path">
XML文件路径
</param>


///

<param name="type">
Object对象类型
</param>


///

<returns></returns>



public

static

object
LoadObjectFromXml(
string
path, Type type)


///

<summary>


///
保存对象到特定格式的XML文件

///

</summary>


///

<param name="path">
XML文件路径.
</param>


///

<param name="obj">
待保存的对象
</param>



public

static

void
SaveObjectToXml(
string
path,
object
obj)


#endregion

2)辅助类FileUtil的使用例子1代码如下所示

string
filePath
=

"
C:\\Test.txt
"
;

//
创建一个文件并添加文本


FileUtil.AppendText(filePath,
"
测试内容
"
);


//
获取文件编码


Encoding encode
=
FileUtil.GetEncoding(filePath);

string
encodename
=
encode.EncodingName;


//
读取文件内容


string
content
=
FileUtil.FileToString(filePath);


//
读取文件到内存流


Stream stream
=
FileUtil.FileToStream(filePath);
stream.Close();


//
获取文件创建时间


DateTime dtCreate
=
FileUtil.GetFileCreateTime(filePath);


//
设置文件只读

FileUtil.SetFileReadonly(filePath,
true
);

例子2如下代码所示

public

static
DatabaseSetting[] ReadSettings()
{

if
(
!
File.Exists(XmlPath))
{

throw

new
FileNotFoundException(
"
File not found: DatabaseSetting.xml
"
);
}

DatabaseSetting[] settings

=
FileUtil.LoadObjectFromXml(XmlPath,
typeof
(DatabaseSetting[]))
as
DatabaseSetting[];

return
settings;
}


public

static

bool
Save(DatabaseSetting[] settings)
{

bool
breturn
=

false
;

if
(settings
!=

null
)
{
FileUtil.SaveObjectToXml(XmlPath, settings);
breturn

=

true
;
}

return
breturn;
}


public

static
DatabaseSetting[] Add(DatabaseSetting setting)
{
DatabaseSetting[] settingArray

=
ReadSettings();

if
(setting
!=

null
)
{
List

<
DatabaseSetting
>
list
=

new
List
<
DatabaseSetting
>
(settingArray);
list.Add(setting);
settingArray

=
list.ToArray();

FileUtil.SaveObjectToXml(XmlPath, settingArray);
}

return
settingArray;

}

4、 常用的目录操作辅助类 DirectoryUtil

实现效果

1)本辅助类主要是用来方便实现目录操作的相关功能,包括目录可写与空间计算、获取指定目录中的文件列表、获取指定目录中的子目录列表、创建目录、生成目录、检测目录等目录操作功能。

2) 辅助类主要提供Environment、Path、Directory、DirectoryInfo等对象的封装,提供目录相关的操作。

实现代码

1) 辅助类提供的方法接口如下所示,由于接口函数较多,提供部分接口:

#region
目录可写与空间计算



///

<summary>


///
检查目录是否可写,如果可以,返回True,否则False

///

</summary>


///

<param name="path"></param>


///

<returns></returns>



public

static

bool
IsWriteable(
string
path)


///

<summary>


///
检查磁盘是否有足够的可用空间

///

</summary>


///

<param name="path"></param>


///

<param name="requiredSpace"></param>


///

<returns></returns>



public

static

bool
IsDiskSpaceEnough(
string
path,
ulong
requiredSpace)


///

<summary>


///
获取驱动盘符的可用空间大小

///

</summary>


///

<param name="driveName">
Direve name
</param>


///

<returns>
free space (byte)
</returns>



public

static

ulong
GetFreeSpace(
string
driveName)


#endregion




#region
目录操作



#region
获取指定目录中的文件列表


///

<summary>


///
获取指定目录中所有文件列表

///

</summary>


///

<param name="directoryPath">
指定目录的绝对路径
</param>



public

static

string
[] GetFileNames(
string
directoryPath)


///

<summary>


///
获取指定目录及子目录中所有文件列表

///

</summary>


///

<param name="directoryPath">
指定目录的绝对路径
</param>


///

<param name="searchPattern">
模式字符串,"*"代表0或N个字符,"?"代表1个字符。

///
范例:"Log*.xml"表示搜索所有以Log开头的Xml文件。
</param>


///

<param name="isSearchChild">
是否搜索子目录
</param>



public

static

string
[] GetFileNames(
string
directoryPath,
string
searchPattern,
bool
isSearchChild)


#endregion




#region
获取指定目录中的子目录列表


///

<summary>


///
获取指定目录中所有子目录列表,若要搜索嵌套的子目录列表,请使用重载方法.

///

</summary>


///

<param name="directoryPath">
指定目录的绝对路径
</param>



public

static

string
[] GetDirectories(
string
directoryPath)


///

<summary>


///
获取指定目录及子目录中所有子目录列表

///

</summary>


///

<param name="directoryPath">
指定目录的绝对路径
</param>


///

<param name="searchPattern">
模式字符串,"*"代表0或N个字符,"?"代表1个字符。

///
范例:"Log*.xml"表示搜索所有以Log开头的Xml文件。
</param>


///

<param name="isSearchChild">
是否搜索子目录
</param>



public

static

string
[] GetDirectories(
string
directoryPath,
string
searchPattern,
bool
isSearchChild)

#endregion

2)辅助类DirectoryUtil的使用例子代码如下所示

string
ticketFilePath
=
PCDataCollector_Config.Default.TickDataFilePath;
DirectoryUtil.AssertDirExist(ticketFilePath);


//
对存在的票据数据进行处理


string
[] ticketFiles
=
Directory.GetFiles(ticketFilePath);

foreach
(
string
file
in
ticketFiles)
{
DealTicketFile(file);
}

5、 打开、保存文件对话框操作辅助类 FileDialogHelper

实现效果

1)本辅助类主要是用来方便实现打开、保存文件对话框的操作,如常用的图片文件、Excel文件、Access文件、文本文件、压缩文件、颜色等对话框的操作。

2) 该辅助类封装了FolderBrowserDialog、OpenFileDialog、SaveFileDialog、ColorDialog等对话框的常用对象的操作,快速实现文件打开、保存等操作。

实现代码

1) 由于提供各种打开对话框,保存对话框等代码类似,在此以文本文件对话框操作为例,辅助类提供的方法接口如下所示,其他如Excel、压缩文件、图片文件、Access数据库文件等类似操作,都提供了多种重载方法。

文件打开或者保存,对应不同的格式,如图片,提供多种格式列表进行选择等。


private

static

string
ExcelFilter
=

"
Excel(*.xls)|*.xls|All File(*.*)|*.*
"
;

private

static

string
ImageFilter
=

"
Image Files(*.BMP;*.bmp;*.JPG;*.jpg;*.GIF;*.gif;*.png)|(*.BMP;*.bmp;*.JPG;*.jpg;*.GIF;*.gif;*.png)|All File(*.*)|*.*
"
;

private

static

string
HtmlFilter
=

"
HTML files (*.html;*.htm)|*.html;*.htm|All files (*.*)|*.*
"
;

private

static

string
AccessFilter
=

"
Access(*.mdb)|*.mdb|All File(*.*)|*.*
"
;

private

static

string
ZipFillter
=

"
Zip(*.zip)|*.zip|All files (*.*)|*.*
"
;

private

const

string
ConfigFilter
=

"
配置文件(*.cfg)|*.cfg|All File(*.*)|*.*
"
;

private

static

string
TxtFilter
=

"
(*.txt)|*.txt|All files (*.*)|*.*
"
;


#region
Txt相关对话框


///

<summary>


///
打开Txt对话框

///

</summary>


///

<returns></returns>



public

static

string
OpenText()
{

return
Open(
"
文本文件选择
"
, TxtFilter);
}


///

<summary>


///
保存Excel对话框,并返回保存全路径

///

</summary>


///

<returns></returns>



public

static

string
SaveText()
{

return
SaveText(
string
.Empty);
}


///

<summary>


///
保存Excel对话框,并返回保存全路径

///

</summary>


///

<returns></returns>



public

static

string
SaveText(
string
filename)
{

return
Save(
"
保存文本文件
"
, TxtFilter, filename);
}


///

<summary>


///
保存Excel对话框,并返回保存全路径

///

</summary>


///

<returns></returns>



public

static

string
SaveText(
string
filename,
string
initialDirectory)
{

return
Save(
"
保存文本文件
"
, TxtFilter, filename, initialDirectory);
}
#endregion

2)辅助类的使用例子代码如下所示,示例弹出一个保存文件对话框,用户选定保存Excel文件后,执行数据导出操作。

private

void
menuExcel_Click(
object
sender, EventArgs e)
{

string
savePath
=
FileDialogHelper.SaveExcel();

if
(
!
string
.IsNullOrEmpty(savePath))
{

string
outError
=

""
;
AsposeExcelTools.DataTableToExcel(dtSource, savePath,

out
outError);


if
(
!
string
.IsNullOrEmpty(outError))
{
MessageBox.Show(outError);
}

else

{
Process.Start(savePath);
}
}
}

例子2代码如下所示

private

void
btnImportContent_Click(
object
sender, EventArgs e)
{

string
fileName
=
FileDialogHelper.OpenText();

if
(
!
string
.IsNullOrEmpty(fileName))
{
ThreadPool.QueueUserWorkItem(

new
WaitCallback(ImportContentData), fileName);
}

}

6、 INI文件操作辅助类 INIFileUtil

实现效果

1)本辅助类主要是用来方便快捷获取或设置INI文件的内容。

2) 辅助类包括下面功能:写INI文件、读取INI文件、删除ini文件下所有段落、删除ini文件下指定段落下的所有键等功能。

INI文件格式如下所示

实现代码

1) 辅助类提供的方法接口如下所示:

///

<summary>


///
写INI文件

///

</summary>


///

<param name="Section">
分组节点
</param>


///

<param name="Key">
关键字
</param>


///

<param name="Value">

</param>



public

void
IniWriteValue(
string
Section,
string
Key,
string
Value)


///

<summary>


///
读取INI文件

///

</summary>


///

<param name="Section">
分组节点
</param>


///

<param name="Key">
关键字
</param>


///

<returns></returns>



public

string
IniReadValue(
string
Section,
string
Key)


public

byte
[] IniReadValues(
string
section,
string
key)


///

<summary>


///
删除ini文件下所有段落

///

</summary>



public

void
ClearAllSection()


///

<summary>


///
删除ini文件下指定段落下的所有键

///

</summary>


///

<param name="Section"></param>

public

void
ClearSection(
string
Section)

2)辅助类INIFileUtil的使用例子代码如下所示

private

void
DetalParkingThread(
object
objFileName)
{

string
fileName
=
objFileName.ToString();

try

{
INIFileUtil iniFile

=

new
INIFileUtil(fileName);

string
parking_no
=
iniFile.IniReadValue(
"
Parking
"
,
"
parking_no
"
).Trim();

string
max_cars
=
iniFile.IniReadValue(
"
Parking
"
,
"
max_cars
"
).Trim();

string
space
=
iniFile.IniReadValue(
"
Parking
"
,
"
space
"
).Trim();

string
in_no
=
iniFile.IniReadValue(
"
Parking
"
,
"
in_no
"
).Trim();

string
out_no
=
iniFile.IniReadValue(
"
Parking
"
,
"
out_no
"
).Trim();

string
in_month_no
=
iniFile.IniReadValue(
"
Parking
"
,
"
in_month_no
"
).Trim();

string
out_month_no
=
iniFile.IniReadValue(
"
Parking
"
,
"
out_month_no
"
).Trim();

string
in_temp_no
=
iniFile.IniReadValue(
"
Parking
"
,
"
in_temp_no
"
).Trim();

string
out_temp_no
=
iniFile.IniReadValue(
"
Parking
"
,
"
out_temp_no
"
).Trim();

string
update_time
=
iniFile.IniReadValue(
"
Parking
"
,
"
update_time
"
).Trim();


string
seqNo
=
DateTime.Now.ToString(
"
yyyyMMdd
"
)
+

new
Random().Next(
99999
).ToString().PadLeft(
5
,
'
0
'
);
//
发送请求


PCParkingInfoUpload data
=

new
PCParkingInfoUpload(seqNo, parking_no, Convert.ToInt32(max_cars),
Convert.ToInt32(space), Convert.ToInt32(in_no), Convert.ToInt32(out_no), Convert.ToInt32(in_month_no),
Convert.ToInt32(out_month_no), Convert.ToInt32(in_temp_no), Convert.ToInt32(out_temp_no), Convert.ToDateTime(update_time));
CommonManager.Instance.Send(data.ToString());


//
记录请求


ReqAnsManager.Instance.Add(
new
RequestRecord(DataTypeKey.PCParkingInfoUpload, seqNo, DateTime.Now.AddSeconds(
10
), fileName,
null
));
}

catch
(Exception ex)
{
Log.WriteError(

string
.Format(
"
{0} INI文件格式错误:{1}
"
, objFileName, ex.Message));
}
}

7、 独立存储操作辅助类 IsolatedStorageHelper

实现效果

1)本辅助类主要是用来方便实现对独立存储区域文件或者目录的快速操作。

2) .NET引入了独立存储区概念。独立存储区就像一个虚拟文件夹。用户不需要知道文件存储的确切的位置。你 所作的就是告诉.NET Framework在独立存储区存储你的文件。对于不同的操作系统独立存储区的物理位置是不同的。在你的应用程序中 简单的使用.NET中的类创建和访问文件,不需要担心文件存储的物理位置。

实现代码

1) 辅助类提供的方法接口如下所示:

#region
程序运行时间的保存操作


///

<summary>


///
加密并保存指定时间到"独立存贮空间" (以分号(;)追加保存)

///

</summary>



public

static

void
SaveDataTime()


///

<summary>


///
加密并保存当前时间到"独立存贮空间" (以分号(;)追加保存)

///

</summary>



public

static

void
SaveDataTime(DateTime fromDate)


///

<summary>


///
从"独立存贮空间"取程序第一次运行的时间并解密

///

</summary>


///

<returns></returns>



public

static

string
GetDataTime()


#endregion




#region
基本操作函数



///

<summary>


///
保存对象到独立存储区

///

</summary>


///

<param name="objectToSave">
待保存的对象
</param>


///

<param name="key">
保存的键值
</param>



public

static

void
Save(
object
objectToSave,
string
key)


///

<summary>


///
根据键值加载独立存储区的内容

///

</summary>


///

<param name="key">
独立存储的键值(路径)
</param>


///

<returns></returns>



public

static

object
Load(
string
key)


///

<summary>


///
加载存在用户标识范围、应用程序范围内的存储值

///

</summary>


///

<param name="d">
待填充的字典对象
</param>


///

<param name="filename">
文件名
</param>



public

static

void
LoadFromUserStoreForApplication(IDictionary d,
string
filename)


///

<summary>


///
保存在用户标识范围、应用程序范围内的值

///

</summary>


///

<param name="d">
待保存的字典对象
</param>


///

<param name="filename">
文件名
</param>



public

static

void
SaveToUserStoreForApplication(IDictionary d,
string
filename)


///

<summary>


///
加载用户范围、应用范围、程序集范围内的存储值

///

</summary>


///

<param name="d">
待填充的字典对象.
</param>


///

<param name="filename">
文件名
</param>



public

static

void
LoadFromUserStoreForDomain(IDictionary d,
string
filename)


///

<summary>


///
保存用户范围、应用范围、程序集范围内的存储值

///

</summary>


///

<param name="d">
待保存的字典对象
</param>


///

<param name="filename">
文件名
</param>



public

static

void
SaveToUserStoreForDomain(IDictionary d,
string
filename)


///

<summary>


///
加载在独立存储内的指定文件内容

///

</summary>


///

<param name="d">
待填充的字典内容
</param>


///

<param name="scope">
独立存储范围对象
</param>


///

<param name="filename">
文件名
</param>



public

static

void
Load(IDictionary d, IsolatedStorageScope scope,
string
filename)


///

<summary>


///
在独立存储范围内保存字典内容到指定文件

///

</summary>


///

<param name="d">
待保存的字典内容
</param>


///

<param name="scope">
独立存储范围对象
</param>


///

<param name="filename">
文件名
</param>



public

static

void
Save(IDictionary d, IsolatedStorageScope scope,
string
filename)


///

<summary>


///
删除指定区域的存储区内容

///

</summary>


///

<param name="fileName">
待删除的文件
</param>


///

<param name="scope">
独立存储范围对象
</param>



public

static

void
Delete(
string
fileName, IsolatedStorageScope scope)


///

<summary>


///
在存储区内创建目录

///

</summary>


///

<param name="storage"></param>


///

<param name="dirName"></param>



public

static

void
CreateDirectory(IsolatedStorageFile storage,
string
dirName)


///

<summary>


///
在存储区内删除目录

///

</summary>


///

<param name="storage"></param>


///

<param name="dirName"></param>



public

static

void
DeleteDirectory(IsolatedStorageFile storage,
string
dirName)

#endregion

2)辅助类的使用例子代码如下所示。例子实现对用户程序执行时间的判断,防止用户擅自修改系统时间。

///

<summary>


///
检查用户的时间记录是否正确

///

</summary>


///

<returns></returns>



public

bool
CheckTimeString()
{

/*

每次启动纪录一个时间date[0]...date[n].
第n+1次启动时date[n+1].
if(date[n+1]   <=   date[n])
exit(-1);
if((date[n+1]-date[0])>30天)
exit(-2);
write   date[n+1]   to   纪录

*/



string
dateTimeString
=
IsolatedStorageHelper.GetDataTime();

string
[] timeArray
=
dateTimeString.Split(
new

char
[] {
'
;
'
});
DateTime lastestTime

=
System.DateTime.Now;
DateTime tempTime;

for
(
int
i
=

0
; i
<
timeArray.Length; i
++
)
{

try

{
tempTime

=
Convert.ToDateTime(timeArray[i]);
}

catch

{
tempTime

=
System.DateTime.Now.AddMinutes(
-
1
);
//
最古老的时间为当前时间的一分钟前


}

if
(i
==

0
)
{
lastestTime

=
tempTime;
Portal.gc.FirstRunTime

=
lastestTime;
}

Portal.gc.TimeList.Add(tempTime);
}


//
验证时间的有效性

//
用户调整了时间

//
用户使用超过指定时间


DateTime newestTime
=
System.DateTime.Now;

if
(newestTime
<
lastestTime)
{
MessageUtil.ShowWarning(

"
对不起,您在本软件的试用期内不可以修改系统日期。\r\n如果您想继续使用本软件,请您恢复系统日期。谢谢合作
"
);

return

false
;
}

TimeSpan span

=

new
TimeSpan(newestTime.Ticks
-
lastestTime.Ticks);

if
(span.Days
>
UIConstants.SoftwareProbationDay)
{
MessageUtil.ShowTips(

"
您使用本软件已经过了试用期,如果您想继续使用本软件,请您联系我们。
"
);
Portal.gc.DisableAllFunction

=

true
;

return

false
;
}

IsolatedStorageHelper.SaveDataTime();

//
记录程序运行的时间



return

true
;

}

8、监视文件变化的类,包括创建、修改、删除等操作的辅助类 MyFileSystemWatcher

实现效果

1)本辅助类主要是用来方便实现监视文件或文件夹变化,包括创建、修改、重新命名、删除等操作的。

2) 本辅助类非常适合用于监听文件或者文件夹的变化,然后做相应的处理,如数据报送、日志记录等操作。示例的效果如下所示。

实现代码

1)辅助类的使用例子代码如下所示

static

void
Main(
string
[] args)
{
MyFileSystemWatcher fsw

=

new
MyFileSystemWatcher(
@"
D:\Test
"
);
fsw.Created

+=

new
System.IO.FileSystemEventHandler(fsw_Created);
fsw.Changed

+=

new
System.IO.FileSystemEventHandler(fsw_Changed);
fsw.Deleted

+=

new
System.IO.FileSystemEventHandler(fsw_Deleted);
fsw.Renamed

+=

new
System.IO.RenamedEventHandler(fsw_Renamed);
fsw.EnableRaisingEvents

=

true
;

Console.ReadLine();
}


static

void
fsw_Renamed(
object
sender, System.IO.RenamedEventArgs e)
{
Console.WriteLine(

"
Renamed: FileName - {0}, ChangeType - {1}, Old FileName - {2}
"
, e.Name, e.ChangeType, e.OldName);
}


static

void
fsw_Deleted(
object
sender, System.IO.FileSystemEventArgs e)
{
Console.WriteLine(

"
Deleted: FileName - {0}, ChangeType - {1}
"
, e.Name, e.ChangeType);
}


static

void
fsw_Changed(
object
sender, System.IO.FileSystemEventArgs e)
{
Console.WriteLine(

"
Changed: FileName - {0}, ChangeType - {1}
"
, e.Name, e.ChangeType);
}


static

void
fsw_Created(
object
sender, System.IO.FileSystemEventArgs e)
{
Console.WriteLine(

"
Created: FileName - {0}, ChangeType - {1}
"
, e.Name, e.ChangeType);
}

2) 实际项目中对文件监控的处理例子。

private
MyFileSystemWatcher watcherParking;

private
MyFileSystemWatcher watcherTicket;


///

<summary>


///
对指定目录或者文件进行监控

///

</summary>



public

void
StartFileWatcher()
{
FileInfo fileInfo

=

new
FileInfo(PCDataCollector_Config.Default.ParkingFilePath);

string
parkingFilePath
=
fileInfo.Directory.FullName;
DirectoryUtil.AssertDirExist(parkingFilePath);


string
ticketFilePath
=
PCDataCollector_Config.Default.TickDataFilePath;
DirectoryUtil.AssertDirExist(ticketFilePath);

StopFileWatcher();

//
先取消后创建新的监控


watcherParking
=

new
MyFileSystemWatcher(parkingFilePath,
"
parking.ini
"
);
watcherParking.Changed

+=

new
FileSystemEventHandler(watcherParking_Changed);
watcherParking.EnableRaisingEvents

=

true
;

watcherTicket

=

new
MyFileSystemWatcher(ticketFilePath,
"
*.ini
"
);
watcherTicket.Created

+=

new
FileSystemEventHandler(watcherTicket_Created);
watcherTicket.EnableRaisingEvents

=

true
;


//
对存在的票据数据进行处理



string
[] ticketFiles
=
Directory.GetFiles(ticketFilePath);

foreach
(
string
file
in
ticketFiles)
{
DealTicketFile(file);
}
}


///

<summary>


///
取消对文件的监控

///

</summary>



public

void
StopFileWatcher()
{

if
(watcherParking
!=

null
)
{
watcherParking.Dispose();
}

if
(watcherTicket
!=

null
)
{
watcherTicket.Dispose();
}
}


private

void
DealTicketFile(
string
fileName)
{
Thread thread

=

new
Thread(
new
ParameterizedThreadStart(DealTicketFileThread));
thread.IsBackground

=

true
;
thread.Start(fileName);
}

感谢大家的支持和鼓励。

CHM帮助文档持续更新中,统一下载地址是:
http://www.iqidi.com/download/commonshelp.rar

系列文章列表如下

厚积薄发,丰富的公用类库积累,助你高效进行系统开发(1)

厚积薄发,丰富的公用类库积累,助你高效进行系统开发(2)

厚积薄发,丰富的公用类库积累,助你高效进行系统开发(3)

很早的时候,曾经写了一篇随笔《
WinForm界面开发之“OutLookBar”工具条
》介绍了OutLookBar样式的工具条,得到很多同行的热烈反馈,我个人也比较喜欢这样的工具条布局,因此我很多共享软件中,都经常看到这种OutLookBar的工具条,这种工具条提供了一种较好的界面布局,方便对工具的分类和排列。如我的多篇随笔《
从开发的软件《备件仓库管理系统》总结的一些经验
》、《
WinForm界面开发之酒店管理系统--开篇
》、《
Winform开发框架之终极应用
》等文章都有介绍,大致的效果如下所示。

本文介绍另外一种效果较好的OutLookBar工具条的实现,其思路来源于www.codeproject.com上的一篇介绍OutLook样式的导航条的文章《
A Serious Outlook Style Navigation Pane Control
》, 该篇文章给出了一个较好的OutLook样式的导航容器,但没提供完整的类似导航条的实现。从该篇文章启发,我结合了TreeView控件的效果,处理得到下面这种界面效果,和之前的OutLookBar样式有较为相似的地方,但体验效果会好一些,也提供更多的工具条控制和样式。

这些界面效果较原来的有一定的提供,而且可以根据需要调整OutLook导航条里面展示的内容,如TreeView、ListBox、Button等均可以放到里面空白区域中,提供较好的控制,下面介绍一下上面的效果代码实现,供大家参考。

1、首先先在一个空白的窗体中防止一个OutLook样式的导航控件,如下所示

2、实现代码如下所示


#region
NavigationBar代码实现



private

void
InitNaviBar()
{
TreeView treeview1

=

new
TreeView();
treeview1.ShowLines

=

false
;
treeview1.ImageList

=

this
.imageList;


if
(Portal.gc.HasFunction(
"
Purchase
"
))
{
treeview1.Nodes.Add(

new
TreeNode(
"
备件入库
"
,
0
,
0
));
}

if
(Portal.gc.HasFunction(
"
TakeOut
"
))
{
treeview1.Nodes.Add(

new
TreeNode(
"
备件出库
"
,
1
,
1
));
}

if
(Portal.gc.HasFunction(
"
StockSearch
"
))
{
treeview1.Nodes.Add(

new
TreeNode(
"
库存查询
"
,
2
,
2
));
}

if
(Portal.gc.HasFunction(
"
ItemDetail
"
))
{
treeview1.Nodes.Add(

new
TreeNode(
"
备件信息
"
,
3
,
3
));
}

if
(Portal.gc.HasFunction(
"
Report
"
))
{
treeview1.Nodes.Add(

new
TreeNode(
"
业务报表
"
,
5
,
5
));
}

TreeView treeview2

=

new
TreeView();
treeview2.ShowLines

=

false
;
treeview2.ImageList

=

this
.imageList;

if
(Portal.gc.HasFunction(
"
Dictionary
"
))
{
treeview2.Nodes.Add(

new
TreeNode(
"
数据字典
"
,
4
,
4
));
}

if
(Portal.gc.HasFunction(
"
WareHouse
"
))
{
treeview2.Nodes.Add(

new
TreeNode(
"
库房管理
"
,
6
,
6
));
}

treeview1.Dock

=
DockStyle.Fill;

this
.naviBand1.ClientArea.Controls.AddRange(
new
Control[] { treeview1 });

treeview2.Dock

=
DockStyle.Fill;

this
.naviBand2.ClientArea.Controls.AddRange(
new
Control[] { treeview2 });

}


之前有写过多篇关于使用Apose.Cell控件制作自定义模板报表和通用的导出Excel表格数据的操作,对这个控件的功能还是比较满意,而且也比较便利。忽然有一天,一个朋友说:你已经有生成基于自定义模板报表了,可是我每个单位都导出一张相同的报表的话,我岂不是要生成很多文件,而且对比查看也不方便,有没有更好的办法合并他们到一个文件里面呢?这样我看报表就方便很多了。本文主要介绍如何实现基于一个自定义报表模式,生成多个类似报表合并在一个文件中具体操作。

查询Apose.Cell控件的使用介绍,WorkBook对象确实有一个Combine的方法,专门做文件合并的工作,实现的代码如下所示。

Workbook SourceBook1
=

new
Workbook();
SourceBook1.Open(

"
c:\\excels\\ChartTest.xls
"
);

Workbook SourceBook2

=

new
Workbook();
SourceBook2.Open(

"
C:\\excels\\PictureTest.xls
"
);

SourceBook1.Combine(SourceBook2);
SourceBook1.Save(
"
c:\\excels\\combined.xls
"
);


既然有了这个方法合并文件,那么客户提出的问题,就也可以通过该思路来解决了。问题只是这个方法合并已有的文件,而客户需要的是在一个自定义模板的基础上生成多个相似的报表,放到一个文件中,每个报表一个Sheet而已。

SourceBook1.Combine(SourceBook2);
SourceBook1.Save(
" c:\\excels\\combined.xls " );

通过以上的代码,我们可以看到,文件合并的逻辑,其实是多个WorkBook之间的合并,然后把最后的WorkBook重新保存为另外一个文件即可。

首先我通过一个简单例子来介绍实现思路,先来设计一个简单的自定义模板,如下所示 。

这样,我们通过基于该自定义模板,生成一系列相似的报表文件,然后逐一合并他们即可,例子实现的代码如下所示:


private
DataTable GetCustomersTable()
{
DataTable dt

=

new
DataTable(
"
Customers
"
);
dt.Columns.Add(

"
Address
"
);
dt.Columns.Add(

"
City
"
);
dt.Columns.Add(

"
CompanyName
"
);
dt.Columns.Add(

"
ContactName
"
);
dt.Columns.Add(

"
ContactTitle
"
);
dt.Columns.Add(

"
Country
"
);
dt.Columns.Add(

"
CustomerID
"
);
dt.Columns.Add(

"
Fax
"
);
dt.Columns.Add(

"
Phone
"
);
dt.Columns.Add(

"
PostalCode
"
);
dt.Columns.Add(

"
Region
"
);

for
(
int
i
=

0
; i
<

10
; i
++
)
{
DataRow row

=
dt.NewRow();

for
(
int
j
=

0
; j
<
dt.Columns.Count; j
++
)
{
row[j]

=
dt.Columns[j].ColumnName
+

"
(
"

+
i.ToString()
+

"
,
"

+
j.ToString()
+

"
)
"
;
}
dt.Rows.Add(row);
}

return
dt;
}


private

void
btnCombind_Click(
object
sender, EventArgs e)
{
Workbook SourceBook1

=

new
Workbook();


string
path
=
System.IO.Path.Combine(Application.StartupPath,
"
SmartMarkerCombind.xls
"
);
DataTable dt

=
GetCustomersTable();
//
使用DataTable对象



List

<
string
>
fileList
=

new
List
<
string
>
();

for
(
int
i
=

0
; i
<

3
; i
++
)
{
Workbook tempBook

=

new
Workbook();


//
创建设计模板对象,并绑定数据源


WorkbookDesigner designer
=

new
WorkbookDesigner();
designer.Open(path);
designer.SetDataSource(dt);
designer.Process();


//
修改Sheet的名称


designer.Workbook.Worksheets[
0
].Name
=

"
test
"

+
i.ToString();


//
根据数据源和自定义模板,生成相应的报表Excel文件



string
fileToSave
=
System.IO.Path.Combine(Application.StartupPath,
string
.Format(
"
Combind{0}.xls
"
, i));
designer.Save(fileToSave, FileFormatType.Excel2003);
fileList.Add(fileToSave);


//
第一次要打开



if
(i
==

0
)
{
SourceBook1.Open(fileToSave);
}

else

{

//
第二个使用Combind函数操作


tempBook.Open(fileToSave);
SourceBook1.Combine(tempBook);
}
}


//
最后将WorkBook保存为一个文件即可



string
soucePath
=
System.IO.Path.Combine(Application.StartupPath,
"
Combind.xls
"
);
SourceBook1.Save(soucePath);


//
删除临时文件



foreach
(
string
file
in
fileList)
{

if
(File.Exists(file))
{
File.Delete(file);
}
}


//
打开文件


Process.Start(soucePath);
}
}

注意,由于Workbook对象默认只创建了一个Sheet对象供使用,因此要逐一修改Sheet对应的名称,如下代码所示:

designer.Workbook.Worksheets[
0
].Name
=

"
test
"

+
i.ToString();

最终生成的多Sheet对象的Excel报表效果如下图所示:

当然,复杂的报表可能相对处理会更加复杂一些,不过大致的逻辑就是通过这样的步骤来实现整合即可,在项目中整合 真正的报表后,对方满意,一切OK。

一般我们在做数据内容展示的时候,只需把该对象的详细信息,分门别类放到一个窗体展示即可,在我的Winform开发框架中,一般也侧重于使用这种传统的方式,只是通过窗体继承方式,把通用的窗体操作封装到基类实现而已。如一般的数据展示窗体,包括查看数据,编辑数据、新建数据等内容的窗体,如下所示。

对于以上窗体,如果仅仅是看当前记录的数据,是没什么问题的,但如果要看下一个记录的、上一个记录的数据,就要关闭该窗体,然后重新打开,操作起来会稍微麻烦一些。如果我们在这个窗体上设计一个导航栏,那么界面会显得友好一些,界面效果如下所示。

这样就可以随意在各个记录之间浏览数据了,原来的保存或或其它操作,并没有影响,两全其美,何乐不为呢?

其实这样的操作,已经是比较通用的界面元素部分,因此可以把它封装到基类中即可,我们在之类只需要传入必要的数据给他即可,首先我们设计一个导航控件,如下所示。

然后再编辑窗体基类中嵌入对应的控件即可,由于需要在窗体子类中调整导航控件的位置,因此把它的属性设置为Protected即可,如下图所示。

这个窗体里面集成了DataNavigator用户控件,该控件除了响应如分页的导航按钮事件外,就是响应和显示对应的记录位置而已,因此在其中编写相关的处理代码即可,如下所示。

namespace
ParkDeviceUserMis.UI.BaseUI
{

public

delegate

void
PostionChangedEventHandler(
object
sender, EventArgs e);


public

partial

class
DataNavigator : DevExpress.XtraEditors.XtraUserControl
{

public

event
PostionChangedEventHandler PositionChanged;

private

int
m_CurrentIndex
=

0
;
//
当前的位置



public
List
<
string
>
IDList
=

new
List
<
string
>
();


///

<summary>


///
获取或设置索引值

///

</summary>



public

int
CurrentIndex
{

get
{
return
m_CurrentIndex; }

set

{
m_CurrentIndex

=
value;
ChangePosition(value);
}
}

public
DataNavigator()
{
InitializeComponent();
}


private

void
btnFirst_Click(
object
sender, EventArgs e)
{
ChangePosition(

0
);
}


private

void
btnPrevious_Click(
object
sender, EventArgs e)
{
ChangePosition(m_CurrentIndex

-

1
);
}


private

void
btnNext_Click(
object
sender, EventArgs e)
{
ChangePosition(m_CurrentIndex

+

1
);
}


private

void
btnLast_Click(
object
sender, EventArgs e)
{
ChangePosition(IDList.Count

-

1
);
}


private

void
EnableControl(
bool
enable)
{

this
.btnFirst.Enabled
=
enable;

this
.btnLast.Enabled
=
enable;

this
.btnNext.Enabled
=
enable;

this
.btnPrevious.Enabled
=
enable;
}


private

void
ChangePosition(
int
newPos)
{

int
count
=
IDList.Count;

if
(count
==

0
)
{
EnableControl(

false
);

this
.txtInfo.Text
=

""
;
}

else

{
EnableControl(

true
);

newPos

=
(newPos
<

0
)
?

0
: newPos;
m_CurrentIndex

=
((count
-

1
)
>
newPos)
?
newPos : (count
-

1
);

this
.btnPrevious.Enabled
=
(m_CurrentIndex
>

0
);

this
.btnNext.Enabled
=
m_CurrentIndex
<
(count
-

1
);

this
.txtInfo.Text
=

string
.Format(
"
{0}/{1}
"
, m_CurrentIndex
+

1
, count);


if
(PositionChanged
!=

null
)
{
PositionChanged(

this
,
new
EventArgs());
}
}
}


private

void
DataNavigator_Load(
object
sender, EventArgs e)
{
}
}

而编辑窗体的基类也需要对控件的事件进行相应的处理,如下所示。我们看到,这个基类需要子类窗体传入一个当前显示的ID(或其它唯一标示)和ID集合,作为数据导航之用,并且对导航控件位置变化的事件进行处理,传入给其新的ID(或其它唯一标示),然后窗体根据这个ID显示相应的数据。


public

partial

class
BaseEditForm : BaseForm
{

public

string
ID
=

string
.Empty;
//
主键主键



public
List
<
string
>
IDList
=

new
List
<
string
>
();
//
所有待展示的ID列表




public
BaseEditForm()
{
InitializeComponent();


this
.dataNavigator1.PositionChanged
+=

new
ParkDeviceUserMis.UI.BaseUI.PostionChangedEventHandler(dataNavigator1_PositionChanged);
}


private

void
dataNavigator1_PositionChanged(
object
sender, EventArgs e)
{

this
.ID
=
IDList[
this
.dataNavigator1.CurrentIndex];
DisplayData();
}


最后在使用的时候,一般在查询界面中,需要弹出编辑窗体的事件响应操作中,传入当前显示的ID和总的ID字段集合即可,如下实现代码。


private

void
winGridViewPager1_OnEditSelected(
object
sender, EventArgs e)
{

string
ID
=

this
.winGridViewPager1.gridView1.GetFocusedRowCellDisplayText(
"
Id
"
);
List

<
string
>
IDList
=

new
List
<
string
>
();

for
(
int
i
=

0
; i
<

this
.winGridViewPager1.gridView1.RowCount; i
++
)
{

string
strTemp
=

this
.winGridViewPager1.GridView1.GetRowCellDisplayText(i,
"
Id
"
);
IDList.Add(strTemp);
}

FrmEditPark dlg

=

new
FrmEditPark();
dlg.ID

=
ID;
dlg.IDList

=
IDList;

if
(DialogResult.OK
==
dlg.ShowDialog())
{
BindData();
}
}

这样我们就看到开始介绍的窗体效果,如下所示。