您的当前位置:首页>全部文章>文章详情

【C#】C#Windows桌面应用开发实践

CrazyPanda发表于:2024-01-28 16:26:27浏览:362次TAG:



必须功能一览

注册表相关的操作(添加与删除)

       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#调用win10系统自带软键盘的方法
上次做了个笔记是关于调用windows系统自带的触摸键盘的方法:C#调用Windows系统自带触摸键盘的方法_c# 虚拟键盘-CSDN博客除了调用触摸键盘,我们也可以通过调用win10的自带软键盘作为输入途径。方法很简单。1、添加using System.Diagnostics引用。2、创建进程Process Winvirkey = Process.Start(&quot;osk.exe&quot;);3、打开键盘:Winvirkey = Process.Start(&quot;osk.exe&amp;
发表于:2024-01-29 浏览:357 TAG:
【C#】C# Winform 相册功能,图片缩放,拖拽,预览图分页
一、前言在一些项目中也会用到预览图片的功能,至于为什么有一个添加图片的按钮,是因为有些项目,比如视觉相关的项目,摄像头拍摄图片,然后显示在界面上,拍一次显示一张。另一个,就是分页功能,当预览图位置不够用时就会用到。当前软件的功能1.添加图片如果8个预览图都满了,会自动分页,就可以点击上一页,或者下一页了。2.点击预览图显示大图点击预览图,之前的拖拽和放大会自动复位3.大图可以拖拽,放大,缩小如果图片比较小,有这个功能就看到图片的更多细节了。4.图片倒序排列最后拍摄的图片,始终显示在前面,方便用户
发表于:2024-02-02 浏览:314 TAG:
【C#】C# Winform Label 控件
目录一、概述二、基本用法1.控件内容显示2.控件的外观3.自定义控件的大小4.控件的内边距&nbsp;5.设置文本的固定位置6.控件的事件结束一、概述Label 控件是 winform 开发中最常用的一个控件,一般用做显示文本,也有时用做打开链接等操作。二、基本用法新建一个 winform 项目,点击 form1 界面,找到工具箱,在工具箱里找到 Label ,拖入到界面即可。1.控件内容显示label 拖入界面中,如下,单击在属性界面就能看到具体的控件属性在这里有两个重要的属性:1.Name在
发表于:2024-02-04 浏览:292 TAG:
【C#】C# Winform 自定义进度条ProgressBar
效果:一、前言Winfrom各种老毛病真的不适合做大型项目,甚至中型项目都不适合,一些小功能都能把你折腾半死,比如,我想在界面上显示一个进度条,用来显示现在硬盘和内存已经使用了多少,使用了 ProgressBar 控件你看看效果:进度条中间一直有个白色光影在晃来晃去的,是不是想让别人感慨:“哇!好强的光芒,我的眼睛快睁不开了...”。而且背景颜色无法改变,这个动画也无法关掉,为了解决这两个问题,我找了很久,终于找到了下面的解决方法。二、自定义进度条于是我在网上找了一些资料,有到效果有,但不是特别
发表于:2024-02-01 浏览:325 TAG:
【C#】C#的DateTimePicker控件(日期控件)
目录一、使用DateTimePicker控件显示时间二、使用DateTimePicker控件以自定义格式显示日期三、返回DateTimePicker控件中选择的日期1.源码2.生成效果&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;DateTimePicker控件(日期控件)用于选择日期和时间,DateTimePicker控件只能选择一个时间,而不是连续的时间段,也可以直接输入日期和时间。一、使用DateTimePicker控件显示时间&nbsp;
发表于:2024-01-29 浏览:382 TAG:
【C#】C# System.Windows.Forms.DataVisualization Demo案例
简介DataVisualization 其实就是Winform 中自带的 Chart 控件,整个图形控件主要由以下几个部份组成:1.Annotations --图形注解集合2.ChartAreas&nbsp; --图表区域集合3.Legends&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; --图例集合4.Series  &nbsp; --图表序列集合(即图表数据对象集合)5.Titles   --图标的标题集合每个集合具体介绍,可以参考下面的帖子,看完了介绍,一定对你理解这个插件
发表于:2024-02-01 浏览:287 TAG:
【C#】C# Winform SplitContainer组件创建侧边菜单
效果一,SplitContainer 基本操作新建一个 Winform 项目,在Form1中拖进一个 SplitContainer 组件默认的界面如下这时候,你会发现,左侧菜单栏的宽度也太宽了吧,按照以前的经验,你一定会用鼠标去拖拽,这时候你就会发现,鼠标根本拖不动,不信你可以试试这时候,我们按Esc键,鼠标再移动到边框的时候,鼠标图标就会变成一个 “+” 状的图标,这时候就可以拖拽了此时,左侧的Panel1内还没有任何组件,运行后的效果我们添加一个按钮到&nbsp;Panel1 试试运行后发现
发表于:2024-02-01 浏览:540 TAG:
【C#】Winform NanUI 0.88版本 JS和C#相互调用
目录一、需求版本二、实例JS调用C#注册的只读属性JS调用C#注册的字段JS调用C#注册的同步方法JS调用C#注册的异步方法C#注册一个方法,JS调用并传递参数C#注册一个方法,JS调用并接收C#返回值C#注册一个方法,接收JS的数组参数C#注册一个方法,接收JS的一个函数,执行这个JS函数,并将C#的值传递过去三、结束一、需求在软件的界面和软件逻辑分离后,最重要的就是要处理参数的传递,和函数的调用,因此存在JS中和C#相互调用的需求。版本NanUI 版本:0.8.80.191二、实例using
发表于:2024-02-06 浏览:371 TAG:
【C#】C# Winform 多个程序之间的通信(非Scoket)
效果功能:打开窗体自动连接主程序,并自动添加到列表,可以向子程序群发消息可以向单个程序单独发送消息在退出程序后,添加的程序列表会自动移除一、概述参考:C# Winfrom程序之间通讯_c# sendmessege copydatastruct 返回多个值_熊思宇的博客-CSDN博客在之前我写过 winform 程序与程序之间的通信,但是这个版本有个问题,那就是只能由两个程序进行通信,同时打开多个程序的话,接收方收到的数据就会一模一样,这次发表这个教程,也就是要解决这个问题。归根结底,还是&amp;nbs
发表于:2024-02-03 浏览:296 TAG:
【C#】Winform NanUI 0.77版本 清除Cookie等本地缓存
目录需求NanUI0.88版本的缓存路径NanUI0.77版本的缓存路径试着实现功能添加扩展出现的问题解决问题结束需求清除web前端保存的一些的数据,或者清除浏览器缓存会用到。NanUI0.88版本的缓存路径在NanUI0.88版本中,只要在Program.cs启动模块中调用app.ClearCacheFile();就可以清除浏览的缓存了。0.88版本的缓存路径:&quot;C:\\Users\\Administrator\\AppData\\Roaming\\Net Dimension Stu
发表于:2024-02-08 浏览:308 TAG: