今天休息在家,由于天气热再加上疫情原因,就在家里呆着,空闲时想着,在很早以前(约3年前),产品人员跟我提了一个需求,那就是winform桌面程序的图标能否根据节日动态更换,这种需求在移动APP上还是比较常见,比如:淘宝、天猫、京东、360等,它们在逢节假日时除了APP内容有更新,APP ICON也是都更新了的,但PC端的应用程序(APP)则很少见到说有动态更新图标的,故当时我是直接回绝了的,明确表示做不了,但今天我仔细想了一下,其实也是可以实现的,虽然无法直接更新桌面图标,但我们可以更新替换掉桌面的快捷文件呀!(PC端桌面的图标本质都是一个LINK文件)想到这里我就开始设计,最终还是实现了无感知更新PC端桌面图标的功能。

先看实现方案的流程图如下:

其中:DynamicIconApp【原生真实程序】、AppLauncher【引导启动程序】 均是我演示的DEMO程序

如上方案核心实现思路与步骤是:

1.桌面快捷方式连接的程序是启动程序(即:前置程序),而非真实要打开的程序,目的是:如果要替换桌面快捷方式必需是另外进程来执行,如果快捷方式打开的是真实程序,而真实程序又来更新替换桌面快捷方式文件,会被该桌面快捷方式文件被占用; 【当然也可以不用单独搞一个启动程序,可以就是真实程序,但真实程序需支持传入参,根据入参的不同的,可以开启多个进程,也可以达到该目的,我之前就实现过类似功能:程序自己更新自己】

2.桌面快捷方式本质只是一个软连接(LINUX中也有),故如果真实程序需要更新,只需通过独立的更新程序(程序更新实现原理有很多,在此就不展开说明)来更新真实的程序即可,而桌面的桌面快捷方式却不用动,仍然通过:桌面快捷方式-》启动程序-》最新的真实程序,用户无感知的。

3.更新桌面图标准备工作与步骤:

3.1.创建AppLauncher【引导启动程序】,在程序内部直接实现:执行启动DynamicIconApp.exe【原生真实程序】,启动时带上额外的参数(告之来自启动程序及自己的进程ID,如:fromlauncher:12345),然后关闭自己即可。(其实就是跳板的作用),示例代码如下:

/// <summary>
/// 引导启动程序
/// author:zuowenjun
/// date:2021-6-19
/// </summary>
namespace AppLauncher
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
} private void Form1_Load(object sender, EventArgs e)
{
ProcessStartInfo proc = new System.Diagnostics.ProcessStartInfo();
proc.UseShellExecute = true;
proc.FileName =Path.Combine(Application.StartupPath, "DynamicIconApp.exe");
proc.Arguments = "fromlauncher:" + Process.GetCurrentProcess().Id;
proc.CreateNoWindow = false;
//启动进程
Process.Start(proc);
this.Close();
}
}
}

3.2.创建DynamicIconApp.exe【原生真实程序】,在程序内部实现:在程序启动界面前,通过参数判断是否来自启动引导程序,并判断AppLauncher【引导启动程序】进程是否已结束,若未结束,则先尝试直接KILL,若KILL失败则老实等待进程退出。若进程已结束,则再判断是否需要更新桌面快捷方式(这个看具体的情况,可以在DB表中或远程配置中心或API中增加可获取是否需要更新桌面快捷方式文件的逻辑),若需要更新,则将当前应用程序目录的指定的桌面快捷方式文件(如:DynamicIconApp.Lnk,如果不在,应该从CDN获取最新的桌面快捷方式文件)替换桌面上已有或不存的桌面快捷方式文件,替换OK后,再正常运行显示程序界面即可,这样就能实现桌面APP的ICON按需动态更换的效果。示例代码如下:

  1 /// <summary>
2 /// 原生真实程序
3 /// author:zuowenjun
4 /// date:2021-6-19
5 /// </summary>
6 namespace DynamicIconApp
7 {
8 static class Program
9 {
10 /// <summary>
11 /// The main entry point for the application.
12 /// </summary>
13 [STAThread]
14 static void Main(String[] args)
15 {
16 if (args != null && args.Length > 0)
17 {
18 bool fromlauncher = args[0].StartsWith("fromlauncher:");
19 if (fromlauncher)
20 {
21 int launcherProcId = int.Parse(args[0].Substring(args[0].IndexOf(":") + 1));
22 //等待AppLauncher程序完全退出后,再正式运行
23 //MessageBox.Show("will starting..." + launcherProcId);
24 Process proc = null;
25 try
26 {
27 proc = Process.GetProcessById(launcherProcId);
28 MessageBox.Show(proc.Id + "," + proc.ProcessName + "," + proc.HasExited
29 + "," + proc.ExitTime);
30 }
31 catch (Exception e)
32 {
33 //MessageBox.Show("Process.GetProcessById error:" + e.ToString());
34 if (!e.Message.Contains("has exited"))
35 {
36 return;
37 }
38 proc = null;
39 }
40
41
42 bool waitExit = false;
43 if (null != proc)
44 {
45 try
46 {
47 Thread.Sleep(500);
48 proc.Kill();
49 waitExit = true;
50 }
51 catch (Exception e)
52 {
53 MessageBox.Show("kill Process error:" + e.ToString());
54 proc.WaitForExit();
55 waitExit = true;
56 }
57 }
58
59 //MessageBox.Show("start run after launcher Process exit (waitExit = " + waitExit + ") !");
60 }
61 }
62 Application.SetHighDpiMode(HighDpiMode.SystemAware);
63 Application.EnableVisualStyles();
64 Application.SetCompatibleTextRenderingDefault(false);
65 Application.Run(new Form1());
66 }
67 }
68 }
69
70
71
72
73 /// <summary>
74 /// 原生真实程序
75 /// author:zuowenjun
76 /// date:2021-6-19
77 /// </summary>
78 namespace DynamicIconApp
79 {
80 public partial class Form1 : Form
81 {
82 public Form1()
83 {
84 InitializeComponent();
85 }
86
87 private void Form1_Load(object sender, EventArgs e)
88 {
89 //TODO:这里只是示例,判断是否需要更新桌面快捷方式文件(换图标)取决于远程动态配置
90 bool needUpdateAppLink = true;
91 //TODO:这里只是判断应用程序根目录有没有快捷方式文件,而实际的可能还要增加:
92 //若本地没有,则去CDN下载到本地
93 if (needUpdateAppLink && File.Exists("DynamicIconApp.lnk"))
94 {
95 MessageBox.Show("will be copy DynamicIconApp.link to desktop dir!");
96 String desktopFilePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), "DynamicIconApp.lnk");
97 if (File.Exists(desktopFilePath))
98 {
99 File.SetAttributes(desktopFilePath, FileAttributes.Normal);
100 File.Delete(desktopFilePath);
101 }
102 string linkFilePath = Path.Combine(Application.StartupPath, "DynamicIconApp.lnk");
103 File.Copy(linkFilePath, desktopFilePath);
104 }
105 }
106 }
107 }

3.3.提前创建快捷方式文件,把图标及链接目标都设置好(当然也可以使用WshShell 组件通过C#来动态创建,这个看需要,我个人觉得没必要),放到CDN或像我示例的放到真实程序根目录即可,注意:DynamicIconApp.lnk 快捷方式的名字虽然叫原生真实程序名,但实际链接执行的是:引导启动程序,目的就是桌面的快捷方式必需是真实程序名,这样对于普通用户来说才是对的。

联想一下,大家有没有发现,原来QQ也玩的是这一套,不信你看桌面的快捷方式及实际目标,截图为证:

桌面快捷方式:

QQ快捷方式的属性(目标链接的是:QQScLauncher.exe,这个就是QQ的引导程序,而本身的程序是QQ.exe)

QQ真实应用:(它们的关系是:QQ快捷方式-》执行QQ引导程序-》QQ程序,与我们的设计是如出一辙呀!)

QQ这样做,除了我说的那个目的(可以动态改快捷图标),也可以在启动QQ前做各种前置验证,比如:是否需要升级等。

好了,回到我们的今天的主题上来,上面已讲了实现方案及具体步骤,现在是见证效果的时刻了。

这是原始安装时的桌面快捷方式:(可以看到目标是指向的引导启动程序)

然后我在应用程序根目录把快捷方式更新(更换图标),如下图示:【当然如果是真实的生产环境,应该是将快捷方式文件放到CDN,同时通过远程配置中心或API来返回是否需要更新快捷方式文件的逻辑】

改后效果:

好了,然后我们仍然模拟用户,是在桌面双击原快捷方式图标,最后运行后,桌面的快捷方式图标也自动更新了。如下图示:(原生真实程序运行起来了,桌面的ICON也同步更新了,当然想改名也是OK的,甚至改快捷链接目标也是可以的)

文末说一下,这篇文章只是空闲时的小研究而矣,至于技术过不过时还是看需求吧,我最近工作重点是JAVA栈的SPRING微服务体系各种研究与实战,比如:最近我实现了基于自定义的Mybatis拦截器来实现SQL语句自动审计功能(即:自动发现SQL语句是否合规,是否存在性能问题,若审计不通过,则会报错,这样在开发阶段就能提前发现问题,及时止损),同时也研究了关于OAUTH2.0+OIDC相关内容,后面有机会再分享(为何今天不分享,因为家里的这个笔记本电脑太差,运行VS2019都比较卡,运行IEDA估计直接死机),近期工作真的很忙,上班没时间,下班又加班太晚没有精力,生活不易,但学习也不能止。

空闲时间研究一个小功能:winform桌面程序如何实现动态更换桌面图标的更多相关文章

  1. 用MVC5+EF6+WebApi 做一个小功能(三) 项目搭建

    一般一个项目开始之前都会有启动会,需求交底等等,其中会有一个环节,大讲特讲项目的意义,然后取一个高大上的项目名字,咱这是一个小功能谈不上项目,但是名字不能太小气了.好吧,就叫Trump吧.没有任何含义 ...

  2. 用MVC5+EF6+WebApi 做一个小功能(二) 项目需求整理

    在一个项目开始前,需求整理大概要占到整个项目周期15%甚至30%的比重,可以说需求理得越清楚,后续开发中返工几率越小.在一个项目中,开发新功能的花费的精力要远远小于修改功能的精力,这基本是一个共识.老 ...

  3. 用MVC5+EF6+WebApi 做一个小功能(四) 项目分层功能以及文件夹命名

    在上一节,我们完成了一个项目搭建,我们看到的是一个项目的分层架子,那接下来每一层做什么以及需要引用哪些内容呢?在本节内容我们还逐步拆分每一层的功能,顺带添加package包 Trump.Domain ...

  4. WPF制作的一个小功能,智能提示(IntelliSense)

    原文http://www.cnblogs.com/scheshan/archive/2012/06/30/2570867.html 最近WPF项目中遇到一个需求,需要给一个RichTextBox添加智 ...

  5. 【一个小功能】点击图标/链接发起QQ临时会话

    有时候,我们需要实现在网页上点击一个QQ图标来实现QQ临时会话,这样不用添加好友,也能满足及时沟通的需求. 实现方案比较简单,只是为a标签修改href属性,代码如下 <a href=" ...

  6. 用MVC5+EF6+WebApi 做一个小功能(一)开场挖坑,在线答题系统

    从哪开始说呢,这几年微软的技术一直在变,像是牟足了劲要累死所有的NET程序员,从WebForm到MVC到现在MPA.SPA .Razor单页,从net2.0一直走到现在.net4.6.2,后面还有一个 ...

  7. 【一个小功能】从js判断ie版本,浅谈navigator对象的appName属性

    判断IE版本主要的是获取两个属性,a.当前浏览器名称,b.当前浏览器版本,为此不得不了解navigator对象. 先贴代码 window.onload = function() { var brows ...

  8. 六、实现一个小功能 todolist

    1.创建一个新的Compnent 如果是通过 cli 创建的会自动加入,如果是手动创建的,需要自己加入. 2.实现添加效果 3.实现删除按钮 4.优化,把点击 添加 改为 回车 添加 5.优化,分成“ ...

  9. c11时间库一个小例子

    #pragma once #include <chrono> #include <string> #include <iostream> #include < ...

随机推荐

  1. burp-suite(Web安全测试工具)教程

    Burp Suite 是用于攻击web 应用程序的集成平台.它包含了许多工具,并为这些工具设计了许多接口,以促进加快攻击应用程序的过程.所有的工具都共享一个能处理并显示HTTP 消息,持久性,认证,代 ...

  2. Spring Cloud 升级之路 - 2020.0.x - 4. 使用 Eureka 作为注册中心

    Eureka 目前的状态:Eureka 目前 1.x 版本还在更新,但是应该不会更新新的功能了,只是对现有功能进行维护,升级并兼容所需的依赖. Eureka 2.x 已经胎死腹中了.但是,这也不代表 ...

  3. 从零开始搞监控系统(1)——SDK

    目前市面上有许多成熟的前端监控系统,但我们没有选择成品,而是自己动手研发.这里面包括多个原因: 填补H5日志的空白 节约公司费用支出 可灵活地根据业务自定义监控 回溯时间能更长久 反哺运营和产品,从而 ...

  4. vue 2.9.6升级到最新版本

    在看文档https://cli.vuejs.org/zh/guide/installation.html中,按步骤升级vue: 于是就先通过 npm uninstall vue-cli -g卸载vue ...

  5. 大数据开发-Flink-数据流DataStream和DataSet

    Flink主要用来处理数据流,所以从抽象上来看就是对数据流的处理,正如前面大数据开发-Flink-体系结构 && 运行架构提到写Flink程序实际上就是在写DataSource.Tra ...

  6. 没有发生GC也进入了安全点?这段关于安全点的JVM源码有点意思!

    文末 JVM 思维导图,有需要的可以自取 熟知并发编程的你认为下面这段代码的执行结果是怎么样的? 我如果说,执行流程是: t1 线程和 t2 线程一直执行 num 的累加操作 主线程睡眠 1 秒,1 ...

  7. BUAA-OO-第四单元总结——终章

    面向对象第四单元博客总结--终章 第四单元作业设计 第13次作业设计 类和对应方法属性设计 类设计如下图所示 本次作业主要涉及六个类,其中包括主类 Main ,通用Map类 UmlElementIdM ...

  8. docker 的常见命令行解析

    1.查看本地镜像 sudo docker images 2.查看本地容器 sudo docker ps 3.根据Dockerfile制作镜像命令 sudo docker build -t Myimag ...

  9. CentOS 7 vs. CentOS 8 版本差异大比拼

    CentOS 7 vs. CentOS 8 版本差異大比拼 2020-02-14 CentOS 最近剛好在撰寫課鋼,必須要以最新的 CentOS 8 版本為主,剛好來做一下 CentOS 7 和 Ce ...

  10. Linux 操作系统(二)搜索文件命令find、locate、which、whereis、grep、wc

    以下命令均已在 Kali Linux 下验证. 1.find 命令 --1-- find /usr/share -name test.lst //精准搜索,文件名需要与-name后的内容一模一样包括后 ...