初学时,有了想法却完全不知道该从何下指,此序列将抛砖引玉,与大家共同学习进步。
一个程序的初始,必然是启动。
我的要求:
1、应用程序保持单例;
2、从配置文件加载一些基础数据进行初始化;
3、显示软件的LOGO页面;
4、判断应用程序是否有更新;
4、进入用户登录界面;
5、用户成功登录后显示主界面。
如上图,基本达成目标,这个项目也是很简单的结构:
看窗体名称就知道各自的用途了。主要的Program.cs代码如下:
static class Program { public static bool 切换用户 = false; /// <summary> /// 主体框架应用程序的主入口点。 /// </summary> [STAThread] static void Main() { string 实例标识 = Assembly.GetExecutingAssembly().GetName().Name + "_SingtonB20"; Mutex mutex = new Mutex(true, 实例标识, out bool 单实例); if (!单实例) { 唤醒进程(实例标识); return; } Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); Application.DoEvents(); //初始化全局DS if (GlbInfo.DSGlb == null) GlbInfo.DSGlb = new System.Data.DataSet(); else { GlbInfo.DSGlb.Clear(); GlbInfo.DSGlb.Tables.Clear(); } //加载全局多语言资源 try { AppInit.CreateDT_MulLang(AppInfo.GetFile(AppInfo.AppFile.MulMessage), "Message_Core"); AppInit.CreateDT_MulLang(AppInfo.GetFile(AppInfo.AppFile.MulSurface), "Surface_Core"); } catch (Exception ex) { MessageBox.Show(ex.Message); Environment.Exit(0); } //展示闪屏窗体 Fm20Splash 闪屏窗 = new Fm20Splash(); 闪屏窗.ShowDialog(); //系统检测正常则显示登录界面,否则退出应用 if (闪屏窗.DialogResult == DialogResult.OK) { 显示登录: 切换用户 = false; Fm20Login fmLogin = new Fm20Login(); fmLogin.ShowDialog(); if (fmLogin.DialogResult == DialogResult.OK) { Application.Run(new Fm20Main()); } else { Application.Exit(); } //当关闭主程序的时候会执行这个代码,在关闭主程序的时候需要给IsLogin 设置成true //重新回到登录窗口. if (切换用户) { goto 显示登录; } } else { Application.Exit(); } } public static void 唤醒进程(string 进程名称) { try { Process proc = Process.GetCurrentProcess(); string assemblyName = Assembly.GetExecutingAssembly().GetName().Name + 进程名称; foreach (Process 进程 in Process.GetProcessesByName(进程名称)) { if (proc.Id != 进程.Id) { IntPtr hWnd = 进程.MainWindowHandle; if (WinAPI.IsIconic(hWnd)) { WinAPI.ShowWindowAsync(hWnd, WinAPIConst.SW_RESTORE); } WinAPI.SetForegroundWindow(hWnd); return; } } } catch { } } }
一、第一个被运行的窗体(闪屏窗口):
/// <summary> /// Splash闪屏窗体 /// </summary> public partial class Fm20Splash : Fm11Base { /// <summary> /// 创建闪屏窗体 /// </summary> public Fm20Splash() { InitializeComponent(); OINI oINI = new OINI(AppInfo.GetFile(AppInfo.AppFile.ConfigCore)); GlbInfo.Language = oINI.ReadString("Global", "CurrentLang", "LL"); string licCompany = oINI.ReadString("Global", "License_To_" + GlbInfo.Language, "所有人"); LabLicTo.Text = MultiLang.Surface(this, "LabLicTo", "授权给: ") + licCompany; if (GlbInfo.Language == "LL") LabSysTitle.Text = "IMES (智能制造执行系统)"; else LabSysTitle.Text= "IMES (Intelligent Manufacturing Execution System)"; } private void Fm20Splash_MouseDown(object sender, MouseEventArgs e) { WinAPI.ReleaseCapture(); WinAPI.SendMessage(Handle, WinAPIConst.WM_SYSCOMMAND, WinAPIConst.SC_MOVE + WinAPIConst.HTCAPTION, 0); } private static bool Delay(int delayTime) { DateTime now = DateTime.Now; int s; do { TimeSpan spand = DateTime.Now - now; s = spand.Seconds; Application.DoEvents(); } while (s < delayTime); return true; } private void Fm20Splash_Shown(object sender, EventArgs e) { //开始检测系统配置是否符合要求 //检测1:FRAMEWORK //检测2:检查组件资源 #region 检测1:检测更新 SetMainStatus(MultiLang.Surface(this, "ChkAppVersion","正在检查系统版本...")); Delay(1); string localPath = AppDomain.CurrentDomain.BaseDirectory; localPath = localPath.Substring(0, localPath.Length - 1); string updateTxtFile = localPath + @"UpdateLog.txt"; //当更新指引文件不存在(如调试时),将跳过自动更新检测. if (File.Exists(updateTxtFile)) { string[] allLines = File.ReadAllLines(localPath + @"UpdateLog.txt"); string remotePath = string.Empty; foreach (string line in allLines) { if (line.IndexOf("UpdatePath=") >= 0) { remotePath = line.Substring(line.IndexOf("UpdatePath=") + 11); break; } } if (NeedUpdate(localPath, remotePath)) { File.Delete(localPath + "\" + @"A19.exe"); File.Copy(remotePath + "\" + @"A19.exe", localPath + "\" + @"A19.exe", true); string updFile = AppDomain.CurrentDomain.BaseDirectory + @"A19.exe"; Process.Start(updFile); DialogResult = DialogResult.Cancel; Application.Exit(); } } #endregion #region 清理过期插件版本,只保留最近的一项 //根目录下不存在DLL或EXE原文件,则查找日期最后面的文件作为加载文件 //查找M开头的exe和dll文件,规划M2-M8为模组EXE命名区域.如M22,M88 var modFiles = Directory.GetFiles(AppDomain.CurrentDomain.BaseDirectory).Select(f => new FileInfo(f)).Where(f => ((f.Name.IndexOf("M") == 0 ) && f.Name.IndexOf("--") == 3) && (f.Name.IndexOf(".exe") >= 0 || f.Name.IndexOf(".dll") >= 0)).OrderBy(f => f.Name); string prevFileSpl = string.Empty; FileInfo ofi = null; foreach (var s in modFiles) { if (!string.IsNullOrEmpty(prevFileSpl)) { if (s.Name.Substring(0, s.Name.IndexOf("--")) == prevFileSpl.Substring(0, prevFileSpl.IndexOf("--"))) { if (s.LastWriteTime >= ofi.LastWriteTime) { ofi.Delete(); } else { s.Delete(); } } } ofi = s; prevFileSpl = s.Name; } #endregion DialogResult = DialogResult.OK; } /// <summary> /// 判断程序是否需要更新 /// </summary> /// <param name="localPath"></param> /// <param name="remotePath"></param> /// <returns></returns> private bool NeedUpdate(string localPath, string remotePath) { try { FileInfo fiUL = new FileInfo(localPath + @"UpdateLog.txt"); FileInfo fiUR = new FileInfo(remotePath + @"UpdateLog.txt"); if (fiUL.LastWriteTime == fiUR.LastWriteTime) { SetMainStatus(MultiLang.Surface(this, "ChkAppVersion","正在检查系统版本...") + "OK!"); return false; } else { return true; } } catch (Exception) { //出错将返回false,比如:没有设置好更新路径。 return false; } } }
它继承自一个基类窗体Fm11Base,默认会做一些例如加载多语言文本的事情,没这个需求直接继承Form也可以。
二、这个页面通过后(有更新的话会跳到自动更新程序,可以忽略),会进入到登录窗体:
/// <summary> /// 登录窗体 /// </summary> public partial class Fm20Login : Fm11Base { bool IsLogin = false; protected internal string SelAccountNO ; protected internal string SelAccountName ; private static readonly string DftAESKeyOfDlls = @"James/Wang/"; private bool m_aeroEnabled; private static Fm20LoginTitle fm20LoginTitle; public string UserID { get { return TxtUserID.Text; } set { TxtUserID.Text = value; } } public Fm20Login() { m_aeroEnabled = false; InitializeComponent(); fm20LoginTitle = new Fm20LoginTitle(); } #region 无边框窗体处理 /// <summary> /// 拖动无边框窗体 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void OnFormDrag(object sender, MouseEventArgs e) { WinAPI.ReleaseCapture(); WinAPI.SendMessage(Handle, WinAPIConst.WM_SYSCOMMAND, WinAPIConst.SC_MOVE + WinAPIConst.HTCAPTION, 0); } protected override CreateParams CreateParams { get { m_aeroEnabled = CheckAeroEnabled(); CreateParams cp = base.CreateParams; if (!m_aeroEnabled) cp.ClassStyle |= WinAPIConst.CS_DropSHADOW; return cp; } } private bool CheckAeroEnabled() { if (Environment.OSVersion.Version.Major >= 6) { int enabled = 0; WinAPI.DwmIsCompositionEnabled(ref enabled); return (enabled == 1) ? true : false; } return false; } protected override void WndProc(ref Message m) { base.WndProc(ref m); switch (m.Msg) { case WinAPIConst.WM_NCPAINT: if (m_aeroEnabled) { var v = 2; WinAPI.DwmSetWindowAttribute(this.Handle, 2, ref v, 4); WinAPIConst.MARGINS margins = new WinAPIConst.MARGINS() { bottomHeight = 1, leftWidth = 1, rightWidth = 1, topHeight = 1 }; WinAPI.DwmExtendFrameIntoClientArea(this.Handle, ref margins); } break; default: break; } if (m.Msg == WinAPIConst.WM_NCHITTEST && (int)m.Result == WinAPIConst.HTCLIENT) m.Result = (IntPtr)WinAPIConst.HTCAPTION; } #endregion private void MitClose_Click(object sender, EventArgs e) { DialogResult = DialogResult.Cancel; } private void MitMini_Click(object sender, EventArgs e) { WindowState = FormWindowState.Minimized; } private void Fm20Login_Shown(object sender, EventArgs e) { if (!fm20LoginTitle.Visible) { fm20LoginTitle.Show(this); } else { fm20LoginTitle.BringToFront(); } fm20LoginTitle.Left = this.Left + this.Width / 2 - fm20LoginTitle.Width / 2; fm20LoginTitle.Top = this.Top + SplPnlMain.Panel1.Height - fm20LoginTitle.Height / 2; } private void Fm20Login_Move(object sender, EventArgs e) { fm20LoginTitle.Left = this.Left + this.Width / 2 - fm20LoginTitle.Width / 2; fm20LoginTitle.Top = this.Top + SplPnlMain.Panel1.Height - fm20LoginTitle.Height / 2; } private void Fm20Login_Load(object sender, EventArgs e) { try { LabStsVersion.Text = "Version: " + this.ProductVersion; TstMILangLL.Checked = (GlbInfo.Language == "LL"); TstMILangLI.Checked = !TstMILangLL.Checked; PicLogo.Image = Image.FromFile(AppInfo.GetPath(AppInfo.AppPath.Image) + @"AppLoginLd.png"); //读取上一次记住的用户名 OINI oPIniFile = new OINI(AppInfo.GetFile(AppInfo.AppFile.ConfigCore)); TxtUserID.Text = oPIniFile.ReadString("Login", "LastUserID", string.Empty); SetMainStatus(".."); } catch (Exception) { } } private void TxtUserCode_TextChanged(object sender, EventArgs e) { CmdClearUser.Visible = !(string.IsNullOrEmpty(TxtUserID.Text.Trim())); } private void TxtUserCode_Enter(object sender, EventArgs e) { PicFeedUser.BackgroundImage= Properties.Resources.LoginUserB; } private void TxtUserCode_Leave(object sender, EventArgs e) { PicFeedUser.BackgroundImage = Properties.Resources.LoginUserH; } private void TxtUserPass_TextChanged(object sender, EventArgs e) { CmdClearPwd.Visible = !(string.IsNullOrEmpty(TxtUserPass.Text.Trim())); } private void TxtUserPass_Enter(object sender, EventArgs e) { PicFeedLock.BackgroundImage = Properties.Resources.LoginPwdB; } private void TxtUserPass_Leave(object sender, EventArgs e) { PicFeedLock.BackgroundImage = Properties.Resources.LoginPwdH; } private void CmdClearUser_Click(object sender, EventArgs e) { TxtUserID.Clear(); } private void CmdClearPwd_Click(object sender, EventArgs e) { TxtUserPass.Clear(); } private void SetLanguage(string tagLang) { try { OINI oINI = new OINI(AppInfo.GetFile(AppInfo.AppFile.ConfigCore)); oINI.WriteString("Global", "CurrentLang", tagLang); TstMILangLL.Checked = (tagLang == "LL"); TstMILangLI.Checked = !TstMILangLL.Checked; GlbInfo.Language = tagLang; InitObjectsText(this); } catch (Exception) { throw; } } private void TstMILangLL_Click(object sender, EventArgs e) { SetLanguage("LL"); } private void TstMILangLI_Click(object sender, EventArgs e) { SetLanguage("LI"); } private void CmdLogin_Click(object sender, EventArgs e) { string tmpSts1 = MultiLang.Surface(this, "VeriUser", "正在验证用户..."); SetMainStatus(tmpSts1); try { string postUserID = TxtUserID.Text; string postUserPass = TxtUserPass.Text; if (string.IsNullOrEmpty(postUserID) || string.IsNullOrEmpty(postUserPass)) { MyMsg.Information("T.201001/用户名和密码不能为空."); TxtUserID.Focus(); return; } if ((SelAccountNO == "DEMOH") ) { MyMsg.Warning("T.201002/此帐套尚未开放,请选择其它帐套."); return; } if (SelAccountNO == "DEMO") { MyMsg.Warning("T.201003/您登录的是演示库,数据可能随时丢失,如需正式作业,请登录NORMAL正式库."); } string connStrCurAct = GetConnStrFromCfg(SelAccountNO, "ConnStr"); string connStrExt1 = GetConnStrFromCfg(SelAccountNO, "ConnStrExt1"); string connStrExt2 = GetConnStrFromCfg(SelAccountNO, "ConnStrExt2"); string connStrExt3 = GetConnStrFromCfg(SelAccountNO, "ConnStrExt3"); string connStrExt4 = GetConnStrFromCfg(SelAccountNO, "ConnStrExt4"); string connStrExt5 = GetConnStrFromCfg(SelAccountNO, "ConnStrExt5"); this.Cursor = Cursors.WaitCursor; #region 用户ERP登录验证部分 DBContext dBContext = new DBContext(connStrCurAct); if (!dBContext.IsConnected) { MyMsg.Error("T.1010/数据库连接已经创建,但无法开启!", dBContext.ConnectErrorMessage); return; } postUserPass = OCrypto.AES16Encrypt(postUserPass, DftAESKeyOfDlls); SqlParameter[] paras = new SqlParameter[] { new SqlParameter("@UserID", postUserID), new SqlParameter("@PasswordHash", postUserPass) }; string sqlText= "SELECT U.*, D.DeptCode, D.DeptNameLL, D.DeptNameLI FROM SYS_User AS U LEFT OUTER JOIN Base_Department AS D ON U.DeptRKEY = D.DeptRKEY WHERE (U.UserID= @UserID) AND (U.PasswordHash= @PasswordHash)"; DataTable tmpDTUInfo = dBContext.SqlToDT(sqlText, paras); if (tmpDTUInfo.Rows.Count > 0) { DataRow drUser = tmpDTUInfo.Rows[0]; if (!OString.NZ2Bool(drUser["IActived"])) { IsLogin = false; SetMainStatus(MultiLang.Surface(this, "LoginFail1", "登录失败(帐号未激活),请联系管理员!")); } else if (OString.NZ2Bool(drUser["ILocked"])) { IsLogin = false; SetMainStatus(MultiLang.Surface(this, "LoginFail2", "登录失败(帐号被锁定),请联系管理员!")); } else { GlbInfo.User.UserID = OString.NZ2Str(drUser["UserID"]); GlbInfo.User.UserNameLL = OString.NZ2Str(drUser["UserNameLL"]); GlbInfo.User.IsAdmin = OString.NZ2Bool(drUser["Hero"]); GlbInfo.User.RolesUserIDS = OString.NZ2Str(drUser["RolesUserIDS"]); GlbInfo.User.RightsList.Clear(); IsLogin = true; } } else { IsLogin = false; SetMainStatus(MultiLang.Surface(this, "LoginFail3", "登录失败(帐号不存在),请重新尝试!")); } if (IsLogin) { GlbInfo.ActNO = SelAccountNO; GlbInfo.ActName = SelAccountName; GlbInfo.ConnStrCurAct = connStrCurAct; GlbInfo.ConnStrExt1 = connStrExt1; GlbInfo.ConnStrExt2 = connStrExt2; GlbInfo.ConnStrExt3 = connStrExt3; GlbInfo.ConnStrExt4 = connStrExt4; GlbInfo.ConnStrExt5 = connStrExt5; fm20LoginTitle.Close(); if (ChkRemember.Checked) { try { //记录下登录用户,以便下次不必输入用户名 OINI oPIniFile = new OINI(AppInfo.GetFile(AppInfo.AppFile.ConfigCore)); oPIniFile.WriteString("Login", "LastUserID", postUserID); } catch (Exception) { } } DialogResult = DialogResult.OK; } else { TxtUserPass.Text = string.Empty; TxtUserPass.Focus(); MyMsg.Warning("T.201004/用户名或密码错误,请检查."); } #endregion } catch (Exception ex) { SetMainStatus(tmpSts1 + "出现异常!"); MyMsg.Exclamation(ex.Message); } finally { this.Cursor = Cursors.Default; } } /// <summary> /// 从配置文件中获取指定帐套数据库连接字符串(已加密) /// </summary> /// <param name="actNO">帐套代码</param> /// <param name="tKey">定义的连接串Key</param> /// <returns></returns> private string GetConnStrFromCfg(string actNO, string tKey) { try { StringCollection Idents = new StringCollection(); OINI oINI = new OINI(AppInfo.GetFile(AppInfo.AppFile.ConfigCore)); return oINI.ReadString("Account:" + actNO, tKey, string.Empty); } catch (Exception ex) { MyMsg.Error(ex.Message); return string.Empty; } } private void LabChkRemember_DoubleClick(object sender, EventArgs e) { try { //记录下登录用户,以便下次不必输入用户名 OINI oPIniFile = new OINI(AppInfo.GetFile(AppInfo.AppFile.ConfigCore)); oPIniFile.WriteString("Login", "LastUserID", string.Empty); MyMsg.Information("已经清除登录历史,下次登录将不再显示当前帐号名称."); } catch (Exception) { } } }
登录界面是一个起点,涉及到文件操作以及数据库的连接/存取,下次再分解。