关联文件类型和图标

5

好的,我已经查看了很多资料,但是我仍然很难弄清楚如何将自定义文件类型与我的C#应用程序关联。我正在使用Visual Studio 2008,并且由于公司部署的限制,我只能使用.NET 2.0。

基本上,我真正想做的就是允许用户双击我的自定义文件类型或将其拖放到应用程序图标上并加载数据,以及在文件本身上显示自定义图标。我可以通过对话框打开文件,一切都很顺利。

尝试阅读微软的在线文档很难理解,而且并没有给我所有需要的详细信息。如果有人知道在哪里可以找到一个好的教程或者能够解释给我听,我将不胜感激。

谢谢 Patrick


限制真是遗憾;ClickOnce在3.5 SP1中已经内置了此功能... - Marc Gravell
这个问题已经在以下stack overflow线程中得到了彻底的解答。我们一直在使用这个实现,它运行良好。它也是开源的,并且可以集成到MSBuild中。 - Blake Niemyjski
3个回答

5

我在几年前的新闻组中写了这篇文章。翻阅一下,我发现这并不是我写过最清晰的文章,但是它相当完整。值得一提的是,下面是我重新印刷的内容:


首先,您需要在HKEY_CLASSES_ROOT下创建一个子键,用于保存启动应用程序的命令。 您应该给此键命名为半描述性名称。您的文件扩展名将映射到此键。例如,默认情况下,TXT文件被映射到名为txtfile的键。使用此设置的好处是,如果您的应用程序可以处理多个扩展名,则可以将它们全部映射到此键。例如,许多图像编辑应用程序会创建一个名为“imagefile”的子键,并将.bmp、.jpg、.gif等文件映射到该键。我们将称您的键为“JoeBlowFile”。接下来,您需要为新的JoeBlowFile键设置“默认”值,以描述用户拥有的文件类型的文本字符串。这是在Windows Explorer下“类型”下显示的内容。再次以TXT文件示例说明,此处的好字符串可能是“文本文件”或“文本文档”(默认值为后者)。您的字符串可能是“Joe Blow数据”。

现在,在您的新密钥下,您可以创建另一个子密钥,称为“DefaultIcon”。正如其名称所示,它设置了与此类型文件一起使用的图标。您应该创建一个自定义图标,以图像方式表示您的程序处理的文档。您可以将此图标保存为ICO文件在您的应用程序目录中,但更好的是,您可以将其包含为EXE或DLL中的资源。无论哪种方式,然后您都需要将子键的默认值设置为表示ICO、EXE或DLL的完整路径和文件名的字符串。如果文件中有多个图标(特别是如果您将其作为资源包含在EXE或DLL中),则还需要添加逗号,并使用基于零的正索引号来表示要使用的图标,或者使用负资源ID,使用您在资源脚本中分配给图标的任何ID的负数。因此,您的可能是“C:\Program Files\JoeBlow\JoeBlow.exe, 2”。
C#开发者注意事项。不幸的是,C#项目无法拥有资源脚本。通过将资源添加到项目并将其指定为“嵌入式资源”,以便将其包含在.NET应用程序中,会以一种与之前方法不兼容的新的.NET特定格式进行包含。在使用VS.NET上的C#时,您唯一能够正确嵌入到应用程序中的图标是应用程序图标,可以从项目属性中访问。如果您需要其他图标(例如,表示由您的应用程序处理的文档文件而不是表示应用程序本身的图标),则需要将ICO文件本身包含在项目中,使用C++编译带有嵌入图标的DLL,或者创建和编译资源脚本,并将其从项目属性中包含在项目中。

无论您选择使用DefaultIcon键与否,现在您需要在JoeBlowFile键下创建一个名为“shell”的子键。在shell键下,您将为您希望用户能够从上下文菜单(右键菜单)执行的每个命令创建单独的键。这些项目称为“动词”。为了保持一致性,其中之一应该是“打开” - 如果存在此键,则默认情况下将执行此键(即当用户双击您类型的文件时,将执行打开命令)。或者,您可以将“shell”键的默认值设置为您想要默认执行的动词。您可以选择将每个动词键的默认值设置为您希望在用户右键单击您类型的文件时出现在上下文菜单中的文本。在此文本中,可以使用“&”符号来指定下一个字符将被加下划线,这意味着用户可以按下对应于该字符的键来从上下文菜单中选择该命令。例如,对于“打开”键,您可以将“使用&Joe Blow的应用程序打开”作为默认值。然后,该文本将显示在该类型文件的上下文菜单中,并且用户可以按字母J启动Joe Blow的应用程序。

你唯一需要做的事情,就是在“open”(和随后的)键下创建另一个名为“command”的子键。命令键的默认值必须设置为表示执行该操作所需命令的字符串。例如,在“open”键下的命令键中,默认字符串可能是“C:\Program Files\JoeBlow\JoeBlow.exe”“%1”。请注意,路径\文件名周围的引号只有在路径或文件名中包含任何空格时才是必需的,但对于32位应用程序,它们绝对需要在%1周围。 %1 就像旧的MS-DOS批处理(.bat)文件中的%1一样。在这种情况下,%1将被替换为命令行上的第一个参数,这在本例中成为您的应用程序应打开的文件的文件名。因为您无法预先知道包含您应该打开的文件的路径或文件名是否包含空格,所以必须在%1周围放置引号。

您的应用程序还应包含其他必需的参数。例如,在“print”键下,“command”键的默认值可能是“C:\Program Files\JoeBlow\JoeBlow.exe”“%1”/print”,或者是“C:\Program Files\JoeBlow\JoeBlow.exe”/print“%1”。您可以自行决定如何处理应用程序中的命令行参数。

关于可替换的参数,如上所述的“%1”。显然,“%1”参数/可能/被替换为要打开的文件名。这并非总是如此,我还没有弄清楚Windows用来确定传递哪个参数的标准——短或长。可能是如果注册表中列出的可执行路径是长文件名,则Windows将使用长文件名替换%1以启动,但如果可执行路径是短文件名或可以解释为短文件名,则Windows将使用短文件名替换%1 。如果您想确保始终获取文件名,请改用“%L”。您可以使用大写L(如我所做)或小写字母,但我更喜欢使用大写字母,因为小写字母“l”看起来太像数字“1”。
此外,如果您的程序知道如何处理Shell项ID,则可以使用“%i”参数而不是长文件名来获取该参数。同样,大写或小写的“i”都很合适,但我发现大写字母“I”更难与小写字母“l”和数字“1”区分开来。如果您不知道Shell项ID是什么,那没关系。您可能永远不需要使用它们。
你终于完成了JoeBlowFile键。其余部分相对简单。您只需在HKEY_CLASSES_ROOT下创建另一个子键(如果尚不存在),并将其命名为文档类型的扩展名相同。以txtfile示例为例,名称将是“.txt”(带点号,但不带引号)。你的(Joe Blow的)可能是“.jbf”(用于Joe Blow文件)。此键的默认值现在必须设置为您创建的第一个键的名称,在我们使用的示例中为“JoeBlowFile”。
就是这样。您已经在注册表中完成了操作。请记住,您必须确保您的应用程序以与您在“shell”键下设置的命令一致的方式处理命令行。当您的应用程序启动时,Windows不会自动为您打开该文件...您必须自己打开它。
图形上看起来像这样:
HKEY_CLASSES_ROOT | +--.jbf = JoeBlowFile | +--JoeBlowFile = Joe Blow Data | +--DefaultIcon = C:\Program Files\JoeBlow\JoeBlow.exe, 2 | +--Shell | +--open = 使用Joe Blow的应用程序打开(&J) | | | +--command = "C:\Program Files\JoeBlow\JoeBlow.exe" "%1" | +--print | +--command = "C:\Program Files\JoeBlow\JoeBlow.exe" "%1" /print

如果您还不知道如何修改注册表,请在MSDN中查找以“Reg”开头的所有函数,包括RegOpenKeyEx、RegCreateKeyEx和RegSetValueEx。您也可以通过创建“.reg”文件并使用ShellExecuteEx()来调用“regedit.exe /s”来进行弱化方式的操作。(使用/s可以防止Regedit弹出一个消息框询问“您确定要将[name of file.reg]中的信息添加到注册表吗?”) REG文件的格式简单明了。以下是一个示例REG文件,用于添加上面提到的“JoeBlow”示例:

REGEDIT4
[HKEY_CLASSES_ROOT\.jbf] @="JoeBlowFile"
[HKEY_CLASSES_ROOT\JoeBlowFile] @="Joe Blow Data"
[HKEY_CLASSES_ROOT\JoeBlowFile\DefaultIcon] @="C:\\Program Files\\JoeBlow\\JoeBlow.exe, 2"
[HKEY_CLASSES_ROOT\JoeBlowFile\Shell]
[HKEY_CLASSES_ROOT\JoeBlowFile\Shell\open] @="使用 Joe Blow 的应用程序打开(&J)"
[HKEY_CLASSES_ROOT\JoeBlowFile\Shell\open\command] @="\"C:\\Program Files\\JoeBlow\\JoeBlow.exe\" \"%1\""
[HKEY_CLASSES_ROOT\JoeBlowFile\Shell\print] @="打印(&P)"
[HKEY_CLASSES_ROOT\JoeBlowFile\Shell\print\command] @="\"C:\\Program Files\\JoeBlow\\JoeBlow.exe \"%1\" /print"
确保在文件的第一行包含"REGEDIT4",否则它将无法工作。同时,确保在最后一行按下回车键,否则该行将不会被读取。总体而言,以这种方式将程序添加到注册表并不像听起来那么方便,因为如果您的应用程序安装在C:\Program Files\JoeBlow之外的任何位置,您将不得不修改您的REG文件。
上述说明针对使用C或C++直接编程Win32 API的用户。对于.NET上的C#来说,要简单得多。请参考Registry类,或者您甚至可以使用VS.NET中的部署项目以图形化方式完成大部分操作。

要向.NET程序集添加本地可访问资源,您需要一个资源脚本。资源脚本是一个纯文本文件,就像代码文件一样。实际上,它就是代码;声明性代码,由资源编译器rc.exe编译。以下是一个示例资源脚本:

#include <windows.h>

#define IDI_APP    100
#define IDI_FILE   200
#define ID_VERSION   1

IDI_APP  ICON "Resources\\Application.ico"
IDI_FILE ICON "Resources\\JowBlowFile.ico"

ID_VERSION VERSIONINFO
    FILEVERSION 1, 0, 19, 186     // change this to your version
    PRODUCTVERSION 1, 0, 19, 186  // change this to your version
    FILEOS VOS__WINDOWS32
    FILETYPE VFT_APP {
        BLOCK "StringFileInfo" {
            BLOCK "040904B0" { // 0x409 = U.S. English, 0x04B0 = dec 1200 = Unicode
                VALUE "CompanyName",      "Joe Blow, Inc.\0"
                VALUE "FileDescription",  "Joe Blow's App\0"
                VALUE "FileVersion",      "1.0.19.186\0" // change this to your version
                VALUE "InternalName",     "JoeBlow\0"
                VALUE "LegalCopyright",   "Copyright © 2008-2009 Joe Blow Incorporated\0"
                VALUE "OriginalFilename", "JoeBlow.exe\0"
                VALUE "ProductName",      "Joe Blow\0"
                VALUE "ProductVersion",   "1.0.19.189\0" // change this to your version
            }
        }
        BLOCK "VarFileInfo" {
            VALUE "Translation", 0x409 /*U.S. English*/, 1200 /*Unicode*/
        }
    }

这样做的最大缺点是,您必须手动将版本信息添加到资源脚本中(除非您想完全放弃版本信息)。在我的应用程序中,我添加了一个自定义构建步骤,运行我编写的程序直接更新可执行文件中的版本信息,以便我不必手动更新资源脚本中的版本号,但该程序不适合公开发布,而且超出了本文的范围。
现在,您需要调用资源编译器将此资源脚本构建为二进制资源文件。将此脚本保存为JoeBlow.rc,然后启动Visual Studio命令提示符。它在Visual Studio开始菜单文件夹的工具下。如果您没有安装Visual Studio,我相信您可以在SDK的一部分中获得rc.exe。Microsoft似乎也在这里提供最新版本。
一旦在VS cmd提示符下(或者在您的路径中有rc.exe),只需键入:
rc JoeBlow.rc

就是这么简单。假设我所包含的图标存在,上面的资源脚本应该可以在没有错误的情况下编译。这将在同一目录中创建一个名为JoeBlow.res的新文件。现在,假设您使用的是Visual Studio,您需要编辑项目属性以包括此资源文件。

这些说明适用于Visual Studio 2005或2008。我不记得如何在旧版本中执行此操作,甚至不确定是否可以,在2010年之前我也没有尝试过,但它可能相似。在解决方案资源管理器中右键单击项目,然后选择属性(或从主菜单栏中的“项目”菜单中选择属性)。在应用程序选项卡上,这是您应该看到的第一个选项卡,在底部是一个资源组框。在这里,您有两个选项:“图标和清单”或“资源文件”。选择后者选项。这将启用文本框,您可以输入(或浏览)您的新资源文件,JoeBlow.res。

最后,只需要构建您的项目,就可以在本机PE格式中嵌入图标,当浏览与您的应用程序关联的文件时,可通过shell访问。现在,如果您将HKEY_CLASSES_ROOT\JoeBlowFile\DefaultIcon的值设置为C:\Program Files\JoeBlow\JoeBlow.exe,1(使用基于0的索引号)或C:\Program Files\JoeBlow\JoeBlow.exe,-200(使用资源标识符的负数,如上所定义的IDI_FILE),则您的图标将显示在Explorer中的.jbf文件中。
要立即在安装后显示新图标,您可能需要刷新shell的图标缓存。我发现如何做到这一点的说明here。基本思路是将shell图标大小(位于HKEY_CURRENT_USER\Control Panel\Desktop\WindowMetrics)从当前值更改为其他值,然后再改回来,在每次更改后广播WM_SETTINGCHANGE消息。
祝你好运。如果您需要其他任何帮助,请告诉我。

嗨,谢谢您的回复,这让我更好地理解了它是如何保存在注册表中的。现在唯一的问题是尝试让图标正常工作。 - Patrick
你的图标有什么问题?你是如何提供这个图标的?你是将它放在一个单独的文件(.ico)中,还是试图将其编译到你的.exe或.dll中?请参考我的回答中关于在C#程序集中包含本地资源的说明。你需要创建一个资源脚本(.rc),并使用资源编译器(启动VS命令提示符,使用rc.exe)进行编译,然后在项目属性的应用程序选项卡中将生成的.res文件设置为应用程序的资源文件。稍后我会尽力提供更详细的说明。 - P Daddy
我已经在项目资源中包含了它,只是不知道它被捆绑在哪里,我猜想应该是在某个清单文件中,但我不确定。我有一个类将在应用程序加载时链接文件类型,只是我无法显示图标。 - Patrick
你是“将其包含在项目资源中”...是按照我描述的方式,还是按照正常的.NET方式? - P Daddy
在VisualStudio 2008中,选择项目 > <项目>属性 > 资源 > 图标 > <自定义图标.icn>。 - Patrick
不,那会添加CLI资源(.NET),而不是PE资源(native)。请查看我更新后的答案以获取详细信息。 - P Daddy

1

这个CodeProject文章包含一些演示如何通过代码进行文件关联的源代码。


我找到了这个http://www.mentalis.org/soft/class.qpx?id=5并将其用作模板,现在我只需要弄清楚如何让图标显示出来,并在应用程序的实例中加载文件(如果它已经在运行)。 - Patrick
@patrickst1 - 以这种方式运行应用程序的单个实例值得另外一个问题。Windows 的默认行为是加载应用程序的新副本,因此您需要进行一些洗牌。 - Jeremy McGee
很好,谢谢。所以除了图标外,我已经把所有东西都搞定了,你知道它在哪里吗?资源文件夹中唯一的文件是主应用程序图标(它在调试阶段不存在,但这是另一回事)。我需要知道我的自定义图标存储在哪里。 - Patrick
图标(通常)嵌入在您的可执行文件中。因此,您必须指定可执行文件的路径和名称以及图标的索引。如果有多个嵌入式图标,则使用索引来选择一个。如果只有一个,则为零。您可以尝试类似以下的操作。myProgramAssociationInfo.SetDefaultIcon(new ProgramIcon(new Uri(Assembly.GetEntryAssembly().CodeBase).AbsolutePath)) - Daniel Brückner

0

这里有一个关于如何使用命令行工具assocftype的讨论,以此来完成这个任务。你可以在程序部署(或应用程序运行时)期间调用命令行窗口来完成这个任务。


嗨,谢谢这个,让我对系统的工作原理有了更好的理解,但是我不确定如何在安装程序中运行shell命令。 - Patrick
@patrickst1 - 这也值得再问一下。请说明您正在使用哪个安装程序。 - Jeremy McGee
只是使用VisualStudio 2008创建的默认一个。 - Patrick
ftypeassoc用于分配关联,但似乎没有任何内容与图标相关。 - matt wilkie

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接