.net form案例大全
WinForm 自动更新程序
自定义组件
跑马灯效果Label
TextBox横线样式及占位提示
监控变量值改变
Visual Studio 2022如何支持低版本.NET框架
Winform 支持高DPI的几种方式
C#实现程序在系统托盘显示图标及开机自启动
VS2022 工具箱中不显示 Devexpress控件的问题
进程间通信
两个exe程序之间通信
管道通信
日志写入到RichTextBox中展示
多线程访问WinForms控件的方法
多线程
线程锁与单多线程简单使用
本文档使用 MrDoc 发布
-
+
首页
WinForm 自动更新程序
https://mp.weixin.qq.com/s/ENlDsB2iu5yF7LSt7SMkOA # 模型+服务端 ```java //xml文件 <?xml version="1.0" encoding="utf-8" ?> <updateList> <url>http://localhost:5000/api/Update/</url> <files> <file name="1.dll" version="1.0"></file> <file name="1.dll" version="1.1"></file> <file name="AutoUpdate.Test.exe" version="1.1"></file> </files> </updateList> ``` ```java //Model public class UpdateModel { public string name { get; set; } public string version { get; set; } } public class UpdateModel_Out { public string url { get; set; } public List<UpdateModel> updateList { get; set; } } ``` ```java //控制器 namespace AutoUpdate.WebApi.Controllers { [Route("api/[controller]/[Action]")] [ApiController] public class UpdateController : ControllerBase { [HttpGet] public JsonResult Index() { return new JsonResult(new { code = 10, msg = "success" }); } [HttpPost] public JsonResult GetUpdateFiles([FromBody] List<UpdateModel> input) { string xmlPath = AppContext.BaseDirectory + "UpdateList.xml"; XDocument xdoc = XDocument.Load(xmlPath); var files = from f in xdoc.Root.Element("files").Elements() select new { name = f.Attribute("name").Value, version = f.Attribute("version").Value }; var url = xdoc.Root.Element("url").Value; List<UpdateModel> updateList = new List<UpdateModel>(); foreach(var file in files) { UpdateModel model = input.Find(s => s.name == file.name); if(model == null || file.version.CompareTo(model.version) > 0) { updateList.Add(new UpdateModel { name = file.name, version = file.version }); } } UpdateModel_Out output = new UpdateModel_Out { url = url, updateList = updateList }; return new JsonResult(output); } [HttpPost] public FileStreamResult DownloadFile([FromBody] UpdateModel input) { string path = AppContext.BaseDirectory + "files\\" + input.name; FileStream fileStream = new FileStream(path, FileMode.Open); return new FileStreamResult(fileStream, "application/octet-stream"); } } } ``` # 客户端 写代码还是要尽量的保证通用性,以便以后需要的时候可以拿来稍微改改甚至直接使用。所以在这里我们将自动更新的程序抽象出来,即对于客户端来说,它只包含三个文件(AutoUpdate.dll、AutoUpdate.exe、UpdateList.xml,如果是.NET Framework的话,其实是没有AutoUpdate.dll文件的,就一个exe就足够了。这也是我为什么一直不用NET Core来写Winform程序的原因之一);然后将这三个文件放到主程序的目录中即可。 然后就是传参调用,在Program文件中做了以下代码操作。所以调用的时候需要将主程序的执行目录以及进程名传过来,作用分别是再更新完后自动启动以及更新之前把相关的进程杀掉以便完成更新。 同时可以看到在更新的时候,有一个图片旋转的动作,也一并放到此篇文章中。  ```java //更新程序 namespace AutoUpdate { static class Program { /// <summary> /// The main entry point for the application. /// </summary> [STAThread] static void Main(string[] args) { if(args.Length != 1) { return; } var arg = args[0].Split("|*|"); if(arg.Length == 0) { return; } string runPath = arg[0]; string procesName = arg[1]; Process[] processes = Process.GetProcesses(); foreach(Process process in processes) { if(process.ProcessName == procesName) { process.Kill(true); } } Application.SetHighDpiMode(HighDpiMode.SystemAware); Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); Application.Run(new Form_update(runPath)); } } } ``` ```java //主程序 namespace AutoUpdate.Test { static class Program { /// <summary> /// The main entry point for the application. /// </summary> [STAThread] static void Main() { Update(); Application.SetHighDpiMode(HighDpiMode.SystemAware); Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); Application.Run(new Form1()); } readonly static string updateXml = Application.StartupPath + "UpdateList.xml"; /// <summary> /// 获取本地更新地址 /// </summary> /// <returns></returns> static string GetUpdateUrl() { XElement xele = XElement.Load(updateXml); string url = xele.Element("url").Value; return url; } /// <summary> /// 获取本地更新文件 /// </summary> /// <returns></returns> static string GetUpdateFiles() { XDocument xdoc = XDocument.Load(updateXml); var files = from f in xdoc.Root.Element("files").Elements() select new { name = f.Attribute("name").Value, version = f.Attribute("version").Value }; return JsonConvert.SerializeObject(files); } /// <summary> /// 检查是否需要更新 /// </summary> /// <returns></returns> static bool CheckUpdate() { string url = GetUpdateUrl(); HttpResult httpResult = HttpUtil.HttpRequest(new HttpItem(url + "GetUpdateFiles", requestData: GetUpdateFiles())); if(httpResult.Status) { UpdateModel_Out output = JsonConvert.DeserializeObject<UpdateModel_Out>(httpResult.HttpStringData); if(output.updateList.Count > 0) return true; } return false; } static void Update() { if(CheckUpdate()) { string processName = Assembly.GetExecutingAssembly().GetName().Name; ProcessStartInfo info = new ProcessStartInfo(Application.StartupPath + "AutoUpdate.exe", Process.GetCurrentProcess().MainModule.FileName + "|*|" + processName); Process.Start(info); Environment.Exit(0); } } } } ``` ```java public static class ImageEx { /// <summary> /// 旋转图片 /// </summary> /// <param name="image"></param> /// <param name="angle"></param> /// <returns></returns> public static Image RotateImage(this Image image, float angle) { if(image == null) throw new ArgumentNullException("image"); float dx = image.Width / 2.0f; float dy = image.Height / 2.0f; Bitmap rotatedBmp = new Bitmap(image.Width, image.Height); rotatedBmp.SetResolution(image.HorizontalResolution, image.VerticalResolution); Graphics g = Graphics.FromImage(rotatedBmp); g.TranslateTransform(dx, dy); g.RotateTransform(angle); g.TranslateTransform(-dx, -dy); g.DrawImage(image, new PointF(0, 0)); g.Dispose(); return rotatedBmp; } } ``` # 重写客户端 这一篇就着重写一下客户端的代码,客户端主要实现的有:启动后检测本地的xml文件,然后发送到服务器获取需要更新的文件以及版本列表。循环下载。下载成功后,备份原始文件->复制到主目录(若失败进行回滚)->修改本地xml文件,更新完成后打开主程序。 主要注释已经在代码中标注了,由于基本都是在线程中进行操作的,所以在更新UI的时候封装了UpdateUI方法,然后就是加了一个定时器实现图标的旋转。关于CopyRun(更新自己)方法,就是用来更新自动更新程序本身的,即运行之前复制一份本身,然后启动复制的程序,否则本身正在运行的时候是不能更新自己的。 ```java /// <summary> /// 更新自己 /// </summary> private void CopyRun() { string runPath = Process.GetCurrentProcess().MainModule.FileName; string runName = Path.GetFileName(runPath); if(!runName.StartsWith("_")) { string copyPath = runPath.Replace(runName, "_" + runName); byte[] bytes; using(FileStream fileStream = new FileStream(runPath, FileMode.Open, FileAccess.Read)) { bytes = new byte[fileStream.Length]; fileStream.Read(bytes, 0, bytes.Length); } using(FileStream fileStream = new FileStream(copyPath, FileMode.Create, FileAccess.Write)) { fileStream.Write(bytes); } ProcessStartInfo info = new ProcessStartInfo(copyPath, _runApp); Process.Start(info); Environment.Exit(0); } } /// <summary> /// 更新UI /// </summary> /// <param name="actoin"></param> private void UpdateUI(Action actoin) { if(this.InvokeRequired) { this.BeginInvoke(actoin); } else { actoin(); } } /// <summary> /// 获取本地更新地址 /// </summary> /// <returns></returns> private string GetUpdateUrl() { XElement xele = XElement.Load(updateXml); string url = xele.Element("url").Value; return url; } /// <summary> /// 获取本地更新文件 /// </summary> /// <returns></returns> private string GetUpdateFiles() { XDocument xdoc = XDocument.Load(updateXml); var files = from f in xdoc.Root.Element("files").Elements() select new { name = f.Attribute("name").Value, version = f.Attribute("version").Value }; return JsonConvert.SerializeObject(files); } /// <summary> /// 更新完成后修改版本文件 /// </summary> /// <param name="list"></param> private void UpdateXml(List<UpdateModel> list) { XDocument xdoc = XDocument.Load(updateXml); foreach(var model in list) { var ele_files = xdoc.Root.Element("files"); XElement xele = ele_files.Elements().FirstOrDefault(s => s.Attribute("name").Value == model.name); if(xele != null) { xele.SetAttributeValue("version", model.version); } else { XElement addXele = new XElement("file"); addXele.SetAttributeValue("name", model.name); addXele.SetAttributeValue("version", model.version); ele_files.Add(addXele); } } xdoc.Save(updateXml); } ``` ```java readonly string _runApp; public Form_update(string runApp) { InitializeComponent(); _runApp = runApp; //CopyRun(); } readonly string updateXml = Application.StartupPath + "UpdateList.xml"; private void btn_update_Click(object sender, EventArgs e) { #region 设置ui btn_update.Enabled = false; label_status.Text = "正在更新"; #endregion #region 初始化路径 string savePath = Application.StartupPath + "temp_update\\"; string backPath = Application.StartupPath + "temp_back\\"; if(Directory.Exists(savePath)) { Directory.Delete(savePath, true); } Directory.CreateDirectory(savePath); if(Directory.Exists(backPath)) { Directory.Delete(backPath, true); } Directory.CreateDirectory(backPath); #endregion #region 图标旋转 int angle = 0; Image img = pictureBox1.BackgroundImage; System.Threading.Timer timer = new System.Threading.Timer(s => { UpdateUI(() => { angle = angle == 360 ? 0 : angle + 10; pictureBox1.BackgroundImage = img.RotateImage(angle); }); }, null, 0, 100); #endregion #region 下载更新 string url = GetUpdateUrl(); bool isSuccess = false; Task.Run(() => { try { //获取下载列表 HttpResult httpResult = HttpUtil.HttpRequest(new HttpItem(url + "GetUpdateFiles", requestData: GetUpdateFiles())); if(httpResult.Status) { UpdateModel_Out output = JsonConvert.DeserializeObject<UpdateModel_Out>(httpResult.HttpStringData); if(output.updateList.Count == 0) { throw new Exception("当前已是最新版本"); } UpdateUI(() => { progressBar1.Maximum = output.updateList.Count + 1; }); //循环下载文件 for(int i = 0; i < output.updateList.Count; i++) { UpdateModel updateModel = output.updateList[i]; #region 进度条 UpdateUI(() => { label_status.Text = $"正在更新第 {i + 1}/{output.updateList.Count} 个文件,文件名:{updateModel.name}"; progressBar1.Value = i + 1; }); #endregion #region 下载文件 httpResult = HttpUtil.HttpRequest(new HttpItem(url + "DownloadFile", requestData: JsonConvert.SerializeObject(updateModel))); if(httpResult.Status) { using(FileStream fileStream = new FileStream(savePath + updateModel.name, FileMode.Create)) { fileStream.Write(httpResult.HttpByteData, 0, httpResult.HttpByteData.Length); } } else { throw new Exception(updateModel.name + "下载失败,请重试"); } #endregion Task.Delay(1000).Wait(); } #region 备份 UpdateUI(() => { label_status.Text = "正在备份"; }); try { File.Copy(updateXml, backPath + "UpdateList.xml"); foreach(var file in output.updateList) { if(File.Exists(Application.StartupPath + file.name)) { File.Copy(Application.StartupPath + file.name, backPath + file.name, true); } } } catch { } #endregion #region 完成更新 UpdateUI(() => { label_status.Text = "正在完成更新"; progressBar1.Value = progressBar1.Maximum; }); string[] files = Directory.GetFiles(savePath); try { #region 更新成功 foreach(string file in files) { File.Copy(file, Application.StartupPath + Path.GetFileName(file), true); } Directory.Delete(savePath, true); #region 保存最新版本 UpdateXml(output.updateList); isSuccess = true; UpdateUI(() => { label_status.Text = "更新完成"; }); #endregion #endregion } catch { #region 失败回滚 UpdateUI(() => { label_status.Text = "更新失败,正在回滚"; }); string[] files_back = Directory.GetFiles(backPath); foreach(string file in files_back) { File.Copy(file, Application.StartupPath + Path.GetFileName(file), true); } File.Copy(backPath + "UpdateList.xml", updateXml, true); UpdateUI(() => { label_status.Text = "回滚完成"; }); return; #endregion } #endregion } else { throw new Exception("获取更新列表失败"); } } catch(Exception ex) { UpdateUI(() => { label_status.Text = "更新失败!" + ex.Message; btn_update.Enabled = true; }); } finally { UpdateUI(() => { timer.Change(-1, -1); pictureBox1.BackgroundImage = img; if(isSuccess) { if(File.Exists(_runApp)) { if(MessageBox.Show("更新完成,是否打开程序", "提示", MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes) { Process.Start(_runApp); } Environment.Exit(0); } } }); } }); #endregion } private void btn_close_Click(object sender, EventArgs e) { Close(); } private void panel1_Paint(object sender, PaintEventArgs e) { Pen pen = new Pen(Color.LightGray); e.Graphics.DrawLine(pen, new Point(36, 36), new Point(panel1.Width - 36, 36)); } ``` # 上传文件 更新都写完了,但是要更新文件要怎么操作呢?连接服务器然后上传上去,修改下xml的版本号当然也是可以的,但是还是没有写个程序使用起来方便,所以这一篇就写一个简单的上传程序。后台依然是使用的.net core webapi,然后新建个控制器来接收上传来的文件。前台就直接使用layui随便搭个上传表单。  ```java //控制器代码 namespace AutoUpdate.WebApi.Controllers { [Route("api/[controller]")] [ApiController] public class UploadController : ControllerBase { private static object obj = new object(); [HttpPost] public JsonResult Upload() { IFormFileCollection formFile = Request.Form.Files; if(formFile.Count == 0) { throw new Exception("没有文件"); } ReturnResult result = new ReturnResult(Result.success); string path = AppContext.BaseDirectory + "files\\"; string xmlPath = AppContext.BaseDirectory + "UpdateList.xml"; try { foreach(var file in formFile) { string fileName = path + file.FileName; using FileStream fileStream = System.IO.File.Create(fileName); file.CopyTo(fileStream); fileStream.Flush(); } List<string> UpdateFiles = formFile.Select(s => s.FileName).ToList(); UpdateXml(UpdateFiles); } catch(Exception ex) { result.result = Result.error; result.msg = ex.Message; } return new JsonResult(result); } private void UpdateXml(List<string> UpdateFiles) { lock(obj) { string xmlPath = AppContext.BaseDirectory + "UpdateList.xml"; XDocument xdoc = XDocument.Load(xmlPath); foreach(string file in UpdateFiles) { var ele_files = xdoc.Root.Element("files"); XElement xele = ele_files.Elements().FirstOrDefault(s => s.Attribute("name").Value == file); if(xele != null) { string oldVer = xele.Attribute("version").Value; xele.SetAttributeValue("version", Math.Round(decimal.Parse(oldVer) + (decimal)0.01, 2)); } else { XElement addXele = new XElement("file"); addXele.SetAttributeValue("name", file); addXele.SetAttributeValue("version", "1.00"); ele_files.Add(addXele); } } xdoc.Save(xmlPath); } } } } ``` ```html //html代码 <!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title>文件更新</title> <link rel="stylesheet" href="./layui/css/layui.css"> </head> <body> <div class="layui-form-item" style="margin:15%"> <div class="layui-col-md10 layui-col-xs9 layui-form-item-mt5"> <label class="layui-form-label">文件上传</label> <div class="layui-input-block"> <input type="text" id="filename" name="filename" class="layui-input" readonly /> </div> </div> <div class="layui-col-md2 layui-col-xs1 layui-form-item-mt5"> <button class="layui-btn" id="btnBrowse">浏览</button> <button class="layui-btn" id="submit" lay-filter="submit" lay-submit>上传</button> </div> </div> <script src="./layui/layui.js"></script> <script> var layer = layui.layer, form = layui.form, $ = layui.jquery, upload = layui.upload; form.render(); upload.render({ elem: '#btnBrowse' , url: 'http://localhost:5000/api/Upload' , headers: {} , data: {} , accept: 'file' , multiple: true , auto: false , bindAction: '#submit' , choose: function (obj) { $("#filename").val(""); obj.preview(function (index, file, result) { $("#filename").val($("#filename").val() + file.name + " ; "); }) } , done: function (res, index, upload) { layer.closeAll('loading'); layer.msg(res.msg); }, error: function (res) { layer.closeAll('loading'); } }) </script> </body> </html> ```
孙端己
2024年9月26日 10:55
转发文档
收藏文档
上一篇
下一篇
手机扫码
复制链接
手机扫一扫转发分享
复制链接
Markdown文件
分享
链接
类型
密码
更新密码