程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> 關於C語言 >> 在C#中實現軟件自動升級

在C#中實現軟件自動升級

編輯:關於C語言
winform程序相對web程序而言,功能更強大,編程更方便,但軟件更新卻相當麻煩,要到客戶端一台一台地升級,本文結合實際情況,通過軟件實現自動升級,彌補了這一缺陷,有較好的參考價值。 由於程序在運行時不能用新的版本覆蓋自己,因此,我們將登錄窗口單獨做成一個可執行文件,用戶登錄時,從網上檢測是否有新的主程序,如果有,則從後台下載並覆蓋老的版本,用戶輸入正確的用戶名和密碼後,通過參數將必要的信息(如用戶名、密碼等)傳遞給主程序,實現登錄,我們還是以實際例子來說明。 創建一個項目,不妨取名為MainPro,作為主程序,切換到代碼窗口,看到如下一段代碼:          /// <summary>          /// 應用程序的主入口點。          /// </summary>          [STAThread]          static void Main()          {               Application.Run(new Form1());       } 為了接收參數,我們添加兩個靜態變量m_UserName和m_PassWord用於存放用戶名和密碼,並修改Main函數為:          private static string m_UserName,m_PassWord;          /// <summary>          /// 應用程序的主入口點。          /// </summary>          [STAThread]          static void Main(string[] args)          {               if(args.Length==2)//有參數輸入,你還可以根據實際情況傳入更多參數               {                   //記錄下用戶名和密碼,供軟件使用                    m_UserName=args[0];                    m_PassWord=args[1];                    Application.Run(new Form1());               }               else               {                    MessageBox.Show("不能從這裡啟動");               }       } 為了顯示登錄是否正確,Load事件的代碼修改為:          private void Form1_Load(object sender, System.EventArgs e)          {               string msg=string.Format("用戶名:{0},密碼:{1}",m_UserName,m_PassWord);               MessageBox.Show(msg);       } 這樣,我們的示例主程序就完成了,只有加入參數才能運行該主程序,例如我們在DOS窗口中用“mainpro user pass”來啟動該軟件。 由於本項目涉及到不止一個程序,為保證運行正確,需要將編譯後的可執行文件放到同一個文件夾,盡管我們可以編譯後再將文件復制到同一個文件夾中,但每次都手工復制比較費事,這裡采取一個簡單的辦法。先在硬盤中創建一個文件夾,如D:\output,選擇菜單“項目”→“屬性”,會彈出一個對話框,在配置(C)後面選擇“所有配置”,選擇配置屬性的生成項,在輸出路徑中輸入“D:\output”(如下圖),再編譯時你就發現,輸出的可執行文件乖乖地跑到D:\output下面了。 接下來做一個上傳工具,目的是將最新版本上傳到服務器上,為簡單,我們這裡使用Access數據庫,當然,在網絡版中可以使用SQL Server,原理完全一樣。 在D:\output下新建一個Access數據庫,取名為mydatabase.mdb吧,新建兩個表,一個為操作員,用來存放操作員的姓名和密碼,另外一個為版本,用來存放主程序的最新版本,兩個表的結構如下: 操作員表 版本表 字段名 類型 用途 字段名 類型 用途 序號 長整型 主鍵 序號 長整型 主鍵 姓名 字符 用戶名 版本號 長整型 存放當前版本 文件名稱 字符 本記錄對應的文件名 密碼 字符 密碼 文件內容 OLE 對象,SQL 中為Image 存放文件的具體內容 我們手工輸入一些用戶名和密碼,如下: 不要關閉剛才的主程序,直接選擇菜單“文件”→“添加項目”→“新建項目”,輸入項目名稱為“UpLoad”,如下圖: 點“確定”,同樣,配置輸出路徑為D:\output。 在窗口上放入三個按鈕(浏覽(btnBrow)、確定(btnOK)和取消(btnCancel))、兩個文本框(txtFileName,txtVersion)和相應的文字說明,如下圖: 在“解決方案資源管理器”窗口中,選擇“upload”項目,單擊鼠標右鍵,選擇“設為啟動項目”,就可以運行該程序了。 添加浏覽按鈕的響應代碼如下:          private void btnBrow_Click(object sender, System.EventArgs e)          {               OpenFileDialog myForm=new OpenFileDialog();               myForm.Filter="應用程序(*.exe)|*.exe|所有程序(*.*)|*.*";               if(myForm.ShowDialog()==DialogResult.OK)               {                    this.txtFileName.Text=myForm.FileName;               }       } 該按鈕的作用是得到要上傳文件的文件名稱(實際應用中,還可以根據得到的文件名,從數據庫中得到相對應文件的最高版本號,自動填入的版本號文本框中供輸入新版本號時參考)。 添加取消按鈕響應代碼,目的是關閉窗口:          private void btnCancel_Click(object sender, System.EventArgs e)          {               this.Close();       } 添加兩個引用:          using System.Data.OleDb;          using System.IO; 再添加兩個變量:          private DataSet m_DataSet=new DataSet();          private string m_TableName="版本"; 下面的函數去掉文件名中的路徑:          /// <summary>          /// 從一個含有路徑的文件名中分離出文件名          /// </summary>          /// <param name="p_Path">包含路徑的文件名</param>          /// <returns>去掉路徑的文件名</returns>          private string GetFileNameFromPath(string p_Path)          {               string strResult="";               int nStart=p_Path.LastIndexOf("\\");               if(nStart>0)               {                    strResult=p_Path.Substring(nStart+1,p_Path.Length-nStart-1);               }               return strResult;       } 添加確定按鈕響應代碼(含注釋): private void btnOK_Click(object sender, System.EventArgs e)          {               //檢查版本號是否合法               try               {                    Decimal.Parse(this.txtVersion.Text);               }               catch               {                    MessageBox.Show("無效的版本號!");                    this.txtVersion.Focus();                    this.txtVersion.SelectAll();                    return;               }                 if(this.txtFileName.Text.Trim().Length>0)               {                    //檢查文件是否存在                    if(!File.Exists(this.txtFileName.Text.Trim()))                    {                        MessageBox.Show("文件不存在!");                        return;                    }                      //連接數據庫                    string strConnection="Provider = Microsoft.Jet.OLEDB.4.0 ;Jet OLEDB:Database PassWord=;Data Source ="+                                           Application.StartupPath.ToString().Trim()+"\\mydatabase.mdb" ;                    OleDbConnection myConnect=new OleDbConnection(strConnection);                    OleDbCommand myCommand=new OleDbCommand("select * from 版本",myConnect);                    OleDbDataAdapter myDataAdapter=new OleDbDataAdapter();                    myDataAdapter.SelectCommand=myCommand;                    OleDbCommandBuilder myCommandBuilder=new OleDbCommandBuilder(myDataAdapter);                    myConnect.Open();                      //獲取已有的數據                    m_DataSet=new DataSet();                    try                    {                        myDataAdapter.Fill(m_DataSet,this.m_TableName);                        //如果是首次上傳,則增加一條記錄                        if(m_DataSet.Tables[m_TableName].Rows.Count==0)                        {                             DataRow newrow=m_DataSet.Tables[m_TableName].NewRow();                             newrow["序號"]="1";                             m_DataSet.Tables[m_TableName].Rows.Add(newrow);                        }                                               DataRow row=m_DataSet.Tables[m_TableName].Rows[0];                        //填入去掉路徑的文件名稱                        row["文件名稱"]=this.GetFileNameFromPath(this.txtFileName.Text.Trim());                        //填入版本號                        row["版本號"]=this.txtVersion.Text.Trim();                          //將實際文件存入記錄中                        FileStream fs=new FileStream(this.txtFileName.Text.Trim(),FileMode.Open);                        byte [] myData = new Byte [fs.Length ];                        fs.Position = 0;                        fs.Read (myData,0,Convert.ToInt32 (fs.Length ));                        row["文件內容"] = myData;                        fs.Close();//關閉文件                          //更新數據庫                        myDataAdapter.Update(this.m_DataSet,this.m_TableName);                         myConnect.Close();                        MessageBox.Show("文件更新成功!");                    }                    catch(Exception ee)                    {                        MessageBox.Show(ee.Message);                    }                                  }               else               {                    MessageBox.Show("請輸入文件名");               }       } 至此,上傳工具制作完成,通過該程序,可以上傳主程序文件,當然,該工具是給軟件開發供應商用於發布新軟件用的,千萬不要給用戶哦。 最後是編寫登錄程序,按照編寫上傳工具的方法添加一個項目,項目名稱為Login,設置輸出路徑為D:\Output,並設置該項目為啟動項目。 添加一個組合框(combUserName),設置DropDownStyle為DropDownList,用來選擇已有的用戶名,添加一個用於輸入密碼的文本框(txtPassword),設置PassWordChar屬性為“*”,並在前面加入相應的文字標簽,再添加確定(btnOK)和取消(btnCancel)按鈕,並將確定按鈕的Enable屬性設置為false,目的是如果新軟件沒有下載完成,不准登錄,布置如下圖: 切換到代碼窗口,添加引用: using System.Data.OleDb; using System.Threading; using System.IO; using Microsoft.Win32;   再添加如下變量:          /// <summary>          /// 存放操作員及密碼的DataSet          /// </summary>          private DataSet m_DataSet;          /// <summary>          /// 本功能用到的數據庫表          /// </summary>          private string m_TableName="操作員";          private DataTable m_Table; 為了避免每次都下載主程序,我們將當前主程序的版本號要保存下來,我采用的辦法是保存到注冊表中,為此,寫兩個函數,用於讀取/寫入注冊表,如下:          /// <summary>          /// 定義本軟件在注冊表中software下的公司名和軟件名稱          /// </summary>          private string m_companyname="lqjt",m_softwarename="autologin";          /// <summary>          /// 從注冊表中讀信息;          /// </summary>          /// <param name="p_KeyName">要讀取的鍵值</param>          /// <returns>讀到的鍵值字符串,如果失敗(如注冊表尚無信息),則返回""</returns>          private string ReadInfo(string p_KeyName)          {               RegistryKey SoftwareKey=Registry.LocalMachine.OpenSubKey("Software",true);               RegistryKey CompanyKey=SoftwareKey.OpenSubKey(m_companyname);               string strValue="";                             if(CompanyKey==null)                    return "";               RegistryKey SoftwareNameKey=CompanyKey.OpenSubKey(m_softwarename);//建立               if(SoftwareNameKey==null)                    return "";                 try               {                    strValue=SoftwareNameKey.GetValue(p_KeyName).ToString().Trim();               }               catch               {}                 if(strValue==null)                    strValue="";               return strValue;          }          /// <summary>          /// 將信息寫入注冊表          /// </summary>          /// <param name="p_keyname">鍵名</param>          /// <param name="p_keyvalue">鍵值</param>          private void WriteInfo(string p_keyname,string p_keyvalue)          {               RegistryKey SoftwareKey=Registry.LocalMachine.OpenSubKey("Software",true);               RegistryKey CompanyKey=SoftwareKey.CreateSubKey(m_companyname);               RegistryKey SoftwareNameKey=CompanyKey.CreateSubKey(m_softwarename);               //寫入相應信息               SoftwareNameKey.SetValue(p_keyname,p_keyvalue);       } 再寫一個函數,用戶來獲取用戶名/密碼和更新主程序版本: /// <summary>          /// 獲取操作員情況,同時更新主程序版本          /// </summary>          private void GetInfo()          {               this.m_DataSet=new DataSet();               this.combUsers.Items.Clear();               string strSql=string.Format("SELECT * FROM  操作員 ORDER BY 姓名");                 //連接數據庫               string strConnection="Provider = Microsoft.Jet.OLEDB.4.0 ;Jet OLEDB:Database PassWord=;Data Source ="+                    Application.StartupPath.ToString().Trim()+"\\mydatabase.mdb" ;               OleDbConnection myConnect=new OleDbConnection(strConnection);               OleDbCommand myCommand=new OleDbCommand(strSql,myConnect);               OleDbDataAdapter myDataAdapter=new OleDbDataAdapter();               myDataAdapter.SelectCommand=myCommand;               try               {                    myConnect.Open();                      //獲取操作員信息                    myDataAdapter.Fill(this.m_DataSet,this.m_TableName);                    //將查詢到的用戶名填充到組合框供用戶選擇                    this.m_Table=this.m_DataSet.Tables[this.m_TableName];                    foreach(DataRow row in m_DataSet.Tables[m_TableName].Rows)                    {                        this.combUsers.Items.Add(row["姓名"]).ToString().Trim();                    }                      //檢查是否有新的版本                    DataSet dataset=new DataSet();                    string tablename="tablename";                    //為減少數據傳送時間,不獲取文件內容                    strSql="select 文件名稱,版本號 from 版本";                    myCommand=new OleDbCommand(strSql,myConnect);                    myDataAdapter=new OleDbDataAdapter();                    myDataAdapter.SelectCommand=myCommand;                    myDataAdapter.Fill(dataset,tablename);                    if(dataset.Tables[tablename].Rows.Count==1)//有文件                    {                        string filename=dataset.Tables[tablename].Rows[0]["文件名稱"].ToString();                        string version=dataset.Tables[tablename].Rows[0]["版本號"].ToString();                        //讀入本機主程序的版本號                        string oldversion=this.ReadInfo(filename);                        if(oldversion.Length==0)//不存在                             oldversion="0";                        if(Decimal.Parse(version)>Decimal.Parse(oldversion))//有新的版本出現                        {                             //取回文件內容                             dataset=new DataSet();                             strSql="select * from 版本";                             myCommand=new OleDbCommand(strSql,myConnect);                             myDataAdapter=new OleDbDataAdapter();                             myDataAdapter.SelectCommand=myCommand;                             myDataAdapter.Fill(dataset,tablename);                             //將文件下載到本地                             DataRow row=dataset.Tables[tablename].Rows[0];                             if(row["文件內容"]!=DBNull.Value)                             {                                    Byte[] byteBLOBData =  new Byte[0];                                  byteBLOBData = (Byte[])row["文件內容"];                                  try                                  {                                      FileStream fs=new FileStream(Application.StartupPath+"\\"+filename,FileMode.OpenOrCreate);                                      fs.Write(byteBLOBData,0,byteBLOBData.Length);                                      fs.Close();                                      //寫入當前版本號,供下次使用                                      this.WriteInfo(filename,version);                                  }                                  catch(Exception ee)                                  {                                      MessageBox.Show(ee.Message);                                  }                             }                          }//有新版本                    }//有文件                      //關閉連接                    myConnect.Close();               }               catch(Exception ee)               {                    MessageBox.Show(ee.Message);                    return;               }               //允許登錄               this.btnOK.Enabled=true;       } 為了不讓用戶等待太久,在啟動時通過一個線程,讓獲取用戶信息和更新在後台完成,即在窗口Load事件中,通過線程調用上面的GetInfo的函數,故窗口Load代碼如下:          private void Form1_Load(object sender, System.EventArgs e)          {               //為加快顯示速度,將數據庫連接等放到另外一個線程中去               Thread thread=new Thread(new ThreadStart(GetInfo));               thread.Start();       } 有了上述准備,我們來編寫確定按鈕的響應代碼如下: private void btnOK_Click(object sender, System.EventArgs e)          {               //根據組合框的選擇,得到當前用戶在DataSet中具體物理位置               if(this.combUsers.SelectedIndex<0)//沒有選擇                    return;               DataRow rowNow=null;               foreach(DataRow row in this.m_DataSet.Tables[this.m_TableName].Rows)               {                    if(row["姓名"].ToString().Trim()==this.combUsers.Text.Trim())                    {                        rowNow=row;                        break;                    }               }               if(rowNow==null)                    return;                 //獲取當前正確密碼               string strPassWord=rowNow["密碼"].ToString().Trim();               this.txtPassword.Text=this.txtPassWord.Text.Trim();               if(this.txtPassword.Text==strPassWord)//密碼正確               {                      //主程序名稱                    string filename=Application.StartupPath+"\\"+"MainPro.exe";                    //參數名稱                    string arg=this.combUsers.Text+" "+this.txtPassWord.Text;                    //運行主程序                    System.Diagnostics.Process fun=System.Diagnostics.Process.Start(filename,arg);                      //關閉登錄框                    this.Close();               }               else               {                    MessageBox.Show("    密碼錯誤!如果你確信密碼輸入正確,\n可以試著檢查一下大寫字母鍵是否按下去了。",                        "警告",MessageBoxButtons.OK,MessageBoxIcon.Warning);                    this.txtPassWord.Focus();                    this.txtPassWord.SelectAll();               }       } 取消按鈕的代碼非常簡單,就是關閉登錄窗口:          private void btnCancel_Click(object sender, System.EventArgs e)          {               this.Close();       } 把Login和MainPro軟件連同其他相關文件打包成安裝程序,將Login以快捷方式放到桌面或開始菜單中供用戶使用(當然,快捷方式名稱可以隨便取了),用戶運行Login後,會自動更新軟件。 本例中所有代碼請到FTP://qydn.vicp.Net/ 下載,文件名為update.rar,解壓縮後別忘了在D:\創建一個output文件夾,並將mydatabase.mdb復制到該文件夾中。 說明:本文只起拋磚引玉的作用,通過該思路進行擴展可以完成許多功能,如通過修改上傳/登錄程序,不僅可以實現對主程序的更新,而且可以實現對任何要用到的資源文件進行更新,本例中不能實現對登錄框本身的更新,我采用的辦法是在主程序的Closing事件中更新登錄窗口,因為此時登錄窗口已經關閉了。在登錄窗口中,可以放一個“保存密碼”的復選框,如果用戶選中該組合框,可以將用戶名和密碼保存到注冊表中,下次登錄時直接讀入,用戶只要點確定按鈕即可,免去了每次都選用戶名和輸密碼的煩惱, 在本例中,我們可以看到,數據庫的連接、查詢等工作是重復性勞動,且三個個項目中用到的數據庫、公司名稱等是一樣的,在實際工作中,我們可以單獨新建一個cs文件,不妨取名為MyTools.cs,將一些常用函數和變量(如數據庫連接、公司名稱等)做成靜態的,各具體項目中鏈接本文件,然後直接使用,我們只需修改MyTools.cs中的相關變量或函數而不必在每個項目中都去改,既方便又不會遺漏,MyTools.cs參考如下: ///<summary> ///預編譯選項,如果定義了NETWORKVERSION,,表示是網絡版,使用SQL2000數據庫,否則,使用Access2000數據庫 ///</summary>   //#define NETWORKVERSION   using System; using System.Drawing; using System.Collections; using System.ComponentModel; using System.Windows.Forms; using System.Drawing.Imaging; using System.IO; using System.Data;   #if NETWORKVERSION using System.Data.SqlClIEnt; #else using System.Data.OleDb; #endif using System.Reflection; using Microsoft.Win32;   namespace OA {      public class Tool      {          public Tool()          {          }          /// <summary>          /// 主程序的文件名          /// </summary>          public const string FileName="OA.exe";       public const string g_TitleName="麗汽集團辦公自動化系統";       public static string g_UserName;          public static void WriteInfo(string p_keyname,string p_keyvalue)          {              ……       } //其他類似代碼略……   } } 如果一個項目中要用到MyTools中的內容,可以按如下方式進行: 在“解決方案資源管理器”窗口中選擇該項目,選擇菜單“項目”→“添加現有項”,此時彈出打開文件對話框,文件類型設為所有文件(*.*),找到MyTools.cs,不要直接點打開按鈕,看到了打開按鈕後面的“↓”了嗎?單擊它可以彈出一個菜單,選擇“鏈接文件(L)”,這樣插入的文件只是一個鏈接,不會生成副本(如下圖)。 使用時,添加MyTools的應用,再使用Tool類中的公共函數,如: using OA; private void myFun() {  string s=Tool.FileName; } 如果單位名稱變了,我們只要修改MyTools.cs中的變量就可以了,不必到每個項目中都去修改。 我們還注意了一個細節: ///<summary> ///預編譯選項,如果定義了NETWORKVERSION,,表示是網絡版,使用SQL2000數據庫,否則,使用Access2000數據庫 ///</summary>   //#define NETWORKVERSION 我們知道,對於Access或Sql server等,除了連接方式外,其余操作幾乎完全一樣,因此,我們定義了一個選項(如上面的注釋),如果#define NETWORKVERSION,表示是網絡版,使用SQL Server數據庫,否則(將#define NETWORKVERSION注釋掉)就是單機版,使用Access數據庫,在MyTools中我們將兩種連接方式有區別的地方分別編寫,就可以通過是否注釋掉#define NETWORKVERSION這一行分別生成單機版和網絡版軟件,參考代碼如下:      /// <summary>          /// 根據SQL語句返回一個查詢結果,主要用於只要求返回一個字段的一個結果的情況          /// </summary>          /// <param name="p_Sql">查詢用到的SQL語句</param>          /// <returns>查詢到的結果,沒有時則返回空""</returns>          public static string GetAValue(string p_Sql)          {               string strResult="";               Tool.OpenConn();                 //設計所需要返回的數據集的內容               try               {                    // 打開指向數據庫連接 #if NETWORKVERSION //網絡版                    SqlCommand aCommand = new SqlCommand ( p_Sql ,m_Connect ) ;                    SqlDataReader aReader = aCommand.ExecuteReader ( ) ; #else  //單機版,注意變量名aCommand和aReader在兩個版本中都是一樣的,有利於編程                    OleDbCommand aCommand = new OleDbCommand ( p_Sql ,m_Connect ) ;                    OleDbDataReader aReader = aCommand.ExecuteReader ( ) ; #endif                                       // 返回需要的數據集內容這裡就不分單機版還是網絡版了,反正變量名一樣                    if(aReader.Read())                        strResult=aReader[0].ToString();                    aReader.Close () ;                 }               catch(Exception ee)               {                    MessageBox.Show(ee.Message);               }               return strResult;       } 以上類似的小技巧還很多,注意總結,定會收益多多。
  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved