[WPF]為什麼使用SaveFileDialog創建文件需要刪除許可權?

来源:https://www.cnblogs.com/dino623/archive/2020/04/07/why_save_file_dialog_needs_delete_permission.html
-Advertisement-
Play Games

1. 問題 好像很少人會遇到這種需求。假設有一個文件夾,用戶有幾乎所有許可權,但沒有刪除的許可權,如下圖所示: 這時候使用SaveFileDialog在這個文件夾里創建文件居然會報如下錯誤: 這哪裡是網路位置了,我又哪裡去找個管理員?更奇怪的是,雖然報錯了,但文件還是會創建出來,不過這是個空文件。不僅W ...


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


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 效果圖預覽 新建MyCustomControl類。 public class MyCustomControl : Control { private static Storyboard MyStory; private ObjectAnimationUsingKeyFrames MyAnimatio ...
  • 在帶鍵盤滑鼠的電腦上編寫應用於觸屏電腦的項目,為了能輸入中文、英文、數字等各種庸人自擾。 一、自己畫了個鍵盤 為了實現能輸入中文,還簡單編寫了個拼音輸入法,各種折騰,始終不是很舒服。最後客戶要求手寫輸入中文,於是就完全放棄了。 二、折騰TabTip.exe win10的虛擬鍵盤是一個程式,即c:\P ...
  • 五、C#流程式控制制 5.1.if語句 1)結構 if(條件判斷表達式) { func1 }else { func2 } 5.2.switch語句 1)結構 switch(表達式) { case 常量表達式:條件語句;break; case 常量表達式:條件語句;break; case 常量表達式:條件 ...
  • IdentityServer 部署踩坑記 Intro 周末終於部署了 以及 項目,踩了幾個坑,在此記錄分享一下。 部署架構 項目是基於 "IdentityServerAdmin" 項目修改的,感謝作者的開源付出,有需要 IdentityServer 管理需求的可以關註一下,覺得好用的可以給個 sta ...
  • Insus.NET有在angularjs中把ng-repeat顯示數據的同時又讓其能更新數據。 html代碼如下: 當用戶點擊更新時,能獲取按鈕當前行的更新數據進行更新。 ...
  • @[toc] 開發 web api 的時候,寫文檔是個痛苦的事情,而沒有文檔別人就不知道怎麼調用,所以又不得不寫。 swagger 可以自動生成介面文檔,並測試介面,極大的解放了程式員的生產力。 1 安裝 通過 NuGet 安裝 Swashbuckle。 安裝完成後,App_Start 文件夾下會多 ...
  • 1.先上效果圖: 2.1t提示框界面。 主視窗界面沒什麼內容,就放了一個觸發按鈕。先繪製通知視窗(一個關閉按鈕,倆個文本控制項),可以設置下ResizeMode="NoResize" WindowStyle="None" Topmost="True", 去掉視窗標題,並使提示視窗始終處於最上層。 <B ...
  • 前言 本文介紹另一種學習ABP框架的方法,該方法為正面硬鋼學習法。。。 我們不去官網下載模板,直接引用DLL,直接使用。 WebApi項目創建 首先創建一個WebApi項目,結構如下。 然後Nuget搜索ABP,安裝ABP框架。(我這裡安裝的是5.1.0,因為最高版本安裝不上) 在安裝ABP前先檢查 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...