员工考勤跟踪
跟踪员工工作时间的程序

引言
上面的屏幕截图显示 Adam 签到,工作了 17 秒,休息了 10 秒,然后再次签到。Chirs 工作了 61 秒,Mary 工作了 16 秒。目前,Adam 是唯一一个还在办公室的人。
上周五,我随意浏览 codeproject 网站,并被很久以前发布的一个问题和答案逗乐了。有人寻找一个考勤跟踪程序,得到一个回复说“不,我们不做你的家庭作业”。我完全同意回复者。
但是到了周日,我在一个美好的早晨醒来,全身感官统一,通过每一个毛孔吸收快乐(瓦尔登湖),再加上一杯热咖啡(瓦尔登湖作者鄙视),我发现自己编写了员工考勤跟踪项目。
我编写代码来演示我最近学到的以下特性 (Microsoft C# 2010 Express)
- 将 datagrideview 与 xml 数据源连接。
- 在一个活动窗体内调用一个窗体
- 将持久数据保存到 sqlite 数据库
- 将代码组织成一个类以简化编程
- 使用一个计时器对象来控制编程流程
- 以交互方式或在后台运行批处理作业
EAT 的设计场景
我在设计程序时考虑了以下两种情况。场景 1,一个小商店只有一台电脑供所有人签到和签退使用。
场景 2,一个办公室,每个人都有自己的电脑,可以从自己的桌面签到和签退。
程序如何工作?
EAT 以秒为单位记录员工工作时间,并以 HH:MM:SS 格式显示给用户。员工每天可以多次签到签退。工作时间将被累积。
一旦员工签到,工作时间将开始增加;一旦签退,时间停止。
签到、签退的时间将被记录和显示。
程序强制使用四位字母的密码作为 ATM 密码的安全性。
如果强制执行密码,用户需要输入正确的密码才能签到、签退和查看报告。
员工可以点击 IN、OUT、NAME 列来指示签到、签退并获取历史报告。
为了简单起见和本次演示,程序立即在底部视图中显示历史记录。
一个计时器对象
计时器可以简化程序执行流程,使程序对用户的命令反应更灵敏。如果没有计时器,程序员需要引入一些人为的延迟,例如睡眠几微秒或进行一些循环,这可能会导致问题。我以以下方式使用一个计时器
如果员工已签到,计时器每秒都会增加员工的工作时间。它检查用户是否已在输入密码窗体中输入。如果没有计时器,则两个窗体之间的控制流很难协调,因为 Windows 不会等待子窗体完成。
三个 SortedDictionary 对象
dTimeWorked
和 dTimeSection
使用员工姓名作为键来存储累积的总工作秒数和每次签到签退的总工作秒数。
dCheckedIn
使用员工姓名作为键来存储 true
或 false
值,以指示员工是否签到或签退。
SortedDictionary<string, int> dTimeWorked = new SortedDictionary<string, int>();//name, second
SortedDictionary<string, int> dTimeSection = new SortedDictionary<string, int>();//name,second
SortedDictionary<string, bool> dCheckedIn = new SortedDictionary<string, bool>();//name,true
XML 数据源
在employees.xml
文件中维护员工姓名和密码非常容易,只需用记事本编辑即可。"EXT." 代表员工的电话分机号。<ROW><IN></IN><OUT>X</OUT><NAME>ADAM</NAME><EXT.>580</EXT.><hhmmssWorked></hhmmssWorked><InOutTime></InOutTime><NOTES></NOTES><PASSCODE>1580</PASSCODE></ROW>
<ROW><IN></IN><OUT>X</OUT><NAME>BOB</NAME><EXT.>581</EXT.><hhmmssWorked></hhmmssWorked><InOutTime></InOutTime><NOTES></NOTES><PASSCODE>1581</PASSCODE></ROW>
<ROW><IN></IN><OUT>X</OUT><NAME>CHIRS</NAME><EXT.>582</EXT.><hhmmssWorked></hhmmssWorked><InOutTime></InOutTime><NOTES></NOTES><PASSCODE>1582</PASSCODE></ROW>
<ROW><IN></IN><OUT>X</OUT><NAME>DEBBIE</NAME><EXT.>380</EXT.><hhmmssWorked></hhmmssWorked><InOutTime></InOutTime><NOTES></NOTES><PASSCODE>1380</PASSCODE></ROW>
<ROW><IN></IN><OUT>X</OUT><NAME>JANE</NAME><EXT.>472</EXT.><hhmmssWorked></hhmmssWorked><InOutTime></InOutTime><NOTES></NOTES><PASSCODE>1472</PASSCODE></ROW>
<ROW><IN></IN><OUT>X</OUT><NAME>JASON</NAME><EXT.>584</EXT.><hhmmssWorked></hhmmssWorked><InOutTime></InOutTime><NOTES></NOTES><PASSCODE>1584</PASSCODE></ROW>
<ROW><IN></IN><OUT>X</OUT><NAME>MARY</NAME><EXT.>460</EXT.><hhmmssWorked></hhmmssWorked><InOutTime></InOutTime><NOTES></NOTES><PASSCODE>1460</PASSCODE></ROW>
<ROW><IN></IN><OUT>X</OUT><NAME>ZACH</NAME><EXT.>468</EXT.><hhmmssWorked></hhmmssWorked><InOutTime></InOutTime><NOTES></NOTES><PASSCODE>1468</PASSCODE></ROW>
以下代码 loadDGViewFromXML
读取 employees.xml
并将其分配给 dataGridView1
,将数据与 employees.xml 中的 ROW
标签连接起来。
private void Form1_Load(object sender, EventArgs e)
{
loadDGViewFromXML("employees.xml");
attendance = new DBAttendance();
dataGridView2.DataSource = attendance.atbl;
chkBoxViewBatch.Checked = true; // default to true
chkBoxSecurity.Checked = false; // default to true; enforece security
timerStart()
}
public void loadDGViewFromXML(string filePath)
{
DataSet dsRows = new DataSet("ROW");
dsRows.ReadXml(filePath);
dataGridView1.DataSource = dsRows;
dataGridView1.DataMember = "ROW";
setdataGridView1ColumnWidth();
}
强制安全模式
当程序在安全模式下运行时,当员工尝试签到或签退时,将弹出以下提示。
在两列之间切换值
// switch value between two columns when user click on either column
private void swapValue(int row, int col1, int col2) //'X' between in/out
{
string temp = dataGridView1.Rows[row].Cells[col1].Value.ToString();
dataGridView1.Rows[row].Cells[col1].Value = dataGridView1.Rows[row].Cells[col2].Value;
dataGridView1.Rows[row].Cells[col2].Value = temp;
}
在主窗体内调用另一个窗体
要实现这一点,需要三个步骤//step1 - in Form1.cs[Design], Properties, Modifiers, changes value to Public
//step2 - Add the following module in Form1
private void checkPassCode()
{ // lblPassCode.Text will hold the value pass back from form2
lblPassCode.Text = "";
Form2 subForm = new Form2(this);
subForm.Show();
}
//step3 - Add the following module in Form2
Form1 mainForm;
public Form2(Form1 mainForm)
{
this.mainForm = mainForm;
InitializeComponent();
}
// return the value to form1 and close form2
private void textBox1_KeyUp(object sender, KeyEventArgs e)
{
if (textBox1.Text.Length == 4)
{
mainForm.lblPassCode.Text = textBox1.Text;
this.Close();
}
}
将日志保存到 sqlite 数据库
如果程序处于交互模式,将出现以下屏幕。按任意键继续。使用 d.bat, debug.sql 来调试 sqllite。
d.bat
cls
sqlite3 employeeattendance.db < debug.sql
pause
debug.sqlSELECT * FROM sqlite_master;
SELECT * FROM worklog order by ename;
.quit
参考文献
MSDN 窗体历史
2012 年 1 月 30 日 - 第一个版本。