【C#】C#Windows桌面应用开发实践
必须功能一览
注册表相关的操作(添加与删除)
RegistryKey hkml = Registry.LocalMachine; RegistryKey software = hkml.OpenSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall", true); RegistryKey softdemo = software.CreateSubKey("再见江湖"); softdemo.SetValue("DisplayName", "再见江湖"); softdemo.SetValue("UninstallString", InstallPath + "\\"+PathNameDef+"\\Uninstall.exe uninstall"); softdemo.SetValue("DisplayIcon", InstallPath + "\\"+PathNameDef+"\\Uninstall.exe");
//清楚注册表 try { RegistryKey hkml = Registry.LocalMachine; RegistryKey software = hkml.OpenSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall", true); RegistryKey softdemo = software.OpenSubKey("再见江湖"); if (softdemo.GetValue("再见江湖") != null) { softdemo.DeleteSubKeyTree("再见江湖"); } //Registry.ClassesRoot.OpenSubKey("再见江湖", true); //Registry.ClassesRoot.DeleteSubKeyTree("再见江湖", true); Registry.CurrentUser.OpenSubKey("longtu", true); Registry.CurrentUser.OpenSubKey("再见江湖", true); if (Registry.CurrentUser.GetValue("再见江湖") != null) { Registry.CurrentUser.DeleteSubKeyTree("再见江湖", true); } } catch (System.Exception exc) { //注册表异常暂时不捕获不抛出 throw exc; }
文件占用问题的处理
此问题发生一般是资源没被释放掉,但也存在如下可能性。我们对文件的操作非常频繁,所以写了特定的操作类/组件来维护文件之间的操作,知道特定的时刻才结束,常见的如日志,随着程序的启动便开始写日志,直到程序关闭。但其中也存在我们需要提供一个特殊的操作(读/写/删除)来操作文件,例如我们需要提供一个日志查看器来查看当前日志或所有日志,这时,便无可避免的发生了以上的问题。解决此问题,只需将文件读写锁改为FileShare.ReadWrite(FileShare就是控制文件流的“访问权限),具体代码如下:
using (FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) { byte[] bt = new byte[fs.Length]; fs.Read(bt, 0, bt.Length); fs.Close(); }
防止重复启动只开一个实例,通用弹窗和提示 使用示例
using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using System.Windows.Forms; using System.Drawing; using System.Threading; namespace Launcher { static class Program { /// <summary> /// 应用程序的主入口点。 /// </summary> [STAThread] static void Main() { //Application.EnableVisualStyles(); //Application.SetCompatibleTextRenderingDefault(false); bool isAppRunning = false; Mutex mutex = new Mutex(true, System.Diagnostics.Process.GetCurrentProcess().ProcessName, out isAppRunning); if (!isAppRunning) { MessageBox.Show("程序已运行,不能再次打开!"); Environment.Exit(1); } Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); var freeSpace = ComTool.GetDiskFreeSpace(System.AppDomain.CurrentDomain.SetupInformation.ApplicationBase); if (freeSpace < 42949672960) { DialogResult dr = MessageBox.Show(string.Format("更新本游戏至少需要5G的安装空间,检测到您的磁盘不足5G({0}),是否继续更新?",ComTool.HumanReadableFilesize(freeSpace)), "提示", MessageBoxButtons.OKCancel, MessageBoxIcon.Question); if (dr == DialogResult.OK) { Application.Run(new Form1()); } } else { Application.Run(new Form1()); } } } }
使用 C# 下载文件 (引用自使用 C# 下载文件的十八般武艺)
简单下载
在 .NET 程序中下载文件最简单的方式就是使用 WebClient 的 DownloadFile 方法:
var url = "https://www.coderbusy.com"; var save = @"D:\1.html"; using (var web = new WebClient()) { web.DownloadFile(url,save); }
异步下载
该方法也提供异步的实现:
var url = "https://www.coderbusy.com"; var save = @"D:\1.html"; using (var web = new WebClient()) { await web.DownloadFileTaskAsync(url, save); }
下载文件的同时向服务器发送自定义请求头
如果需要对文件下载请求进行定制,可以使用 HttpClient :
var url = "https://www.coderbusy.com"; var save = @"D:\1.html"; var http = new HttpClient(); var request = new HttpRequestMessage(HttpMethod.Get,url); //增加 Auth 请求头 request.Headers.Add("Auth","123456"); var response = await http.SendAsync(request); response.EnsureSuccessStatusCode(); using (var fs = File.Open(save, FileMode.Create)) { using (var ms = response.Content.ReadAsStream()) { await ms.CopyToAsync(fs); } }
如何解决下载文件不完整的问题
以上所有代码在应对小文件的下载时没有特别大的问题,在网络情况不佳或文件较大时容易引入错误。以下代码在开发中很常见:
var url = "https://www.coderbusy.com"; var save = @"D:\1.html"; if (!File.Exists(save)) { Console.WriteLine("文件不存在,开始下载..."); using (var web = new WebClient()) { await web.DownloadFileTaskAsync(url, save); } Console.WriteLine("文件下载成功"); } Console.WriteLine("开始处理文件"); //TODO:对文件进行处理
如果在 DownloadFileTaskAsync 方法中发生了异常(通常是网络中断或网络超时),那么下载不完整的文件将会保留在本地系统中。在该任务重试执行时,因为文件已存在(虽然它不完整)所以会直接进入处理程序,从而引入异常。
一个简单的修复方式是引入异常处理,但这种方式对应用程序意外终止造成的文件不完整无效:
var url = "https://www.coderbusy.com"; var save = @"D:\1.html"; if (!File.Exists(save)) { Console.WriteLine("文件不存在,开始下载..."); using (var web = new WebClient()) { try { await web.DownloadFileTaskAsync(url, save); } catch { if (File.Exists(save)) { File.Delete(save); } throw; } } Console.WriteLine("文件下载成功"); } Console.WriteLine("开始处理文件"); //TODO:对文件进行处理
笔者更喜欢的方式是引入一个临时文件。下载操作将数据下载到临时文件中,当确定下载操作执行完毕时将临时文件改名:
var url = "https://www.coderbusy.com"; var save = @"D:\1.html"; if (!File.Exists(save)) { Console.WriteLine("文件不存在,开始下载..."); //先下载到临时文件 var tmp = save + ".tmp"; using (var web = new WebClient()) { await web.DownloadFileTaskAsync(url, tmp); } File.Move(tmp, save, true); Console.WriteLine("文件下载成功"); } Console.WriteLine("开始处理文件"); //TODO:对文件进行处理
使用 Downloader 进行 HTTP 多线程下载
在网络带宽充足的情况下,单线程下载的效率并不理想。我们需要多线程和断点续传才可以拿到更好的下载速度。
Downloader 是一个现代化的、流畅的、异步的、可测试的和可移植的 .NET 库。这是一个包含异步进度事件的多线程下载程序。Downloader 与 .NET Standard 2.0 及以上版本兼容,可以在 Windows、Linux 和 macOS 上运行。
GitHub 开源地址: https://github.com/bezzad/Downloader
NuGet 地址:https://www.nuget.org/packages/Downloader
从 NuGet 安装 Downloader 之后,创建一个下载配置:
var downloadOpt = new DownloadConfiguration() { BufferBlockSize = 10240, // 通常,主机最大支持8000字节,默认值为8000。 ChunkCount = 8, // 要下载的文件分片数量,默认值为1 MaximumBytesPerSecond = 1024 * 1024, // 下载速度限制为1MB/s,默认值为零或无限制 MaxTryAgainOnFailover = int.MaxValue, // 失败的最大次数 OnTheFlyDownload = false, // 是否在内存中进行缓存? 默认值是true ParallelDownload = true, // 下载文件是否为并行的。默认值为false TempDirectory = "C:\\temp", // 设置用于缓冲大块文件的临时路径,默认路径为Path.GetTempPath()。 Timeout = 1000, // 每个 stream reader 的超时(毫秒),默认值是1000 RequestConfiguration = // 定制请求头文件 { Accept = "*/*", AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate, CookieContainer = new CookieContainer(), // Add your cookies Headers = new WebHeaderCollection(), // Add your custom headers KeepAlive = false, ProtocolVersion = HttpVersion.Version11, // Default value is HTTP 1.1 UseDefaultCredentials = false, UserAgent = $"DownloaderSample/{Assembly.GetExecutingAssembly().GetName().Version.ToString(3)}" } };
创建一个下载服务:
var downloader = new DownloadService(downloadOpt);
配置事件处理器(该步骤可以省略):
// Provide `FileName` and `TotalBytesToReceive` at the start of each downloads // 在每次下载开始时提供 "文件名 "和 "要接收的总字节数"。 downloader.DownloadStarted += OnDownloadStarted; // Provide any information about chunker downloads, like progress percentage per chunk, speed, total received bytes and received bytes array to live streaming. // 提供有关分块下载的信息,如每个分块的进度百分比、速度、收到的总字节数和收到的字节数组,以实现实时流。 downloader.ChunkDownloadProgressChanged += OnChunkDownloadProgressChanged; // Provide any information about download progress, like progress percentage of sum of chunks, total speed, average speed, total received bytes and received bytes array to live streaming. // 提供任何关于下载进度的信息,如进度百分比的块数总和、总速度、平均速度、总接收字节数和接收字节数组的实时流。 downloader.DownloadProgressChanged += OnDownloadProgressChanged; // Download completed event that can include occurred errors or cancelled or download completed successfully. // 下载完成的事件,可以包括发生错误或被取消或下载成功。 downloader.DownloadFileCompleted += OnDownloadFileCompleted;
接着就可以下载文件了:
string file = @"D:\1.html"; string url = @"https://www.coderbusy.com"; await downloader.DownloadFileTaskAsync(url, file);
下载非 HTTP 协议的文件
除了 WebClient 可以下载 FTP 协议的文件之外,上文所示的其他方法只能下载 HTTP 协议的文件。
aria2 是一个轻量级的多协议和多源命令行下载工具。它支持 HTTP/HTTPS、FTP、SFTP、BitTorrent 和 Metalink。aria2 可以通过内置的 JSON-RPC 和 XML-RPC 接口进行操作。
我们可以调用 aria2 实现文件下载功能。
GitHub 地址:https://github.com/aria2/aria2
下载地址:https://github.com/aria2/aria2/releases
将下载好的 aria2c.exe 复制到应用程序目录,如果是其他系统则可以下载对应的二进制文件。
public static async Task Download(string url, string fn) { var exe = "aria2c"; var dir = Path.GetDirectoryName(fn); var name = Path.GetFileName(fn); void Output(object sender, DataReceivedEventArgs args) { if (string.IsNullOrWhiteSpace(args.Data)) { return; } Console.WriteLine("Aria:{0}", args.Data?.Trim()); } var args = $"-x 8 -s 8 --dir={dir} --out={name} {url}"; var info = new ProcessStartInfo(exe, args) { UseShellExecute = false, CreateNoWindow = true, RedirectStandardOutput = true, RedirectStandardError = true, }; if (File.Exists(fn)) { File.Delete(fn); } Console.WriteLine("启动 aria2c: {0}", args); using (var p = new Process { StartInfo = info, EnableRaisingEvents = true }) { if (!p.Start()) { throw new Exception("aria 启动失败"); } p.ErrorDataReceived += Output; p.OutputDataReceived += Output; p.BeginOutputReadLine(); p.BeginErrorReadLine(); await p.WaitForExitAsync(); p.OutputDataReceived -= Output; p.ErrorDataReceived -= Output; } var fi = new FileInfo(fn); if (!fi.Exists || fi.Length == 0) { throw new FileNotFoundException("文件下载失败", fn); } }
以上代码通过命令行参数启动了一个新的 aria2c 下载进程,并对下载进度信息输出在了控制台。调用方式如下:
var url = "https://www.coderbusy.com"; var save = @"D:\1.html"; await Download(url, save);
常用接口示例
using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; public class ComTool { public static long GetDiskFreeSpace(string Path) { long freeSpace = 0; var LocalDrive = DriveInfo.GetDrives(); for (int i = 0; i < LocalDrive.Length; i++) { //Console.WriteLine("------------------------------------------"); //Console.WriteLine(string.Format("驱动器名称:{0}", LocalDrive[i].Name)); //Console.WriteLine(string.Format("存储空间大小:{0}字节", LocalDrive[i].TotalSize)); //Console.WriteLine(string.Format("可用空间大小:{0}字节", LocalDrive[i].AvailableFreeSpace)); //Console.WriteLine(string.Format("可用空闲空间:{0}字节", LocalDrive[i].TotalFreeSpace)); //Console.WriteLine(string.Format("文件系统:{0}", LocalDrive[i].DriveFormat)); //Console.WriteLine(string.Format("驱动器类型:{0}", LocalDrive[i].DriveType)); //Console.WriteLine(string.Format("驱动器IsReady:{0}", LocalDrive[i].IsReady)); //Console.WriteLine(string.Format("驱动器的根目录:{0}", LocalDrive[i].RootDirectory)); //Console.WriteLine(string.Format("驱动器卷标:{0}", LocalDrive[i].VolumeLabel)); if (Path.Contains(LocalDrive[i].RootDirectory.Name)) { Console.WriteLine(string.Format("可用空间大小:{0}字节", LocalDrive[i].AvailableFreeSpace)); freeSpace = LocalDrive[i].AvailableFreeSpace; } } return freeSpace; } /// <summary> /// 转换方法 /// </summary> /// <param name="size">字节值</param> /// <returns></returns> public static String HumanReadableFilesize(double size) { String[] units = new String[] { "B", "KB", "MB", "GB", "TB", "PB" }; double mod = 1024.0; int i = 0; while (size >= mod) { size /= mod; i++; } return Math.Round(size) + units[i]; } }
原文连接https://blog.csdn.net/osuckseed/article/details/123755926
猜你喜欢
- 【C#】从零开始用C#写一个桌面应用程序(一)基础操作
- 准备winform应用程序编写桌面应用客户端的技术。xaml一种标记语言。winform程序组成。 程序入口: form.cs和它的设计文件: 启动的过程以及涉及的文件:main函数: form1的构造函数和它的设计文件: main-》构造form-》initializeComponent-》 拖入一个 button控件可以看到: 这时我们已经梳理启动过程。使用组件的方法 可以在
- 【C#】C# Winform程序之间通讯
- 实现原理通过Windows系统中 User32.dll 中的 FindWindow 方法来寻找系统正在运行的程序句柄,通过 SendMessage 方法来发送消息,winform 中的 WndProc 方法来接收消息,下面是SendMessage,FindWindow 这两个参数的具体介绍:1.SendMessage该函数将指定的消息发送到一个或多个窗口。此函数为指定的窗口调用窗口程序,直到窗口程序处理完消息再返回。该函数是应用程序和应用程序之间进行消息传递的主要手段之一 函数原型:I
- 【C#】C# Winform Label 控件
- 目录一、概述二、基本用法1.控件内容显示2.控件的外观3.自定义控件的大小4.控件的内边距 5.设置文本的固定位置6.控件的事件结束一、概述Label 控件是 winform 开发中最常用的一个控件,一般用做显示文本,也有时用做打开链接等操作。二、基本用法新建一个 winform 项目,点击 form1 界面,找到工具箱,在工具箱里找到 Label ,拖入到界面即可。1.控件内容显示label 拖入界面中,如下,单击在属性界面就能看到具体的控件属性在这里有两个重要的属性:1.Name在
- 【C#】C# 解压zip文件,并覆盖
- 使用ZipFile.ExtractToFile方法,并将overwrite参数设置为true,这样可以覆盖同名的目标文件。代码如下:using System; using System.IO; using System.IO.Compression; namespace ConsoleApplication { class Program &nbs
- 【C#】C# Winform 配置文件App.config
- 目录一、简介二、添加引用 三、添加节点1.普通配置节点2.数据源配置节点四、管理类 ConfigHelper.cs1.获取配置节点2.更新或加入配置节点结束一、简介在C#中,配置文件很常用,ASP.NET 和 Winform 名称不同,用法一样,如下图config 文件通常用来存储一些需要修改的数据,比如用户名密码,连接数据库的IP地址等,而不是在代码中写死。有人可能会问,那我自己自定义一个配置文件也行,为什么要用它这个?区别当然有,微软自己封装的读取和写入会更简单一些,你自己封装的,
- 【C#】c#开发桌面应用程序用什么框架
- style="text-wrap: wrap;">在C#开发桌面应用程序时,可以使用以下几种常见的框架:</p><ol class=" list-paddingleft-2" style="width: 1529.1px; text-wrap: wrap;"><li><p>Windows Forms:这是一种由.NET Framework提供的GUI框架,它提供了丰富的GUI控件和易于使用的编程模型。Windows Forms在C#开发领域中使用非常广泛,并且已经存在多年,获得了广泛的支持和优化。</p></li></ol
- 【C#】C# Winform 定时清理日志
- 一、前言在 Winform 开发中经常有这样的需求,在用户执行一些操作不正确时,需要将错误信息反馈给用户,比如:登录密码不正确,无法连接到服务器等,一般常见的用法有两个:1.弹框使用 MessageBox.Show("密码错误"); 这样的方式,弹框后,用户必须点击确定后才能执行下一步操作,给用户的体验并不是特别好。2.在界面中显示错误信息,定时清除如果是输入框,直接用 ErrorProvider 控件就行了。如果只是做一些简单的提示信息,那么就要定时清除
- 【C#】Winform解决方案打包成.exe 安装版Windows桌面应用程序
- 踩了几天的坑,慢慢爬出来了。帮助一下新手友人吧,高手请绕路。IDE Version:Visual Studio 20191.安装Microsoft Visual Studio Installer Project(1)打开Visual Studio 2019,扩展->管理扩展(2)搜索install,下载图中的扩展即可(我已经安装了,所以没有下载按钮)按照操作安装即可2.打包(1)右键 解决方案->添加->新建项目(2)搜索setup->选择 Setup Project-&