C# winForm製做程序管理客戶端

前言:最近接到一個需求,要實現客戶端對程序進行集中管理,相似於軟件管家。 一開始心想,他媽的這麼複雜。但咱們初步只是作簡化版,因此沒什麼技術難度。 但做爲一個常年作web開發的工程師來講,要作好一個客戶端,也不是一件容易的事情。恰好如今作的差很少了,總結一下這個過程。前端

1.準備工做,找到幾個開源源代碼,研究其代碼的核心實現方法。web

   咱們找了三個源代碼,有兩個較爲複雜,沒有太深刻去研究,找了一個稍微簡單點的加以研究分析。 數據庫

   同時也經過百度,查了一些資料,最爲基礎的是:如何下載文件。 json

   有兩種方式,一種經過http數據流傳輸,另外一種是利用webclient的下載文件的方法。 windows

   研究代碼發現,它用的是webclient下載文件的方式,這種方式顯而易見的好處是很是便於作進度條展現。api

    webclient 示例僞代碼以下服務器

     private WebClient client = null;網絡

       client = new WebClient();
       client.Credentials = CredentialCache.DefaultCredentials;
       client.Headers.Add("Content-Type:application/octet-stream"); 
       client.Encoding = Encoding.UTF8;多線程

        client.DownloadProgressChanged += client_onDownloadChanged;
        client.DownloadFileCompleted += client_onDownloadComplete;app

         String uri = "http://xxx/test.rar";

         String localfile = 本地文件路徑

          String targetName = token對象,傳遞給完成下載時的回調。這裏我用的文件名(不帶路徑)

          client.DownloadFileAsync(uri, localfile, targetName);

2.需求分析

   核心的需求是這樣,指定客戶存放程序的路徑,若是該路徑下沒有相應的程序,則從服務器上下載,這裏咱們把這個過程叫作安裝,若是存在文件了,則比較版本信息,若是須要升級,則自動升到最新版本,不然直接啓動程序。

   額外的需求有:1.版本控制,不一樣版本對應不一樣的程序,並且須要作版本發佈。 

                         2.日誌記錄, 記錄程序運行期間出現的錯誤信息

                         3.軟件排行,軟件使用頻率的排行

                         4.其餘需求(程序安裝部署等)

3.具體實現

   1.設計數據庫,針對每個環節設計相應的數據以及關聯。

   2.客戶端實現(使用C# winform,因爲客戶機大多數是windows xp系統,爲了保證系統兼容性,沒有使用wpf技術)

      客戶端分爲上、左、右三個部分,上爲標題欄,能夠控制窗體移動,左側爲不一樣的軟件類型,右側爲應用列表,用圖標加文字的方式展現。軟件類型、應用圖標,各自有通用的處理,因此將這兩個封裝成了用戶控件,主窗體只要遍歷輸出空間就能夠了。 

      數據,是經過http接口方式提供,返回的是json格式的數據。

      在下載數據的時候,剛開始爲了快速實現,直接使用的同步方法,致使有時候該彈出對話框的地方,等待了好久纔出來。 後來,改用多線程,在線程中進行下載操做,同時傳入對象,更新UI,這樣就不會出現看上去卡死的狀況。

   3.後臺功能實現

        後臺的界面採用easyui,爲了快速開發,後臺是直接寫代碼,沒有采用什麼框架技術,用的ADO.NET訪問數據庫。包括接口的實現和各個管理功能的實現。功能模塊有:應用分類,應用管理,應用版本,文件上傳(針對指定版本的應用,上傳對應的文件,但此功能因爲實現較爲複雜,不夠穩定,暫時不用此功能),軟件排行,查看日誌。

   4.功能調整小插曲

         原本但願能經過後臺上傳文件,以達到上傳完成後,發佈版本,客戶端就能夠從上傳文件的目標路徑,下載到客戶端指定的程序存放路徑。 但通過屢次測試,發現上傳的文件不穩定,並且flash上傳控件能夠多選文件,但不能選文件夾,前端若是須要選文件夾,極可能存在不兼容的問題。所以,改變技術方案,後臺只發布版本,程序手動傳到服務器上,讓客戶端仍然從指定版本下載文件。這樣雖說,有了人工介入,但可以保證程序是正確可用的,投入生產環境時,出現問題的機率下降了。

          因爲程序由不一樣的方式產生,因此在服務器配置上須要增長不少擴展配置,大多數mime配置爲application/octet-stream 。對於沒有後綴的文件,應該這樣配置: mime    .   application/octet-stream

  5. 安裝部署

       第一次使用installsheild,對其研究也花了些時間,第一步是配置一些安裝程序的基本信息,第二步是安裝以前所須要具有的環境,第三步是選文件,第四步是設置菜單欄和卸載程序,第五步是註冊表設置,第六步是安裝過程的一些提示信息的配置。  

       在項目下會有1-6共6個目錄,每一個目錄裏面有幾個項,每一項都是一些配置。特別注意Redistributables  這裏能夠配置安裝哪些基礎程序或依賴程序。勾選選項會下載相應程序安裝包,而且在生成時會帶上相應的安裝包。

   6.一些winform開發的代碼細節

        1.控件.SuspendLayout() 減小觸發layout事件,提升性能。

        2.圖片設置居中的辦法(示例代碼):

            this.pictureBox1.BackgroundImageLayout = System.Windows.Forms.ImageLayout.Zoom;
            this.pictureBox1.Location = new System.Drawing.Point(10, 0);
            this.pictureBox1.Name = "pictureBox1";
            this.pictureBox1.Size = new System.Drawing.Size(60, 60);
            this.pictureBox1.TabIndex = 0;
            this.pictureBox1.TabStop = false;
            this.pictureBox1.Click += new System.EventHandler(this.pictureBox1_Click);

       3. 退出程序(關閉進程)

              this.Close();

              Environment.Exit(0);

        4.窗口最大化最小化

             最大化 :   this.WindowState = FormWindowState.Maximized;

             最小化 :  this.WindowState = FormWindowState.Minimized;  

        5.開啓新線程,定時檢測網絡狀態代碼(兩種方法都在裏面,只是在使用的時候,只用了ping ip的方式,沒有調用 InternetGetConnectedState方法,這是由於我這裏須要測試內網網絡環境是否正常,而調用InternetGetConnectedState只能判斷是否連上外網)

               //導入dll
                [DllImport("wininet.dll", EntryPoint = "InternetGetConnectedState")]
                //判斷網絡情況的方法,返回值true爲鏈接,false爲未鏈接
                public extern static bool InternetGetConnectedState(out int conState, int reder);

                public  constructor(){

                     Thread netThread = new Thread(ListenNetWork);
                    netThread.Start();

                }

                
        private void ListenNetWork() {
            //定時檢測網絡狀態
            System.Timers.Timer timer = new System.Timers.Timer();
            //時間頻率
            timer.Interval = 1000;
            //設置爲一直執行  false表示只執行一次
            timer.AutoReset = true;
            //開啓System.Timers.Timer.Elapsed事件
            timer.Enabled = true;
            //執行檢測網絡方法
            timer.Elapsed += new System.Timers.ElapsedEventHandler(CheckNetState);
        }
        

       

 private void CheckNetState(object source, System.Timers.ElapsedEventArgs e)
        {
            int result = 0;
            String message = "";
            //調用api檢測網絡狀態
            // bool flag = InternetGetConnectedState(out result, 0);
            Ping ping = new Ping();
            IPAddress address = IPAddress.Parse(ServerIp);    //IPAddress.Loopback;
            PingReply reply=  ping.Send(address);
            if (reply.Status==IPStatus.Success)
            {
                message = "網絡正常";
                NetState.CurrentState = 1;
            }
            else
            {
                message = "網絡鏈接失敗";
                NetState.CurrentState = -1;
            }

            this.status_label.Text = message;

        }

     

      6.窗體移動代碼

          

        #region 鼠標選中移動窗體

        [DllImport("user32.dll")]
        public static extern bool ReleaseCapture();
        [DllImport("user32.dll")]
        public static extern bool SendMessage(IntPtr hwnd, int wMsg, int wParam, int lParam);

        public const int WM_SYSCOMMAND = 0x0112;
        public const int SC_MOVE = 0xF010;
        public const int HTCAPTION = 0x0002;

        Point mouseOff;//鼠標移動位置變量
        bool leftFlag;//標籤是否爲左鍵
        private void FrmMain_MouseDown(object sender, MouseEventArgs e)
        {
            ReleaseCapture();
            SendMessage(this.Handle, WM_SYSCOMMAND, SC_MOVE + HTCAPTION, 0);
        }

        private void FrmMain_MouseMove(object sender, MouseEventArgs e)
        {
            if (leftFlag)
            {
                Point mouseSet = System.Windows.Forms.Control.MousePosition;
                mouseSet.Offset(mouseOff.X, mouseOff.Y); //設置移動後的位置
                Location = mouseSet;
            }
        }

        private void FrmMain_MouseUp(object sender, MouseEventArgs e)
        {
            if (leftFlag)
            {
                leftFlag = false;//釋放鼠標後標註爲false;
            }
        }
        #endregion

 

    7.點擊菜單,切換菜單項代碼

       

             System.Windows.Forms.Control.ControlCollection collection = this.panel3.Controls;

                //重置樣式

                for (int i = 0; i < collection.Count; i++)
                {
                    Biz.Controls.MenuItem m_item = (Biz.Controls.MenuItem)collection[i];
                    m_item.BackColor = System.Drawing.SystemColors.InactiveCaption;

                }

                //選中當前項

                Biz.Controls.MenuItem menuitem = (Biz.Controls.MenuItem)sender;
                curMenuId = menuitem.Menuid;
                menuitem.BackColor = System.Drawing.SystemColors.ActiveCaption;

    8.給控件畫虛邊線 

            Pen pen = new Pen(SystemColors.ControlDark);
            pen.DashStyle = System.Drawing.Drawing2D.DashStyle.Dot;
            e.Graphics.DrawLine(pen, panel.Width - 1, panel.Height - 1, panel.Width - 1, 0);

    9.多線程更新UI控件方法

             private delegate void UpdateLabelCallback(String info); 

        private void FrmMain_Load(object sender, EventArgs e)
        {

            Thread  subThread= new Thread(new ThreadStart(doSomeThing));
            subThread.Start(); 

        }

        

 private void UpdateLabel(String info)
        {
            //this.lab_fileinfo.Text = info ;

            if (this.lab_fileinfo.InvokeRequired)//若是調用控件的線程和建立建立控件的線程不是同一個則爲True
            {
                while (!this.lab_fileinfo.IsHandleCreated)
                {
                    if (this.lab_fileinfo.Disposing || this.lab_fileinfo.IsDisposed)
                    {
                        return;
                    }
                }
                UpdateLabelCallback callback = new UpdateLabelCallback(UpdateLabel);
                this.lab_fileinfo.Invoke(callback, new object[] { info });
            }
            else {
                this.lab_fileinfo.Text = info;
            }

        }

        private void doSomeThing(){

                 //省略邏輯代碼

                 UpdateLabel("doSomeThing");

        }

     10. 進程啓動程序方法

              1.調用Process.Start方法  

                    String exeFile = "D:\\QQ.exe";

                    System.Diagnostics.Process.Start(exeFile);

              2.有些綠色版免安裝程序啓動exe文件時,必須制定啓動程序的工做路徑,不能直接用上述方法啓動,正確啓動方法以下:

                        String exeFile = "D:\\test\\QQ.exe";
                        String workDir = "D:\\test";

                        ProcessStartInfo process = new ProcessStartInfo();
                        process.FileName = exeFile;
                        process.UseShellExecute = false;
                        //關鍵在這裏,至關因而切換到該目錄下,打開exe 文件
                        process.WorkingDirectory = workDir;
                        process.CreateNoWindow = true;
                        Process.Start(process);

11.獲取機器名稱和mac地址

              

public class SysUtil
    {
        public static String machineName {
            get {
                
                return Dns.GetHostName();
            }
        }

        public static String machineIp
        {
            get
            {
                IPAddress addr;
                addr = new IPAddress(Dns.GetHostByName(Dns.GetHostName()).AddressList[0].Address);
                return addr.ToString();
            }
        }
    }

12.寫入日誌文件

       

public static void Write(string msg, bool isAppend)
        {
            try
            {
                string filename = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "run.log");
                if (!Directory.Exists(Path.GetDirectoryName(filename)))
                {
                    Directory.CreateDirectory(Path.GetDirectoryName(filename));
                }
                using (FileStream stream = new FileStream(filename, isAppend ? FileMode.Append : FileMode.Create, FileAccess.Write, FileShare.None))
                {
                    StreamWriter writer = new StreamWriter(stream);
                    writer.WriteLine(msg);
                    writer.Close();
                    stream.Close();
                }
            }
            catch
            {
            }
        }

13.http請求封裝

     

public String GetResponse(String uri)
        {
            HttpWebRequest request =(HttpWebRequest) HttpWebRequest.Create(uri);
            request.Method = "GET";
            HttpWebResponse response=(HttpWebResponse)request.GetResponse();
            Stream respStream = response.GetResponseStream();
            StreamReader reader = new StreamReader(respStream);
            String info = reader.ReadToEnd();
            return info;
        }

14.執行dos命令的封裝

    

/// <summary>
        /// DOS命令運行函數
        /// </summary>
        /// <param name="commandText"></param>
        public void ExeCommand(string commandText)
        {
            Process p = new Process();
            p.StartInfo.FileName = "cmd.exe";
            p.StartInfo.UseShellExecute = false;
            p.StartInfo.RedirectStandardInput = true;
            p.StartInfo.RedirectStandardOutput = true;
            p.StartInfo.RedirectStandardError = true;
            p.StartInfo.CreateNoWindow = true;
            try
            {
                p.Start();
                p.StandardInput.WriteLine(commandText);
                p.StandardInput.WriteLine("exit");
                 
            }
            catch
            {

            }
        }

 

如下是部分截圖

 

 

 

 

本博客文章大可能是經驗積累總結,以避免從此忘卻,記錄下來。同時感謝您的閱讀,也但願能對您有所幫助。

相關文章
相關標籤/搜索