9

Is there a way to launch the OpenFileDialog in the C:\Users\Public\Documents folder?

I am writing a C# application, using the DotNet framework. I am trying to launch an OpenFileDialog, with an InitialDirectory of "C:\\Users\\Public\\Documents\\" and a FileName of "world.txt". Unfortunately, the OpenFileDialog is putting me in the Documents shortcut instead of into C:\Users\Public\Documents .

Expected results
I expect to see the OpenFileDialog open, with the top textbox showing > This PC > Windows7_OS (C:) > Users > Public > Documents and the bottom textbox showing world.txt . I expect that if I click in the top textbox, it will show C:\Users\Public\Documents .

Actual results
The OpenFileDialog opens. The top textbox shows > This PC > Documents and the bottom textbox shows world.txt . If I click in the top textbox, it shows Documents . The displayed folder contents are not the same as the contents of C:\Users\Public\Documents .

Things I have tried
I have stopped the code in the Visual Studio debugger after the following line of code:
OpenFileDialog dlg = new OpenFileDialog();

In the Immediate Window, I have executed code such as the following:

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();  

I cancel out of each dialog.

I used C:\WINDOWS\System32\cmd.exe to cd between C:\ and C:\Users\ and C:\Users\Public and C:\Users\Public\Documents\ .

Results of things I have tried

  • When dlg.InitialDirectory = "C:\\NonExistentDirectory\\" , the dialog's folder initially displays as This PC > Documents > Visual Studio 2015 > Projects > SimpleGame > Controller > bin > Debug" . Clicking in the textbox causes it to display C:\Users\Owner\Documents\Visual Studio 2015\Projects\SimpleGame\Controller\bin\Debug . I therefore assume that OpenFileDialog silently handles an invalid InitialDirectory by not changing directories. In this case, it is defaulting to my project's assembly's bin's Debug folder.

  • When dlg.InitialDirectory is "C:\\" or "C:\\Users\\" or "C:\\Users\\Public\\" the dialog behaves as expected. Clicking in the top textbox produces C:\ or C:\Users or C:\Users\Public respectively.

  • When dlg.InitialDirectory = "C:\\Users\\Public\\Documents\\" the dialog behaves incorrectly. The top textbox shows > This PC > Documents and the bottom textbox shows world.txt . If I click in the top textbox, it shows Documents . The displayed folder contents are not the same as the contents of C:\Users\Public\Documents .

  • Using cmd.exe lets me cd between the folders as expected, including into C:\Users\Public\Documents .

My environment
I am running Microsoft Visual Studio Community 2015 Version 14.0.23107.0 D14REL, using Microsoft Visual C# 2015. My operating system is Windows 10 Pro.

silver
  • 1,633
  • 1
  • 20
  • 32
Jasper
  • 409
  • 4
  • 17
  • might be a privilege thing ? have you tried running vs as administrator ? – Noctis Nov 13 '16 at 06:34
  • 1
    I am also using Windows 10 Pro and can confirm this behavior. Note that if you use `dlg.InitialDirectory = "C:\\Users\\Public\\Music\\";` or `dlg.InitialDirectory = $"C:\\Users\\{Environment.UserName}\\Documents\\";` then the dialog will open to the corresponding library. If you specify the path to a directory that is a part of a custom library, however, then the dialog opens to that physical directory. Perhaps the dialog is trying to be "smart" when opened to a directory that is part of a library with a [`KNOWNFOLDERID`](https://msdn.microsoft.com/library/windows/desktop/dd378457.aspx). – Lance U. Matthews Nov 13 '16 at 07:20
  • Have you tried retrieving the directory using `Environment.GetFolderPath(Environment.SpecialFolder.CommonDocuments)`? – Theodoros Chatzigiannakis Nov 13 '16 at 07:31
  • @TheodorosChatzigiannakis -- `dlg.InitialDirectory = Environment.GetFolderPath(Environment.SpecialFolder.CommonDo‌​cuments); dlg.ShowDialog();` also produces the unwanted `Documents` . The `GetFolderPath` was `"C:\\Users\\Public\\Documents"` . – Jasper Nov 13 '16 at 07:41
  • @Noctis -- I have all the permissions I need to create/modify/read/write/list *et cetera* files in the `C:\Users\Public\Documents` folder. I can do so using `cmd.exe` . – Jasper Nov 13 '16 at 14:38
  • 1
    It's 2022 and I'm having the same issue! If I set the initial directory to `C:\Users\SomeUser\Pictures` it puts me in `Libraries\Pictures` instead of `ThisPC\Pictures`. Yes, adding `dlg.AutoUpgradeEnabled = false` does open to the correct folder, but it's ugly and *shouldn't be necessary*. Apparently Microsoft doesn't see this as a bug?!? – LesFerch Apr 14 '22 at 01:33

4 Answers4

2

although as stated by Silver, this is a bug but can be roughly bypassed using SendMessage API with WM_SETTEXT on a different thread although hackish to say the least, it will most likely work.

i've put together some dirty code snippet using NSGaga's post to show a proof of concept, this raw example should not be used as is.

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, but doable

Community
  • 1
  • 1
Stavm
  • 7,833
  • 5
  • 44
  • 68
  • I have adapted this code, and added it to my program. I seem to have a run-time caching problem. (I have noticed that other features of my program are very slow when I start the program, and become faster later.) Within a given run of the program, the first time I open a file using this code, the `OpenFileDialog` still goes into `Documents` . Subsequent times that I open a file work correctly, and `OpenFileDialog` goes into the `C:\Users\Public\Documents` folder. – Jasper Nov 13 '16 at 15:11
  • When I build in Release mode, this code works intermittently. As I mentioned previously, this code works for me in Debug mode after the first use. In Release mode, it does not work on the first use; it works on the second use; and it works intermittently on subsequent uses. – Jasper Nov 13 '16 at 15:57
1

It seems that it is impossible!

You can see in the following link that it is a bug in .net framework :( msdn link you can find in the last comment:

It is hard to believe but:

It is a bug and nothing else.

Jasper
  • 409
  • 4
  • 17
silver
  • 1,633
  • 1
  • 20
  • 32
  • I believe this can be done using Win32 SendMessage API with WM_SETTEXT on a different thread, although hackish to say the least, it will most likely work. – Stavm Nov 13 '16 at 07:56
  • if you will post a working solution I will upvote it and delete my answer... (but if you fail please upvote mine) – silver Nov 13 '16 at 07:58
  • @silver -- Please do not delete your answer. I am happy to have two different useful answers, and upvote them both. – Jasper Nov 13 '16 at 14:18
  • The [MSDN link](https://social.msdn.microsoft.com/Forums/vstudio/en-US/b7559bbe-fc4f-4554-a9b6-ef72d181f7d1/openfiledialog-will-not-show-initaldirectorycuserspublicpublic-documents?forum=csharpgeneral) (in this answer) suggests that perhaps 25 paths are likely to have this problem. – Jasper Nov 16 '16 at 16:55
  • I have written a check to see if the path is known to have this problem. If so, I set `dlg.AutoUpgradeEnabled = false` per @DavidHollinshead's suggestion. If I run into problems with any of the other 24 likely problematic paths, I can generalize the test to deal with them all. – Jasper Nov 16 '16 at 17:00
1

If you are using System.Windows.Forms.OpenFileDialog you can set:

dialog.AutoUpgradeEnabled = false;

The dialog will look a little dated/flat/"old school" but it does at least display the correct contents!

David Hollinshead
  • 1,710
  • 17
  • 18
1

You would think the obvious answer would be to use Environment.SpecialFolder.CommonDo‌​‌​cuments, but that seems to do the exact same thing as Environment.SpecialFolder.MyDo‌​‌​cuments on Windows 10. This must be a bug in .NET!

I ran into this issue today and found a solution that worked for me, and might work for someone else. Just add a subfolder to the Public Documents, and then use that as the initial directory instead. It is probably better practice to be storing your stuff in an application-specific subfolder instead of just at the root anyway. Like this:

Path.Combine(Environment.SpecialFolder.CommonDocuments, "SomeSubfolder").
Grant Winney
  • 65,241
  • 13
  • 115
  • 165
StalePhish
  • 131
  • 4
  • Does this give you "C:\Users\Public\Documents\SomeSubfolder\" ? – Jasper Jul 03 '17 at 19:23
  • Yes, that's exactly what it does! Without adding a subfolder, CommonDocuments just plain won't work the way you expect. But as soon as you add in the subfolder, you get exactly what you desire – StalePhish Aug 01 '17 at 18:43