【C#】注意用“划算”的方式使用图标
先解释一下何谓“划算”:假定一个Winform程序包含若干个窗体,每个窗体左上角都要显示图标(即要设置Form.Icon属性),该程序本身也要有个图标(用于在OS资源管理器中显示),所有这些图标都是一个样子——这是一种很常见的情形。如图:

即同一个图标要用在程序本身和程序中的各个窗体之上。那么所谓“划算”就是指,在程序文件(exe)中只存储1份图标数据,所有要用到该图标的地方都从这里取。而不是存储多份,各取各的,因为这样显然会增大程序体积,很不“划算”。
之所以有这个话题,是因为如果不注意操作技巧,就会造成同一个图标存储多份的情况,VS和编译器并没有智能到会帮我们自动清除冗余资源的地步。那么如何才能做到划算而不浪费,关键就是要弄清楚每种操作会造成什么样的结果。
一、先看程序图标的设置方法

这个地方有几种选取方式:
1、直接浏览到ico文件进行选取。VS会自动把ico文件拷贝到项目根目录
2、把ico文件存放到项目根目录或任意子目录(该目录必须“包括在项目中”),然后就可以在这里下拉选取。如上图的Resources\test.ico和test.ico就是这种情况
无论用何种方式选取,项目编译成PE文件后,这个图标都是存放在PE文件的资源节中,可以用eXeScope之类的工具看出。有关PE的信息请参看:
http://msdn.microsoft.com/en-us/windows/hardware/gg463119.aspx
http://blog.csdn.net/evileagle/article/details/11693499
换句话说,程序图标的选取没什么可注意的,因为结果都一样。
二、窗体图标的设置方法
1、在VS的属性面板中直接浏览ico文件。如图:

这可能是最直观简单的方法了。但是不幸,这恰恰是最容易造成浪费的方法,因为这样选取的图标,会嵌在相应窗体的资源里(Form.resx),有几个窗体这样设置图标,图标数据就会存几份。
2、把图标添加进项目资源(Resources.resx)中。然后在所有窗体代码中都这样设置:
this.Icon = xx.Properties.Resources.test;//xx是项目默认命名空间;test是资源名
这种方式的结果是,图标会以程序集资源的形式存储1份在程序集中,所有窗体共用这个资源。相比第1种方式,这种方式不会造成图标存储多份。但也只是解决了多个窗体共用一个图标的问题,还有程序本身的图标是个问题。
上面说过,程序图标只有一个地方可以设置,设置的结果是把图标存放到PE资源中,这里存在【程序集资源】和【PE资源】两个概念,就是虽然在程序集资源只有1份图标数据,但只要一设置程序图标,项目编译时就会把图标再存一份到PE资源中,所以在整个PE文件中还是存在2份图标数据。那么要想让程序和窗体共用一个资源,就有两种思路,一是让程序图标使用程序集资源,二是让窗体使用PE资源。
对于前一种,我怎么可能去找虐呢,即使自宫也未必成功的事,pass~
3、获取程序图标,给窗体使用。一开始想到的自然是Icon.ExtractAssociatedIcon(),但是这个方法只能取到32x32的图标,而窗体图标是16x16的,会造成缩放,对于我这种纠结视觉细节的人来说,是不可接受的。理想的情况是,取到完整的图标组(包含多种尺寸和色深的图标集合)赋值给窗体Icon,这样才能在窗体左上角和NT6任务栏拥有完美的表现。如图:

那怎样才能取到图标组呢。为此我啃了若干对于由.net起步的码农来说臣妾做不到的知识,包括SHGetFileInfo、LoadIcon、LoadImage、ExtractIcon、FindResource等API,甚至啃了下PE结构,OMG~越啃越觉得想死的心都有了。天幸在codeproject.com找到了高人的现成方案:
http://www.codeproject.com/Articles/32617/Extracting-Icons-from-EXE-DLL-and-Icon-Manipulatio
先感谢一下这位仁兄,好人一生平安。他这方案挺全,可以从各种源获取图标,本来想精简一下,只要获取PE图标组的方法,但发现整个方案中,大部分代码就是干这个的,精简意义不大,索性整个用上。回正题,在所有窗体中写上:
this.Icon = IconHelper.ExtractIcon(Application.ExecutablePath, 0);
即可。当然我对他的IconHelper稍微改造了一下,增加AppIcon属性:
static Icon appIcon;
/// <summary>
/// 主程序图标
/// </summary>
public static Icon AppIcon
{
get
{
if (appIcon == null)
{
try { appIcon = ExtractIcon(Application.ExecutablePath, ); }
catch { return null; }
}
return appIcon;
}
}
这样,到窗体中,写成:
this.Icon = IconHelper.AppIcon;
即可。
至此,实现了程序和窗体共用一个图标,程序PE文件也只存储1份图标数据的目的。再次感谢高人!只是项目加入该方案后,最终生成的程序大概会增加接近20K的体积。所以是不是划算用上该方案,需从实际权衡。比如图标文件不大的程序,俩图标加起来还没有1图标+20K,那存两份就存两份,反正最终目的是程序体积,又不是追求彻底共用。
----------------------------------为什么非得给分隔线起个名字----------------------------------
另外,顺便说一下托盘区图标,如果给NotifyIcon.Icon设一个图标组,它是不会自动取16x16那一个的,取的是32x32,像这样:

非得直接丢给它一个16x16的才行,所以如果你已经拥有图标组,得这样设置托盘图标:
notifyIcon1.Icon = new System.Drawing.Icon(IconHelper.AppIcon,16,16);//意思就是从图标组中取出指定尺寸的单一图标。
这样就能得到完美的托盘图标了:

----------------------------------另一条分隔线----------------------------------
最后,附一张关于项目中各种资源的简易说明图:

最最后~我啥时候变这么啰嗦了,文中说的“划算”仅仅是在文件系统层面而言的,就是尽可能唯一存储PE文件中的图标数据,减小程序体积。然而在内存层面,上述方法是不是会造成复制多份图标数据,即文件是小了,但运行起来的内存占用可能并不少,这个我没求证,等蛋疼再追求一下内存层面的“划算”。
文毕。
【C#】注意用“划算”的方式使用图标的更多相关文章
- Ubuntu 如何为 XMind 添加快速启动方式和图标
目录 Ubuntu 如何为 XMind 添加快速启动方式和图标 Ubuntu 如何为 XMind 添加快速启动方式和图标 按照教程Ubuntu16.04LTS安装XMind8并创建运行图标进行Xmin ...
- Element-UI 使用 class 方式和 css 方式引入图标
今天在使用 vxe-table 时,需要引入 Element UI的图标,顺便就找了下这些组件库中图标的引用方式. 我们知道 Element .Ant Design.Font Awesome 等很多组 ...
- Windows中与系统关联自己开发的程序(默认打开方式、图标、右击菜单等)
1. 默认打开方式 1.1. 代码支持 在Windows下,某个特定后缀名类型的文件,如果要双击时默认用某个程序(比如自己开发的WinForm程序)打开,代码中首先肯定要支持直接根据这个文件进行下一步 ...
- JAVA学习Swing章节标签JLabel中图标的使用
package com.swing; import java.awt.Color; import java.awt.Component; import java.awt.Container; impo ...
- 强行替换exe图标的方法
说句实话,要想用普通的方法来替换图标,不是完全不可行,当然也不是完全可行.这个看似简单的问题并不是想象中那么容易解决,为什么有人修改exe的图标总是失败,其实他忽视了exe和图标的复杂性,用简单的方法 ...
- ionic中修改图标的问题
有两种修改图标的方法,一种是手动配置,另外一种是使用命令 1.手动配置 把图标icon.png复制到resources\android\icon目录下 修改根目录的config.xml文件 <p ...
- ionic2中使用自定义图标
在ionic2中使用自定义图标,如iconfont(阿里巴巴矢量图标). 先在http://www.iconfont.cn/ 中找到自己需要的图标,然后将图标加入购物车,然后下载该图标. 下载完成后解 ...
- svn -- svn图标解析
1.代表服务器端与客户端文件相同,没有任何更改 2.当前文件如果有修改,那么将显示如下图标 3.当前要提交的文件,与服务器上其他人提交的文件有冲突,那么将显示如下图标 4.当前文件,在服务器上已被删除 ...
- 在线使用iconfont字体图标
登录https://www.iconfont.cn 把需要的图标加入购物车,然后加入项目 打开我的项目,生成代码 有3中方式使用图标 unicode和font class本质都是使用字体,好处在于兼容 ...
随机推荐
- jdk研究——java.lang
jdk研究 volatile 是什么意思? 如何看jdk源码? 如何调试源码!---------仔细解读关键类,关键代码,常用的api的解释! 自己有疑问的不懂地方-------- 不懂的太多怎么办. ...
- 每天一个linux命令(53):route命令
Linux系统的route命令用于显示和操作IP路由表(show / manipulate the IP routing table).要实现两个不同的子网之间的通信,需要一台连接两个网络的路由器,或 ...
- js笔记——js异常处理
异常捕获 try...catch结构: try { //需要捕获的代码块 } catch (e) { console.log(e.name + ": " + e.message); ...
- [数据库事务与锁]详解六: MySQL中的共享锁与排他锁
注明: 本文转载自http://www.hollischuang.com/archives/923 在MySQL中的行级锁,表级锁,页级锁中介绍过,行级锁是Mysql中锁定粒度最细的一种锁,行级锁能大 ...
- Atitit 数据存储的分组聚合 groupby的实现attilax总结
Atitit 数据存储的分组聚合 groupby的实现attilax总结 1. 聚合操作1 1.1. a.标量聚合 流聚合1 1.2. b.哈希聚合2 1.3. 所有的最优计划的选择都是基于现有统计 ...
- Atitit 词法分析器的设计最佳实践说明attilax总结
Atitit 词法分析器的设计最佳实践说明attilax总结 1.1. 手写的优点:代码可读,对源代码中的各种错误给出友好的提示信息,用户体验高,1 1.2. 使用状态表比较简单,dfa比较麻烦1 1 ...
- OutputCache属性详解(四)— SqlDependency
目录 OutputCache概念学习 OutputCache属性详解(一) OutputCache属性详解(二) OutputCache属性详解(三) OutputCache属性详解(四)— SqlD ...
- iOS ---Swift学习与复习
swift中文网 http://www.swiftv.cn http://swifter.tips/ http://objccn.io/ http://www.swiftmi.com/code4swi ...
- ES6 对象解构
ES6 对象解构 第一眼看到,什么鬼? const { body } = document `` 其实等于: const body = document.body ``` http://es6.rua ...
- Java 线程 — ConcurrentLinkedQueue
ConcurrentLinkedQueue 在考虑并发的时候可以先考虑单线程的情况,然后再将并发的情况考虑进来. 比如ConcurrentLinkedQueue: 先考虑单线的offer 再考虑多线程 ...