【问题标题】:Can I launch DotNet's OpenFileDialog in C:\Users\Public\Documents?我可以在 C:\Users\Public\Documents 中启动 DotNet 的 OpenFileDialog 吗?
【发布时间】:2016-11-13 06:20:33
【问题描述】:

有没有办法在C:\Users\Public\Documents 文件夹中启动OpenFileDialog

我正在使用 DotNet 框架编写 C# 应用程序。我正在尝试使用"C:\\Users\\Public\\Documents\\"InitialDirectory"world.txt"FileName 启动OpenFileDialog。不幸的是,OpenFileDialog 让我进入了Documents 快捷方式而不是C:\Users\Public\Documents

预期结果
我希望看到 OpenFileDialog 打开,顶部文本框显示 > This PC > Windows7_OS (C:) > Users > Public > Documents ,底部文本框显示 world.txt 。我希望如果我点击顶部的文本框,它将显示 C:\Users\Public\Documents

实际结果
打开文件对话框打开。顶部文本框显示 > This PC > Documents ,底部文本框显示 world.txt 。如果我单击顶部文本框,它会显示 Documents 。显示的文件夹内容C:\Users\Public\Documents的内容相同。

我尝试过的事情
在以下代码行之后,我已停止 Visual Studio 调试器中的代码:
OpenFileDialog dlg = new OpenFileDialog();

在即时窗口中,我执行了如下代码:

dlg.FileName = "world.txt"  
? dlg.FileName  
dlg.InitialDirectory = "C:\\NonExistentDirectory\\";  
dlg.ShowDialog();  
dlg.InitialDirectory = "C:\\";  
dlg.ShowDialog();  
dlg.InitialDirectory = "C:\\Users\\";  
dlg.ShowDialog(); 
dlg.InitialDirectory = "C:\\Users\\Public\\";  
dlg.ShowDialog(); 
dlg.InitialDirectory = "C:\\Users\\Public\\Documents\\";  
dlg.ShowDialog();  

我取消了每个对话框。

我在C:\C:\Users\C:\Users\PublicC:\Users\Public\Documents\之间使用了C:\WINDOWS\System32\cmd.execd

我尝试过的事情的结果

  • dlg.InitialDirectory = "C:\\NonExistentDirectory\\" 时,对话框的文件夹最初显示为 This PC > Documents > Visual Studio 2015 > Projects > SimpleGame > Controller > bin > Debug" 。单击文本框会使其显示 C:\Users\Owner\Documents\Visual Studio 2015\Projects\SimpleGame\Controller\bin\Debug 。因此,我假设OpenFileDialog 通过不更改目录以静默方式处理无效的InitialDirectory。在这种情况下,它默认为我项目的程序集的 bin 的 Debug 文件夹。

  • dlg.InitialDirectory"C:\\""C:\\Users\\""C:\\Users\\Public\\" 时,对话框按预期运行。单击顶部文本框会分别生成C:\C:\UsersC:\Users\Public

  • dlg.InitialDirectory = "C:\\Users\\Public\\Documents\\" 时,对话框的行为不正确。顶部文本框显示 > This PC > Documents 底部文本框显示 world.txt 。如果我单击顶部文本框,它会显示 Documents 。显示的文件夹内容C:\Users\Public\Documents的内容相同。

  • 使用cmd.exe 可以让我在文件夹之间按预期使用cd,包括C:\Users\Public\Documents

我的环境
我正在使用 Microsoft Visual C# 2015 运行 Microsoft Visual Studio Community 2015 版本 14.0.23107.0 D14REL。我的操作系统是 Windows 10 Pro。

【问题讨论】:

  • 可能是一种特权?你试过以管理员身份运行 vs 吗?
  • 我也在使用 Windows 10 Pro 并且可以确认此行为。请注意,如果您使用dlg.InitialDirectory = "C:\\Users\\Public\\Music\\";dlg.InitialDirectory = $"C:\\Users\\{Environment.UserName}\\Documents\\";,则对话框将打开到相应的库。但是,如果您指定作为自定义库一部分的目录的路径,则对话框将打开到该物理目录。当打开到属于具有KNOWNFOLDERID 的库的一部分的目录时,对话框可能正试图变得“智能”。
  • 您是否尝试过使用Environment.GetFolderPath(Environment.SpecialFolder.CommonDocuments) 检索目录?
  • @TheodorosChatzigiannakis -- dlg.InitialDirectory = Environment.GetFolderPath(Environment.SpecialFolder.CommonDo‌​cuments); dlg.ShowDialog(); 也会产生不需要的 DocumentsGetFolderPath"C:\\Users\\Public\\Documents"
  • @Noctis -- 我拥有在C:\Users\Public\Documents 文件夹中创建/修改/读取/写入/列出et cetera 文件所需的所有权限。我可以使用cmd.exe 来做到这一点。

标签: c# .net openfiledialog


【解决方案1】:

尽管如 Silver 所述,这是一个错误,但可以在不同的线程上使用带有 WM_SETTEXT 的 SendMessage API 大致绕过,尽管至少可以说,它很可能会起作用。

我使用NSGaga's post 整理了一些脏代码sn-p 以显示概念证明,这个原始示例不应按原样使用。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Windows.Forms;

namespace WindowsFormsApplication13
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            OpenFileDialog dlg = new OpenFileDialog();

            //start a thread to change the dialog path just before displaying it to the user
            Thread posThread = new Thread(setDialogPath);
            posThread.Start();

            //display dialog to the user
            DialogResult dr = dlg.ShowDialog();
        }

        [DllImport("user32.dll")]
        static extern IntPtr FindWindow(IntPtr lpClassName, string lpWindowName);
        [DllImport("user32.dll")]
        static extern IntPtr SendMessage(IntPtr hWnd, int Msg, int wParam, string lParam);
        [DllImport("user32.dll")]
        static extern IntPtr PostMessage(IntPtr hWnd, int Msg, int wParam, int lParam);
        [DllImport("user32.Dll")]
        [return: MarshalAs(UnmanagedType.Bool)]
        static extern bool EnumChildWindows(IntPtr parentHandle, Win32Callback callback, IntPtr lParam);
        [DllImport("user32.dll", CharSet = CharSet.Auto)]
        static extern IntPtr GetClassName(IntPtr hWnd, System.Text.StringBuilder lpClassName, int nMaxCount);

        private void setDialogPath()
        {
            const string FULL_PATH = "C:\\Users\\Public\\Documents";

            //messages
            const int WM_SETTEXT = 0xC;
            const int WM_KEYDOWN = 0x100;
            const int WM_KEYUP = 0x101;
            //enter key code
            const int VK_RETURN = 0x0D;

            //dialog box window handle
            IntPtr _window_hwnd;

            //how many attempts to detect the window
            int _attempts_count = 0;

            //get the dialog window handle
            while ((_window_hwnd = FindWindow(IntPtr.Zero, "Open")) == IntPtr.Zero)
                if (++_attempts_count > 100)
                    return;
                else
                    Thread.Sleep(500); //try again

            //in it - find the path textbox's handle.
            var hwndChild = EnumAllWindows(_window_hwnd, "Edit").FirstOrDefault();

            //set the path
            SendMessage(hwndChild, WM_SETTEXT, 0, FULL_PATH);

            //apply the path (send 'enter' to the textbox)
            PostMessage(hwndChild, WM_KEYDOWN, VK_RETURN, 0);
            PostMessage(hwndChild, WM_KEYUP, VK_RETURN, 0);
        }


        public delegate bool Win32Callback(IntPtr hwnd, IntPtr lParam);

        private static bool EnumWindow(IntPtr handle, IntPtr pointer)
        {
            GCHandle gch = GCHandle.FromIntPtr(pointer);
            List<IntPtr> list = gch.Target as List<IntPtr>;
            if (list == null)
                throw new InvalidCastException("GCHandle Target could not be cast as List<IntPtr>");
            list.Add(handle);
            return true;
        }

        public static List<IntPtr> GetChildWindows(IntPtr parent)
        {
            List<IntPtr> result = new List<IntPtr>();
            GCHandle listHandle = GCHandle.Alloc(result);
            try
            {
                Win32Callback childProc = new Win32Callback(EnumWindow);
                EnumChildWindows(parent, childProc, GCHandle.ToIntPtr(listHandle));
            }
            finally
            {
                if (listHandle.IsAllocated)
                    listHandle.Free();
            }
            return result;
        }

        public static string GetWinClass(IntPtr hwnd)
        {
            if (hwnd == IntPtr.Zero)
                return null;
            StringBuilder classname = new StringBuilder(100);
            IntPtr result = GetClassName(hwnd, classname, classname.Capacity);
            if (result != IntPtr.Zero)
                return classname.ToString();
            return null;
        }

        public static IEnumerable<IntPtr> EnumAllWindows(IntPtr hwnd, string childClassName)
        {
            List<IntPtr> children = GetChildWindows(hwnd);
            if (children == null)
                yield break;
            foreach (IntPtr child in children)
            {
                if (GetWinClass(child) == childClassName)
                    yield return child;
                foreach (var childchild in EnumAllWindows(child, childClassName))
                    yield return childchild;
            }
        }

    }
}

Hackish,但可行

【讨论】:

  • 我已经修改了这段代码,并将其添加到我的程序中。我似乎有一个运行时缓存问题。 (我注意到我的程序的其他功能在我启动程序时非常慢,后来变得更快。)在程序的给定运行中,我第一次使用此代码打开文件时,OpenFileDialog 仍然存在进入Documents。我打开文件的后续时间正常工作,OpenFileDialog 进入C:\Users\Public\Documents 文件夹。
  • 当我在发布模式下构建时,此代码会间歇性地工作。正如我之前提到的,此代码在第一次使用后在调试模式下对我有用。在Release模式下,首次使用无效;它适用于第二次使用;它会在后续使用中间歇性地工作。
【解决方案2】:

如果你使用 System.Windows.Forms.OpenFileDialog 你可以设置:

dialog.AutoUpgradeEnabled = false;

对话框看起来有点过时/平淡/“老派”,但它至少显示了正确的内容!

【讨论】:

    【解决方案3】:

    看来不可能!

    您可以在以下链接中看到它是 .net 框架中的一个错误 :( msdn link 您可以在最后一条评论中找到:

    很难相信,但是:

    这是一个错误,仅此而已。

    【讨论】:

    • 我相信这可以在不同的线程上使用带有 WM_SETTEXT 的 Win32 SendMessage API 来完成,尽管至少可以说有点骇人听闻,但它很可能会起作用。
    • 如果您将发布一个可行的解决方案,我将对其进行投票并删除我的答案...(但如果您失败,请为我投票)
    • @silver -- 请不要删除您的答案。我很高兴有两个不同的有用答案,并为它们都投了赞成票。
    • MSDN link(在此答案中)表明可能有 25 条路径可能存在此问题。
    • 我写了一个检查看看路径是否已知有这个问题。如果是这样,我根据@DavidHollinshead 的建议设置dlg.AutoUpgradeEnabled = false。如果我在其他 24 条可能有问题的路径中遇到任何问题,我可以概括测试以处理所有问题。
    【解决方案4】:

    您可能认为显而易见的答案是使用 Environment.SpecialFolder.CommonDo‌​‌​cuments,但这似乎与 Windows 10 上的 Environment.SpecialFolder.MyDo‌​‌​cuments 完全相同。这一定是 .NET 中的错误!

    我今天遇到了这个问题,并找到了一个对我有用的解决方案,并且可能对其他人有用。只需将子文件夹添加到公共文档,然后将其用作初始目录即可。将您的东西存储在特定于应用程序的子文件夹中而不是仅仅存储在根目录中可能是更好的做法。像这样:

    Path.Combine(Environment.SpecialFolder.CommonDocuments, "SomeSubfolder").
    

    【讨论】:

    • 这会给你“C:\Users\Public\Documents\SomeSubfolder\”吗?
    • 是的,这正是它的作用!如果不添加子文件夹,CommonDocuments 将无法按您期望的方式工作。但是,只要您添加子文件夹,您就会得到您想要的
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-01-18
    • 1970-01-01
    • 2011-06-06
    • 2017-12-17
    • 2013-02-16
    相关资源
    最近更新 更多