WPF开发一款软件自动升级组件

前几天介绍了WPF进行自定义窗口的开发,关注的朋友还挺多,超出本人意料,呵呵,那么我就再接再励,在上篇的基础上,讲述一下软件自动升级组件的开发原理,大家时间宝贵,不想搞太长的篇幅,所以尽可能拣重要的说说,附件中有源码,没时间朋友直接下载吧,希望有需要的朋友能用的上,有时间的朋友还是跟着本文一起,体验一下开发的过程吧,因为这个组件做的挺赶,问题估计不少,大家发现问题欢迎踊跃留言,本文只做抛砖引玉的作用...

  废话不说,开始!

  软件发布后,自动升级往往是一项必备的功能,本篇博客的目标就是用WPF打造一个自动升级组件。先看效果:

  

升级提醒界面

升级过程界面

升级完成界面

  其实升级的过程很简单,大致如下:

  检测服务器上的版本号—>比较本地程序的版本号和服务器上的版本号—>如果不相同则下载升级的压缩包—>下载完成后解压升级包—>解压后的文件覆盖到应用程序文件目录—>升级完成

  有两点需要注意:

  1. 因为升级的过程就是用新文件覆盖旧文件的过程,所以要防止老文件被占用后无法覆盖的情况,因而升级之前应该关闭运用程序。
  2. 升级程序本身也可能需要升级,而升级程序启动后如问题1所说,就不可能被覆盖了,因而应该想办法避免这种情况。

  有了上面的分析,下面我们就来具体实现之。

  首先新建一个WPF Application项目,命名为AutoUpdater,因为升级程序需要能够单独执行,必须编译成exe文件,所以不能是类库项目。

  接下来新建一个类Updater.cs来处理检测的过程。

  服务器上的版本信息我们存储到一个XML文件中,文件格式定义如下:

<?xml version="1.0" encoding="utf-8"?>

<UpdateInfo>

    <AppName>Test</AppName>

    <AppVersion>1.0.0.1</AppVersion>

    <RequiredMinVersion>1.0.0.0</RequiredMinVersion>

    <Desc>shengji</Desc>

</UpdateInfo>

然后定义一个实体类对应XML定义的升级信息,如下:

public class UpdateInfo

    {

    public string AppName { get; set; }

 

         /// <summary>

         /// 应用程序版本

         /// </summary>

         public Version AppVersion { get; set; }

 

         /// <summary>

         /// 升级需要的最低版本

         /// </summary>

         public Version RequiredMinVersion { get; set; }

 

         public Guid MD5

         {

             get;

              set;

         }

 

        private string _desc;

        /// <summary>

        /// 更新描述

        /// </summary>

        public string Desc

        {

             get

             {

                 return _desc;

             }

             set

             {

                 _desc = string.Join(Environment.NewLine, value.Split(new char[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries));

             }

        }

    }

检测的详细步骤应该如下:

  1. 异步下载update.xml到本地
  2. 分析xml文件信息,存储到自定义的类UpdateInfo中
  3. 判断升级需要的最低版本号,如果满足,启动升级程序。这里就碰到了上面提到的问题,文件被占用的问题。因为如果直接启动AutoUpdater.exe,升级包中的AutoUpdater.exe是无法覆盖这个文件的,所以采取的办法是将AutoUpdater.exe拷贝到缓存文件夹中,然后启动缓存文件夹中的AutoUpdater.exe文件来完成升级的过程。

  具体代码如下,第一个方法CheckUpdateStatus()完成1、2两个步骤,第二个方法StartUpdate(UpdateInfo updateInfo)完成步骤3:

public static void CheckUpdateStatus()

        {

            System.Threading.ThreadPool.QueueUserWorkItem((s) =>

            {

                string url = Constants.RemoteUrl + Updater.Instance.CallExeName + "/update.xml";

                var client = new System.Net.WebClient();

                client.DownloadDataCompleted += (x, y) =>

                {

                    try

                    {

                        MemoryStream stream = new MemoryStream(y.Result);

 

                        XDocument xDoc = XDocument.Load(stream);

                        UpdateInfo updateInfo = new UpdateInfo();

                        XElement root = xDoc.Element("UpdateInfo");

                        updateInfo.AppName = root.Element("AppName").Value;

                        updateInfo.AppVersion = root.Element("AppVersion") == null || string.IsNullOrEmpty(root.Element("AppVersion").Value) ? null : new Version(root.Element("AppVersion").Value);

                        updateInfo.RequiredMinVersion = root.Element("RequiredMinVersion") == null || string.IsNullOrEmpty(root.Element("RequiredMinVersion").Value) ? null : new Version(root.Element("RequiredMinVersion").Value);

                        updateInfo.Desc = root.Element("Desc").Value;

                        updateInfo.MD5 = Guid.NewGuid();

 

                        stream.Close();

                        Updater.Instance.StartUpdate(updateInfo);

                    }

                    catch

                    { }

                };

                client.DownloadDataAsync(new Uri(url));

 

            });

 

        }

 

        public void StartUpdate(UpdateInfo updateInfo)

        {

            if (updateInfo.RequiredMinVersion != null && Updater.Instance.CurrentVersion < updateInfo.RequiredMinVersion)

            {

                //当前版本比需要的版本小,不更新

                return;

            }

 

            if (Updater.Instance.CurrentVersion >= updateInfo.AppVersion)

            {

                //当前版本是最新的,不更新

                return;

            }

 

            //更新程序复制到缓存文件夹

            string appDir = System.IO.Path.Combine(System.Reflection.Assembly.GetEntryAssembly().Location.Substring(0, System.Reflection.Assembly.GetEntryAssembly().Location.LastIndexOf(System.IO.Path.DirectorySeparatorChar)));

            string updateFileDir = System.IO.Path.Combine(System.IO.Path.Combine(appDir.Substring(0, appDir.LastIndexOf(System.IO.Path.DirectorySeparatorChar))), "Update");

            if (!Directory.Exists(updateFileDir))

            {

                Directory.CreateDirectory(updateFileDir);

            }

            updateFileDir = System.IO.Path.Combine(updateFileDir, updateInfo.MD5.ToString());

            if (!Directory.Exists(updateFileDir))

            {

                Directory.CreateDirectory(updateFileDir);

            }

 

            string exePath = System.IO.Path.Combine(updateFileDir, "AutoUpdater.exe");

            File.Copy(System.IO.Path.Combine(appDir, "AutoUpdater.exe"), exePath, true);

 

            var info = new System.Diagnostics.ProcessStartInfo(exePath);

            info.UseShellExecute = true;

            info.WorkingDirectory = exePath.Substring(0, exePath.LastIndexOf(System.IO.Path.DirectorySeparatorChar));

            updateInfo.Desc = updateInfo.Desc;

            info.Arguments = "update " + Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes(CallExeName)) + " " + Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes(updateFileDir)) + " " + Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes(appDir)) + " " + Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes(updateInfo.AppName)) + " " + Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes(updateInfo.AppVersion.ToString())) + " " + (string.IsNullOrEmpty(updateInfo.Desc) ? "" : Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes(updateInfo.Desc)));

            System.Diagnostics.Process.Start(info);

        }

在方法StartUpdate的最后,启动Autoupdate.exe的代码中,需要将升级信息当做参数传递过去,各参数间用空格分隔,考虑到信息本身(如AppName或Desc中)可能含有空格,所以传递前将信息进行Base64编码。

      接下来打开Program.cs文件(没有可以自己创建一个,然后在项目属性中修改启动对象,如下图),

  在Main函数中接收传递过来的参数。代码如下:

static void Main(string[] args)

      {

            if (args.Length == 0)

            {

                return;

            }

            else if (args[0] == "update")

            {

                try

                {

                    string callExeName = args[1];

                    string updateFileDir = args[2];

                    string appDir = args[3];

                    string appName = args[4];

                    string appVersion = args[5];

                    string desc = args[6];

 

                    Ezhu.AutoUpdater.App app = new Ezhu.AutoUpdater.App();

                    UI.DownFileProcess downUI = new UI.DownFileProcess(callExeName, updateFileDir, appDir, appName, appVersion, desc) { WindowStartupLocation = WindowStartupLocation.CenterScreen };

                    app.Run(downUI);

                }

                catch (Exception ex)

                {

                    MessageBox.Show(ex.Message);

                }

      }

参数接收成功后,打开下载界面,显示升级的主要内容,如果用户点击升级按钮,则开始下载升级包。

  步骤应该如下:

  1. 关闭应用程序进程
  2. 下载升级包到缓存文件夹
  3. 解压升级包到缓存文件夹
  4. 从缓存文件夹复制解压后的文件和文件夹到运用程序目录
  5. 提醒用户升级成功  

     具体代码如下:

public partial class DownFileProcess : WindowBase

    {

        private string updateFileDir;//更新文件存放的文件夹

        private string callExeName;

        private string appDir;

        private string appName;

        private string appVersion;

        private string desc;

        public DownFileProcess(string callExeName, string updateFileDir, string appDir, string appName, string appVersion, string desc)

        {

            InitializeComponent();

            this.Loaded += (sl, el) =>

            {

                YesButton.Content = "现在更新";

                NoButton.Content = "暂不更新";

 

                this.YesButton.Click += (sender, e) =>

                {

                    Process[] processes = Process.GetProcessesByName(this.callExeName);

 

                    if (processes.Length > 0)

                    {

                        foreach (var p in processes)

                        {

                            p.Kill();

                        }

                    }

 

                    DownloadUpdateFile();

                };

 

                this.NoButton.Click += (sender, e) =>

                {

                    this.Close();

                };

 

                this.txtProcess.Text = this.appName + "发现新的版本(" + this.appVersion + "),是否现在更新?";

                txtDes.Text = this.desc;

            };

            this.callExeName = System.Text.Encoding.UTF8.GetString(Convert.FromBase64String(callExeName));

            this.updateFileDir = System.Text.Encoding.UTF8.GetString(Convert.FromBase64String(updateFileDir));

            this.appDir = System.Text.Encoding.UTF8.GetString(Convert.FromBase64String(appDir));

            this.appName = System.Text.Encoding.UTF8.GetString(Convert.FromBase64String(appName));

            this.appVersion = System.Text.Encoding.UTF8.GetString(Convert.FromBase64String(appVersion));

 

            string sDesc = System.Text.Encoding.UTF8.GetString(Convert.FromBase64String(desc));

            if (sDesc.ToLower().Equals("null"))

            {

                this.desc = "";

            }

            else

            {

                this.desc = "更新内容如下:\r\n" + sDesc;

            }

        }

 

        public void DownloadUpdateFile()

        {

            string url = Constants.RemoteUrl + callExeName + "/update.zip";

            var client = new System.Net.WebClient();

            client.DownloadProgressChanged += (sender, e) =>

            {

                UpdateProcess(e.BytesReceived, e.TotalBytesToReceive);

            };

            client.DownloadDataCompleted += (sender, e) =>

            {

                string zipFilePath = System.IO.Path.Combine(updateFileDir, "update.zip");

                byte[] data = e.Result;

                BinaryWriter writer = new BinaryWriter(new FileStream(zipFilePath, FileMode.OpenOrCreate));

                writer.Write(data);

                writer.Flush();

                writer.Close();

 

                System.Threading.ThreadPool.QueueUserWorkItem((s) =>

                {

                    Action f = () =>

                    {

                       txtProcess.Text = "开始更新程序...";

                    };

                    this.Dispatcher.Invoke(f);

 

                    string tempDir = System.IO.Path.Combine(updateFileDir, "temp");

                    if (!Directory.Exists(tempDir))

                    {

                        Directory.CreateDirectory(tempDir);

                    }

                    UnZipFile(zipFilePath, tempDir);

 

                    //移动文件

                    //App

                    if(Directory.Exists(System.IO.Path.Combine(tempDir,"App")))

                    {

                        CopyDirectory(System.IO.Path.Combine(tempDir,"App"),appDir);

                    }

 

                    f = () =>

                    {

                        txtProcess.Text = "更新完成!";

 

                        try

                        {

                            //清空缓存文件夹

                            string rootUpdateDir = updateFileDir.Substring(0, updateFileDir.LastIndexOf(System.IO.Path.DirectorySeparatorChar));

                            foreach (string p in System.IO.Directory.EnumerateDirectories(rootUpdateDir))

                            {

                                if (!p.ToLower().Equals(updateFileDir.ToLower()))

                                {

                                    System.IO.Directory.Delete(p, true);

                                }

                            }

                        }

                        catch (Exception ex)

                        {

                            //MessageBox.Show(ex.Message);

                        }

 

                    };

                    this.Dispatcher.Invoke(f);

 

                    try

                    {

                        f = () =>

                        {

                            AlertWin alert = new AlertWin("更新完成,是否现在启动软件?") { WindowStartupLocation = WindowStartupLocation.CenterOwner, Owner = this };

                            alert.Title = "更新完成";

                            alert.Loaded += (ss, ee) =>

                            {

                                alert.YesButton.Width = 40;

                                alert.NoButton.Width = 40;

                            };

                            alert.Width=300;

                            alert.Height = 200;

                            alert.ShowDialog();

                            if (alert.YesBtnSelected)

                            {

                                //启动软件

                                string exePath = System.IO.Path.Combine(appDir, callExeName + ".exe");

                                var info = new System.Diagnostics.ProcessStartInfo(exePath);

                                info.UseShellExecute = true;

                                info.WorkingDirectory = appDir;// exePath.Substring(0, exePath.LastIndexOf(System.IO.Path.DirectorySeparatorChar));

                                System.Diagnostics.Process.Start(info);

                            }

                            else

                            {

 

                            }

                            this.Close();

                        };

                        this.Dispatcher.Invoke(f);

                    }

                    catch (Exception ex)

                    {

                        //MessageBox.Show(ex.Message);

                    }

                });

 

            };

            client.DownloadDataAsync(new Uri(url));

        }

 

        private static void UnZipFile(string zipFilePath, string targetDir)

        {

            ICCEmbedded.SharpZipLib.Zip.FastZipEvents evt = new ICCEmbedded.SharpZipLib.Zip.FastZipEvents();

            ICCEmbedded.SharpZipLib.Zip.FastZip fz = new ICCEmbedded.SharpZipLib.Zip.FastZip(evt);

            fz.ExtractZip(zipFilePath, targetDir, "");

        }

 

        public void UpdateProcess(long current, long total)

        {

            string status = (int)((float)current * 100 / (float)total) + "%";

            this.txtProcess.Text = status;

            rectProcess.Width = ((float)current / (float)total) * bProcess.ActualWidth;

        }

 

        public void CopyDirectory(string sourceDirName, string destDirName)

        {

            try

            {

                if (!Directory.Exists(destDirName))

                {

                    Directory.CreateDirectory(destDirName);

                    File.SetAttributes(destDirName, File.GetAttributes(sourceDirName));

                }

                if (destDirName[destDirName.Length - 1] != Path.DirectorySeparatorChar)

                    destDirName = destDirName + Path.DirectorySeparatorChar;

                string[] files = Directory.GetFiles(sourceDirName);

                foreach (string file in files)

                {

                    File.Copy(file, destDirName + Path.GetFileName(file), true);

                    File.SetAttributes(destDirName + Path.GetFileName(file), FileAttributes.Normal);

                }

                string[] dirs = Directory.GetDirectories(sourceDirName);

                foreach (string dir in dirs)

                {

                    CopyDirectory(dir, destDirName + Path.GetFileName(dir));

                }

            }

            catch (Exception ex)

            {

                throw new Exception("复制文件错误");

            }

        }

    }

注:

  1. 压缩解压用到开源库SharpZipLib,官网: http://www.icsharpcode.net/OpenSource/SharpZipLib/Download.aspx
  2. 服务器上升级包的目录层次应该如下(假如要升级的运用程序为Test.exe):

  Test(与exe的名字相同)

  ----update.xml

  ----update.zip

  update.zip包用如下方式生成:

  新建一个目录APP,将所用升级的文件拷贝到APP目录下,然后压缩APP文件夹为update.zip文件

  1. 升级服务器的路径配置写到Constants.cs类中。
  2. 使用方法如下,在要升级的运用程序项目的Main函数中,加上一行语句:
  3. Ezhu.AutoUpdater.Updater.CheckUpdateStatus();

  到此,一款简单的自动升级组件就完成了!

完整代码下载

### 回答1: WPF DataGrid 和 ComboBox 在 MVVM 架构模式中的使用 WPF DataGrid 是 WPF 中非常常用的控件之一,它可以方便地展示大量的数据,并且提供了丰富的编辑、排序、筛选等功能。而 MVVM 架构模式则是一种非常适合 WPF 开发的设计模式,它能够有效地将 UI 与业务逻辑分离开来,使得代码的可读性和可维护性得到很大的提升。 在 MVVM 架构模式中,数据绑定是非常重要的一环。WPF DataGrid 和 ComboBox 都支持数据绑定,可以将它们绑定到 ViewModel 中的 ObservableCollection 类型的属性上,以实现自动更新 UI 的效果。例如,可以将 DataGrid 的 ItemsSource 属性绑定到 ViewModel 中的一个 ObservableCollection<T> 对象上,当 ObservableCollection<T> 中的数据发生变化时,DataGrid 的显示内容也会自动更新。 当然,在使用 DataGrid 和 ComboBox 时,还需要考虑到一些特殊情况。例如,当需要在 ComboBox 中选择一个数据项时,有时会需要根据当前选中的值来动态地改变 DataGrid 的显示内容。这时,可以使用 WPF 中的 DataTrigger 或者 Command 绑定等技术来实现。 总之,在 MVVM 架构模式中,WPF DataGrid 和 ComboBox 是非常重要的控件,它们可以方便地展示和编辑大量的数据,并且能够与 ViewModel 中的数据进行无缝的数据绑定。同时,在使用这两个控件时,还需要注意一些特殊情况,以实现更加灵活和丰富的功能。 ### 回答2: WPF DataGrid是一个非常常用的控件,允许我们在WPF应用程序中以表格的形式展示数据。而Combox也是WPF中的常见控件之一,用于显示下拉列表并允许用户从列表中选择一个选项。在使用WPF DataGrid时,Combox的整合也是非常常见的需求。 而在MVVM架构中,我们通常使用ViewModel来管理数据和业务逻辑,使得View与Model之间解耦。在使用WPF DataGrid和Combox时,我们也可以运用MVVM的思想,将DataGrid和Combox的数据与ViewModel之间进行绑定,实现通过ViewModel管理数据和业务逻辑。 具体来说,在使用WPF DataGrid时,我们可以使用DataGridComboBoxColumn来添加Combox,并且使用Binding将Combox中选择的值绑定到ViewModel中的属性。在ViewModel中,则需要定义一个ObservableCollection作为DataGrid的数据源,并且在设置和获取属性时触发PropertyChanged事件,以便让View及时更新数据。 总之,WPF DataGrid和Combox与MVVM的结合可以大大提高应用程序的模块化和可维护性,使得我们能够更好地管理数据和业务逻辑,为用户提供更好的体验。 ### 回答3: WPF DataGrid是WPF(Windows Presentation Foundation)中提供的一个很棒的数据显示组件,它支持在表格视图中显示和编辑多种数据类型。而ComboBox是一种下拉列表框。MVVM是一种软件模式,用于分离应用程序的用户界面(View),业务逻辑(Model)和用户交互行为(ViewModel)。 在使用WPF DataGrid组件时,可以使用ComboBox类型作为DataGrid列的数据源,从而实现对数据进行选择。而在MVVM模式下,ViewModel可以控制数据的加载和更新,从而实现数据的绑定和显示。ViewModel还可以处理用户交互操作,例如在ComboBox中选择某一项时触发的事件。 在使用WPF DataGrid和ComboBox时,建议使用MVVM模式,将业务逻辑和用户交互行为与View解耦,从而使代码更加容易维护和升级。此外,在MVVM模式下,可以使用绑定和命令来实现数据的更新和操作,从而减少了代码的复杂性和耦合度。 总之,WPF DataGrid和ComboBox以及MVVM模式是WPF应用程序中非常常用的组件和模式,它们可以很好地帮助开发人员实现数据的显示和交互,提高应用程序的可维护性和可升级性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值