Related
Background
I am working on a application that make extensive use of SetParent functionality in order to play several video players into a single application while keeping memory of main applicaton under control.
Everytime the user requests to see a video, a new player.exe is executed and attached to the main window. This is working fine for most use case scenarios.
But there is one I am struggling with. In this scenario the user is playing lots of videos in a fast sequence, which means that main application is constantly killing and creating new players.
Everytime a player.exe is executed, a small hourglass icon appears on the mouse icon, and given that in this case scenario those players are created pretty fast, then the hourglass icon keeps playing constantly.
Motivation
I guess this is possible as for example google chrome makes use of this for each tab and you can add multiple taps without the busy hourglass icon to appear on each tab creation.
Details
I am in control of both main application and player applications just to note that I could do any change to both.
I have made a small windows form application as example of this behaviour, with 2 buttons and 1 panel.
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Windows.Forms;
namespace SetParentTest
{
public partial class Form1 : Form
{
private Process _childProcessPlayer;
public Form1()
{
InitializeComponent();
this.Closing += (sender, args) =>
{
Clear();
};
}
public const UInt32 WS_POPUP = 0x80000000;
public const UInt32 WS_CHILD = 0x40000000;
[DllImport("user32.dll", SetLastError = true)]
public static extern IntPtr SetParent(
IntPtr windowChildHandle,
IntPtr windowNewParentHandle);
[DllImport("user32.dll", SetLastError = true)]
public static extern UInt32 GetWindowLong(IntPtr hWnd, int nIndex);
[DllImport ( "user32.dll" )]
public static extern int SetWindowLong ( IntPtr hWnd, int nIndex, uint dwNewLong );
public enum WindowLongFlags : int
{
GWL_EXSTYLE = -20,
GWLP_HINSTANCE = -6,
GWLP_HWNDPARENT = -8,
GWL_ID = -12,
GWL_STYLE = -16,
GWL_USERDATA = -21,
GWL_WNDPROC = -4,
DWLP_USER = 0x8,
DWLP_MSGRESULT = 0x0,
DWLP_DLGPROC = 0x4
}
[DllImport("user32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool MoveWindow(
IntPtr windowHandle,
int x,
int y,
int width,
int height,
[MarshalAs(UnmanagedType.Bool)] bool repaint);
[DllImport("User32.dll", ExactSpelling = true, CharSet = CharSet.Auto)]
public static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);
/// <summary>Enumeration of the different ways of showing a window using
/// ShowWindow</summary>
private enum WindowShowStyle : uint
{
/// <summary>Hides the window and activates another window.</summary>
/// <remarks>See SW_HIDE</remarks>
Hide = 0,
/// <summary>Activates and displays a window. If the window is minimized
/// or maximized, the system restores it to its original size and
/// position. An application should specify this flag when displaying
/// the window for the first time.</summary>
/// <remarks>See SW_SHOWNORMAL</remarks>
ShowNormal = 1,
/// <summary>Activates the window and displays it as a minimized window.</summary>
/// <remarks>See SW_SHOWMINIMIZED</remarks>
ShowMinimized = 2,
/// <summary>Activates the window and displays it as a maximized window.</summary>
/// <remarks>See SW_SHOWMAXIMIZED</remarks>
ShowMaximized = 3,
/// <summary>Maximizes the specified window.</summary>
/// <remarks>See SW_MAXIMIZE</remarks>
Maximize = 3,
/// <summary>Displays a window in its most recent size and position.
/// This value is similar to "ShowNormal", except the window is not
/// actived.</summary>
/// <remarks>See SW_SHOWNOACTIVATE</remarks>
ShowNormalNoActivate = 4,
/// <summary>Activates the window and displays it in its current size
/// and position.</summary>
/// <remarks>See SW_SHOW</remarks>
Show = 5,
/// <summary>Minimizes the specified window and activates the next
/// top-level window in the Z order.</summary>
/// <remarks>See SW_MINIMIZE</remarks>
Minimize = 6,
/// <summary>Displays the window as a minimized window. This value is
/// similar to "ShowMinimized", except the window is not activated.</summary>
/// <remarks>See SW_SHOWMINNOACTIVE</remarks>
ShowMinNoActivate = 7,
/// <summary>Displays the window in its current size and position. This
/// value is similar to "Show", except the window is not activated.</summary>
/// <remarks>See SW_SHOWNA</remarks>
ShowNoActivate = 8,
/// <summary>Activates and displays the window. If the window is
/// minimized or maximized, the system restores it to its original size
/// and position. An application should specify this flag when restoring
/// a minimized window.</summary>
/// <remarks>See SW_RESTORE</remarks>
Restore = 9,
/// <summary>Sets the show state based on the SW_ value specified in the
/// STARTUPINFO structure passed to the CreateProcess function by the
/// program that started the application.</summary>
/// <remarks>See SW_SHOWDEFAULT</remarks>
ShowDefault = 10,
/// <summary>Windows 2000/XP: Minimizes a window, even if the thread
/// that owns the window is hung. This flag should only be used when
/// minimizing windows from a different thread.</summary>
/// <remarks>See SW_FORCEMINIMIZE</remarks>
ForceMinimized = 11
}
/// <summary>
/// Handles the Click event of the button1 control.
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="e">The <see cref="EventArgs"/> instance containing the event data.</param>
private void button1_Click(object sender, EventArgs e)
{
AttachWindow();
}
/// <summary>
/// Handles the Click event of the button1 control.
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="e">The <see cref="EventArgs"/> instance containing the event data.</param>
private void button2_Click(object sender, EventArgs e)
{
Clear();
}
private void Clear()
{
if (_childProcessPlayer == null)
return;
if (_childProcessPlayer.HasExited)
{
_childProcessPlayer = null;
return;
}
_childProcessPlayer.Kill();
_childProcessPlayer = null;
}
private void AttachWindow()
{
// do it only once per test.
if (_childProcessPlayer != null)
return;
// Instance of the remote process to start.
//_childProcessPlayer = Process.GetProcessesByName("notepad").FirstOrDefault();
_childProcessPlayer = new Process
{
StartInfo =
{
FileName = #"C:\Windows\System32\notepad.exe",
//CreateNoWindow = true,
UseShellExecute = true,
WindowStyle = ProcessWindowStyle.Minimized
},
EnableRaisingEvents = true
};
Cursor.Current = Cursors.Default;
_childProcessPlayer.Start();
Cursor.Current = Cursors.Default;
_childProcessPlayer.WaitForInputIdle();
Cursor.Current = Cursors.Default;
ShowWindow(_childProcessPlayer.MainWindowHandle, (int)WindowShowStyle.Hide);
// Get process window handle.
var mainWindowHandle = _childProcessPlayer.MainWindowHandle;
// To prevent focus steal when SetParent is called I need to add WS_CHILD to the style.
uint windowLong = GetWindowLong(
mainWindowHandle,
(int) WindowLongFlags.GWL_STYLE);
// add ws_child
windowLong |= WS_CHILD;
// remove pop_up (most cases this is not necessary as it is already unset)
windowLong &= ~WS_POPUP;
// modify the style.
SetWindowLong(
mainWindowHandle,
(int)WindowLongFlags.GWL_STYLE,
windowLong);
// Disable panel to prevent focus being stolen. (necessary in some cases)
panel1.Enabled = false;
// Execute Set parent.
SetParent(mainWindowHandle, panel1.Handle);
// Restore child state in order to allow editing in the notepad.
windowLong &= ~WS_CHILD;
SetWindowLong(
mainWindowHandle,
(int)WindowLongFlags.GWL_STYLE,
windowLong);
// Hide panel while notepad is resized.
panel1.Visible = false;
// Show notepad so resizing work
ShowWindow(_childProcessPlayer.MainWindowHandle, (int)WindowShowStyle.ShowNormal);
// Resize and move the window to the panel size.
MoveWindow(mainWindowHandle, 0, 0, panel1.Width, panel1.Height, true);
panel1.Visible = true;
panel1.Enabled = true;
}
}
}
I leave the whole code as someone might be interested. Obviously I am not in control of notepad.exe though.
This code is the closest I can get to the one I use in the original application. For unknown reasons starting hidden was not working fine with notepad so I had to hide it afterwards so it appears nicely without blinks into the panel.
Already tried
I have noticed that retrieving notepad if it is already started have
no issues.
Calling Process.Start in a Task, or a different thread,
have no effect.
I have noticed that the hourglass appears even when
you execute it directly on windows so the hourglass effect might me
something that could be disabled by the child executable. (which
means the adhoc test provided would never work as it is using
notepad.exe).
Question
Finally the question is: is it possible to call Process Start of a child application avoiding the hourglass to appear on the mouse while doing so?
Worst case scenario would be to disable hourglass icon at windows registry (I guess this is possible although I currenltly don't know) but this would be my last option.
Edit 1:
Another thing I have tried:
Forcing the mouse icon before calling Process start.
I thought it was going to work as it worked for the hand icon or a different icon than the pointer, but if the icon is the pointer, it does not work.
Cursor cr = new Cursor(Cursors.Arrow.Handle);
Icon ico = Icon.FromHandle(cr.Handle);
cr = new Cursor(ico.Handle);
Cursor.Current = cr;
_childProcessPlayer.Start();
_childProcessPlayer.WaitForInputIdle();
Cursor.Current = Cursors.Default;
I came up with 2 workarounds although not perfect ones:
1- Disable mouse with hourglass at windows level by configuring a different icon.
Obviously that affects everything.
2- Forcing the icon before the operation did work if it does not know the icon is the arrow:
Icon ico = Icon.FromHandle(Cursor.Current.Handle);
ico = (Icon)ico.Clone();
Cursor.Current = new Cursor(ico.Handle);
_childProcessPlayer.Start();
_childProcessPlayer.BeginOutputReadLine();
_childProcessPlayer.WaitForInputIdle();
Cursor.Current = Cursors.Default;
The problem with this other solution is that it only works well if the mouse remains over your application. If the mouse is outside it setting the icon does not work 100% of the times.
I want to set the foreground of a window in Visual Studio.
What i've tried:
When i have 2 windows "docked" side by side i can't set the foreground of a window under the mouse in VS.
I'm using GetCursorPos and WindowFromPoint which usually works for standard windows. I also tried to use EnumChildWindows from pinvoke.net (sample code 2 -https://www.pinvoke.net/default.aspx/user32.enumchildwindows) but it returns 0 when i pass the WindowFromPoint or MainWindowHandle of the process.
I assume it's not a typical child window technically or something else that i don't understand?
public static IntPtr GetProcessMainWindowHandle()
{
Process process = null;
if (GetCursorPos(out Point point))
{
IntPtr hWnd = WindowFromPoint(point);
GetWindowThreadProcessId(hWnd, out uint pid);
process = Process.GetProcessById((int)pid);
}
return process.MainWindowHandle;
}
public static IntPtr GetHandle()
{
IntPtr hWnd = IntPtr.Zero;
if (GetCursorPos(out Point point))
{
hWnd = WindowFromPoint(point);
}
return hWnd;
}
As mentioned in the comments from Lars, it could be a WPF window.
I can see that i can only get the handle for the parent window as described for WPF behaviour but i get still null with the following code. Maybe i have something wrong? (Any hint would be great.)
public static void GetWPFObject(IntPtr handle)
{
HwndSource source = HwndSource.FromHwnd(handle) as HwndSource;
}
I have a wpf application which runs on the windows 8 tablet . And in order to bring the keyboard for typing when the focus is on any TextBox.
I am invoking the process TabTip.exe to show the keyboard, and when the keyboard is shown my application shrinks. And after all manipulation, there is a save button. When I click on the save button, the keyboard should disappear and my application should come back to its original size.
I am killing the process TabTip.exe to close the keyboard, but the application will not get re-sized to its original size .
I tried:
if (process.ProcessName == "TabTip")
{
Application.Current.MainWindow.VerticalAlignment = VerticalAlignment.Stretch;
process.Kill();
Application.Current.MainWindow.Height = SystemParameters.WorkArea.Height;
Application.Current.MainWindow.Width = SystemParameters.WorkArea.Width;
Application.Current.MainWindow.WindowState = WindowState.Normal;
Application.Current.MainWindow.WindowState = WindowState.Maximized;
break;
}
Does anybody knows to restore the application to its original size after killing the TabTip.exe?
The Windows 8 keyboard has a number of rendering problems. These can be mitigated by starting the keyboard in its smaller mode (equivalent to hitting the minimize button). It plays much better with WPF then, actually minimizing and expanding when the process is launched and closed.
This requires launching the process in this mode, and closing it in a nicer way than you are doing right now
Include these libraries:
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Windows.Interop;
And define this external functions:
[return: MarshalAs(UnmanagedType.Bool)]
[DllImport("user32.dll", SetLastError = true)]
public static extern bool PostMessage(int hWnd, uint Msg, int wParam, int lParam);
[DllImport("user32.dll")]
public static extern IntPtr FindWindow(String sClassName, String sAppName);
Open the keyboard with:
public static void openKeyboard()
{
ProcessStartInfo startInfo = new ProcessStartInfo(#"C:\Program Files\Common Files\Microsoft Shared\ink\TabTip.exe");
startInfo.WindowStyle = ProcessWindowStyle.Hidden;
myProcess = Process.Start(startInfo);
}
and close it with:
public static void closeKeyboard()
{
uint WM_SYSCOMMAND = 274;
uint SC_CLOSE = 61536;
IntPtr KeyboardWnd = FindWindow("IPTip_Main_Window", null);
PostMessage(KeyboardWnd.ToInt32(), WM_SYSCOMMAND, (int)SC_CLOSE, 0);
}
This will give you the best behaved windows 8 on screen keyboard you can get. With any luck it will fix your rendering issues.
Abin - you asked about closing the keyboard window rather than killing the process. That's what I'm doing in a WPF app and by closing the window, my main application's window will resize as expected. A quick console app to demonstrate hiding the keyboard is here (note that this assumes you're using the keyboard in docked mode rather than the floating minimal mode):
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
namespace TabTipTest
{
class Program
{
[DllImport("user32.dll")]
public static extern IntPtr FindWindow(String sClassName, String sAppName);
[DllImport("user32.dll")]
public static extern int SendMessage(IntPtr hWnd, uint Msg, int wParam, int lParam);
/// <summary>
/// The command for a user choosing a command from the Window menu (see http://msdn.microsoft.com/en-gb/library/windows/desktop/ms646360(v=vs.85).aspx).
/// </summary>
public const int WM_SYSCOMMAND = 0x0112;
/// <summary>
/// Closes the window.
/// </summary>
public const int SC_CLOSE = 0xF060;
static void Main(string[] args)
{
HideKeyboard();
}
/// <summary>
/// Gets the window handler for the virtual keyboard.
/// </summary>
/// <returns>The handle.</returns>
public static IntPtr GetKeyboardWindowHandle()
{
return FindWindow("IPTip_Main_Window", null);
}
/// <summary>
/// Hides the keyboard by sending the window the close command.
/// </summary>
public static void HideKeyboard()
{
IntPtr keyboardHandle = GetKeyboardWindowHandle();
if (keyboardHandle != IntPtr.Zero)
{
SendMessage(keyboardHandle, WM_SYSCOMMAND, SC_CLOSE, 0);
}
}
}
}
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.
How can I bring my WPF application to the front of the desktop? So far I've tried:
SwitchToThisWindow(new WindowInteropHelper(Application.Current.MainWindow).Handle, true);
SetWindowPos(new WindowInteropHelper(Application.Current.MainWindow).Handle, IntPtr.Zero, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE);
SetForegroundWindow(new WindowInteropHelper(Application.Current.MainWindow).Handle);
None of which are doing the job (Marshal.GetLastWin32Error() is saying these operations completed successfully, and the P/Invoke attributes for each definition do have SetLastError=true).
If I create a new blank WPF application, and call SwitchToThisWindow with a timer, it works exactly as expected, so I'm not sure why it's not working in my original case.
Edit: I'm doing this in conjunction with a global hotkey.
myWindow.Activate();
Attempts to bring the window to the foreground and activates it.
That should do the trick, unless I misunderstood and you want Always on Top behavior. In that case you want:
myWindow.TopMost = true;
I have found a solution that brings the window to the top, but it behaves as a normal window:
if (!Window.IsVisible)
{
Window.Show();
}
if (Window.WindowState == WindowState.Minimized)
{
Window.WindowState = WindowState.Normal;
}
Window.Activate();
Window.Topmost = true; // important
Window.Topmost = false; // important
Window.Focus(); // important
In case you need the window to be in front the first time it loads then you should use the following:
private void Window_ContentRendered(object sender, EventArgs e)
{
this.Topmost = false;
}
private void Window_Initialized(object sender, EventArgs e)
{
this.Topmost = true;
}
Or by overriding the methods:
protected override void OnContentRendered(EventArgs e)
{
base.OnContentRendered(e);
Topmost = false;
}
protected override void OnInitialized(EventArgs e)
{
base.OnInitialized(e);
Topmost = true;
}
I know this question is rather old, but I've just come across this precise scenario and wanted to share the solution I've implemented.
As mentioned in comments on this page, several of the solutions proposed do not work on XP, which I need to support in my scenario. While I agree with the sentiment by #Matthew Xavier that generally this is a bad UX practice, there are times where it's entirely a plausable UX.
The solution to bringing a WPF window to the top was actually provided to me by the same code I'm using to provide the global hotkey. A blog article by Joseph Cooney contains a link to his code samples that contains the original code.
I've cleaned up and modified the code a little, and implemented it as an extension method to System.Windows.Window. I've tested this on XP 32 bit and Win7 64 bit, both of which work correctly.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Interop;
using System.Runtime.InteropServices;
namespace System.Windows
{
public static class SystemWindows
{
#region Constants
const UInt32 SWP_NOSIZE = 0x0001;
const UInt32 SWP_NOMOVE = 0x0002;
const UInt32 SWP_SHOWWINDOW = 0x0040;
#endregion
/// <summary>
/// Activate a window from anywhere by attaching to the foreground window
/// </summary>
public static void GlobalActivate(this Window w)
{
//Get the process ID for this window's thread
var interopHelper = new WindowInteropHelper(w);
var thisWindowThreadId = GetWindowThreadProcessId(interopHelper.Handle, IntPtr.Zero);
//Get the process ID for the foreground window's thread
var currentForegroundWindow = GetForegroundWindow();
var currentForegroundWindowThreadId = GetWindowThreadProcessId(currentForegroundWindow, IntPtr.Zero);
//Attach this window's thread to the current window's thread
AttachThreadInput(currentForegroundWindowThreadId, thisWindowThreadId, true);
//Set the window position
SetWindowPos(interopHelper.Handle, new IntPtr(0), 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE | SWP_SHOWWINDOW);
//Detach this window's thread from the current window's thread
AttachThreadInput(currentForegroundWindowThreadId, thisWindowThreadId, false);
//Show and activate the window
if (w.WindowState == WindowState.Minimized) w.WindowState = WindowState.Normal;
w.Show();
w.Activate();
}
#region Imports
[DllImport("user32.dll")]
private static extern IntPtr GetForegroundWindow();
[DllImport("user32.dll")]
private static extern uint GetWindowThreadProcessId(IntPtr hWnd, IntPtr ProcessId);
[DllImport("user32.dll")]
private static extern bool AttachThreadInput(uint idAttach, uint idAttachTo, bool fAttach);
[DllImport("user32.dll")]
public static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, uint uFlags);
#endregion
}
}
I hope this code helps others who encounter this problem.
To make this a quick copy-paste one -
Use this class' DoOnProcess method to move process' main window to foreground (but not to steal focus from other windows)
public class MoveToForeground
{
[DllImportAttribute("User32.dll")]
private static extern int FindWindow(String ClassName, String WindowName);
const int SWP_NOMOVE = 0x0002;
const int SWP_NOSIZE = 0x0001;
const int SWP_SHOWWINDOW = 0x0040;
const int SWP_NOACTIVATE = 0x0010;
[DllImport("user32.dll", EntryPoint = "SetWindowPos")]
public static extern IntPtr SetWindowPos(IntPtr hWnd, int hWndInsertAfter, int x, int Y, int cx, int cy, int wFlags);
public static void DoOnProcess(string processName)
{
var allProcs = Process.GetProcessesByName(processName);
if (allProcs.Length > 0)
{
Process proc = allProcs[0];
int hWnd = FindWindow(null, proc.MainWindowTitle.ToString());
// Change behavior by settings the wFlags params. See http://msdn.microsoft.com/en-us/library/ms633545(VS.85).aspx
SetWindowPos(new IntPtr(hWnd), 0, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW | SWP_NOACTIVATE);
}
}
}
HTH
Why some of the answers on this page are wrong!
Any answer that uses window.Focus() is wrong.
Why? If a notification message pops up, window.Focus() will grab the focus away from whatever the user is typing at the time. This is insanely frustrating for end users, especially if the popups occur quite frequently.
Any answer that uses window.Activate() is wrong.
Why? It will make any parent windows visible as well.
Any answer that omits window.ShowActivated = false is wrong.
Why? It will grab the focus away from another window when the message pops up which is very annoying!
Any answer that does not use Visibility.Visible to hide/show the window is wrong.
Why? If we are using Citrix, if the window is not collapsed when it is closed, it will leave a weird black rectangular hold on the screen. Thus, we cannot use window.Show() and window.Hide().
Essentially:
The window should not grab the focus away from any other window when it activates;
The window should not activate its parent when it is shown;
The window should be compatible with Citrix.
MVVM Solution
This code is 100% compatible with Citrix (no blank areas of the screen). It is tested with both normal WPF and DevExpress.
This answer is intended for any use case where we want a small notification window that is always in front of other windows (if the user selects this in the preferences).
If this answer seems more complex than the others, it's because it is robust, enterprise level code. Some of the other answers on this page are simple, but do not actually work.
XAML - Attached Property
Add this attached property to any UserControl within the window. The attached property will:
Wait until the Loaded event is fired (otherwise it cannot look up the visual tree to find the parent window).
Add an event handler that ensures that the window is visible or not.
At any point, you can set the window to be in front or not, by flipping the value of the attached property.
<UserControl x:Class="..."
...
attachedProperties:EnsureWindowInForeground.EnsureWindowInForeground=
"{Binding EnsureWindowInForeground, Mode=OneWay}">
C# - Helper Method
public static class HideAndShowWindowHelper
{
/// <summary>
/// Intent: Ensure that small notification window is on top of other windows.
/// </summary>
/// <param name="window"></param>
public static void ShiftWindowIntoForeground(Window window)
{
try
{
// Prevent the window from grabbing focus away from other windows the first time is created.
window.ShowActivated = false;
// Do not use .Show() and .Hide() - not compatible with Citrix!
if (window.Visibility != Visibility.Visible)
{
window.Visibility = Visibility.Visible;
}
// We can't allow the window to be maximized, as there is no de-maximize button!
if (window.WindowState == WindowState.Maximized)
{
window.WindowState = WindowState.Normal;
}
window.Topmost = true;
}
catch (Exception)
{
// Gulp. Avoids "Cannot set visibility while window is closing".
}
}
/// <summary>
/// Intent: Ensure that small notification window can be hidden by other windows.
/// </summary>
/// <param name="window"></param>
public static void ShiftWindowIntoBackground(Window window)
{
try
{
// Prevent the window from grabbing focus away from other windows the first time is created.
window.ShowActivated = false;
// Do not use .Show() and .Hide() - not compatible with Citrix!
if (window.Visibility != Visibility.Collapsed)
{
window.Visibility = Visibility.Collapsed;
}
// We can't allow the window to be maximized, as there is no de-maximize button!
if (window.WindowState == WindowState.Maximized)
{
window.WindowState = WindowState.Normal;
}
window.Topmost = false;
}
catch (Exception)
{
// Gulp. Avoids "Cannot set visibility while window is closing".
}
}
}
Usage
In order to use this, you need to create the window in your ViewModel:
private ToastView _toastViewWindow;
private void ShowWindow()
{
if (_toastViewWindow == null)
{
_toastViewWindow = new ToastView();
_dialogService.Show<ToastView>(this, this, _toastViewWindow, true);
}
ShiftWindowOntoScreenHelper.ShiftWindowOntoScreen(_toastViewWindow);
HideAndShowWindowHelper.ShiftWindowIntoForeground(_toastViewWindow);
}
private void HideWindow()
{
if (_toastViewWindow != null)
{
HideAndShowWindowHelper.ShiftWindowIntoBackground(_toastViewWindow);
}
}
Additional links
For tips on how ensure that a notification window always shifts back onto the visible screen, see my answer: In WPF, how to shift a window onto the screen if it is off the screen?.
If the user is interacting with another application, it may not be possible to bring yours to the front. As a general rule, a process can only expect to set the foreground window if that process is already the foreground process. (Microsoft documents the restrictions in the SetForegroundWindow() MSDN entry.) This is because:
The user "owns" the foreground. For example, it would be extremely annoying if another program stole the foreground while the user is typing, at the very least interrupting her workflow, and possibly causing unintended consequences as her keystrokes meant for one application are misinterpreted by the offender until she notices the change.
Imagine that each of two programs checks to see if its window is the foreground and attempts to set it to the foreground if it is not. As soon as the second program is running, the computer is rendered useless as the foreground bounces between the two at every task switch.
I know that this is late answer, maybe helpful for researchers
if (!WindowName.IsVisible)
{
WindowName.Show();
WindowName.Activate();
}
I have had a similar problem with a WPF application that gets invoked from an Access application via the Shell object.
My solution is below - works in XP and Win7 x64 with app compiled to x86 target.
I'd much rather do this than simulate an alt-tab.
void Window_Loaded(object sender, RoutedEventArgs e)
{
// make sure the window is normal or maximised
// this was the core of the problem for me;
// even though the default was "Normal", starting it via shell minimised it
this.WindowState = WindowState.Normal;
// only required for some scenarios
this.Activate();
}
Well, since this is such a hot topic... here is what works for me. I got errors if I didn't do it this way because Activate() will error out on you if you cannot see the window.
Xaml:
<Window ....
Topmost="True"
....
ContentRendered="mainWindow_ContentRendered"> .... </Window>
Codebehind:
private void mainWindow_ContentRendered(object sender, EventArgs e)
{
this.Topmost = false;
this.Activate();
_UsernameTextBox.Focus();
}
This was the only way for me to get the window to show on top. Then activate it so you can type in the box without having to set focus with the mouse. control.Focus() wont work unless the window is Active();
Well I figured out a work around. I'm making the call from a keyboard hook used to implement a hotkey. The call works as expected if I put it into a BackgroundWorker with a pause. It's a kludge, but I have no idea why it wasn't working originally.
void hotkey_execute()
{
IntPtr handle = new WindowInteropHelper(Application.Current.MainWindow).Handle;
BackgroundWorker bg = new BackgroundWorker();
bg.DoWork += new DoWorkEventHandler(delegate
{
Thread.Sleep(10);
SwitchToThisWindow(handle, true);
});
bg.RunWorkerAsync();
}
To show ANY currently opened window import those DLL:
public partial class Form1 : Form
{
[DllImportAttribute("User32.dll")]
private static extern int FindWindow(String ClassName, String WindowName);
[DllImportAttribute("User32.dll")]
private static extern int SetForegroundWindow(int hWnd);
and in program We search for app with specified title (write title without first letter (index > 0))
foreach (Process proc in Process.GetProcesses())
{
tx = proc.MainWindowTitle.ToString();
if (tx.IndexOf("Title of Your app WITHOUT FIRST LETTER") > 0)
{
tx = proc.MainWindowTitle;
hWnd = proc.Handle.ToInt32(); break;
}
}
hWnd = FindWindow(null, tx);
if (hWnd > 0)
{
SetForegroundWindow(hWnd);
}
These codes will work fine all times.
At first set the activated event handler in XAML:
Activated="Window_Activated"
Add below line to your Main Window constructor block:
public MainWindow()
{
InitializeComponent();
this.LocationChanged += (sender, e) => this.Window_Activated(sender, e);
}
And inside the activated event handler copy this codes:
private void Window_Activated(object sender, EventArgs e)
{
if (Application.Current.Windows.Count > 1)
{
foreach (Window win in Application.Current.Windows)
try
{
if (!win.Equals(this))
{
if (!win.IsVisible)
{
win.ShowDialog();
}
if (win.WindowState == WindowState.Minimized)
{
win.WindowState = WindowState.Normal;
}
win.Activate();
win.Topmost = true;
win.Topmost = false;
win.Focus();
}
}
catch { }
}
else
this.Focus();
}
These steps will works fine and will bring to front all other windows into their parents window.
Just wanted to add another solution to this question. This implementation works for my scenario, where CaliBurn is responsible for displaying the main Window.
protected override void OnStartup(object sender, StartupEventArgs e)
{
DisplayRootViewFor<IMainWindowViewModel>();
Application.MainWindow.Topmost = true;
Application.MainWindow.Activate();
Application.MainWindow.Activated += OnMainWindowActivated;
}
private static void OnMainWindowActivated(object sender, EventArgs e)
{
var window = sender as Window;
if (window != null)
{
window.Activated -= OnMainWindowActivated;
window.Topmost = false;
window.Focus();
}
}
The problem could be that the thread calling your code from the hook hasn't been initialized by the runtime so calling runtime methods don't work.
Perhaps you could try doing an Invoke to marshal your code on to the UI thread to call your code that brings the window to the foreground.
If you are trying to hide the window, for example you minimize the window, I have found that using
this.Hide();
will hide it correctly, then simply using
this.Show();
will then show the window as the top most item once again.
Remember not to put the code that shows that window inside a PreviewMouseDoubleClick handler as the active window will switch back to the window who handled the event.
Just put it in the MouseDoubleClick event handler or stop bubbling by setting e.Handled to True.
In my case i was handling the PreviewMouseDoubleClick on a Listview and was not setting the e.Handled = true then it raised the MouseDoubleClick event witch sat focus back to the original window.
This is a combination of a few suggestions above that works well and is simple. It only comes to front when those events fire, so any window that pops up after the event will stay on top of course.
public partial class MainWindow : Window
{
protected override void OnContentRendered(EventArgs e)
{
base.OnContentRendered(e);
Topmost = true;
Topmost = false;
}
protected override void OnInitialized(EventArgs e)
{
base.OnInitialized(e);
Topmost = true;
Topmost = false;
}
....
}
I wanted to create a launcher with the keyboard hook and had the same problem.
After much trial and error, this solved the problem.
void Active()
{
MainWindow0.Show();
MainWindow0.Focus();
MainWindow0.Activate();
MainWindow0.WindowState = WindowState.Normal;
}
void Deactive()
{
MainWindow0.Hide();
MainWindow0.WindowState = WindowState.Minimized;
}
I built an extension method to make for easy reuse.
using System.Windows.Forms;
namespace YourNamespace{
public static class WindowsFormExtensions {
public static void PutOnTop(this Form form) {
form.Show();
form.Activate();
}// END PutOnTop()
}// END class
}// END namespace
Call in the Form Constructor
namespace YourNamespace{
public partial class FormName : Form {
public FormName(){
this.PutOnTop();
InitalizeComponents();
}// END Constructor
} // END Form
}// END namespace