1. 问题

好像很少人会遇到这种需求。假设有一个文件夹,用户有几乎所有权限,但没有删除的权限,如下图所示:

这时候使用SaveFileDialog在这个文件夹里创建文件居然会报如下错误:

这哪里是网络位置了,我又哪里去找个管理员?更奇怪的是,虽然报错了,但文件还是会创建出来,不过这是个空文件。不仅WPF,普通的记事本也会有这个问题,SaveFileDialog会创建一个空文件,记事本则没有被保存。具体可以看以下GIF:

2. 问题原因

其实当SaveFileDialog关闭前,对话框会创建一个测试文件,用于检查文件名、文件权限等,然后又删除它。所以如果有文件的创建权限,而没有文件的删除权限,在创建测试文件后就没办法删除这个测试文件,这时候就会报错,而测试文件留了下来。

有没有发现SaveFileDialog中有一个属性Options?

//
// 摘要:
// 获取 Win32 通用文件对话框标志,文件对话框使用这些标志来进行初始化。
//
// 返回结果:
// 一个包含 Win32 通用文件对话框标志的 System.Int32,文件对话框使用这些标志来进行初始化。
protected int Options { get; }

本来应该可以设置一个NOTESTFILECREATE的标志位,但WPF中这个属性是只读的,所以WPF的SaveFileDialog肯定会创建测试文件。

3. 解决方案

SaveFileDialog本身只是Win32 API的封装,我们可以参考SaveFileDialog的源码,伪装一个调用方法差不多的MySaveFileDialog,然后自己封装GetSaveFileName这个API。代码大致如下:

internal class FOS
{
public const int OVERWRITEPROMPT = 0x00000002;
public const int STRICTFILETYPES = 0x00000004;
public const int NOCHANGEDIR = 0x00000008;
public const int PICKFOLDERS = 0x00000020;
public const int FORCEFILESYSTEM = 0x00000040;
public const int ALLNONSTORAGEITEMS = 0x00000080;
public const int NOVALIDATE = 0x00000100;
public const int ALLOWMULTISELECT = 0x00000200;
public const int PATHMUSTEXIST = 0x00000800;
public const int FILEMUSTEXIST = 0x00001000;
public const int CREATEPROMPT = 0x00002000;
public const int SHAREAWARE = 0x00004000;
public const int NOREADONLYRETURN = 0x00008000;
public const int NOTESTFILECREATE = 0x00010000;
public const int HIDEMRUPLACES = 0x00020000;
public const int HIDEPINNEDPLACES = 0x00040000;
public const int NODEREFERENCELINKS = 0x00100000;
public const int DONTADDTORECENT = 0x02000000;
public const int FORCESHOWHIDDEN = 0x10000000;
public const int DEFAULTNOMINIMODE = 0x20000000;
public const int FORCEPREVIEWPANEON = 0x40000000;
} [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
public class OpenFileName
{
internal int structSize = 0;
internal IntPtr hwndOwner = IntPtr.Zero;
internal IntPtr hInstance = IntPtr.Zero;
internal string filter = null;
internal string custFilter = null;
internal int custFilterMax = 0;
internal int filterIndex = 0;
internal string file = null;
internal int maxFile = 0;
internal string fileTitle = null;
internal int maxFileTitle = 0;
internal string initialDir = null;
internal string title = null;
internal int flags = 0;
internal short fileOffset = 0;
internal short fileExtMax = 0;
internal string defExt = null;
internal int custData = 0;
internal IntPtr pHook = IntPtr.Zero;
internal string template = null;
} public class LibWrap
{
// Declare a managed prototype for the unmanaged function.
[DllImport("Comdlg32.dll", SetLastError = true, ThrowOnUnmappableChar = true, CharSet = CharSet.Auto)]
public static extern bool GetSaveFileName([In, Out] OpenFileName ofn);
} public bool? ShowDialog()
{
var openFileName = new OpenFileName();
Window window = Application.Current.Windows.OfType<Window>().Where(w => w.IsActive).FirstOrDefault();
if (window != null)
{
var wih = new WindowInteropHelper(window);
IntPtr hWnd = wih.Handle;
openFileName.hwndOwner = hWnd;
} openFileName.structSize = Marshal.SizeOf(openFileName);
openFileName.filter = MakeFilterString(Filter);
openFileName.filterIndex = FilterIndex;
openFileName.fileTitle = new string(new char[64]);
openFileName.maxFileTitle = openFileName.fileTitle.Length;
openFileName.initialDir = InitialDirectory;
openFileName.title = Title;
openFileName.defExt = DefaultExt;
openFileName.structSize = Marshal.SizeOf(openFileName);
openFileName.flags |= FOS.NOTESTFILECREATE | FOS.OVERWRITEPROMPT;
if (RestoreDirectory)
openFileName.flags |= FOS.NOCHANGEDIR; // lpstrFile
// Pointer to a buffer used to store filenames. When initializing the
// dialog, this name is used as an initial value in the File Name edit
// control. When files are selected and the function returns, the buffer
// contains the full path to every file selected.
char[] chars = new char[FILEBUFSIZE]; for (int i = 0; i < FileName.Length; i++)
{
chars[i] = FileName[i];
}
openFileName.file = new string(chars);
// nMaxFile
// Size of the lpstrFile buffer in number of Unicode characters.
openFileName.maxFile = FILEBUFSIZE; if (LibWrap.GetSaveFileName(openFileName))
{
FileName = openFileName.file;
return true;
}
return false;
} /// <summary>
/// Converts the given filter string to the format required in an OPENFILENAME_I
/// structure.
/// </summary>
private static string MakeFilterString(string s, bool dereferenceLinks = true)
{
if (string.IsNullOrEmpty(s))
{
// Workaround for VSWhidbey bug #95338 (carried over from Microsoft implementation)
// Apparently, when filter is null, the common dialogs in Windows XP will not dereference
// links properly. The work around is to provide a default filter; " |*.*" is used to
// avoid localization issues from description text.
//
// This behavior is now documented in MSDN on the OPENFILENAME structure, so I don't
// expect it to change anytime soon.
if (dereferenceLinks && System.Environment.OSVersion.Version.Major >= 5)
{
s = " |*.*";
}
else
{
// Even if we don't need the bug workaround, change empty
// strings into null strings.
return null;
}
} StringBuilder nullSeparatedFilter = new StringBuilder(s); // Replace the vertical bar with a null to conform to the Windows
// filter string format requirements
nullSeparatedFilter.Replace('|', '\0'); // Append two nulls at the end
nullSeparatedFilter.Append('\0');
nullSeparatedFilter.Append('\0'); // Return the results as a string.
return nullSeparatedFilter.ToString();
}

注意其中的这句:

openFileName.flags |= FOS.NOTESTFILECREATE | FOS.OVERWRITEPROMPT;

因为我的需求就是不创建TestFile,所以我直接这么写而不是提供可选项。一个更好的方法是给WPF提ISSUE,我已经这么做了:

Make SaveFileDialog support NOTESTFILECREATE.

但看来我等不到有人处理的这天,如果再有这种需求,还是将就着用我的这个自创的SaveFileDialog吧:

CustomSaveFileDialog

4. 参考

Common Item Dialog (Windows) Microsoft Docs

GetSaveFileNameA function (commdlg.h) - Win32 apps Microsoft Docs

OPENFILENAMEW (commdlg.h) - Win32 apps Microsoft Docs

[WPF]为什么使用SaveFileDialog创建文件需要删除权限?的更多相关文章

  1. java使用io创建文件与删除文件的工具类

    java中对于文件的操作,是再常见不过了.以下代码是自己代码中所用到的工具类,仅供参考. import java.io.File; import java.io.IOException; /** * ...

  2. Linux常用命令,查看树形结构、删除目录(文件夹)、创建文件、删除文件或目录、复制文件或目录(文件夹)、移动、查看文件内容、权限操作

    5.查看树结构(tree) 通常情况下系统未安装该命令,需要yum install -y tree安装 直接使⽤tree显示深度太多,⼀般会使⽤ -L选项⼿⼯设定⽬录深度 格式:tree -L n [ ...

  3. PHP调用Linux的命令行执行文件压缩命令&&创建文件夹修改权限

    一开始,我和普通青年一样,想到用PHP内置的 ZipArchive纠结的是环境上没安装zip扩展,想采用用PHP调用Linux的命令行 ,执行压缩命令,感兴趣的朋友可以了解下,希望本文对你有所帮助 前 ...

  4. ubuntu下无法在目录下创建文件夹,权限不足解决办法

    问题详情:偶然在根目录创建文件夹的时候,突然显示错误,当时很惊讶,以前没遇见过这样的问题.当时界面是这样的. 用了一个 cd / 命令从用户磁盘跳到了根目录 使用 mkdir 命令准备创建一个文件夹, ...

  5. WPF使用Microsoft.VisualBasic创建单例模式引起的权限降低问题

    在进行WPF开发时,总是在找更加优雅去写单例模式的代码. 很多人都喜欢用Mutex,一个App.cs下很多的Mutex,我也喜欢用. 看完<WPF编程宝典>的第七章Applicaton类后 ...

  6. php创建文件夹后设置文件夹权限(转)

    原文链接:http://www.phpstudy.net/b.php/69873.html PHP mkdir()无写权限的问题解决方法 使用mkdir创建文件夹时,发现这个函数有两个参数,第二个参数 ...

  7. centos中,tomcat项目创建文件的权限

    参考文章:https://cloud.tencent.com/info/5f02caa932fd6dbfc46a3bb01af135e0.html 我们在centos中输入umask,会看到输出002 ...

  8. mac环境下java项目无创建文件的权限

    1.问题: 先抛问题,由于刚刚换用mac环境,之前windows上开发的代码调试完毕,还未上线.之后上线部署之前,tl直连测试本地环境(mac)环境,功能无法使用,显示java.io.IOExcept ...

  9. umask计算创建文件、目录的默认权限

    很多人以为 创建文件默认权限就是 666-umask=创建文件的默认权限 创建目录的默认权限就是 777-umask=创建目录的默认权限   这种计算其实是不严谨的 为什么我们创建的文件的权限是 64 ...

随机推荐

  1. 蚂蚁金服开源 | 可视化图形语法G2 3.3 琢磨

    G2 是蚂蚁金服数据可视化解决方案 AntV 的一个子产品,是一套数据驱动的.高交互的可视化图形语法. 经过两个多月密锣紧鼓的开发,400+次提交,G2 3.3版本今天终于和大家见面了.自上次3.2版 ...

  2. .Net 特性分析与妙用

    一.特性是什么 1.想象很多小伙伴们都看过在一个类上方.或者在控制器见过类似的东东,加上之后就可以标识这个类或者方法就具备了某些特点 ,那我们就进入它的内心一探究竟吧. 2.我们进入某个特性之后,可以 ...

  3. .Net vs .Net Core,我改如何选择?看这一篇文章就够了

    前言 .Net目前支持构建服务器端应用程序的两种实现主要有两种,.NET Framework和.NET Core.两者共享许多相同的组件,并且您可以在两者之间共享代码.但是,两者之间存在根本差异,在我 ...

  4. 研究开源源码之Myrmec

    好久没写博客了,自己也弄不清是懒了还是忙了.毕竟白天需要工作,晚上有时候看看资料,有时候陪家人,有时候约朋友......更加累了,可能由于累了就懒得总结了. 今天有同事问我关于代码检查文件类型的问题. ...

  5. 选择结构二switch选择结构

     在上一章节我们讲解了if选择结构  本章我们学习 switch选择结构 还要知道if选择结构和switch结构的区别 为什么学习了if选择结构还要学习switch选择结构  以及 两种选择结构的运用 ...

  6. vnstat 流量统计 并附带一个小 php 查看流量的页面

    安装apt-get install vnstat 配置到自动启动update-rc.d vnstat enable 启动/etc/init.d/vnstat start vnstat基本使用命令 vn ...

  7. Redis01——Redis究竟支持哪些数据结构

    Redis已经越来越多地应用到互联网技术中,而关于Redis的相关问题,也成为面试中必不可少的一部分,本文开始将会逐渐把我了解到的关于Redis的一些面试问题整理出来,供各位参考,如有不对之处,烦请指 ...

  8. MATLAB神经网络(2) BP神经网络的非线性系统建模——非线性函数拟合

    2.1 案例背景 在工程应用中经常会遇到一些复杂的非线性系统,这些系统状态方程复杂,难以用数学方法准确建模.在这种情况下,可以建立BP神经网络表达这些非线性系统.该方法把未知系统看成是一个黑箱,首先用 ...

  9. Simulink仿真入门到精通(十九) 总结回顾&自我练习

    从2019年12月27到2020年2月12日,学习了Simulink仿真及代码生成技术入门到精通,历时17天. 学习的比较粗糙,有一些地方还没理解透彻,全书梳理总结: Simulink的基础模块已基本 ...

  10. 关于pytorch在windows上编辑的问题集合

    cmake在windows上自动寻找v140(VS2015)的编译器,现在只有VS2013的IDE,所以要修改编译器 修改掉VS2015的编译器名称,报错提示参数CMAKE_C_COMPILER和CM ...