一个winform自动更新程序
1.背景
再一次给客户现场的刷卡用餐程序做二次功能开发时,测试阶段客户总是要求修改程序页面显示的东西,但是电脑不允许远程操作,每次只能联系客户方IT进行部署,感觉十分麻烦,因此希望能做一个类似TIM一样的启动检查新版本自动更新的功能。这里我把我开发这个程序过程中我是怎么做的,怎么想的,分享出来,请大家多多指正。
2.功能分析
通过观察TIM的更新过程,我们不难总结出 要实现这样的功能,我们需要单独开发更新程序一个专门用来更新主应用程序,如图:
整体功能设计的思路已经有了,接下来我们具体再深入看看具体的要如何设计。
1.首先我们会遇到第一个问题: 程序更新前判断是否需要更新,我们不禁又要问
- 我要如何去判断,去哪里判断是否需要更新?
- 一个程序的更新信息应该如何存储?
- 不考虑存储方式,那应该存储程序的那些更新信息呢?
要想回答第一个问题,我们必须要先解决问题2和问题3
Q:更新信息如何存储?
@:我们在项目中经常用什么存储数据?最常用的无非就是数据存储,或者使用文件存储(如:xml)。我们观察整体构思图,我们可以发现自动更新程序其实最核心做的事情就是对文件的操作(下载文件,替换文件)。那如果我们使用数据库存储更新信息,那么我们的更新程序既要实现对文件操作,还需要对数据库进行操作,然而如果我们用文件存储更新信息,那么我们的更新程序就只是在实现对文件的操作。所以我这里选择使用一个单独的xml文件用来存储我们程序的更新信息。
Q:要存储那些更新信息
@:首先常见的我们一个程序的更新信息(版本信息)一般都会包含这个程序当前是多少版本、版本是什么时候发布的。再然后我们可以看到更新程序核心要做的第一件事是”从服务器下载文件“,既然要从服务器下载文件,那我必然是要知道文件下载的地址,以及要下载那些文件。
Q:判断是否需要更新
@:问题1和问题2解决后,我们以xml文件存储更新信息,那么在我们当前应用程序下有当前版本信息的xml,而在服务更新文件目录下必然也会有服务器最新程序的更新信息(版本信息),那我们只需要将我们本地的xml文件信息和服务器上xml文件信息进行对比 不就知道是不是需要更新。(这里的对比指的是xml内存储的版本号,更新时间这样的数据)
2.是否进行更新知道如何判断之后,我们就进入到了更新应用程序,更新程序需要做2件核心的事儿
- 从服务器上下载文件到本地
- 把下载文件替换到应用程序中
需要做这两件事儿我们就在程序去写对应可以实现的代码就可以,不会的嘛,百度欢迎你。
3.更新文件要从服务器下载,那我们自然要在服务器上部署个网站使其可以访问下载文件
网站发布方法参考:IIS发布可下载文件的站点
经过这样的梳理 面向对象编程 接下来就到了把上面的思路以及需要做的操作,都设计成相关的类的形式
如下图:
4.功能实现
更新信息xml
<?xml version="1.0" encoding="utf-8" ?>
<AutoUpdate>
<URLAddress URL="http://115.159.63.169/DownLoadFile"/>
<Updateinfo>
<UpdateTime Date="2020-01-02 12:30"/>
<Version Num="1.0.0.2"/>
</Updateinfo>
<UpdateFileList>
<!--升级文件列表-->
<UpdateFile Ver="1.0.0.2" FileName= "DAL.dll" ContentLength="21k"/>
<UpdateFile Ver="1.0.0.2" FileName= "MyTestWinForm.exe" ContentLength="541k"/>
<UpdateFile Ver="1.0.0.2" FileName= "FlowPortal.net.rar" ContentLength="6.77M"/>
</UpdateFileList>
</AutoUpdate>
更新信息类
using System.Collections.Generic;
namespace UpdatePro
{
/// <summary>
/// 更新信息的实体类
/// </summary>
public class UpdateInfo
{
//url
public string UpdateFileURL {
get; set; }
//版本号
public string Version {
get; set; }
//更新日期
public string UpdateDate {
get; set; }
//更新文件列表
public List<string[]> UpdateFileList {
get; set; }
}
}
更新功能操作类
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Xml;
using System.Net;
using System.Windows.Forms;
namespace UpdatePro
{
public class UpdateManager
{
public UpdateManager()
{
this.LastUpdateInfo = new UpdateInfo();
this.NowUpdateInfo = new UpdateInfo();
//调用获取上一次更新信息的方法
this.GetLastUpdateInfo();
//调用获取本次更新信息的方法
this.GetNowUpdateInfo();
}
#region 属性
//上次更新的信息【版本号,更新时间,文件,所在服务器地址】
public UpdateInfo LastUpdateInfo {
get; set; }
//当前更新信息
public UpdateInfo NowUpdateInfo {
get; set; }
//是否需要更新
public bool IsUpdate {
get {
DateTime dt1 = Convert.ToDateTime(LastUpdateInfo.UpdateDate);
DateTime dt2 = Convert.ToDateTime(NowUpdateInfo.UpdateDate);
return dt2 > dt1;
}
}
//临时目录
public string TempFilePath
{
get {
//获取系统的临时文件夹
string newTempPath = Environment.GetEnvironmentVariable("Temp")+"\\WinFormUpdateFiles";
if (!Directory.Exists(newTempPath))
{
Directory.CreateDirectory(newTempPath);
}
return newTempPath;
}
}
#endregion
#region 方法
//从根目录的xml配置文件中获取上次更新的配置信息
private void GetLastUpdateInfo()
{
using (FileStream myFile = new FileStream("UpdateList.xml", FileMode.Open))
{
XmlTextReader xmlReader = new XmlTextReader(myFile);
while (xmlReader.Read())
{
switch (xmlReader.Name)
{
case "URLAddress":
this.LastUpdateInfo.UpdateFileURL = xmlReader.GetAttribute("URL");
break;
case "UpdateTime":
this.LastUpdateInfo.UpdateDate = xmlReader.GetAttribute("Date");
break;
case "Version":
this.LastUpdateInfo.Version = xmlReader.GetAttribute("Num");
break;
default:
break;
}
}
}
}
/// <summary>
/// 获取最新的文件更新的信息
/// </summary>
private void GetNowUpdateInfo() {
//下载最新的更新文件的信息
string newXmlTempPath = TempFilePath + "\\UpdateList.xml";
WebClient objClient = new WebClient();
objClient.DownloadFile(this.LastUpdateInfo.UpdateFileURL+ "/UpdateList.xml", newXmlTempPath);
//封装更新信息
FileStream myFile = new FileStream(newXmlTempPath,FileMode.Open);
XmlTextReader xmlReader = new XmlTextReader(myFile);
this.NowUpdateInfo.UpdateFileList = new List<string[]>();
while (xmlReader.Read())
{
switch (xmlReader.Name)
{
case "UpdateTime":
this.NowUpdateInfo.UpdateDate = xmlReader.GetAttribute("Date");
break;
case "Version":
this.NowUpdateInfo.Version = xmlReader.GetAttribute("Num");
break;
case "UpdateFile":
string ver = xmlReader.GetAttribute("Ver");
string fileName = xmlReader.GetAttribute("FileName");
string contentLength = xmlReader.GetAttribute("ContentLength");
string[] fileinfoAry = new string[] {
fileName,ver,contentLength,"0%"};
this.NowUpdateInfo.UpdateFileList.Add(fileinfoAry);
break;
default:
break;
}
}
myFile.Close();
//xmlReader.Close();
}
//声明一个委托用于随时更新文件下载进度
public delegate void showDownLoadPerson(int fileIndex,int percent);
//创建一个委托对象
public showDownLoadPerson showProgressDelegate;
//下载文件的方法
public void DownLoadFiles()
{
List<string[]> fileList = this.NowUpdateInfo.UpdateFileList;
for (int i = 0; i < fileList.Count; i++)
{
string[] item = fileList[i];
//Application.DoEvents();
//[1] 连接远程服务器中指定的文件
string fileName = item[0];//文件名
string fileUrl = this.LastUpdateInfo.UpdateFileURL + "/" + fileName;
WebRequest objWebRequest = WebRequest.Create(fileUrl);//根据文件的url连接服务器,创建请求对象
WebResponse objWebResponse = objWebRequest.GetResponse();//根据请求对象创建响应对象
Stream objStream = objWebResponse.GetResponseStream();//通过相应对象返回数据流对象
StreamReader objReader = new StreamReader(objStream);
//[2]在线读取已经连接的远程文件,并基于委托反馈文件读取(下载进度)
long fileLeng = objWebResponse.ContentLength;//根据响应对象获取文件的长度(流的长度)
byte[] fileByte = new byte[fileLeng];
int allByte = fileByte.Length;
int startByte = 0;
while (fileLeng>0)
{
Application.DoEvents();
int downLoadByte= objStream.Read(fileByte, startByte, allByte);//读取数据
if (downLoadByte==0)//已经彻底读取完了,再也读取不到其他字节了
{
break;
}
startByte += downLoadByte;//下一次起始读取字节的位置
allByte -= downLoadByte;//未下载的字节数
//计算完成的百分比
float part = (float)startByte;//已经下载的字节
float total = (float)fileByte.Length;//总的字节数
int percent = Convert.ToInt32((part/total)*100);
showProgressDelegate(i,percent);
}
//保存文件
string newFileName = this.TempFilePath + "\\" + fileName;
FileStream fs = new FileStream(newFileName, FileMode.OpenOrCreate, FileAccess.Write);
fs.Write(fileByte,0,fileByte.Length);
objStream.Close();
objReader.Close();
}
}
// //替换文件的方法
public bool CopyFiles()
{
string[] files = Directory.GetFiles(this.TempFilePath);
foreach (string name in files)
{
string currentFile = name.Substring(name.LastIndexOf(@"\") + 1);
if (File.Exists(currentFile))
{
File.Delete(currentFile);
}
File.Copy(name,currentFile);
}
return true;
}
#endregion
}
}
主程序界面
主程序调用更新程序
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
namespace MyTestWinForm
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
/// <summary>
/// 更新应用程序的方法
/// </summary>
private void UpdateList()
{
UpdatePro.UpdateManager updateManager = new UpdatePro.UpdateManager();
if (updateManager.IsUpdate)
{
//打开更新程序
System.Diagnostics.Process.Start("UpdatePro.exe");
//关闭主程序
Application.ExitThread();
Application.Exit();
}
}
private void label1_Click(object sender, EventArgs e)
{
}
//点击按钮开始更新
private void btnUpdate_Click(object sender, EventArgs e)
{
DialogResult result= MessageBox.Show("您即将进行应用程序最新版本的更新,系统会自动识别是否需要更新,如需更新系统将会退出当前应用程序,是否继续操作!", "温馨提示", MessageBoxButtons.OKCancel);
if (result==DialogResult.OK)
{
UpdateList();
}
}
/// <summary>
/// 我的项目不想做点击这些操作 所以我使用这个定时器 窗体加载1秒后 开始执行更新程序
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void timer1_Tick(object sender, EventArgs e)
{
this.timer1.Enabled = false;
UpdateList();
}
}
}
更新应用程序窗体代码
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
namespace UpdatePro
{
public partial class Form1 : Form
{
//
private UpdateManager objUpdateManager = new UpdateManager();
public Form1()
{
InitializeComponent();
init();
}
/// <summary>
/// 窗体加载初始化
/// </summary>
private void init()
{
//将同步显示进度的方法和委托变量关联
objUpdateManager.showProgressDelegate = this.ShowUpdateProgress;
//显示需要更新的文件列表
List<string[]> fileList = objUpdateManager.NowUpdateInfo.UpdateFileList;
foreach (string[] item in fileList)
{
this.lvUpdatelist.Items.Add(new ListViewItem(item));
}
//显示更新版本信息
}
/// <summary>
/// 用于更新列表文件下载进度的方法
/// </summary>
/// <param name="fileIndex"></param>
/// <param name="finishedPercent"></param>
private void ShowUpdateProgress(int fileIndex,int finishedPercent)
{
this.lvUpdatelist.Items[fileIndex].SubItems[3].Text = finishedPercent + "%";
//进度条设置
this.pbDownLoadFile.Maximum = 100;
this.pbDownLoadFile.Value = finishedPercent;
}
private void StartAndStop()
{
System.Diagnostics.Process.Start("MyTestWinForm.exe");
Application.ExitThread();
Application.Exit();
}
private void button1_Click(object sender, EventArgs e)
{
UpdateManager objUpdateManager = new UpdateManager();
MessageBox.Show(objUpdateManager.NowUpdateInfo.Version);
}
private void bntUpdate_Click(object sender, EventArgs e)
{
//下载文件
objUpdateManager.DownLoadFiles();
//复制文件
objUpdateManager.CopyFiles();
StartAndStop();
}
private void timer1_Tick(object sender, EventArgs e)
{
//注意这需要先禁用掉线程
//this.timer1.Stop();
this.timer1.Enabled = false;
//下载文件
objUpdateManager.DownLoadFiles();
//复制文件
objUpdateManager.CopyFiles();
StartAndStop();
}
}
}
效果图:
源码网盘链接:https://pan.baidu.com/s/1XLqgnS2RyhldMZV_5CBJbQ
提取码:9dq1
4.开发总结
做这样一个简易的winform更新程序其实并不难,只要梳理清思路和结构,基本这个就没啥问题,真正难的是在一开始做这个的时候会因为某些局部代码不知道怎么写,而停滞不前,不去从全局思考这个程序应该怎么设计。
在一开始做这个的时候,我会因为我不知道怎么写从winform从服务器下载文件到本地的代码在哪里发愁,导致工作迟迟不敢开展,知道后面在前辈的引导下,先从全局思考要做些什么,不要去考虑细节的代码要怎么写,整体的都梳理了之后,再针对里面具体的每一处要实现的功能去思考代码要怎么写。