As show in this screen shot, the selected folder is not in the view. It needs to be scrolled down to view the selected folder.
Same dialog shows selected folder visible on different computer
I ran it on two computers both having windows 7. It works correctly on one but does not on 2nd. It looks something with windows environment instead some code issue? Can anyone suggest any fix?
There is no change in code. I used longer paths from different drives but results are same.
private void TestDialog_Click ( object sender, EventArgs e )
{
//Last path store the selected path, to show the same directory as selected on next application launch.
//Properties.Settings.Default.LastPath
FolderBrowserDialog dlgFolder = new FolderBrowserDialog ();
dlgFolder.RootFolder = Environment.SpecialFolder.DesktopDirectory;
dlgFolder.SelectedPath = Properties.Settings.Default.LastPath;
if (dlgFolder.ShowDialog () == System.Windows.Forms.DialogResult.OK)
{
Properties.Settings.Default.LastPath = dlgFolder.SelectedPath;
Properties.Settings.Default.Save ();
}
}
The fundamental problem is a poor design decision in the FolderBrowserDialog. First, we need to realize that the FolderBrowserDialog is not a .NET control, but is rather the Common Dialog and is part of Windows. The designer of this dialog elected not to send the TreeView control a TVM_ENSUREVISIBLE message after the dialog is displayed and an initial folder is selected. This message causes a TreeView control to scroll so that the currently selected item is visible in the window.
So, all we need to do to fix this is to send the TreeView that is part of the FolderBrowserDialog the TVM_ENSUREVISIBLE message and everything will be great. Right? Well, not so fast. This is indeed the answer, but there some things standing in our way.
First, because the FolderBrowserDialog is not really a .NET control, it does not have an internal Controls collection. This means that we can't just find and access the TreeView child control from .NET.
Second, the designers of the .NET FolderBrowserDialog class decided to seal this class. This unfortunate decision prevents us from deriving from it and overriding the window message handler. Had we been able to do this, we might have tried to post the TVM_ENSUREVISIBLE message when we got the WM_SHOWWINDOW message in the message handler.
The third issue is that we can’t send the TVM_ENSUREVISIBLE message until the Tree View control actually exists as a real window, and it does not exist until we call the ShowDialog method. However, this method blocks, so we won’t have the opportunity to post our message once this method is called.
To get around these issues, I created a static helper class with a single method that can be used to show a FolderBrowserDialog, and will cause it to scroll to the selected folder. I manage this by starting a short Timer just prior to calling the dialogue's ShowDialog method, and then tracking down the handle of the TreeView control in the Timer handler (i.e., after the dialogue is displayed) and sending our TVM_ENSUREVISIBLE message.
This solution is not perfect because it depends on some prior knowledge about the FolderBrowserDialog. Specifically, I find the dialogue using its window title. This will break with non-English installations. I track down the child controls in the dialogue using their dialogue Item IDs, rather than title text or class name, because I felt this would be more reliable over time.
This code has been tested on Windows 7 (64 bit), and Windows XP.
Here is the code:
(You may need: using System.Runtime.InteropServices;)
public static class FolderBrowserLauncher
{
/// <summary>
/// Using title text to look for the top level dialog window is fragile.
/// In particular, this will fail in non-English applications.
/// </summary>
const string _topLevelSearchString = "Browse For Folder";
/// <summary>
/// These should be more robust. We find the correct child controls in the dialog
/// by using the GetDlgItem method, rather than the FindWindow(Ex) method,
/// because the dialog item IDs should be constant.
/// </summary>
const int _dlgItemBrowseControl = 0;
const int _dlgItemTreeView = 100;
[DllImport("user32.dll", SetLastError = true)]
static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
[DllImport("user32.dll")]
static extern IntPtr GetDlgItem(IntPtr hDlg, int nIDDlgItem);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
static extern IntPtr SendMessage(IntPtr hWnd, UInt32 Msg, IntPtr wParam, IntPtr lParam);
/// <summary>
/// Some of the messages that the Tree View control will respond to
/// </summary>
private const int TV_FIRST = 0x1100;
private const int TVM_SELECTITEM = (TV_FIRST + 11);
private const int TVM_GETNEXTITEM = (TV_FIRST + 10);
private const int TVM_GETITEM = (TV_FIRST + 12);
private const int TVM_ENSUREVISIBLE = (TV_FIRST + 20);
/// <summary>
/// Constants used to identity specific items in the Tree View control
/// </summary>
private const int TVGN_ROOT = 0x0;
private const int TVGN_NEXT = 0x1;
private const int TVGN_CHILD = 0x4;
private const int TVGN_FIRSTVISIBLE = 0x5;
private const int TVGN_NEXTVISIBLE = 0x6;
private const int TVGN_CARET = 0x9;
/// <summary>
/// Calling this method is identical to calling the ShowDialog method of the provided
/// FolderBrowserDialog, except that an attempt will be made to scroll the Tree View
/// to make the currently selected folder visible in the dialog window.
/// </summary>
/// <param name="dlg"></param>
/// <param name="parent"></param>
/// <returns></returns>
public static DialogResult ShowFolderBrowser( FolderBrowserDialog dlg, IWin32Window parent = null )
{
DialogResult result = DialogResult.Cancel;
int retries = 10;
using (Timer t = new Timer())
{
t.Tick += (s, a) =>
{
if (retries > 0)
{
--retries;
IntPtr hwndDlg = FindWindow((string)null, _topLevelSearchString);
if (hwndDlg != IntPtr.Zero)
{
IntPtr hwndFolderCtrl = GetDlgItem(hwndDlg, _dlgItemBrowseControl);
if (hwndFolderCtrl != IntPtr.Zero)
{
IntPtr hwndTV = GetDlgItem(hwndFolderCtrl, _dlgItemTreeView);
if (hwndTV != IntPtr.Zero)
{
IntPtr item = SendMessage(hwndTV, (uint)TVM_GETNEXTITEM, new IntPtr(TVGN_CARET), IntPtr.Zero);
if (item != IntPtr.Zero)
{
SendMessage(hwndTV, TVM_ENSUREVISIBLE, IntPtr.Zero, item);
retries = 0;
t.Stop();
}
}
}
}
}
else
{
//
// We failed to find the Tree View control.
//
// As a fall back (and this is an UberUgly hack), we will send
// some fake keystrokes to the application in an attempt to force
// the Tree View to scroll to the selected item.
//
t.Stop();
SendKeys.Send("{TAB}{TAB}{DOWN}{DOWN}{UP}{UP}");
}
};
t.Interval = 10;
t.Start();
result = dlg.ShowDialog( parent );
}
return result;
}
}
I know this thread is WAY old, but with extension methods, this can be added to the FolderBrowserDialog.ShowDialog method, and then used repeatedly where needed.
The sample (below) is just using the easy SendKeys method (which I hate doing, but in this case, it works well). When using the SendKeys method to jump to the selected folder in the dialog, if you are debugging this in Visual Studio, then the SendKeys call applies to the current window, which would be the active VS window. To be more foolproof and to avoid the wrong window from getting the SendKeys message, then the extension method would contain the external method calls to send messages to the specific window similar to what Marc F posted, but translated to C#.
internal static class FolderBrowserDialogExtension
{
public static DialogResult ShowDialog(this FolderBrowserDialog dialog, bool scrollIntoView)
{
return ShowDialog(dialog, null, scrollIntoView);
}
public static DialogResult ShowDialog(this FolderBrowserDialog dialog, IWin32Window owner, bool scrollIntoView)
{
if (scrollIntoView)
{
SendKeys.Send("{TAB}{TAB}{RIGHT}");
}
return dialog.ShowDialog(owner);
}
}
I have used a workaround from https://www.daniweb.com/software-development/csharp/threads/300578/folderbrowserdialog-expanding-the-selected-directory-
FolderBrowserDialog^ oFBD = gcnew FolderBrowserDialog;
oFBD->RootFolder = Environment::SpecialFolder::MyComputer;
oFBD->SelectedPath = i_sPathImport;
oFBD->ShowNewFolderButton = false; // use if appropriate in your application
SendKeys::Send ("{TAB}{TAB}{RIGHT}"); // <<-- Workaround
::DialogResult oResult = oFBD->ShowDialog ();
It's not the nicest way, but it works for me.
Without the RootFolder it does NOT work on the first call, but on the 2nd and following. With it, it works always.
As others have observed that this failure is dependent on the operating system:
I am using Win 7 Pro x64 SP1
on VB.Net code, just put this line of code right before showing the dialog.
SendKeys.Send ("{TAB}{TAB}{RIGHT}")
this works for me
folderBrowserDialog1.Reset();
folderBrowserDialog1.RootFolder = Environment.SpecialFolder.MyComputer;
folderBrowserDialog1.SelectedPath = WorkingFolder;
but only after the second use of the dialog
I read at different forums that it could be due to RootFolder because SelectedPath and RootFolder are are mutually exclusive, that means both cannot co-exists but with default RootFolder(.Desktop), It allows ,at least, climbing the Tree(navigate the drive/folders).
However, if RootFolder is changed to other than Desktop, you would not be able to navigate to UNC paths.
Answer to Hans Passant:
I tried this Dialog Extension, which has TextBox, but no luck.
Customising the browse for folder dialog to show the path
I have found that:
If .SelectedPath ends with "\", the Dialog will scroll down to make the path visible.
If .SelectedPath does not end with "\", the path is still selected, but not ensured visible.
I computed something in VB.NET, so it would be easy to transform it into C#.
I'm French, and I'm beginner in VB.
Anyway, you can try my solution.
My idea is to launch an asynchronous task just before showing the folderBrowserDialog.
I found this myself, but I was inspired by Brad post.
Here's my code:
Imports System.Threading.Tasks
Imports Microsoft.VisualBasic.FileIO.FileSystem
Public Enum GW
HWNDFIRST = 0
HWNDLAST = 1
HWNDNEXT = 2
HWNDPREV = 3
OWNER = 4
CHILD = 5
ENABLEDPOPUP = 6
End Enum
Public Declare Function SendMessageW Lib "user32.dll" (ByVal hWnd As IntPtr, ByVal msg As UInteger, ByVal wParam As Integer, <MarshalAs(UnmanagedType.LPWStr)> ByVal lParam As String) As IntPtr
Public Declare Function FindWindowExW Lib "user32.dll" (ByVal hWndParent As IntPtr, ByVal hWndChildAfter As IntPtr, <MarshalAs(UnmanagedType.LPWStr)> ByVal lpszClass As String, <MarshalAs(UnmanagedType.LPWStr)> ByVal lpszWindow As String) As IntPtr
Public Declare Function GetWindow Lib "user32" (ByVal hwnd As IntPtr, ByVal wCmd As Long) As Long
Public Declare Function GetDesktopWindow Lib "user32" () As IntPtr
Public Declare Function GetClassName Lib "user32" Alias "GetClassNameA" (ByVal hwnd As IntPtr, ByVal lpClassName As System.Text.StringBuilder, ByVal nMaxCount As Integer) As Integer
Private Sub FolderBrowserDialog_EnsureVisible(FB As FolderBrowserDialog, _Owner As IntPtr)
Dim hwnd As IntPtr
Dim sClassname As New System.Text.StringBuilder(256)
Thread.Sleep(50) 'necessary to let FolderBrowserDialog construct its window
hwnd = GetDesktopWindow() 'Desktop window handle.
hwnd = GetWindow(hwnd, GW.CHILD) 'We will find all children.
Do Until hwnd = 0
If GetWindow(hwnd, GW.OWNER) = _Owner Then 'If one window is owned by our main window...
GetClassName(hwnd, sClassname, 255)
If sClassname.ToString = "#32770" Then 'Check if the class is FolderBrowserDialog.
Exit Do 'Then we found it.
End If
End If
hwnd = GetWindow(hwnd, GW.HWNDNEXT) 'Next window.
Loop 'If no found then exit.
If hwnd = 0 Then Exit Sub
Dim hChild As IntPtr = 0
Dim hTreeView As IntPtr = 0
Dim i As Integer = 0
Do
i += 1
If i > 1000 Then Exit Sub 'Security to avoid infinite loop.
hChild = FindWindowExW(hwnd, hChild, Nothing, Nothing) 'Look for children windows of FolderBrowserDialog.
hTreeView = FindWindowExW(hChild, 0, "SysTreeView32", Nothing) 'Look for treeview of FolderBrowserDialog.
Thread.Sleep(5) 'delay necessary because FolderBrowserDialog is in construction, then treeview maybe not yet exist.
Loop While hTreeView = 0
If SendMessageW(hwnd, &H46A, 1, FB.SelectedPath) = 0 Then 'Send message BFFM_SETEXPANDED to FolderBrowserDialog.
SendMessageW(hTreeView, &H7, 0, Nothing) 'Send message WM_SETFOCUS to the treeeview.
End If
End Sub
Dim My_save_dir = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments) & "\My-Saves"
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Dim FolderBrowserDialog1 As New FolderBrowserDialog
FolderBrowserDialog1.Description = "Choose your save files path."
If Directory.Exists(My_save_dir) Then
FolderBrowserDialog1.SelectedPath = My_save_dir
Else
FolderBrowserDialog1.SelectedPath = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData)
End If
Dim Me_handle = Me.Handle 'Store the main handle to compare after with each windows owner.
Task.Run(Sub() FolderBrowserDialog_EnsureVisible(FolderBrowserDialog1, Me_handle)) 'Here's the trick, run an asynchronous task to modify the folderdialog.
If FolderBrowserDialog1.ShowDialog(Me) = System.Windows.Forms.DialogResult.OK Then
My_save_dir = FolderBrowserDialog1.SelectedPath
End If
End Sub
I'm waiting for your suggestions.
And someone can translate it into C# because I don't know C#.
I run into same problem in c++ /mfc. It worked for me to use ::PostMessage rather than ::SendMessage in the BFFM_INITIALIZED callback to place the TVM_ENSUREVISIBLE msg
case BFFM_INITIALIZED:
{
// select something
::SendMessage(m_hDialogBox, BFFM_SETSELECTION, TRUE, (LPARAM) pszSelection);
// find tree control
m_hTreeCtrl = 0;
HWND hchild = GetWindow(hWnd, GW_CHILD) ;
while (hchild != NULL)
{
VS_TChar classname[200] ;
GetClassName(hchild, classname, 200) ;
if (VS_strcmp(classname, _T("SHBrowseForFolder ShellNameSpace Control")) == 0)
{
HWND hlistctrl = GetWindow(hchild, GW_CHILD) ;
do
{
GetClassName(hlistctrl, classname, 200) ;
if (lstrcmp(classname, _T("SysTreeView32")) == 0)
{
m_hTreeCtrl = hlistctrl;
break ;
}
hlistctrl = GetWindow(hlistctrl, GW_HWNDNEXT) ;
} while (hlistctrl != NULL);
}
if (m_hTreeCtrl)
break;
hchild = GetWindow(hchild, GW_HWNDNEXT);
}
if (m_hTreeCtrl)
{
int item = ::SendMessage(m_hTreeCtrl, TVM_GETNEXTITEM, TVGN_CARET, 0);
if (item != 0)
::PostMessage(m_hTreeCtrl, TVM_ENSUREVISIBLE,0,item);
}
break;
}
I have read the above discussion and solutions. Particularly Brat Oestreicher put me in the right direction. In essence, we must first find the TreeView control in the SHBrowseForFolder dialog, and send that window the TVM_ENSUREVISIBLE message. The following does this in C.
#include <windows.h>
#include <objbase.h>
#include <objidl.h>
#include <Shlobj.h>
#include <Dsclient.h>
#include <wchar.h>
//
// EnumCallback - Callback function for EnumWindows
//
static BOOL CALLBACK EnumCallback(HWND hWndChild, LPARAM lParam)
{
char szClass[MAX_PATH];
HTREEITEM hNode;
if (GetClassName(hWndChild, szClass, sizeof(szClass))
&& strcmp(szClass,"SysTreeView32")==0) {
hNode = TreeView_GetSelection(hWndChild); // found the tree view window
TreeView_EnsureVisible (hWndChild, hNode); // ensure its selection is visible
return(FALSE); // done; stop enumerating
}
return(TRUE); // continue enumerating
}
//
// BrowseCallbackProc - Callback function for SHBrowseForFolder
//
static INT CALLBACK BrowseCallbackProc (HWND hWnd, UINT uMsg, LPARAM lParam, LPARAM lpData)
{
switch (uMsg)
{
case BFFM_INITIALIZED:
SendMessage (hWnd, BFFM_SETEXPANDED, TRUE, lpData); // expand the tree view
SendMessage (hWnd, BFFM_SETSELECTION, TRUE, lpData); // select the item
break;
case BFFM_SELCHANGED:
EnumChildWindows(hWnd, EnumCallback,0);
break;
}
return 0;
}
//
// SelectDirectory - User callable entry point
//
int SelectDirectory (HWND hWndParent, char *path, int pathSize)
{
BROWSEINFO bi = {0};
LPITEMIDLIST pidl = NULL;
wchar_t ws[MAX_PATH];
CoInitialize(0);
if (pathSize < MAX_PATH) return(FALSE);
swprintf(ws, MAX_PATH, L"%hs", path);
bi.hwndOwner = hWndParent;
bi.lpszTitle = "Select Directory";
bi.ulFlags = BIF_RETURNONLYFSDIRS | BIF_NEWDIALOGSTYLE;
bi.lpfn = BrowseCallbackProc;
bi.lParam = (LPARAM) ws;
pidl = SHBrowseForFolder (&bi);
if (pidl != NULL)
{
LPMALLOC pMalloc = NULL;
SHGetPathFromIDList (pidl, path);
path[pathSize-1]= '\0';
SHGetMalloc(&pMalloc);
pMalloc->lpVtbl->Free(pMalloc,pidl); // deallocate item
pMalloc->lpVtbl->Release(pMalloc);
return (TRUE);
}
return (FALSE);
}
Many thanks to Gary Beene.
dlgFolder.RootFolder = Environment.SpecialFolder.DesktopDirectory;
is not the same as
dlgFolder.RootFolder = Environment.SpecialFolder.Desktop;
What's the difference between SpecialFolder.Desktop and SpecialFolder.DesktopDirectory?
The thread linked indicates that as a path, they do get the same result. But they are not the same, as one is a logical path and the other is a physical path.
I have found when either one is assigned to the RootFolder of the open folder dialog, the resulting behavior can be different.
As a .RootFolder assignment, some versions of windows, like win7, treat either one as "Desktop". That is, you can see the "Computer" sub-entry, and open that to see the individual drive letters. The .SelectedPath gets selected either way, but the selected path is only made visible when the logical path of the desktop is assigned to the .RootFolder.
Worse, when using the browse folder dialog in win10 pre-release, it appears that "DesktopDirectory" as just that, the contents of the Desktop Directory only, with no link whatsoever to the logical desktop directory. And not listing any sub-items under it. Very frustrating if an app written for win7 is trying to be used with win10.
I think the problem the OP is having is that they employed the physical desktop as the root, when they should have employed the logical desktop.
I don't have an explanation for why the OP's two different machines respond differently. I would speculate that they have two different versions of the .NET framework installed.
The fact that win10 prerelease has the "Stuck on Desktop" issue with the browse folder dialog may be due to the more recent .NET framework shipped with win10 prerelease. Unfortunately, I remain ignorant of all the facts in this (win10) case, as I have not updated yet.
P.S. I found that win8 also experiences the "Stuck on Desktop" symptom:
https://superuser.com/questions/869928/windows-8-1-folder-selection-dialog-missing-my-computer-and-sub-items
The workaround there was to select the alternate GUI in win8. Perhaps something similar can be done in win10 prerelease.
In response to Marc F's post - I've converted the VB.Net to C#
public enum GW
{
HWNDFIRST = 0,
HWNDLAST = 1,
HWNDNEXT = 2,
HWNDPREV = 3,
OWNER = 4,
CHILD = 5,
ENABLEDPOPUP = 6
}
[System.Runtime.InteropServices.DllImport("user32.dll", EntryPoint = "SendMessageW", ExactSpelling = true, CharSet = System.Runtime.InteropServices.CharSet.Ansi, SetLastError = true)]
public static extern IntPtr SendMessageW(IntPtr hWnd, uint msg, int wParam, [MarshalAs(UnmanagedType.LPWStr)] string lParam);
[System.Runtime.InteropServices.DllImport("user32.dll", EntryPoint = "FindWindowExW", ExactSpelling = true, CharSet = System.Runtime.InteropServices.CharSet.Ansi, SetLastError = true)]
public static extern IntPtr FindWindowExW(IntPtr hWndParent, IntPtr hWndChildAfter, [MarshalAs(UnmanagedType.LPWStr)] string lpszClass, [MarshalAs(UnmanagedType.LPWStr)] string lpszWindow);
[System.Runtime.InteropServices.DllImport("user32", EntryPoint = "GetWindow", ExactSpelling = true, CharSet = System.Runtime.InteropServices.CharSet.Ansi, SetLastError = true)]
public static extern UInt32 GetWindow(IntPtr hwnd, UInt32 wCmd);
[System.Runtime.InteropServices.DllImport("user32", EntryPoint = "GetDesktopWindow", ExactSpelling = true, CharSet = System.Runtime.InteropServices.CharSet.Ansi, SetLastError = true)]
public static extern IntPtr GetDesktopWindow();
[System.Runtime.InteropServices.DllImport("user32", EntryPoint = "GetClassNameA", ExactSpelling = true, CharSet = System.Runtime.InteropServices.CharSet.Ansi, SetLastError = true)]
public static extern int GetClassName(IntPtr hwnd, System.Text.StringBuilder lpClassName, int nMaxCount);
private void FolderBrowserDialog_EnsureVisible(FolderBrowserDialog FB, IntPtr _Owner)
{
IntPtr hwnd = System.IntPtr.Zero;
System.Text.StringBuilder sClassname = new System.Text.StringBuilder(256);
Thread.Sleep(50); //necessary to let FolderBrowserDialog construct its window
hwnd = GetDesktopWindow(); //Desktop window handle.
hwnd = (System.IntPtr)GetWindow(hwnd, (UInt32)GW.CHILD); //We will find all children.
while (!(hwnd == (System.IntPtr)0))
{
if (GetWindow(hwnd, (UInt32)GW.OWNER) == (UInt32)_Owner) //If one window is owned by our main window...
{
GetClassName(hwnd, sClassname, 255);
if (sClassname.ToString() == "#32770") //Check if the class is FolderBrowserDialog.
{
break; //Then we found it.
}
}
hwnd = (System.IntPtr)GetWindow(hwnd, (UInt32)GW.HWNDNEXT); //Next window.
} //If no found then exit.
if (hwnd == (System.IntPtr)0)
{
return;
}
IntPtr hChild = (System.IntPtr)0;
IntPtr hTreeView = (System.IntPtr)0;
int i = 0;
do
{
i += 1;
if (i > 1000) //Security to avoid infinite loop.
{
return;
}
hChild = FindWindowExW(hwnd, hChild, null, null); //Look for children windows of FolderBrowserDialog.
hTreeView = FindWindowExW(hChild, (System.IntPtr)0, "SysTreeView32", null); //Look for treeview of FolderBrowserDialog.
Thread.Sleep(5); //delay necessary because FolderBrowserDialog is in construction, then treeview maybe not yet exist.
} while (hTreeView == (System.IntPtr)0);
if (SendMessageW(hwnd, 0x46A, 1, FB.SelectedPath) == (System.IntPtr)0) //Send message BFFM_SETEXPANDED to FolderBrowserDialog.
{
SendMessageW(hTreeView, 0x7, 0, null); //Send message WM_SETFOCUS to the treeeview.
}
}
Tested this and it works fine. Make sure you reference System.Runtime.InteropServices, System.Threading, an System.Threading.Tasks
This link has a simple answer that worked for me fine (I have windows 8.1)
FolderBrowserDialog: Expanding the selected directory
I tried the code SendKeys.Send("{TAB}{TAB}{RIGHT}");, but the same code sometimes just didn't work.
At the end the best solution which I went with, where I can't get it not working, was:
public static class FolderBrowserDialogExt
{
public static void ScrollSelectedPathIntoView(this FolderBrowserDialog fbd)
{
System.Threading.Tasks.Task.Run(() =>
{
SendKeys.SendWait("{TAB}");
SendKeys.SendWait("{TAB}");
SendKeys.SendWait("{RIGHT}");
});
}
}
void SomeMeth(string selPath)
{
using (var fbd = new FolderBrowserDialog())
{
fbd.SelectedPath = selPath;
fbd.ScrollSelectedPathIntoView();
fbd.ShowDialog();//fbd.ShowDialog(owner);
}
}
This is how I use the folder browser dialog. This code solves the selected folder issue, and also selects the folder from the clipboard or the registry (if any), and if the folder is deleted it goes up throw parents until selecting an existing folder. This makes using the dialog very comfortable:
Dim FldrBr As New FolderBrowserDialog With {
.RootFolder = Environment.SpecialFolder.Desktop,
.Description = "Chose a flder",
.ShowNewFolderButton = False
}
Dim x = Clipboard.GetText()
Dim lastDir = GetSetting("Mp4Joiner", "SrcFolder", "Path", "")
Try
If x = "" Then
x = lastDir
ElseIf File.Exists(x) Then
x = Path.GetDirectoryName(x)
ElseIf Not Directory.Exists(x) Then
x = lastDir
End If
Catch
x = lastDir
End Try
Do
If x = "" OrElse Directory.GetDirectoryRoot(x) = x OrElse Directory.Exists(x) Then
Exit Do
End If
x = Path.GetDirectoryName(x)
Loop
FldrBr.SelectedPath = x
Dim th As New Threading.Thread(
Sub()
Threading.Thread.Sleep(300)
SendKeys.SendWait("{TAB}{TAB}{RIGHT}")
End Sub)
th.Start()
If FldrBr.ShowDialog(Me) = Windows.Forms.DialogResult.OK Then
SaveSetting("Mp4Joiner", "SrcFolder", "Path", FldrBr.SelectedPath)
' ........
End If
The best approach, at least the most reliable is to make your own browser class dialog box. The tree scrolling issue has been a pain for many years - it will never get fixed!
If you know how to render in paint there is not much you can't do.. fast in paint well that is another story.
The first place I would look is at the open source .Net source code on GitHub, in your .Net version of choice, for the dialog class you're interested in improving. You may be surprised what you can achieve with a little effort and follow through. Just duplicate the control and debug to the point where the error occurs and patch - that'a what Microsoft does, so too can you!
Since this is an old thread and posting samples may never get read. It would make more since to post if asked.
Yet for someone looking to solve such an issue as with tree scrolling to the "expected" directory, here is some solid advise. If an issue exists with a control or library that has no immediate solution, create your own version, when possible extend the original and patch the problem. I've revamped everything from the Windows.Form.Control class to Win32 libraries for the sole purpose of getting predictable and accurate results.
The good news is that with C# there is a lot of low level control available to achieve almost any reasonable objective and the is C too.
In the past I have spent way too many hours searching for a solution to a problem where had I just recreated what was not working a lot of time would have been saved.
The goal is
I want to change the opacity of some application (target) running on win xp.
The situation
I logged in win xp as AD (active dir) user account (some-domain\username)
most of the target applications are run as local user or another ad user
The problem and my Question
SetWindowLong & SetLayeredWindowAttributes doesn't work on target application that run as other user account. But it works on target application that runs under the same user account (logged user account)
How to change other app's window opacity that run as different user account?
Illustration app
It's a win form app (let's call it OpaciToggler.exe). I have 2 buttons (btnRunSomething and btnHideThatThing) and a textbox (txtPid). As simple as that.
When I click the btnRunSomething, it run an .exe as different user. All the details is in the app.config. In this case I run this application (OpaciToggler.exe, from the debug/bin) as different user (localComputer\user1)
The txtPid is for manual pid input. Usually I open the task manager (win > run > taskmgr) and find the pid (under the process tab) of any application (target) I want to test with, then type it here.
When I click the btnHideThatThing, it'll toggle the opacity of the target application (whose pid in the txtPid)
Code C#
This is how I call the target app.
private void btnRunSomething_Click(object sender, EventArgs e)
{
System.Diagnostics.Process p = new System.Diagnostics.Process();
p.StartInfo.Domain = ConfigurationManager.AppSettings["StartInfoDomain"];
p.StartInfo.UserName = ConfigurationManager.AppSettings["StartInfoUserName"];
p.StartInfo.FileName = ConfigurationManager.AppSettings["StartInfoFileName"];
p.StartInfo.Arguments = ConfigurationManager.AppSettings["StartInfoArguments"];
System.String rawPassword = ConfigurationManager.AppSettings["StartInfoPassword"];
System.Security.SecureString encPassword = new System.Security.SecureString();
foreach (System.Char c in rawPassword)
{
encPassword.AppendChar(c);
}
p.StartInfo.Password = encPassword;
p.StartInfo.UseShellExecute = false;
p.Start();
}
This is how I try to call the opacity thing
private void btnHideThatThing_Click(object sender, EventArgs e)
{
// manual input pid
int pid = int.Parse(txtPid.Text);
IntPtr mwh = Process.GetProcessById(pid).MainWindowHandle;
doHideThing(mwh);
// doHideThing(Process.GetProcessById(int.Parse(txtPid.Text)).MainWindowHandle);
}
The hide method
private void doHideThing(IntPtr hndl)
{
SetWindowLong(hndl, GWL_EXSTYLE, GetWindowLong(hndl, GWL_EXSTYLE) ^ WS_EX_LAYERED).ToString();
// _whatNow = Marshal.GetLastWin32Error();
SetLayeredWindowAttributes(hndl, 0, (255 * 20) / 100, LWA_ALPHA).ToString();
// _whatNow = Marshal.GetLastWin32Error();
RedrawWindow(hndl, IntPtr.Zero, IntPtr.Zero,
RedrawWindowFlags.Erase | RedrawWindowFlags.Invalidate |
RedrawWindowFlags.Frame | RedrawWindowFlags.AllChildren);
}
other codes
private const int GWL_EXSTYLE = -20;
private const int GWL_STYLE = -16;
private const int WS_EX_LAYERED = 0x80000;
private const int LWA_ALPHA = 0x2;
private const int LWA_COLORKEY = 0x1;
[DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
static extern int SetWindowLong(IntPtr hWnd, int nIndex, int dwNewLong);
[DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall, SetLastError = true)]
private static extern int GetWindowLong(IntPtr hWnd, int nIndex);
[DllImport("user32.dll", SetLastError = true)]
static extern bool RedrawWindow(IntPtr hWnd, IntPtr lprcUpdate, IntPtr hrgnUpdate, RedrawWindowFlags flags);
RedrawWindowFlags enum (I don't remember where I get this)
[Flags()]
enum RedrawWindowFlags : uint
{
/// <summary>
/// Invalidates the rectangle or region that you specify in lprcUpdate or hrgnUpdate.
/// You can set only one of these parameters to a non-NULL value. If both are NULL, RDW_INVALIDATE invalidates the entire window.
/// </summary>
Invalidate = 0x1,
/// <summary>Causes the OS to post a WM_PAINT message to the window regardless of whether a portion of the window is invalid.</summary>
InternalPaint = 0x2,
/// <summary>
/// Causes the window to receive a WM_ERASEBKGND message when the window is repainted.
/// Specify this value in combination with the RDW_INVALIDATE value; otherwise, RDW_ERASE has no effect.
/// </summary>
Erase = 0x4,
/// <summary>
/// Validates the rectangle or region that you specify in lprcUpdate or hrgnUpdate.
/// You can set only one of these parameters to a non-NULL value. If both are NULL, RDW_VALIDATE validates the entire window.
/// This value does not affect internal WM_PAINT messages.
/// </summary>
Validate = 0x8,
NoInternalPaint = 0x10,
/// <summary>Suppresses any pending WM_ERASEBKGND messages.</summary>
NoErase = 0x20,
/// <summary>Excludes child windows, if any, from the repainting operation.</summary>
NoChildren = 0x40,
/// <summary>Includes child windows, if any, in the repainting operation.</summary>
AllChildren = 0x80,
/// <summary>Causes the affected windows, which you specify by setting the RDW_ALLCHILDREN and RDW_NOCHILDREN values, to receive WM_ERASEBKGND and WM_PAINT messages before the RedrawWindow returns, if necessary.</summary>
UpdateNow = 0x100,
/// <summary>
/// Causes the affected windows, which you specify by setting the RDW_ALLCHILDREN and RDW_NOCHILDREN values, to receive WM_ERASEBKGND messages before RedrawWindow returns, if necessary.
/// The affected windows receive WM_PAINT messages at the ordinary time.
/// </summary>
EraseNow = 0x200,
Frame = 0x400,
NoFrame = 0x800
}
Example, Test and Test Result
logged in win xp as domainA\ad_user1
run the OpaciToggler.exe application (Instance1, run as domainA\ad_user1)
Then I click the run something. It opens another OpaciToggler.exe (Instance2), runs as another user account (run as localcomputer\l_user1) OR right click the .exe then click run as..
Instance1
pid: 1234
run as: domainA\ad_user1
txtPid: 5678 (Instance2, doesn't work)
txtPid: 1234 (self, works)
txtPid: any pid run as the same account (ex: notepad.exe, calc.exe, etc, works)
Instance2
pid: 5678
run as: localcomputer\l_user1
txtPid: 1234 (Instance 1, doesnt work)
txtPid: 5678 (self, works)
txtPid: any pid run as the same account (localcomputer\l_user1) (ex: notepad.exe, calc.exe, etc, doesn't work!!)
Again, my Question
How to change other app's window opacity that run as different user account?
Thanks and sorry for my bad English :(.
Hi,
We have a winform application that is only to be executed as a singelton, If a second instance try to start this new instance will connect to the current and transmit parameters over namedpipes.
The problem is that when starting the first instance there will be a try to connect to existing host. If the host is not existing(like in this case) an exception will be thrown. There is no problem to handle this exception but our developers is often using "Break on Exception" and that means that every time we startup the application the developer will get two(in this case) breaks about exception. Thay will have to hit F5 twice for every start.
Is there any way to check if the service is available without throw exception if its not?
BestRegards
Edit1:
[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool CloseHandle(IntPtr hObject);
[DllImport("kernel32.dll", SetLastError = true)]
static extern IntPtr OpenFileMapping(uint dwDesiredAccess, bool bInheritHandle, string lpName);
The following code says : Error 152 Cannot implicitly convert type 'System.IntPtr' to 'Orbit.Client.Main.Classes.Controllers.MyClientController.SafeFileMappingHandle'
using (SafeFileMappingHandle fileMappingHandle
= OpenFileMapping(FILE_MAP_READ, false, sharedMemoryName))
{
If there is already a WCF server listening on the named pipe endpoint, there will be a shared memory object created, via which the server publishes the actual name of the pipe. See here for details of this.
You can check for the existence of this shared memory object with code something like the following, which will not throw, just return false, if there is no server running already. (I've extracted this from code I already have working, and then edited it to do what you want - but without testing the edited version, so apologies if you have to fix up assembly/namespace refs etc to get it running.)
public static class ServiceInstanceChecker
{
public static bool DoesAServerExistAlready(string hostName, string path)
{
return IsNetNamedPipeSharedMemoryMetaDataPublished(DeriveSharedMemoryName(hostName, path));
}
private static string DeriveSharedMemoryName(string hostName, string path)
{
StringBuilder builder = new StringBuilder();
builder.Append(Uri.UriSchemeNetPipe);
builder.Append("://");
builder.Append(hostName.ToUpperInvariant());
builder.Append(path);
byte[] uriBytes = Encoding.UTF8.GetBytes(builder.ToString());
string encodedNameRoot;
if (uriBytes.Length >= 0x80)
{
using (HashAlgorithm algorithm = new SHA1Managed())
{
encodedNameRoot = ":H" + Convert.ToBase64String(algorithm.ComputeHash(uriBytes));
}
}
else
{
encodedNameRoot = ":E" + Convert.ToBase64String(uriBytes);
}
return Uri.UriSchemeNetPipe + encodedNameRoot;
}
private static bool IsNetNamePipeSharedMemoryMetaDataPublished(string sharedMemoryName)
{
const uint FILE_MAP_READ = 0x00000004;
const int ERROR_FILE_NOT_FOUND = 2;
using (SafeFileMappingHandle fileMappingHandle
= OpenFileMapping(FILE_MAP_READ, false, sharedMemoryName))
{
if (fileMappingHandle.IsInvalid)
{
int errorCode = Marshal.GetLastWin32Error();
if (ERROR_FILE_NOT_FOUND == errorCode) return false;
throw new Win32Exception(errorCode); // The name matched, but something went wrong opening it
}
return true;
}
}
private class SafeFileMappingHandle : SafeHandleZeroOrMinusOneIsInvalid
{
public SafeFileMappingHandle() : base(true) { }
public SafeFileMappingHandle(IntPtr handle) : base(true) { base.SetHandle(handle); }
protected override bool ReleaseHandle()
{
return CloseHandle(base.handle);
}
}
}
The host name and path you pass in are derived from the WCF service url. Hostname is either a specific hostname (e.g. localhost) or +, or *, depending on the setting for HostNameComparisonMode.
EDIT: You'll also need a couple of P/Invoke declarations for the Win API functions:
[DllImport("kernel32.dll", SetLastError = true)]
static extern bool CloseHandle(IntPtr hObject);
[DllImport("kernel32.dll", SetLastError = true)]
static extern SafeFileMappingHandle OpenFileMapping(
uint dwDesiredAccess,
bool inheritHandle,
string name
);
EDIT2: We need to tweak the return value of DeriveSharedMemoryName to specify the Local kernel namespace, assuming that your application is not run with elevated privileges. Change the last line of this function to read:
return #"Local\" + Uri.UriSchemeNetPipe + encodedNameRoot;
You also need to specify the hostname parameter correctly to match the hostNameComparisonMode setting used in your binding. As far as I recall, this defaults to StrongWildcard matching in the NetNamedPipeBinding, so you probably need to pass in "+" rather than "localhost".
Can you try to list the named pipes available using
String[] listOfPipes = System.IO.Directory.GetFiles(#"\.\pipe\");
and then determine is your named pipe is amongst them?
My solution is the following :
if (Debugger.IsAttached)
return true;
This will make sure that the code for checking the service is never runned during debugging.
BestRegards