Reputation: 391
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
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;
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
Reputation: 65564
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");
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:
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