C# Winform下一個熱插拔的MIS/MRP/ERP框架11(啟航)
阿新 • • 發佈:2018-10-16
aer tab chan byname 可能 清理 contex cati break
初學時,有了想法卻完全不知道該從何下指,此序列將拋磚引玉,與大家共同學習進步。
一個程序的初始,必然是啟動。
我的要求:
1、應用程序保持單例;
2、從配置文件加載一些基礎數據進行初始化;
3、顯示軟件的LOGO頁面;
4、判斷應用程序是否有更新;
4、進入用戶登錄界面;
5、用戶成功登錄後顯示主界面。
如上圖,基本達成目標,這個項目也是很簡單的結構:
看窗體名稱就知道各自的用途了。主要的Program.cs代碼如下:
static class Program { public static bool 切換用戶 = falseProgram.cs; /// <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 = newSystem.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; } } }Fm20Splash.cs
它繼承自一個基類窗體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) + @"\App\LoginLd.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) { } } }Login.cs
登錄界面是一個起點,涉及到文件操作以及數據庫的連接/存取,下次再分解。
C# Winform下一個熱插拔的MIS/MRP/ERP框架11(啟航)