[Effective WX] win7上的wxFileDialog的默认路径问题分析与解决
2012-08-15 09:54
525 查看
wxFileDialog是wxWidget库的一个类(关于wxWidget库,可以参考 <a href="http://www.wxwidgets.org/">http://www.wxwidgets.org/</a>)。这个类用来操作本地文件的打开与保存。在不同的平台上,wxFileDialog封装平台原生的API来实现,譬如win32平台,封装了GetOpenFileName和GetSaveFileName这2个API函数。我们只讨论win32平台。既然只是简单的封装这2个函数,那么wx在调用API之前必然要初始化一个结构体:OPENFILENAME。这个结构体有2个重要的变量:lpstrInitialDir和lpstrFile,前者是默认打开的目录路径,后者是默认打开的文件名。
1. 问题提出
假如我们要调用wxFileDialog来允许用户选择一个本地文件打开,通常会写下面的代码:
如果我们希望打开时,定位到一个固定目录,那么我们会初始化$defaultDir。这一切都运行的不错,在winXP上。然而在win7上,运行结果却不是我们想要的(定位的目录,并不是我们期望的)。同样的代码,在不同的windows系统上运行的结果不同,主要是win7 OS对GetOpenFileName的实现算法进行了修改,相比于以前的系统。
2. 问题分析
前面提到win7上运行结果不同,是因为win7对API函数GetOpenFileName实现进行了更改,特别是结构体OPNEFILENAME里的成员变量lpstrInitialDir的处理。以下来自于MSDN的说明 (http://msdn.microsoft.com/en-us/library/windows/desktop/ms646839(v=vs.85).aspx):
lpstrInitialDir
Type: LPCTSTR
The initial directory. The algorithm for selecting the initial directory varies on different platforms.
<em>Windows 7:</em>
If lpstrInitialDir has the same value as was passed the first time the application used an Open or Save As dialog box, the path most recently selected by the user is used as the initial directory.
Otherwise, if lpstrFile contains a path, that path is the initial directory.
Otherwise, if lpstrInitialDir is not NULL, it specifies the initial directory.
If lpstrInitialDir is NULL and the current directory contains any files of the specified filter types, the initial directory is the current directory.
Otherwise, the initial directory is the personal files directory of the current user.
Otherwise, the initial directory is the Desktop folder.
<em>Windows 2000/XP/Vista:</em>
If lpstrFile contains a path, that path is the initial directory.
Otherwise, lpstrInitialDir specifies the initial directory.
Otherwise, if the application has used an Open or Save As dialog box in the past, the path most recently used is selected as the initial directory. However, if an application is not run for a long time, its saved selected path is discarded.
If lpstrInitialDir is NULL and the current directory contains any files of the specified filter types, the initial directory is the current directory.
Otherwise, the initial directory is the personal files directory of the current user.
Otherwise, the initial directory is the Desktop folder.
其实在win7上,我们期望得到item3的结果,也就是我们设置了默认路径,就应该定位到那个路径,除非那个路径无效。然而第一项提到“如果这个默认路径与程序运行时<strong>第一次</strong>传给open/save as dialog的<strong>有效的</strong>的路径<strong>相同</strong>,那么win7 api就会采用<strong>相同名字的程序</strong>用户最近选择的路径(也就是上次成功打开/保存的那个路径)”。唉,这个也太诡异了。请看来自于微软一位Program
Manager的解释:
Historically lpstrInitialDir would override "most recently used" folder. In general, this was not a good or expected user experience because the dialog did not remember the last place where the user saved, which is usually, the place where they want
to save next time. The new API (IFileDialog) allows applications to ask for a default folder (IFileDialog::SetDefaultFolder) which is prefferred for most cases, or explicitly set the folder (IFileDialog::SetFolder) which should be sparingly used in select
cases.
In Windows 7 we updated the legacy API beahvior (GetOpenFileName) to match the preferred behavior since “the file dialog forgot where I saved” was was a significant source of negative feedback in Windows Vista.
Let me know if you have any further questions.
Thanks much,
David Washington
Program Manager
Microsoft
请注意:
a) 第一次是指,不管该程序被运行多少次,只要第一次设置lpstrInitialDir为一个有效的路径,这个路径就会被记住,然后与后面传进来的lpstrInitialDir进行比较。哪怕程序实际上并没有打开那里面的任何文件,或者保存任何文件到那路径。
b) 相同名字的程序,是指程序名字相同,不关心程序的路径。
3. 问题解决
GetOpenFileName和GetSaveFileName这2个API函数被实现在comdlg32.dll中。关于它有一个专门的注册表项: HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\ComDlg32\
win7相比于winXP多了一个重要的folder key: FirstFolder。这个folder key就是用来存储前面提到的每个应用程序首次传给lpstrInitialDir的有效路径值。在这个folder key中有很多以阿拉伯数字命名的value,value对应的数值为二进制格式,而且是双字节存储。举个例子:
数值17里面存储着某个应用程序全路径与它的第一个有效的lpstrInitialDir值的双字节ASCIC码。例如:"E"对应45 00,":"对应3A 00。以低位字节打头。
一个案例:
1. 初始化lpstrInitialDir到一个有效路径A,然后打开路径,cancel;
2. 初始化lpstrInitialDir为空,然后保存一个文件到另一个目录B;
3. go to 1。将会发现窗口定位到了目录B。
前面提到,在win7上我们期待的是item3的效果,那么就需要越过item1,不让win7 API定位窗口默认目录到上次成功访问过的路径。我们发现在打开open/save之前,如果将这个程序对应的FirstFolder中对应的value删除,win7 api将会跳过item1,因为它没有发现第一次初始化值,也就不会用上次成功访问过的路径。
所以方案就是
a) 定位到folder key (HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\ComDlg32\FirstFolder)
b) loop所有的values,获取它们的数值,将双字节的ASCII码转换成字符串
c) 与当前程序路径进行比对,如果match,那就将这个value删除。
但请注意,即使在打开文件打开对话框之前,将某个值删除了,打开之后,win7 api仍然会产生一个新的value。因为这时传给lpstrInitialDir是一个有效的路径,按照规则,该程序在FirstFolder里面没有了value,那么它就得再创建一个。但是没关系,下次再打开时,那个值仍然会被删除(有没点像打地鼠游戏?:))。
一下是程序片段,如果发现传进来的lpstrInitialDir不空:
1. 问题提出
假如我们要调用wxFileDialog来允许用户选择一个本地文件打开,通常会写下面的代码:
wxFileDialog fileOpenDlg($parentWin, $title, $defaultDir, $defaultFile, $filterStr, wxOPEN); fileOpenDlg.ShowModal();
如果我们希望打开时,定位到一个固定目录,那么我们会初始化$defaultDir。这一切都运行的不错,在winXP上。然而在win7上,运行结果却不是我们想要的(定位的目录,并不是我们期望的)。同样的代码,在不同的windows系统上运行的结果不同,主要是win7 OS对GetOpenFileName的实现算法进行了修改,相比于以前的系统。
2. 问题分析
前面提到win7上运行结果不同,是因为win7对API函数GetOpenFileName实现进行了更改,特别是结构体OPNEFILENAME里的成员变量lpstrInitialDir的处理。以下来自于MSDN的说明 (http://msdn.microsoft.com/en-us/library/windows/desktop/ms646839(v=vs.85).aspx):
lpstrInitialDir
Type: LPCTSTR
The initial directory. The algorithm for selecting the initial directory varies on different platforms.
<em>Windows 7:</em>
If lpstrInitialDir has the same value as was passed the first time the application used an Open or Save As dialog box, the path most recently selected by the user is used as the initial directory.
Otherwise, if lpstrFile contains a path, that path is the initial directory.
Otherwise, if lpstrInitialDir is not NULL, it specifies the initial directory.
If lpstrInitialDir is NULL and the current directory contains any files of the specified filter types, the initial directory is the current directory.
Otherwise, the initial directory is the personal files directory of the current user.
Otherwise, the initial directory is the Desktop folder.
<em>Windows 2000/XP/Vista:</em>
If lpstrFile contains a path, that path is the initial directory.
Otherwise, lpstrInitialDir specifies the initial directory.
Otherwise, if the application has used an Open or Save As dialog box in the past, the path most recently used is selected as the initial directory. However, if an application is not run for a long time, its saved selected path is discarded.
If lpstrInitialDir is NULL and the current directory contains any files of the specified filter types, the initial directory is the current directory.
Otherwise, the initial directory is the personal files directory of the current user.
Otherwise, the initial directory is the Desktop folder.
其实在win7上,我们期望得到item3的结果,也就是我们设置了默认路径,就应该定位到那个路径,除非那个路径无效。然而第一项提到“如果这个默认路径与程序运行时<strong>第一次</strong>传给open/save as dialog的<strong>有效的</strong>的路径<strong>相同</strong>,那么win7 api就会采用<strong>相同名字的程序</strong>用户最近选择的路径(也就是上次成功打开/保存的那个路径)”。唉,这个也太诡异了。请看来自于微软一位Program
Manager的解释:
Historically lpstrInitialDir would override "most recently used" folder. In general, this was not a good or expected user experience because the dialog did not remember the last place where the user saved, which is usually, the place where they want
to save next time. The new API (IFileDialog) allows applications to ask for a default folder (IFileDialog::SetDefaultFolder) which is prefferred for most cases, or explicitly set the folder (IFileDialog::SetFolder) which should be sparingly used in select
cases.
In Windows 7 we updated the legacy API beahvior (GetOpenFileName) to match the preferred behavior since “the file dialog forgot where I saved” was was a significant source of negative feedback in Windows Vista.
Let me know if you have any further questions.
Thanks much,
David Washington
Program Manager
Microsoft
请注意:
a) 第一次是指,不管该程序被运行多少次,只要第一次设置lpstrInitialDir为一个有效的路径,这个路径就会被记住,然后与后面传进来的lpstrInitialDir进行比较。哪怕程序实际上并没有打开那里面的任何文件,或者保存任何文件到那路径。
b) 相同名字的程序,是指程序名字相同,不关心程序的路径。
3. 问题解决
GetOpenFileName和GetSaveFileName这2个API函数被实现在comdlg32.dll中。关于它有一个专门的注册表项: HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\ComDlg32\
win7相比于winXP多了一个重要的folder key: FirstFolder。这个folder key就是用来存储前面提到的每个应用程序首次传给lpstrInitialDir的有效路径值。在这个folder key中有很多以阿拉伯数字命名的value,value对应的数值为二进制格式,而且是双字节存储。举个例子:
数值17里面存储着某个应用程序全路径与它的第一个有效的lpstrInitialDir值的双字节ASCIC码。例如:"E"对应45 00,":"对应3A 00。以低位字节打头。
一个案例:
1. 初始化lpstrInitialDir到一个有效路径A,然后打开路径,cancel;
2. 初始化lpstrInitialDir为空,然后保存一个文件到另一个目录B;
3. go to 1。将会发现窗口定位到了目录B。
前面提到,在win7上我们期待的是item3的效果,那么就需要越过item1,不让win7 API定位窗口默认目录到上次成功访问过的路径。我们发现在打开open/save之前,如果将这个程序对应的FirstFolder中对应的value删除,win7 api将会跳过item1,因为它没有发现第一次初始化值,也就不会用上次成功访问过的路径。
所以方案就是
a) 定位到folder key (HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\ComDlg32\FirstFolder)
b) loop所有的values,获取它们的数值,将双字节的ASCII码转换成字符串
c) 与当前程序路径进行比对,如果match,那就将这个value删除。
但请注意,即使在打开文件打开对话框之前,将某个值删除了,打开之后,win7 api仍然会产生一个新的value。因为这时传给lpstrInitialDir是一个有效的路径,按照规则,该程序在FirstFolder里面没有了value,那么它就得再创建一个。但是没关系,下次再打开时,那个值仍然会被删除(有没点像打地鼠游戏?:))。
一下是程序片段,如果发现传进来的lpstrInitialDir不空:
void RemoveFirstFolderKeyForProgram() { wxRegKey mainKey(wxRegKey::HKCU, "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\ComDlg32\\FirstFolder"); if (!mainKey.Exists()) return; if (!mainKey.Open()) return; wxString progPath = GetExecutablePath(); long index = 0; wxString name; bool bCont = mainKey.GetFirstValue(name, index); while (bCont) { if (mainKey.GetValueType(name) == wxRegKey::Type_Binary) { wxMemoryBuffer valStr; if (mainKey.QueryValue(name, valStr)) { // the value data is double-bytes wxString val = ConvertBinDataToString((const char*)valStr.GetData(), valStr.GetDataLen(), 2); if (val.StartsWith(_T(progPath.c_str()))) mainKey.DeleteValue(name); } } bCont = mainKey.GetNextValue(name, index); } mainKey.Close(); }
相关文章推荐
- c# 疑难(一)之打开“OpenFileDialog”文本对话框后默认路径改变问题和解决方法
- C# 关于使用OpenFileDialog 默认路径被重置的问题。
- C#中打开OpenFileDialog会改变默认路径的问题
- OpenFileDialog在win7或vista开发环境中生成的SetUp程序中无法打开的问题解决
- file控件上传文件,解决路径出现C:\fakepath的问题
- VC新建目录CreateDirectory、得到exe存放路径GetModuleFileName、获取当前目录GetCurrentDirectory、CFileDialog设置打开默认路径
- Win7中Javascript读取File框中文件路径变更为C:\Fakepath的解决方法
- 关于include标签导致js路径找不到的问题分析及解决
- 【COCOS2DX-LUA 脚本开发之十三】解决lua项目编译Android出现get data from file failed、Cocos2dxActivity cannot be 路径等问题
- VC6 在win7下无法兼容office2010问题解决 FileTool.dll
- Android Studio 配置模拟器AVD存放路径(默认在c盘,解决c盘空间不够问题)
- RouteTask执行最短路径计算时“无法完成 操作”问题分析解决
- 解决center7默认shell不显示完整路径问题
- win7 64位系统web项目导出excel问题分析及解决方法汇总
- 路径不对 导致FileNotFoundError: [WinError 2] 系统找不到指定的文件, 问题解决办法
- 在Mybatis-spring中由于默认Autowired导致不能配置多个数据源的问题分析及解决
- 关于GDAL180中文路径不能打开的问题分析与解决
- jsp中base标签解决jetty和tomcat默认访问路径不同的问题浅谈
- OpenFileDialog获取文件名和文件路径问题
- linux中PHP dirname(__FILE__)路径问题解决