写在前面
C# Tips是博主开启的第一个项目,以C#为示例语言,旨在普及编程技巧、经典算法以及计算机视觉、图形学知识。
关联文件的意义
想必各位同学在双击打开非txt的文本文件时通常会遇到这样的困扰:
因为以.cue为后缀的文件并未与电脑中的任何程序关联,所以无法直接打开文件,选择一个能够打开文件的软件其实就是将cue文件与之进行关联。好处显而易见,就是凡是cue文件都可以通过指定软件直接双击打开。当然除此之外,文件如果还带有特定的图标,那么更能提升文件的逼格。
修改注册表
文件的关联信息存储在注册表(Win+R再输入regedit即可打开注册表)中,无论是修改默认程序还是指定程序,其本质都是修改了注册表信息。自定义文件关联信息通常存储在HKEY_CLASSES_ROOT结点下:
- 第一项为*,其含义为通用的关联方式,换句话说就是可以利用一个软件打开所有文件;
- 其他项则是特定后缀文件的关联方式。
通用关联
对于第一种情况,以Sublime Text软件(可以正常打开所有纯文本格式文件)为例,需要实现如下图所示的功能,即右键cue文件,然后点击Open With Sublime Text,利用Sublime Text打开cue文件。
具体修改注册表的方法为:在*结点下的shell结点中添加结点Sublime Text,然后修改默认项数据为Open With Sublime Text,然后在Sublime Text结点下添加结点command,修改默认项数据为"D:\Sublime Text\sublime_text.exe"<“Sublime Text程序所在路径”> “%1”。
特定关联
对于第二种情况,最普遍的需求是将自己编写的文件格式关联到自己编写的程序上。拿博主之前写过的一个三维重建系统MeasureMan举例,该系统的工程文件是以.msm为后缀的自定义文本文件,如下图所示msm文件与程序图标相同,且通过双击可以直接打开msm文件。
具体修改注册表的方法为:在HKEY_CLASSES_ROOT结点下添加.msm/Shell/Open/Command,然后在.msm结点中添加File Type项并填入数据msm File,最后修改Command结点的默认项数据为<“MeasureMan程序所在路径”> “%1”。
程序关联文件代码
.NET提供了Registry、RegistryKey等类可以很方便地对注册表进行操作,如下代码实现了在HKEY_CLASSES_ROOT结点下添加结点的过程,注意添加或修改注册表项必须先获得管理员权限:如果结点不存在直接添加,关联当前程序路径;如果结点存在但当前程序路径与已有路径不同,则替换程序路径。
try
{
RegistryKey mainkey = Registry.ClassesRoot.OpenSubKey(@".msm\Shell\Open\Command",false);
string value="\"" + Application.StartupPath + "\\MeasureMan.exe\"" + " \"%1\"";
if (mainkey == null)
{
RegistryKey key = Registry.ClassesRoot.CreateSubKey(".msm");
key.SetValue("File Type", "msm File");
key.CreateSubKey(@"Shell\Open\Command").SetValue("",value);
SHChangeNotify(HChangeNotifyEventID.SHCNE_ASSOCCHANGED, HChangeNotifyFlags.SHCNF_IDLIST | HChangeNotifyFlags.SHCNF_FLUSH, IntPtr.Zero, IntPtr.Zero);
}
else if (!value.Equals(mainkey.GetValue("").ToString()))
{
RegistryKey anotherKey = Registry.ClassesRoot.OpenSubKey(@".msm\Shell\Open\Command", true);
anotherKey.SetValue("", value);
SHChangeNotify(HChangeNotifyEventID.SHCNE_ASSOCCHANGED, HChangeNotifyFlags.SHCNF_IDLIST | HChangeNotifyFlags.SHCNF_FLUSH, IntPtr.Zero, IntPtr.Zero);
}
}
catch
{
MessageBox.Show("以管理员身份运行可将工程文件关联到此程序");
}
但在修改完注册表后一定要进行注册表的刷新,对应上面的SHChangeNotify方法,这不是一个.NET自带的方法,而是系统文件shell32.dll中的方法,引用方法的方式如下:
[DllImport("shell32.dll")]
static extern void SHChangeNotify(HChangeNotifyEventID wEventId,
HChangeNotifyFlags uFlags,
IntPtr dwItem1,
IntPtr dwItem2);
其中HChangeNotifyEventID和HChangeNotifyFlags都是枚举类型,其定义如下:
enum HChangeNotifyEventID
{
/// <summary>
/// All events have occurred.
/// </summary>
SHCNE_ALLEVENTS = 0x7FFFFFFF,
/// <summary>
/// A file type association has changed. <see cref="HChangeNotifyFlags.SHCNF_IDLIST"/>
/// must be specified in the <i>uFlags</i> parameter.
/// <i>dwItem1</i> and <i>dwItem2</i> are not used and must be <see langword="null"/>.
/// </summary>
SHCNE_ASSOCCHANGED = 0x08000000,
/// <summary>
/// The attributes of an item or folder have changed.
/// <see cref="HChangeNotifyFlags.SHCNF_IDLIST"/> or
/// <see cref="HChangeNotifyFlags.SHCNF_PATH"/> must be specified in <i>uFlags</i>.
/// <i>dwItem1</i> contains the item or folder that has changed.
/// <i>dwItem2</i> is not used and should be <see langword="null"/>.
/// </summary>
SHCNE_ATTRIBUTES = 0x00000800,
/// <summary>
/// A nonfolder item has been created.
/// <see cref="HChangeNotifyFlags.SHCNF_IDLIST"/> or
/// <see cref="HChangeNotifyFlags.SHCNF_PATH"/> must be specified in <i>uFlags</i>.
/// <i>dwItem1</i> contains the item that was created.
/// <i>dwItem2</i> is not used and should be <see langword="null"/>.
/// </summary>
SHCNE_CREATE = 0x00000002,
/// <summary>
/// A nonfolder item has been deleted.
/// <see cref="HChangeNotifyFlags.SHCNF_IDLIST"/> or
/// <see cref="HChangeNotifyFlags.SHCNF_PATH"/> must be specified in <i>uFlags</i>.
/// <i>dwItem1</i> contains the item that was deleted.
/// <i>dwItem2</i> is not used and should be <see langword="null"/>.
/// </summary>
SHCNE_DELETE = 0x00000004,
/// <summary>
/// A drive has been added.
/// <see cref="HChangeNotifyFlags.SHCNF_IDLIST"/> or
/// <see cref="HChangeNotifyFlags.SHCNF_PATH"/> must be specified in <i>uFlags</i>.
/// <i>dwItem1</i> contains the root of the drive that was added.
/// <i>dwItem2</i> is not used and should be <see langword="null"/>.
/// </summary>
SHCNE_DRIVEADD = 0x00000100,
/// <summary>
/// A drive has been added and the Shell should create a new window for the drive.
/// <see cref="HChangeNotifyFlags.SHCNF_IDLIST"/> or
/// <see cref="HChangeNotifyFlags.SHCNF_PATH"/> must be specified in <i>uFlags</i>.
/// <i>dwItem1</i> contains the root of the drive that was added.
/// <i>dwItem2</i> is not used and should be <see langword="null"/>.
/// </summary>
SHCNE_DRIVEADDGUI = 0x00010000,
/// <summary>
/// A drive has been removed. <see cref="HChangeNotifyFlags.SHCNF_IDLIST"/> or
/// <see cref="HChangeNotifyFlags.SHCNF_PATH"/> must be specified in <i>uFlags</i>.
/// <i>dwItem1</i> contains the root of the drive that was removed.
/// <i>dwItem2</i> is not used and should be <see langword="null"/>.
/// </summary>
SHCNE_DRIVEREMOVED = 0x00000080,
/// <summary>
/// Not currently used.
/// </summary>
SHCNE_EXTENDED_EVENT = 0x04000000,
/// <summary>
/// The amount of free space on a drive has changed.
/// <see cref="HChangeNotifyFlags.SHCNF_IDLIST"/> or
/// <see cref="HChangeNotifyFlags.SHCNF_PATH"/> must be specified in <i>uFlags</i>.
/// <i>dwItem1</i> contains the root of the drive on which the free space changed.
/// <i>dwItem2</i> is not used and should be <see langword="null"/>.
/// </summary>
SHCNE_FREESPACE = 0x00040000,
/// <summary>
/// Storage media has been inserted into a drive.
/// <see cref="HChangeNotifyFlags.SHCNF_IDLIST"/> or
/// <see cref="HChangeNotifyFlags.SHCNF_PATH"/> must be specified in <i>uFlags</i>.
/// <i>dwItem1</i> contains the root of the drive that contains the new media.
/// <i>dwItem2</i> is not used and should be <see langword="null"/>.
/// </summary>
SHCNE_MEDIAINSERTED = 0x00000020,
/// <summary>
/// Storage media has been removed from a drive.
/// <see cref="HChangeNotifyFlags.SHCNF_IDLIST"/> or
/// <see cref="HChangeNotifyFlags.SHCNF_PATH"/> must be specified in <i>uFlags</i>.
/// <i>dwItem1</i> contains the root of the drive from which the media was removed.
/// <i>dwItem2</i> is not used and should be <see langword="null"/>.
/// </summary>
SHCNE_MEDIAREMOVED = 0x00000040,
/// <summary>
/// A folder has been created. <see cref="HChangeNotifyFlags.SHCNF_IDLIST"/>
/// or <see cref="HChangeNotifyFlags.SHCNF_PATH"/> must be specified in <i>uFlags</i>.
/// <i>dwItem1</i> contains the folder that was created.
/// <i>dwItem2</i> is not used and should be <see langword="null"/>.
/// </summary>
SHCNE_MKDIR = 0x00000008,
/// <summary>
/// A folder on the local computer is being shared via the network.
/// <see cref="HChangeNotifyFlags.SHCNF_IDLIST"/> or
/// <see cref="HChangeNotifyFlags.SHCNF_PATH"/> must be specified in <i>uFlags</i>.
/// <i>dwItem1</i> contains the folder that is being shared.
/// <i>dwItem2</i> is not used and should be <see langword="null"/>.
/// </summary>
SHCNE_NETSHARE = 0x00000200,
/// <summary>
/// A folder on the local computer is no longer being shared via the network.
/// <see cref="HChangeNotifyFlags.SHCNF_IDLIST"/> or
/// <see cref="HChangeNotifyFlags.SHCNF_PATH"/> must be specified in <i>uFlags</i>.
/// <i>dwItem1</i> contains the folder that is no longer being shared.
/// <i>dwItem2</i> is not used and should be <see langword="null"/>.
/// </summary>
SHCNE_NETUNSHARE = 0x00000400,
/// <summary>
/// The name of a folder has changed.
/// <see cref="HChangeNotifyFlags.SHCNF_IDLIST"/> or
/// <see cref="HChangeNotifyFlags.SHCNF_PATH"/> must be specified in <i>uFlags</i>.
/// <i>dwItem1</i> contains the previous pointer to an item identifier list (PIDL) or name of the folder.
/// <i>dwItem2</i> contains the new PIDL or name of the folder.
/// </summary>
SHCNE_RENAMEFOLDER = 0x00020000,
/// <summary>
/// The name of a nonfolder item has changed.
/// <see cref="HChangeNotifyFlags.SHCNF_IDLIST"/> or
/// <see cref="HChangeNotifyFlags.SHCNF_PATH"/> must be specified in <i>uFlags</i>.
/// <i>dwItem1</i> contains the previous PIDL or name of the item.
/// <i>dwItem2</i> contains the new PIDL or name of the item.
/// </summary>
SHCNE_RENAMEITEM = 0x00000001,
/// <summary>
/// A folder has been removed.
/// <see cref="HChangeNotifyFlags.SHCNF_IDLIST"/> or
/// <see cref="HChangeNotifyFlags.SHCNF_PATH"/> must be specified in <i>uFlags</i>.
/// <i>dwItem1</i> contains the folder that was removed.
/// <i>dwItem2</i> is not used and should be <see langword="null"/>.
/// </summary>
SHCNE_RMDIR = 0x00000010,
/// <summary>
/// The computer has disconnected from a server.
/// <see cref="HChangeNotifyFlags.SHCNF_IDLIST"/> or
/// <see cref="HChangeNotifyFlags.SHCNF_PATH"/> must be specified in <i>uFlags</i>.
/// <i>dwItem1</i> contains the server from which the computer was disconnected.
/// <i>dwItem2</i> is not used and should be <see langword="null"/>.
/// </summary>
SHCNE_SERVERDISCONNECT = 0x00004000,
/// <summary>
/// The contents of an existing folder have changed,
/// but the folder still exists and has not been renamed.
/// <see cref="HChangeNotifyFlags.SHCNF_IDLIST"/> or
/// <see cref="HChangeNotifyFlags.SHCNF_PATH"/> must be specified in <i>uFlags</i>.
/// <i>dwItem1</i> contains the folder that has changed.
/// <i>dwItem2</i> is not used and should be <see langword="null"/>.
/// If a folder has been created, deleted, or renamed, use SHCNE_MKDIR, SHCNE_RMDIR, or
/// SHCNE_RENAMEFOLDER, respectively, instead.
/// </summary>
SHCNE_UPDATEDIR = 0x00001000,
/// <summary>
/// An image in the system image list has changed.
/// <see cref="HChangeNotifyFlags.SHCNF_DWORD"/> must be specified in <i>uFlags</i>.
/// </summary>
SHCNE_UPDATEIMAGE = 0x00008000,
}
enum HChangeNotifyFlags
{
/// <summary>
/// The <i>dwItem1</i> and <i>dwItem2</i> parameters are DWORD values.
/// </summary>
SHCNF_DWORD = 0x0003,
/// <summary>
/// <i>dwItem1</i> and <i>dwItem2</i> are the addresses of ITEMIDLIST structures that
/// represent the item(s) affected by the change.
/// Each ITEMIDLIST must be relative to the desktop folder.
/// </summary>
SHCNF_IDLIST = 0x0000,
/// <summary>
/// <i>dwItem1</i> and <i>dwItem2</i> are the addresses of null-terminated strings of
/// maximum length MAX_PATH that contain the full path names
/// of the items affected by the change.
/// </summary>
SHCNF_PATHA = 0x0001,
/// <summary>
/// <i>dwItem1</i> and <i>dwItem2</i> are the addresses of null-terminated strings of
/// maximum length MAX_PATH that contain the full path names
/// of the items affected by the change.
/// </summary>
SHCNF_PATHW = 0x0005,
/// <summary>
/// <i>dwItem1</i> and <i>dwItem2</i> are the addresses of null-terminated strings that
/// represent the friendly names of the printer(s) affected by the change.
/// </summary>
SHCNF_PRINTERA = 0x0002,
/// <summary>
/// <i>dwItem1</i> and <i>dwItem2</i> are the addresses of null-terminated strings that
/// represent the friendly names of the printer(s) affected by the change.
/// </summary>
SHCNF_PRINTERW = 0x0006,
/// <summary>
/// The function should not return until the notification
/// has been delivered to all affected components.
/// As this flag modifies other data-type flags, it cannot by used by itself.
/// </summary>
SHCNF_FLUSH = 0x1000,
/// <summary>
/// The function should begin delivering notifications to all affected components
/// but should return as soon as the notification process has begun.
/// As this flag modifies other data-type flags, it cannot by used by itself.
/// </summary>
SHCNF_FLUSHNOWAIT = 0x2000
}
最后,为了能双击打开msm文件,还需要修改Application.Run方法,Environment.GetCommandLineArgs可以获得当前程序运行的参数,至少有1个参数,就是程序所在路径,第二个参数可选,为msm文件路径,在cmd中操作同理。
string[] args = Environment.GetCommandLineArgs();
if (args.Length == 2)
Application.Run(new MeasureMan(args[1]));
else
Application.Run(new MeasureMan(null));