本网站(662p.com)打包出售,且带程序代码数据,662p.com域名,程序内核采用TP框架开发,需要联系扣扣:2360248666 /wx:lianweikj
精品域名一口价出售:1y1m.com(350元) ,6b7b.com(400元) , 5k5j.com(380元) , yayj.com(1800元), jiongzhun.com(1000元) , niuzen.com(2800元) , zennei.com(5000元)
需要联系扣扣:2360248666 /wx:lianweikj
为什么使用SaveFileDialog创建文件需要删除权限?
chenguangming9 · 746浏览 · 发布于2020-04-07 +关注

1. 问题#

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

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

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

2. 问题原因#

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

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

Copy//// 摘要://     获取 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();

}

注意其中的这句:

CopyopenFileName.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

相关推荐

PHP实现部分字符隐藏

沙雕mars · 1312浏览 · 2019-04-28 09:47:56
Java中ArrayList和LinkedList区别

kenrry1992 · 896浏览 · 2019-05-08 21:14:54
Tomcat 下载及安装配置

manongba · 957浏览 · 2019-05-13 21:03:56
JAVA变量介绍

manongba · 953浏览 · 2019-05-13 21:05:52
什么是SpringBoot

iamitnan · 1077浏览 · 2019-05-14 22:20:36
加载中

0评论

评论
分类专栏
小鸟云服务器
扫码进入手机网页