C# WPF Message Only Window [duplicate] - c#

I create a global hot key to show a window by PInvoking RegisterHotKey(). But to do this I need that window's HWND, which doesn't exist until the window is loaded, that means shown for the first time. But I don't want to show the window before I can set the hot key. Is there a way to create a HWND for that window that is invisible to the user?

If you are targeting .NET 4.0 you can make use of the new EnsureHandle method available on the WindowInteropHelper:
public void InitHwnd()
{
var helper = new WindowInteropHelper(this);
helper.EnsureHandle();
}
(thanks to Thomas Levesque for pointing this out.)
If you are targeting an older version of the .NET Framework, the easiest way is to show the window to get to the HWND while setting a few properties to make sure that the window is invisible and doesn't steal focus:
var window = new Window() //make sure the window is invisible
{
Width = 0,
Height = 0,
WindowStyle = WindowStyle.None,
ShowInTaskbar = false,
ShowActivated = false
};
window.Show();
Once you want to show the actual window you can then set the Content, the size and change the style back to a normal window.

You can also change the window into a so called message-only window. As this window type does not support graphical elements it will never be shown. Basically it comes down to calling:
SetParent(hwnd, (IntPtr)HWND_MESSAGE);
Either create a dedicated message window which will always be hidden, or use the real GUI window and change it back to a normal window when you want to display it. See the code below for a more complete example.
[DllImport("user32.dll")]
static extern IntPtr SetParent(IntPtr hwnd, IntPtr hwndNewParent);
private const int HWND_MESSAGE = -3;
private IntPtr hwnd;
private IntPtr oldParent;
protected override void OnSourceInitialized(EventArgs e)
{
base.OnSourceInitialized(e);
HwndSource hwndSource = PresentationSource.FromVisual(this) as HwndSource;
if (hwndSource != null)
{
hwnd = hwndSource.Handle;
oldParent = SetParent(hwnd, (IntPtr)HWND_MESSAGE);
Visibility = Visibility.Hidden;
}
}
private void OpenWindowMenuItem_Click(object sender, RoutedEventArgs e)
{
SetParent(hwnd, oldParent);
Show();
Activate();
}
For me the solution of setting the width, height to zero and style to none didn't work out, as it still showed a tiny window, with an annoying shadow of what seems to be the border around a 0x0 window (tested on Windows 7). Therefore I'm providing this alternative option.

This is a dirty hack, but it should work, and doesn't have the downsides of changing the opacity :
set the WindowStartupLocation to Manual
set the Top and Left properties to somewhere outside the screen
set ShowInTaskbar to false so that the user doesn't realize there is a new window
Show and Hide the window
You should now be able to retrieve the HWND
EDIT: another option, probably better : set ShowInTaskBar to false and WindowState to Minimized, then show it : it won't be visible at all

I had already posted an answer to that question, but I just found a better solution.
If you just need to make sure that the HWND is created, without actually showing the window, you can do this:
public void InitHwnd()
{
var helper = new WindowInteropHelper(this);
helper.EnsureHandle();
}
(actually the EnsureHandle method wasn't available when the question was posted, it was introduced in .NET 4.0)

I've never tried to do what you are doing, but if you need to show the Window to get the HWND, but don't want to show it, set the Window Opacity to 0. This will also prevent any hit testing from occurring. Then you could have a public method on the Window to change the Opacity to 100 when you want to make it visible.

I know absolutely nothing about WPF, but could you create a message only window using other means (PInvoke for example) to receive the WM_HOTKEY message? If yes, then once you receive the WM_HOTKEY, you could launch the WPF window from there.

I've noticed that the last thing that happens when the window is being initialized, is the change of WindowState, if it differs from normal. So, you can actually make use of it:
public void InitializeWindow(Window window) {
window.Top = Int32.MinValue;
window.Left = Int32.MinValue;
window.Width = 0;
window.Height = 0;
window.ShowActivated = false;
window.ShowInTaskbar = false;
window.Opacity = 0;
window.StateChanged += OnBackgroundStateChanged;
window.WindowStyle = WindowStyle.None;
}
public void ShowWindow(Window window) {
window.Show();
window.WindowState = WindowState.Maximized;
}
protected bool isStateChangeFirst = true;
protected void OnBackgroundStateChanged(object sender, EventArgs e) {
if (isStateChangeFirst) {
isStateChangeFirst = false;
window.Top = 300;
window.Left = 200;
window.Width = 760;
window.Height = 400;
window.WindowState = WindowState.Normal;
window.ShowInTaskbar = true;
window.Opacity = 1;
window.Activate();
}
}
That works fair enough for me. And it does not require working with any handles and stuff, and, more importantly, does not require to have a custom class for a window. Which is great for dynamically loaded XAML. And it is also a great way if you are making a fullscreen app. You do not even need to change its state back to normal or set proper width and height. Just go with
protected bool isStateChangeFirst = true;
protected void OnBackgroundStateChanged(object sender, EventArgs e) {
if (isStateChangeFirst) {
isStateChangeFirst = false;
window.ShowInTaskbar = true;
window.Opacity = 1;
window.Activate();
}
}
And you're done.
And even if I am wrong in my assumption that change of state is last thing done when window is being loaded, you can still change to any other event, it does not really matter.

The WindowInteropHelper class should allow you to get the HWND for the WPF window.
MyWindow win = new MyWindow();
WindowInteropHelper helper = new WindowInteropHelper(win);
IntPtr hwnd = helper.Handle;
MSDN Documentation

Another option in a similar vein to setting the opacity to 0, is to set the size to 0 and set the position to be off the screen. This won't require the AllowsTransparency = True.
Also remember that once you have shown it once you can then hide it and still get the hwnd.

Make the size of the window 0 x 0 px, put ShowInTaskBar to false, show it, then resize it when needed.

I've created extension method for showing invisible window, next Show calls will behave OK.
public static class WindowHelper
{
public static void ShowInvisible(this Window window)
{
// saving original settings
bool needToShowInTaskbar = window.ShowInTaskbar;
WindowState initialWindowState = window.WindowState;
// making window invisible
window.ShowInTaskbar = false;
window.WindowState = WindowState.Minimized;
// showing and hiding window
window.Show();
window.Hide();
// restoring original settings
window.ShowInTaskbar = needToShowInTaskbar;
window.WindowState = initialWindowState;
}
}

Start Wpf Window in Hidden mode:
WpfWindow w = new WpfWindow() { Visibility = Visibility.Hidden };
Start Wpf Window in Visible mode:
WpfWindow w = new WpfWindow();
w.Show();

Related

How can I hide a window WITHOUT Visibility property?

I have an application that has a lot of modal popup windows. Some are custom windows displayed with .ShowDialog(), other are generic message boxes using a 3rd party tool (DXMessageBox.Show(...)), and others are system dialogs such as an OpenFileDialog. If the user leaves the application running long enough, it should time out and show a "Session Locked" screen and prevent users from seeing or interacting with the application until they login again. This is problematic because of the modal dialogs.
My current solution is to host the Relogin screen in the current modal window if there is one, and hide all other windows. The problem is that if I set Visibility = Hidden on any window I have called using .ShowDialog(), it treats that dialog as having received a result and processes the code that handles the dialog result. They are also no longer modal after they are re-shown.
So my current attempt is to try to hide the window using something other than Visibility, and prevent it from being activated. The closest I've come is by setting Minimized=true and ShowInTaskbar=false, but this results in an undesirable minimized titlebar above my taskbar.
Is there a way to prevent this from happening, or alternatively is there another way to hide a window and prevent it's activation without causing .ShowDialog to return?
Here's some code to re-create a sample application to test this with. Just add a button to launch the ShowLock_Click event handler.
private readonly Dictionary<System.Windows.Window, WindowStyle> _hiddenWindows = new Dictionary<System.Windows.Window, WindowStyle>();
// Create a button to launch this for testing
private void ShowLock_Click(object sender, RoutedEventArgs e)
{
// Will show another window with .ShowDialog, then 2s timeout will trigger lock window
using (new System.Threading.Timer(OnLockTimerElapsed, null, 2000, System.Threading.Timeout.Infinite))
{
ShowTestDialog();
}
}
private void OnLockTimerElapsed(object state)
{
_hiddenWindows.Clear();
Application.Current.Dispatcher.BeginInvoke(
DispatcherPriority.Background,
new Action(() =>
{
var mainWindow = Application.Current.MainWindow;
Window host = null;
foreach (Window win in Application.Current.Windows)
{
if (IsModalDialog(win))
host = win;
_hiddenWindows.Add(win, win.WindowStyle);
// Been testing various ways to hide window without using Visibility
win.ShowInTaskbar = false;
win.WindowStyle = WindowStyle.None;
win.WindowState = WindowState.Minimized;
win.Opacity -= 1;
win.IsHitTestVisible = false;
}
ShowLockScreen(host);
}));
}
private void ShowLockScreen(Window owner = null)
{
var lockScreen = new Window
{
Title = "Relogin Window",
Content = "This is a test Relogin Window. Close Window via X to continue",
WindowStartupLocation = WindowStartupLocation.CenterScreen
};
if (owner != null)
lockScreen.Owner = owner;
lockScreen.ShowDialog();
// Once that window closes, restore other windows
RestoreSession();
}
private void RestoreSession()
{
Application.Current.Dispatcher.BeginInvoke(
DispatcherPriority.Background,
new Action(() =>
{
foreach (var win in _hiddenWindows.Keys)
{
win.ShowInTaskbar = true;
win.WindowStyle = _hiddenWindows[win];
win.WindowState = WindowState.Normal;
win.IsHitTestVisible = true;
win.Opacity += 1;
}
}));
}
private void ShowTestDialog()
{
var test = new Window
{
Title = "Test Modal Dialog",
Content = "This is a test Modal Dialog. Close window via X to continue.",
Height = 100,
Width = 350,
WindowStartupLocation = WindowStartupLocation.CenterOwner,
Owner = this
};
var result = test.ShowDialog();
// This code gets run if I set Visibility=Hidden. I do not want that.
MessageBox.Show($"Test Dialog result returned. Result : {result}. This should ONLY happen when you click X on the dialog window");
}
private static bool IsModalDialog(Window window)
{
return (bool)typeof(System.Windows.Window)
.GetField("_showingAsDialog", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic)
.GetValue(window);
}

How to force WPF startup window to specific screen?

I have a WPF application that will show information on a projector through a dedicated window.
I would like to configure what screen to be used for projector display and what to be used for main application window.
This code will generate projector output on specified screen:
var screen = GetProjectorScreen();
_projectorWindow = new ProjectorWindow();
_projectorWindow.Left = screen.WorkingArea.Left;
_projectorWindow.Top = screen.WorkingArea.Top;
_projectorWindow.Owner = _parentWindow;
_projectorWindow.Show();
public static Screen GetProjectorScreen()
{
var screens = Screen.AllScreens;
if (screens.Length > 1 && Settings.Default.DisplayScreen < screens.Length)
{
return screens[Settings.Default.DisplayScreen];
}
return screens[0];
}
I have tried to do the same trick with startup form, but so far without success.
I tried to set Top and Left properties in MainWindow constructor but that did not work.
The startup window is launched from App.xaml.cs by setting StartupUri:
StartupUri = new Uri("Windows/MainWindow.xaml", UriKind.Relative);
Is there any other way to launch startup form?
I tried to just call the constructor but that causes a crash because some resources are no longer loaded.
I got it working. It is necessary to set WindowState to Normal before setting window location. And the setting will not work at all until the window is created, i.e. after constructor call. I therefore call the explicit setting in Windows_Loaded event. That might cause a flickering if window need to be moved, but that is acceptable to me.
The SetScreen method should also be called after screen settings have changed manually by user.
private void SetScreen()
{
var mainScreen = ScreenHandler.GetMainScreen();
var currentScreen = ScreenHandler.GetCurrentScreen(this);
if (mainScreen.DeviceName != currentScreen.DeviceName)
{
this.WindowState = WindowState.Normal;
this.Left = mainScreen.WorkingArea.Left;
this.Top = mainScreen.WorkingArea.Top;
this.Width = mainScreen.WorkingArea.Width;
this.Height = mainScreen.WorkingArea.Height;
this.WindowState = WindowState.Maximized;
}
}
The backup ScreenHandler utility is very simple:
public static class ScreenHandler
{
public static Screen GetMainScreen()
{
return GetScreen(Settings.Default.MainScreenId);
}
public static Screen GetProjectorScreen()
{
return GetScreen(Settings.Default.ProjectorScreenId);
}
public static Screen GetCurrentScreen(Window window)
{
var parentArea = new Rectangle((int)window.Left, (int)window.Top, (int)window.Width, (int)window.Height);
return Screen.FromRectangle(parentArea);
}
private static Screen GetScreen(int requestedScreen)
{
var screens = Screen.AllScreens;
var mainScreen = 0;
if (screens.Length > 1 && mainScreen < screens.Length)
{
return screens[requestedScreen];
}
return screens[0];
}
}
The accepted answer no longer works on Windows 10 with per-monitor DPI in the app’s manifest.
Here’s what worked for me:
public partial class MyWindow : Window
{
readonly Rectangle screenRectangle;
public MyWindow( System.Windows.Forms.Screen screen )
{
screenRectangle = screen.WorkingArea;
InitializeComponent();
}
[DllImport( "user32.dll", SetLastError = true )]
static extern bool MoveWindow( IntPtr hWnd, int X, int Y, int nWidth, int nHeight, bool bRepaint );
protected override void OnSourceInitialized( EventArgs e )
{
base.OnSourceInitialized( e );
var wih = new WindowInteropHelper( this );
IntPtr hWnd = wih.Handle;
MoveWindow( hWnd, screenRectangle.Left, screenRectangle.Top, screenRectangle.Width, screenRectangle.Height, false );
}
void Window_Loaded( object sender, RoutedEventArgs e )
{
WindowState = WindowState.Maximized;
}
}
Just setting Left/Top doesn’t work. Based on my tests, per-monitor DPI awareness only kicks in after window is already created and placed on some monitor. Before that, apparently Left/Top properties of the window scale with DPI of the primary monitor.
For some combinations of per-monitor DPI and monitors layout, this caused a bug where setting Left/Top properties to the pixels values of System.Windows.Forms.Screen rectangle caused the window to be positioned somewhere else.
The above workaround is only suitable for maximizing, it does not always sets the correct size of the window. But at least it sets correct top-left corner which is enough for the maximize to work correctly.

PresentationSource.FromVisual(this) returns null value in WPF

I'm using the following code for my:
protected override void OnSourceInitialized(EventArgs e)
{
...
....
HwndSource source = PresentationSource.FromVisual(this) as HwndSource;
...
...
}
In some systems the "source" value comes out to be null and i cant find the reason why...
I think you may have to wait until the UI is rendered until you try to assign the Hwnd. Try putting it in the event handler for Window.Loaded instead.
This happened to me before, I had the Hwnd assignment after InitializeComponent() was called in the code-behind's constructor. It always came back null, which may sense when I stepped through and saw the UI hadn't appeared yet. I popped it into the _Loaded handler and voila, the UI renders before hitting that line and all of the sudden 'this' stopped being null.
Starting with .Net 4.0, you can access HwndSource without having to show the window first:
var helper = new WindowInteropHelper(this);
var hwndSource = HwndSource.FromHwnd(helper.EnsureHandle());
WumpasTamer's answer is correct. I'd just like to add a quick code sample for anyone else looking for a "turnkey" solution. If you're using WPF already then window is not necessary, but if you're using Winforms and want to use PresentationSource you'll need to use this.
void Main()
{
var window = new Window
{
Width = 0,
Height = 0,
WindowStyle = WindowStyle.None,
ShowInTaskbar = false,
ShowActivated = false
};
window.Loaded += a_Loaded;
window.Show();
}
void a_Loaded(object sender, EventArgs e)
{
var s = (Window) sender;
var source = PresentationSource.FromVisual(s);
//...
s.Close();
}

Setting the start position for OpenFileDialog/SaveFileDialog

For any custom dialog (form) in a WinForm application I can set its size and position before I display it with:
form.StartPosition = FormStartPosition.Manual;
form.DesktopBounds = MyWindowPosition;
This is particularly important when dealing with multiple monitors. Without such code, when you open a dialog from an application that you have dragged to a second monitor, the dialog appears on the primary monitor. This presents a poor user experience.
I am wondering if there are any hooks to set the position for the standard .NET OpenFileDialog and SaveFileDialog (which do not have a StartPosition property).
I suspect that the best you can do is make sure you use the overload of ShowDialog that accepts an IWin32Window to use as the parent. This might help it choose an appropriate location; most commonly:
using(var dlg = new OpenFileDialog()) {
.... setup
if(dlg.ShowDialog(this) == DialogResult.OK) {
.... use
}
}
Check out this article on CodeProject. Excerpt:
Here is when the handy .NET
NativeWindow comes into the picture, a
NativeWindow is a window wrapper where
it processes the messages sent by the
handle associated to it. It creates a
NativeWindow and associates the
OpenFileWindow handle to it. From this
point, every message sent to
OpenFileWindow will be redirected to
our own WndProc method in the
NativeWindow instead, and we can
cancel, modify, or let them pass
through.
In our WndProc, we process the message
WM_WINDOWPOSCHANGING. If the open
dialog is opening, then we will change
the original horizontal or vertical
size depending of the StartLocation
set by the user. It will increment the
size of the window to be created. This
happens only once when the control is
opened.
Also, we will process the message
WM_SHOWWINDOW. Here, all controls
inside the original OpenFileDialog are
created, and we are going to append
our control to the open file dialog.
This is done by calling a Win32 API
SetParent. This API lets you change
the parent window. Then, basically
what it does is attach our control
to the original OpenFileDialog in the
location it set, depending on the
value of the StartLocation property.
The advantage of it is that we still
have complete control over the
controls attached to the
OpenFileDialog window. This means we
can receive events, call methods, and
do whatever we want with those
controls.
OpenFileDialog and SaveFileDialog position themselves in the upper-left corner of
the client area of the most recently displayed window. So just create a new invisible window positioned where you want the the dialog to appear before creating and showing that dialog.
Window dialogPositioningWindow = new Window();
dialogPositioningWindow.Left = MainWindow.Left + <left position within main window>;
dialogPositioningWindow.Top = MainWindow.Top + <top position within main window>;
dialogPositioningWindow.Width = 0;
dialogPositioningWindow.Height = 0;
dialogPositioningWindow.WindowStyle = WindowStyle.None;
dialogPositioningWindow.ResizeMode = ResizeMode.NoResize;
dialogPositioningWindow.Show();// OpenFileDialog is positioned in the upper-left corner
// of the last shown window (dialogPositioningWindow)
Microsoft.Win32.OpenFileDialog dialog = new Microsoft.Win32.OpenFileDialog();
...
if ((bool)dialog.ShowDialog()){
...
}
dialogPositioningWindow.Close();
I had this problem for most of yesterday. BobB's answer was the one that helped me out the most (Thanks BobB).
You can even go as far as to make a private method that creates a window and closes it before the dialog.ShowDialog() method call and it will still centre the OpenFileDialog.
private void openFileDialogWindow()
{
Window openFileDialogWindow = new Window();
openFileDialogWindow.Left = this.Left;
openFileDialogWindow.Top = this.Top;
openFileDialogWindow.Width = 0;
openFileDialogWindow.Height = 0;
openFileDialogWindow.WindowStyle = WindowStyle.None;
openFileDialogWindow.ResizeMode = ResizeMode.NoResize;
openFileDialogWindow.WindowStartupLocation = WindowStartupLocation.CenterScreen;
openFileDialogWindow.Show();
openFileDialogWindow.Close();
openFileDialogWindow = null;
}
Then call it in any method before the ShowDialog() method.
public string SelectWebFolder()
{
string WebFoldersDestPath = null;
CommonOpenFileDialog filePickerDialog = new CommonOpenFileDialog();
// OpenFileDialog Parameters..
openFileDialogWindow();
if (filePickerDialog.ShowDialog() == CommonFileDialogResult.Ok)
{
WebFoldersDestPath = filePickerDialog.FileName + "\\";
}
filePickerDialog = null;
return WebFoldersDestPath;
}
Here's how I did it:
The point where I want to display the OpenFileDialog:
Thread posThread = new Thread(positionOpenDialog);
posThread.Start();
DialogResult dr = ofd.ShowDialog();
The repositioning code:
[DllImport("user32.dll", EntryPoint = "FindWindow", SetLastError = true)]
static extern IntPtr FindWindowByCaption(IntPtr ZeroOnly, string lpWindowName);
[DllImport("user32.dll", EntryPoint = "SetWindowPos")]
public static extern IntPtr SetWindowPos(IntPtr hWnd, int hWndInsertAfter, int x, int Y, int cx, int cy, int wFlags);
/// <summary>
/// Find the OpenFileDialog window when it appears, and position it so
/// that we can see both dialogs at once. There is no easier way to
/// do this (&^%$! Microsoft!).
/// </summary>
private void positionOpenDialog ()
{
int count = 0;
IntPtr zero = (IntPtr)0;
const int SWP_NOSIZE = 0x0001;
IntPtr wind;
while ((wind = FindWindowByCaption(zero, "Open")) == (IntPtr)0)
if (++count > 100)
return; // Find window failed.
else
Thread.Sleep(5);
SetWindowPos(wind, 0, Right, Top, 0, 0, SWP_NOSIZE);
}
I start a thread that looks for a window with the "Open" title. (Typically found in 3 iterations or 15 milliseconds.) Then I set its position with the obtained handle. (See SetWindowPos documentation for the position/size parameters.)
Kludgy.
There is quite an old example of one approach on MSDN.
http://msdn.microsoft.com/en-us/library/ms996463.aspx
It includes all the code needed to implement your own OpenFileDialog class that allows extensibility.
Very grateful for BobB's reply on this one. There are a few more "gotchas". You have to pass the handle of PositionForm when calling OpenFileDialog1.ShowDialog(PositionForm) otherwise BobB's technique is not reliable in all cases. Also, now that W8.1 launches a new fileopen control with SkyDrive in it, the Documents folder location in the W8.1 fileopen control is now screwed. So I frig fileopen to use the old W7 control by setting ShowHelp = True.
Here is the VB.NET code I ended up using, my contribution to the community in case it helps.
Private Function Get_FileName() As String
' Gets an Input File Name from the user, works with multi-monitors
Dim OpenFileDialog1 As New OpenFileDialog
Dim PositionForm As New Form
Dim MyInputFile As String
' The FileDialog() opens in the last Form that was created. It's buggy! To ensure it appears in the
' area of the current Form, we create a new hidden PositionForm and then delete it afterwards.
PositionForm.StartPosition = FormStartPosition.Manual
PositionForm.Left = Me.Left + CInt(Me.Width / 2)
PositionForm.Top = Me.Top + CInt(Me.Height / 2)
PositionForm.Width = 0
PositionForm.Height = 0
PositionForm.FormBorderStyle = Forms.FormBorderStyle.None
PositionForm.Visible = False
PositionForm.Show()
' Added the statement "ShowHelp = True" to workaround a problem on W8.1 machines with SkyDrive installed.
' It causes the "old" W7 control to be used that does not point to SkyDrive in error.
OpenFileDialog1.InitialDirectory = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments)
OpenFileDialog1.Filter = "Excel files (*.xls*)|*.xls*|CSV Files (*.csv)|*.csv"
OpenFileDialog1.FilterIndex = 1
OpenFileDialog1.RestoreDirectory = True
OpenFileDialog1.AutoUpgradeEnabled = False
OpenFileDialog1.ShowHelp = True
OpenFileDialog1.FileName = ""
OpenFileDialog1.SupportMultiDottedExtensions = False
OpenFileDialog1.Title = "Select an Excel or .csv file containing patent data or list of Publication Numbers for your project."
If OpenFileDialog1.ShowDialog(PositionForm) <> System.Windows.Forms.DialogResult.OK Then
Console.WriteLine("No file was selected. Please try again!")
PositionForm.Close()
PositionForm.Dispose()
OpenFileDialog1.Dispose()
Return ""
End If
PositionForm.Close()
PositionForm.Dispose()
MyInputFile = OpenFileDialog1.FileName
OpenFileDialog1.Dispose()
Return MyInputFile
End Function
Using Rob Sherrit's response on Jan 22 '14 as inspiration, I created a new module and called it CKRFileDialog (call it what you want) which contains the following code:
Public Function Show(fd As Object, CoveredForm As Form, Optional bShowHelp As Boolean = False) As DialogResult
Dim oDR As DialogResult
'The .Net FileDialogs open in the last Form that was created.
'To ensure they appear in the area of the current Form, we create a new HIDDEN PositionForm and then
'delete it afterwards.
Dim PositionForm As New Form With {
.StartPosition = FormStartPosition.Manual,
.Left = CoveredForm.Left + CInt(CoveredForm.Width / 8), 'adjust as required
.Top = CoveredForm.Top + CInt(CoveredForm.Height / 8), 'adjust as required
.Width = 0,
.Height = 0,
.FormBorderStyle = Windows.Forms.FormBorderStyle.None,
.Visible = False
}
PositionForm.Show()
'If you use SkyDrive you need to ensure that "bShowHelp" is set to True in the passed parameters.
'This is a workaround for a problem on W8.1 machines with SkyDrive installed.
'Setting it to "true" causes the "old" W7 control to be used which avoids a pointing to SkyDrive error.
'If you do not use SkyDrive then simply do not pass the last parameter (defaults to "False")
fd.ShowHelp = bShowHelp
'store whether the form calling this routine is set as "topmost"
Dim oldTopMost As Integer = CoveredForm.TopMost
'set the calling form's topmost setting to "False" (else the dialogue will be "buried"
CoveredForm.TopMost = False
oDR = fd.ShowDialog(PositionForm)
'set the "topmost" setting of the calling form back to what it was.
CoveredForm.TopMost = oldTopMost
PositionForm.Close()
PositionForm.Dispose()
Return oDR
End Function
I then call this code in my various modules as follows:
If performing a "FileOpen" ensure that there is a FileOpenDialog component added to your form or code and adjust the properties of the component if you wish
(e.g. InitDirectory,Multiselect,etc.)
Do the same when using FileSaveDialog components (Different properties to the FileOpenDialog component may apply).
To "show" the dialog component use a line of code as follows, passing two parameters, the first the FileDialog you are using ("Open" or "Save") and the second parameter the Form upon which you wish to overlay the dialogue.
CKRFileDialog.Show(saveFileDialog1, CoveredForm)
or
CKRFileDialog.Show(openFileDialog1, CoveredForm)
Remember, if you are using SkyDrive you must pass "True" as a third parameter:
CKRFileDialog.Show(saveFileDialog1, CoveredForm, True)
or
CKRFileDialog.Show(openFileDialog1, CoveredForm, True)
I set the "offset" of the dialogue to be 1/8 the way across and down on the form
"CoveredForm", but you can set that back to 1/2 (as in Rob Sherret's code) or whatever value you wish.
This seemed the easiest approach
Thanks Rob! :-)

Show a Form without stealing focus?

I'm using a Form to show notifications (it appears at the bottom right of the screen), but when I show this form it steals the focus from the main Form. Is there a way to show this "notification" form without stealing focus?
Hmmm, isn't simply overriding Form.ShowWithoutActivation enough?
protected override bool ShowWithoutActivation
{
get { return true; }
}
And if you don't want the user to click this notification window either, you can override CreateParams:
protected override CreateParams CreateParams
{
get
{
CreateParams baseParams = base.CreateParams;
const int WS_EX_NOACTIVATE = 0x08000000;
const int WS_EX_TOOLWINDOW = 0x00000080;
baseParams.ExStyle |= ( int )( WS_EX_NOACTIVATE | WS_EX_TOOLWINDOW );
return baseParams;
}
}
Stolen from PInvoke.net's ShowWindow method:
private const int SW_SHOWNOACTIVATE = 4;
private const int HWND_TOPMOST = -1;
private const uint SWP_NOACTIVATE = 0x0010;
[DllImport("user32.dll", EntryPoint = "SetWindowPos")]
static extern bool SetWindowPos(
int hWnd, // Window handle
int hWndInsertAfter, // Placement-order handle
int X, // Horizontal position
int Y, // Vertical position
int cx, // Width
int cy, // Height
uint uFlags); // Window positioning flags
[DllImport("user32.dll")]
static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);
static void ShowInactiveTopmost(Form frm)
{
ShowWindow(frm.Handle, SW_SHOWNOACTIVATE);
SetWindowPos(frm.Handle.ToInt32(), HWND_TOPMOST,
frm.Left, frm.Top, frm.Width, frm.Height,
SWP_NOACTIVATE);
}
(Alex Lyman answered this, I'm just expanding it by directly pasting the code. Someone with edit rights can copy it over there and delete this for all I care ;) )
This is what worked for me. It provides TopMost but without focus-stealing.
protected override bool ShowWithoutActivation
{
get { return true; }
}
private const int WS_EX_TOPMOST = 0x00000008;
protected override CreateParams CreateParams
{
get
{
CreateParams createParams = base.CreateParams;
createParams.ExStyle |= WS_EX_TOPMOST;
return createParams;
}
}
Remember to omit setting TopMost in Visual Studio designer, or elsewhere.
This is stolen, err, borrowed, from here (click on Workarounds):
https://connect.microsoft.com/VisualStudio/feedback/details/401311/showwithoutactivation-is-not-supported-with-topmost
If you're willing to use Win32 P/Invoke, then you can use the ShowWindow method (the first code sample does exactly what you want).
Doing this seems like a hack, but it seems to work:
this.TopMost = true; // as a result the form gets thrown to the front
this.TopMost = false; // but we don't actually want our form to always be on top
Edit: Note, this merely raises an already created form without stealing focus.
The sample code from pinvoke.net in Alex Lyman/TheSoftwareJedi's answers will make the window a "topmost" window, meaning that you can't put it behind normal windows after it's popped up. Given Matias's description of what he wants to use this for, that could be what he wants. But if you want the user to be able to put your window behind other windows after you've popped it up, just use HWND_TOP (0) instead of HWND_TOPMOST (-1) in the sample.
In WPF you can solve it like this:
In the window put these attributes:
<Window
x:Class="myApplication.winNotification"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Notification Popup" Width="300" SizeToContent="Height"
WindowStyle="None" AllowsTransparency="True" Background="Transparent" ShowInTaskbar="False" Topmost="True" Focusable="False" ShowActivated="False" >
</Window>
The last one attribute is the one you need ShowActivated="False".
I have something similar, and I simply show the notification form and then do
this.Focus();
to bring the focus back on the main form.
Create and start the notification Form in a separate thread and reset the focus back to your main form after the Form opens. Have the notification Form provide an OnFormOpened event that is fired from the Form.Shown event. Something like this:
private void StartNotfication()
{
Thread th = new Thread(new ThreadStart(delegate
{
NotificationForm frm = new NotificationForm();
frm.OnFormOpen += NotificationOpened;
frm.ShowDialog();
}));
th.Name = "NotificationForm";
th.Start();
}
private void NotificationOpened()
{
this.Focus(); // Put focus back on the original calling Form
}
You can also keep a handle to your NotifcationForm object around so that it can be programmatically closed by the main Form (frm.Close()).
Some details are missing, but hopefully this will get you going in the right direction.
This works well.
See: OpenIcon - MSDN and SetForegroundWindow - MSDN
using System.Runtime.InteropServices;
[DllImport("user32.dll")]
static extern bool OpenIcon(IntPtr hWnd);
[DllImport("user32.dll")]
static extern bool SetForegroundWindow(IntPtr hWnd);
public static void ActivateInstance()
{
IntPtr hWnd = IntPtr hWnd = Process.GetCurrentProcess().MainWindowHandle;
// Restore the program.
bool result = OpenIcon(hWnd);
// Activate the application.
result = SetForegroundWindow(hWnd);
// End the current instance of the application.
//System.Environment.Exit(0);
}
You might want to consider what kind of notification you would like to display.
If it's absolutely critical to let the user know about some event, using Messagebox.Show would be the recommended way, due to its nature to block any other events to the main window, until the user confirms it. Be aware of pop-up blindness, though.
If it's less than critical, you might want to use an alternative way to display notifications, such as a toolbar on the bottom of the window. You wrote, that you display notifications on the bottom-right of the screen - the standard way to do this would be using a balloon tip with the combination of a system tray icon.
You can handle it by logic alone too, although I have to admit that the suggestions above where you end up with a BringToFront method without actually stealing focus is the most elegant one.
Anyhow, I ran into this and solved it by using a DateTime property to not allow further BringToFront calls if calls were made already recently.
Assume a core class, 'Core', which handles for example three forms, 'Form1, 2, and 3'. Each form needs a DateTime property and an Activate event that call Core to bring windows to front:
internal static DateTime LastBringToFrontTime { get; set; }
private void Form1_Activated(object sender, EventArgs e)
{
var eventTime = DateTime.Now;
if ((eventTime - LastBringToFrontTime).TotalMilliseconds > 500)
Core.BringAllToFront(this);
LastBringToFrontTime = eventTime;
}
And then create the work in the Core Class:
internal static void BringAllToFront(Form inForm)
{
Form1.BringToFront();
Form2.BringToFront();
Form3.BringToFront();
inForm.Focus();
}
On a side note, if you want to restore a minimized window to its original state (not maximized), use:
inForm.WindowState = FormWindowState.Normal;
Again, I know this is just a patch solution in the lack of a BringToFrontWithoutFocus. It is meant as a suggestion if you want to avoid the DLL file.
I don't know if this is considered as necro-posting, but this is what I did since I couln't get it working with user32's "ShowWindow" and "SetWindowPos" methods. And no, overriding "ShowWithoutActivation" doesn't work in this case since the new window should be always-on-top.
Anyway, I created a helper method that takes a form as parameter; when called, it shows the form, brings it to the front and makes it TopMost without stealing the focus of the current window (apparently it does, but the user won't notice).
[DllImport("user32.dll")]
static extern IntPtr GetForegroundWindow();
[DllImport("user32.dll")]
static extern IntPtr SetForegroundWindow(IntPtr hWnd);
public static void ShowTopmostNoFocus(Form f)
{
IntPtr activeWin = GetForegroundWindow();
f.Show();
f.BringToFront();
f.TopMost = true;
if (activeWin.ToInt32() > 0)
{
SetForegroundWindow(activeWin);
}
}
I know it may sound stupid, but this worked:
this.TopMost = true;
this.TopMost = false;
this.TopMost = true;
this.SendToBack();
I needed to do this with my window TopMost. I implemented the PInvoke method above but found that my Load event wasn't getting called like Talha above. I finally succeeded. Maybe this will help someone. Here is my solution:
form.Visible = false;
form.TopMost = false;
ShowWindow(form.Handle, ShowNoActivate);
SetWindowPos(form.Handle, HWND_TOPMOST,
form.Left, form.Top, form.Width, form.Height,
NoActivate);
form.Visible = true; //So that Load event happens
You don't need to make it anywhere near as complicated.
a = new Assign_Stock();
a.MdiParent = this.ParentForm;
a.Visible = false; //hide for a bit.
a.Show(); //show the form. Invisible form now at the top.
this.Focus(); //focus on this form. make old form come to the top.
a.Visible = true; //make other form visible now. Behind the main form.
Github Sample
Form.ShowWithoutActivation Property
Add this in your child form class
protected override bool ShowWithoutActivation
{
get { return true; }
}
Working Code
Form2
public partial class Form2 : Form
{
Form3 c;
public Form2()
{
InitializeComponent();
c = new Form3();
}
private void textchanged(object sender, EventArgs e)
{
c.ResetText(textBox1.Text.ToString());
c.Location = new Point(this.Location.X+150, this.Location.Y);
c .Show();
//removethis
//if mdiparent 2 add this.focus() after show form
c.MdiParent = this.MdiParent;
c.ResetText(textBox1.Text.ToString());
c.Location = new Point(this.Location.X+150, this.Location.Y);
c .Show();
this.Focus();
////-----------------
}
}
Form3
public partial class Form3 : Form
{
public Form3()
{
InitializeComponent();
//ShowWithoutActivation = false;
}
protected override bool ShowWithoutActivation
{
get { return true; }
}
internal void ResetText(string toString)
{
label2.Text = toString;
}
}
When you create a new form using
Form f = new Form();
f.ShowDialog();
it steals focus because your code can't continue executing on the main form until this form is closed.
The exception is by using threading to create a new form then Form.Show(). Make sure the thread is globally visible though, because if you declare it within a function, as soon as your function exits, your thread will end and the form will disappear.
Figured it out: window.WindowState = WindowState.Minimized;.

Categories

Resources