Doug Barense
Doug Barense

Reputation: 391

Can OpenFileDialog automatically select the file with the value set in FileName if InitialDirectory is set as well?

This is nit picky but why doesn't the file get automatically selected if it exists and both FileName and InitialDirectory are set correctly?

I have an OpenFileDialog with both FileName and InitialDirectory set correctly and the files exists in this folder. Why isn't the file selected when I run the ShowDialog() method?

No file is selected but it would be nice if it was selected so I wouldn't have to scroll down to select the next file adjacent to it.

Any suggestions?

Upvotes: 29

Views: 7938

Answers (2)

Berkay Yaylacı
Berkay Yaylacı

Reputation: 4513

Maybe it is not perfect but it meets the expectation somehow.

I have a Button that Shows OpenFileDialog on click event. And async method that will SendKeys to OpenFileDialog.

    private async void button1_Click(object sender, EventArgs e){
                string initialDir = "directory\\";
                string FileName = "filename.smthng";
                string combinedDir = initialDir + FileName;
                if (File.Exists(combinedDir)) // if there is a file with that name at that directory
                {
                    openFileDialog1.InitialDirectory = initialDir; // setting directory name
                    openFileDialog1.FileName = FileName; // filename
                    BeginInvoke((Action)(() => openFileDialog1.ShowDialog())); // we need to use BeginInvoke to continue to the following code.
                    await SendKey(FileName); // Sends Key to Dialog 
                }
                else // if there is not file with that name works here because no keys need to send.
                {
                    openFileDialog1.InitialDirectory = initialDir;
                    openFileDialog1.FileName = FileName;
                    openFileDialog1.ShowDialog();
                }
    
    }

    private async Task SendKey(string FileName){
            await Task.Delay(250); // Wait for the Dialog shown at the screen
            SendKeys.SendWait("+{TAB}"); // First Shift + Tab moves to Header of DataGridView of OpenFileDialog
            SendKeys.SendWait("+{TAB}"); // Second Shift + Tab moves to first item of list
            SendKeys.SendWait(FileName); // after sending filename will directly moves it to the file that we are looking for
    }

Result;

openfiledialog

Edit 1;

Okay, For .Net 3.5 there is also TaskParalelLibrary but using Thread will be much easier.

 Thread t;
 private const string initialDir = "C:\\";
 private const string FileName = "test.txt";
 private void button1_Click(object sender, EventArgs e){
       string combinedDir = initialDir + FileName;
       if (File.Exists(combinedDir)) // if there is a file with that name at that directory
            {
                openFileDialog1.InitialDirectory = initialDir; // setting directory name
                openFileDialog1.FileName = FileName; // filename
                BeginInvoke((Action)(() => openFileDialog1.ShowDialog())); // we need to use BeginInvoke to continue to the following code.
                t = new Thread(new ThreadStart(SendKey)); // Sends Key to Dialog with an seperate Thread.
                t.Start(); // Thread starts.
            }
            else // if there is not file with that name works here because no keys need to send.
            {
                openFileDialog1.InitialDirectory = initialDir;
                openFileDialog1.FileName = FileName;
                openFileDialog1.ShowDialog();
            }
        }
      
        private void SendKey()
        {
            Thread.Sleep(100); // Wait for the Dialog shown at the screen
            SendKeys.SendWait("+{TAB}"); // First Shift + Tab moves to Header of DataGridView of OpenFileDialog
            SendKeys.SendWait("+{TAB}"); // Second Shift + Tab moves to first item of list
            SendKeys.SendWait(FileName); // after sending filename will directly moves it to the file that we are looking for
        }

Upvotes: 13

Jeremy Thompson
Jeremy Thompson

Reputation: 65564

SendKeys

SendKeys seems like a hack but it is the easiest and for the reasons explained below probably more sustainable long term than using Win32 APIs.

Relying on a await Task.Delay(250); is risky. The timeout might be too quick before a dialog shows on a slow system, on the other hand if the timeout is increased a fast user might interact while competing with the SendKeys automation.

I recommend you use the Message Loop to notify when to SendKeys.

bool IsOpenFileDialog = false;
private void openDialog_Click(object sender, EventArgs e)
{
    IsOpenFileDialog = true;
    openFileDialog1.ShowDialog();
    IsOpenFileDialog = false;
}

uint _lastDialogHandle = 0;
protected override void WndProc(ref Message m)
{
    base.WndProc(ref m);
    if (!IsOpenFileDialog) return;
    if (m.Msg == 289) //Notify of message loop
    {
        try
        {
            uint dialogHandle = (uint)m.LParam; //handle of the file dialog
            if (dialogHandle != _lastDialogHandle) //only when not already changed
            {
                _lastDialogHandle = dialogHandle;
                SendKeys.SendWait("+{TAB}");
                SendKeys.SendWait("+{TAB}");   
                SendKeys.SendWait(EscapeSendKeySpecialCharacters("temp.xls"));

                //Or try via Win32
                //List<string> childWindows = GetDialogChildWindows(dialogHandle);
                //TODO set ListView Item
            }
        }
        catch (Exception ex) {}
    }
}

private string EscapeSendKeySpecialCharacters(string sentence)
{
    sentence = sentence.Replace("+", "{+}");
    sentence = sentence.Replace("^", "{^}");
    sentence = sentence.Replace("%", "{%}");
    sentence = sentence.Replace("~", "{~}");
    sentence = sentence.Replace("(", "{(}");
    sentence = sentence.Replace(")", "{)}");
    return sentence;
}

Note: The plus sign (+), caret (^), percent sign (%), tilde (~), and parentheses () have special meanings to SendKeys. To specify one of these characters, enclose it within braces ({}).

So for example if your filename has parentheses you need to escape them with curly brackets:

SendKeys.SendWait("New Text Document - Copy {(}8{)}.txt");

Win32 (attempt - incomplete and not working)

You could try iterating through the OpenFileDialogs' Child Windows to find the listview control:

private List<string> GetDialogChildWindows(dialogHandle) {
//IEnumerable<IntPtr> allWindows = EnumWindowsAndChild.EnumAllWindows((IntPtr)dialogHandle, "Dialog");
List<IntPtr>  children = EnumWindowsAndChild.GetChildWindows((IntPtr)dialogHandle);
List<string> childWindows = new List<string>();
foreach (IntPtr ptr in children)
{
    string s = ptr.ToString() + ",  " + EnumWindowsAndChild.GetWindowsTextTitle(ptr) + ", " + EnumWindowsAndChild.GetWindowClassName(ptr);
System.Diagnostics.Debug.WriteLine(s);
        childWindows.Add(s);
   }
  return childWindows;
 }

The Enum Windows and Child class helper:

public static class EnumWindowsAndChild
{
    public delegate bool Win32Callback(IntPtr hwnd, IntPtr lParam);

    [DllImport("user32.Dll")]
    [return: MarshalAs(UnmanagedType.Bool)]
    public static extern bool EnumChildWindows(IntPtr parentHandle, Win32Callback callback, IntPtr lParam);

    [DllImport("user32.dll", CharSet = CharSet.Auto)]
    static public extern IntPtr GetClassName(IntPtr hWnd, System.Text.StringBuilder lpClassName, int nMaxCount);

    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;
        }
    }

    [DllImport("user32.dll", CharSet = CharSet.Auto)]
    public static extern int GetWindowTextLength(HandleRef hWnd);
    [DllImport("user32.dll", CharSet = CharSet.Auto)]
    public static extern int GetWindowText(HandleRef hWnd, StringBuilder lpString, int nMaxCount);

    public static string GetWindowsTextTitle(IntPtr hwnd)
    {
        int capacity = GetWindowTextLength(new HandleRef(null, hwnd)) * 2;
        StringBuilder stringBuilder = new StringBuilder(capacity);
        GetWindowText(new HandleRef(null, hwnd), stringBuilder, stringBuilder.Capacity);
        return stringBuilder.ToString();

    }

    public static string GetWindowClassName(IntPtr hWnd)
    {
        StringBuilder buffer = new StringBuilder(128);
        GetClassName(hWnd, buffer, buffer.Capacity);
        return buffer.ToString();
    }
}

Once you have the Hwnd to the ListView you could try to set its item, but you'd need to use a NativeWindow and its not going to be pretty, see this article to know what I mean: http://www.codeproject.com/Articles/2890/Using-ListView-control-under-Win-API

As much as I hate to admit it, this is one of the rare cases where I'd suggest SendKeys over the Win32 API. Win32 Api's might not even work in the end and SendKeys is much more likely to continue to work if/when the APIs change. For example we saw with XP to Vista, the API FindWindowEx(IntPtr parentHandle, IntPtr childAfter, string className, string windowTitle) is now almost useless since Aero hides the Windows Captions which are needed for the windowTitle parameter. One solution is to use Classic Theme, not many people will accept that, so this is one of the reasons I wouldn't count on Win32 apis to do this particular thing and simply use SendKeys instead. See here:

enter image description here

I couldn't get any of this stuff to work anymore: https://bytes.com/topic/c-sharp/answers/262498-openfiledialog-how-select-files-coding, which is from a Microsoft email Dist.

// Getting the handle of the ListBox in the OpenFileDialog dialog.
uint listviewHandle = FindWindowEx(dialogHandle, 0,
"SHELLDLL_DefView", "");

// Sending message to the ListBox to set the view to Thumbnails
//Icons=0x7029, List=0x702b, Details=0x702c, Thumbnails=0x702d,
Tiles=0x702e
SendMessage(listviewHandle, 0x0111/*WM_COMMAND*/, (uint)0x702d, 0);

// Sending message to the ListBox to select all items.
SendMessage(listviewHandle, 0x0111/*WM_COMMAND*/, (uint)0x00017021,
(uint)0);

Upvotes: 10

Related Questions