C#實現軟件自動升級

 

winform程序相對web程序而言,功能更強大,編程更方便,但軟件更新卻至關麻煩,要到客戶端一臺一臺地升級,本文結合實際狀況,經過軟件實現自動升級,彌補了這一缺陷,有較好的參考價值。
因爲程序在運行時不能用新的版本覆蓋本身,所以,咱們將登陸窗口單獨作成一個可執行文件,用戶登陸時,從網上檢測是否有新的主程序,若是有,則從後臺下載並覆蓋老的版本,用戶輸入正確的用戶名和密碼後,經過參數將必要的信息(如用戶名、密碼等)傳遞給主程序,實現登陸,咱們仍是以實際例子來講明。

建立一個項目,不妨取名爲MainPro,做爲主程序,切換到代碼窗口,看到以下一段代碼:
         ///
         /// 應用程序的主入口點。
         ///
         [STAThread]
         static void Main()
         {
              Application.Run(new Form1());
      }
web

爲了接收參數,咱們添加兩個靜態變量m_UserName和m_Password用於存放用戶名和密碼,並修改Main函數爲:

         private static string m_UserName,m_Password;
         ///
         /// 應用程序的主入口點。
         ///
         [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;
ide

再添加兩個變量:

         private DataSet m_DataSet=new DataSet();
         private string m_TableName="版本";
函數

下面的函數去掉文件名中的路徑:

         ///
         /// 從一個含有路徑的文件名中分離出文件名
         ///
         /// 包含路徑的文件名
         /// 去掉路徑的文件名
         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("請輸入文件名");
              }
      }
ui

至此,上傳工具製做完成,經過該程序,能夠上傳主程序文件,固然,該工具是給軟件開發供應商用於發佈新軟件用的,千萬不要給用戶哦。
最後是編寫登陸程序,按照編寫上傳工具的方法添加一個項目,項目名稱爲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;
 
this

再添加以下變量:

         ///
         /// 存放操做員及密碼的DataSet
         ///
         private DataSet m_DataSet;
         ///
         /// 本功能用到的數據庫表
         ///
         private string m_TableName="操做員";
         private DataTable m_Table;

爲了不每次都下載主程序,咱們將當前主程序的版本號要保存下來,我採用的辦法是保存到註冊表中,爲此,寫兩個函數,用於讀取/寫入註冊表,以下:

         ///
         /// 定義本軟件在註冊表中software下的公司名和軟件名稱
         ///
         private string m_companyname="lqjt",m_softwarename="autologin";
         ///
         /// 從註冊表中讀信息;
         ///
         /// 要讀取的鍵值
         /// 讀到的鍵值字符串,若是失敗(如註冊表尚無信息),則返回""
         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;
         }
         ///
         /// 將信息寫入註冊表
         ///
         /// 鍵名
         /// 鍵值
         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);
      }

再寫一個函數,用戶來獲取用戶名/密碼和更新主程序版本:

///
         /// 獲取操做員狀況,同時更新主程序版本
         ///
         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參考以下:

///
///預編譯選項,若是定義了NETWORKVERSION,,表示是網絡版,使用SQL2000數據庫,不然,使用ACCESS2000數據庫
///
 
//#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()
         {
         }
         ///
         /// 主程序的文件名
         ///
         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中的變量就能夠了,沒必要到每一個項目中都去修改。
咱們還注意了一個細節:

///
///預編譯選項,若是定義了NETWORKVERSION,,表示是網絡版,使用SQL2000數據庫,不然,使用ACCESS2000數據庫
///
 
//#define NETWORKVERSION

咱們知道,對於ACCESS或Sql server等,除了鏈接方式外,其他操做幾乎徹底同樣,所以,咱們定義了一個選項(如上面的註釋),若是#define NETWORKVERSION,表示是網絡版,使用Sql server數據庫,不然(將#define NETWORKVERSION註釋掉)就是單機版,使用ACCESS數據庫,在MyTools中咱們將兩種鏈接方式有區別的地方分別編寫,就能夠經過是否註釋掉#define NETWORKVERSION這一行分別生成單機版和網絡版軟件,參考代碼以下:

     ///
         /// 根據SQL語句返回一個查詢結果,主要用於只要求返回一個字段的一個結果的狀況
         ///
         /// 查詢用到的SQL語句
         /// 查詢到的結果,沒有時則返回空""
         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;
      }

以上相似的小技巧還不少,注意總結,定會收益多多。
相關文章
相關標籤/搜索