I'm writing a windows forms application in C# whereby some windows utilities can be launched (e.g. CMD prompt, Registry editor, Events Viewer etc) and placed in an MdiClient control on the main form.
Everything is working great except that the scroll bars in the MdiClient control aren't automatically appearing when a child window falls beyond the MdiClient's borders. If the child windows were windows forms then I know that the MdiClient's scroll bars would automatically appear as expected. I've tried many things, including some complex workarounds.. and i'm beginning to think there must be something i'm completely overlooking.
I have attached some sample code below:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Diagnostics;
using System.Threading;
using System.Runtime.InteropServices;
namespace MdiClient
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
System.Windows.Forms.MdiClient mdiClient = new System.Windows.Forms.MdiClient();
mdiClient.Dock = DockStyle.Fill;
mdiClient.BackColor = Color.WhiteSmoke;
this.Controls.Add(mdiClient);
int processID = StartCMD();
AddToMDIClient(processID, mdiClient.Handle);
}
private int StartCMD()
{
int processID = -1;
using (Process process = new Process())
{
ProcessStartInfo startInfo = process.StartInfo;
startInfo.FileName = "cmd.exe";
try
{
process.Start();
processID = process.Id;
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString());
}
}
return processID;
}
private void AddToMDIClient(int processID, IntPtr mdiClientHandle)
{
try
{
Process process = Process.GetProcessById(processID);
int numberOfAttempts = 0;
while (string.IsNullOrEmpty(process.MainWindowTitle) && numberOfAttempts < 30)//max of 3 seconds
{
Thread.Sleep(100);
process.Refresh();
numberOfAttempts++;
}
if (!string.IsNullOrEmpty(process.MainWindowTitle))
{
SetWindowPos(process.MainWindowHandle, HWND_TOPMOST, 1, 1, 0, 0, TOPMOST_FLAGS);
SetParent(process.MainWindowHandle, mdiClientHandle);
}
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString());
}
}
[DllImport("user32.dll", SetLastError = true)]
public static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X,
int Y, int cx, int cy, uint uFlags);
[DllImport("user32.dll", SetLastError = true)]
public static extern IntPtr SetParent(IntPtr hWndChild, IntPtr hWndNewParent);
public static readonly IntPtr HWND_TOPMOST = new IntPtr(-1);
public const UInt32 TOPMOST_FLAGS = /*SWP_NOMOVE | */SWP_NOSIZE;
public const UInt32 SWP_NOSIZE = 0x0001;
}
}
The screenshot below shows that when the CMD window is moved so its borders are outside the borders of the MdiClient there are no scroll bars:
Please see this link for the image: http://picasaweb.google.com/lh/photo/75rMVJMCWRg_s_DFF6LmNg?authkey=Gv1sRgCIKRlsu8xuDh8AE&feat=directlink
Any help would be much appreciated!
Thanks,
Shady
Without the screenshot it is hard to say, but I think the way you create the MDIParanet is way too complicated.
private void Form1_Load(object sender, EventArgs e)
{
// System.Windows.Forms.MdiClient mdiClient = new System.Windows.Forms.MdiClient();
// mdiClient.Dock = DockStyle.Fill;
// mdiClient.BackColor = Color.WhiteSmoke;
// this.Controls.Add(mdiClient);
this.IsMdiContainer = true;
int processID = StartCMD();
AddToMDIClient(processID, mdiClient.Handle);
}
If you need the Client, you can filter it from Controls.
Another problem might be setting a MDIChild as TOP_MOST, I don't think that's a good combination.
I have been doing some testing and it work ok for me was long as I have Autoscroll = true in the forms properties.
Also, I noticed if you enlarge the form and move the command window to say the bottom right it will not show the scroll bars, it will only when you minimize the form past the command windows (see screenshots below)
Screenshot 1
http://picasaweb.google.com/lh/photo/rfwm-S8y06Fl3HFNshgj3g?feat=directlink
Screenshot 2
http://picasaweb.google.com/lh/photo/y6qkN9Jj19vDGFNkTuL4FQ?feat=directlink
Also, can you set on the Form's properties AutoScrollMinSize, so that you always have scroll bars in the form is smaller than the size set
Hope that helps
Josh
Related
I am trying to open a form as an overlay to another running application which runs in full screen mode. I am only interested to have this overlay only on top of this specific running app.
I've done some searching and I tried different behavior:
First I tried to set the TopMost property of the form as true:
private void TrackerManagerOnGameStarted(object? sender, EventArgs e)
{
_frm = new DeckOverlayForm();
_frm.TopMost = true;
_frm.Show();
}
This didn't work at all. I see the form if I alt-tab, but it doesn't show over the running app, which I think is normal, since TopMost property as stated in the documentation
A topmost form is a form that overlaps all the other (non-topmost) forms even if it is not the active or foreground form.
I believe the other running app, since it's in full screen, has the priority.
I then tried to modify my form using Win32 Api with the following code:
static readonly IntPtr HWND_TOPMOST = new IntPtr(-1);
static readonly IntPtr HWND_NOTOPMOST = new IntPtr(-2);
static readonly IntPtr HWND_TOP = new IntPtr(0);
static readonly IntPtr HWND_BOTTOM = new IntPtr(1);
const UInt32 SWP_NOSIZE = 0x0001;
const UInt32 SWP_NOMOVE = 0x0002;
const UInt32 TOPMOST_FLAGS = SWP_NOMOVE | SWP_NOSIZE;
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, uint uFlags);
protected override bool ShowWithoutActivation => true;
public DeckOverlayForm()
{
InitializeComponent();
}
private void DeckOverlayForm_Load(object sender, EventArgs e)
{
SetWindowPos(this.Handle, HWND_TOPMOST, 0, 0, 0, 0, TOPMOST_FLAGS);
}
This also didn't work.
I, then, combined the logic from point 2 with the following:
private void TrackerManagerOnGameStarted(object? sender, EventArgs e)
{
var processes = Process.GetProcesses();
var proc = processes.First(x => x.ProcessName == PROCESS_NAME);
NativeWindow win32Parent = new NativeWindow();
win32Parent.AssignHandle(proc.MainWindowHandle);
_frm = new DeckOverlayForm();
_frm.TopMost = true; //I tried with this and without it.
_frm.Show(win32Parent);
}
After trying this, I can see the form for a little bit, but the application become unresponsive for a little time. I can click on buttons inside it but nothing happens. Then after like 10 to 20 seconds, the form crashes and the running app becomes fully operational again.
How can I achieve this and end up with the form showing as an overlay without problems?
Thanks to #RezaAghaei's comment I ended up figuring it out.
The way I was opening that form was after an event was being raised from a file watcher. After adding a button to open the form manually to see if it works over other applications, it worked even with the application I want it to work with.
Therefor, I tried to change the following which ended up working:
private void TrackerManagerOnGameStarted(object? sender, EventArgs e)
{
_statisticsForm?.AnnounceGameStarted();
if (InvokeRequired)
{
Invoke(new MethodInvoker(delegate
{
var processes = Process.GetProcesses();
var proc = processes.First(x => x.ProcessName == PROCESS_NAME);
NativeWindow win32Parent = new NativeWindow();
win32Parent.AssignHandle(proc.MainWindowHandle);
_frm = Program.ServiceProvider!.GetRequiredService<DeckOverlayForm>();
_frm.TopMost = true;
_frm.Show(win32Parent);
}));
}
else
{
var processes = Process.GetProcesses();
var proc = processes.First(x => x.ProcessName == PROCESS_NAME);
NativeWindow win32Parent = new NativeWindow();
win32Parent.AssignHandle(proc.MainWindowHandle);
_frm = Program.ServiceProvider!.GetRequiredService<DeckOverlayForm>();
_frm.TopMost = true;
_frm.Show(win32Parent);
}
}
I wasn't getting the usual errors we get when something needs invoking, but when I made sure to check if invoke is required, it worked like a charm.
I'm trying to embed the osk in a wpf window or a user control and I've found the code below and it's working for notepad but for tabtip.exe, it's saying that it doesn't have a graphical interface??
WaitForInputIdle failed. This could be because the process does not have a graphical interface.
I tried letting it sleep for awhile instead of calling waitForInputIdle method but it throws another exception:
Process has exited, so the requested information is not available.
But in my task manager, I can still see TabTip.exe running.
namespace WpfApplication1
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private System.Windows.Forms.Panel _panel;
private Process _process;
public MainWindow()
{
InitializeComponent();
_panel = new System.Windows.Forms.Panel();
windowsFormsHost1.Child = _panel;
}
[DllImport("user32.dll")]
private static extern int SetWindowLong(IntPtr hWnd, int nIndex, int dwNewLong);
[DllImport("user32.dll", SetLastError = true)]
private static extern int GetWindowLong(IntPtr hWnd, int nIndex);
[DllImport("user32")]
private static extern IntPtr SetParent(IntPtr hWnd, IntPtr hWndParent);
[DllImport("user32")]
private static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, int uFlags);
private const int SWP_NOZORDER = 0x0004;
private const int SWP_NOACTIVATE = 0x0010;
private const int GWL_STYLE = -16;
private const int WS_CAPTION = 0x00C00000;
private const int WS_THICKFRAME = 0x00040000;
protected override void OnClosing(System.ComponentModel.CancelEventArgs e)
{
base.OnClosing(e);
if (_process != null)
{
_process.Refresh();
_process.Close();
}
}
private void ResizeEmbeddedApp()
{
if (_process == null)
return;
SetWindowPos(_process.MainWindowHandle, IntPtr.Zero, 0, 0, (int)_panel.ClientSize.Width, (int)_panel.ClientSize.Height, SWP_NOZORDER | SWP_NOACTIVATE);
}
protected override Size MeasureOverride(Size availableSize)
{
Size size = base.MeasureOverride(availableSize);
ResizeEmbeddedApp();
return size;
}
private void button1_Click_1(object sender, RoutedEventArgs e)
{
button1.Visibility = Visibility.Hidden;
ProcessStartInfo psi = new ProcessStartInfo("C:\\Program Files\\Common Files\\microsoft shared\\ink\\TabTip.exe");
_process = Process.Start(psi);
Thread.Sleep(500);
//_process.WaitForInputIdle();
SetParent(_process.MainWindowHandle, _panel.Handle);
// remove control box
int style = GetWindowLong(_process.MainWindowHandle, GWL_STYLE);
style = style & ~WS_CAPTION & ~WS_THICKFRAME;
SetWindowLong(_process.MainWindowHandle, GWL_STYLE, style);
// resize embedded application & refresh
ResizeEmbeddedApp();
}
}
}
Edit: Inspired by rene's comment, I've tried to obtain the window ptr as below and used spy++ to verify that the address that FindWindow gives is pointing to the correct window, but it's still not moving:
IntPtr KeyboardWnd = FindWindow("IPTip_Main_Window", null);
int style = GetWindowLong(KeyboardWnd, GWL_STYLE);
style = style & ~WS_CAPTION & ~WS_THICKFRAME;
SetWindowLong(KeyboardWnd, GWL_STYLE, style);
SetWindowPos(KeyboardWnd, IntPtr.Zero, 0, 0, (int)_panel.ClientSize.Width, (int)_panel.ClientSize.Height, SWP_NOZORDER | SWP_NOACTIVATE);
Edit 2: My first thought was that tab tip couldn't be resized, but then I noticed a behavior when I try to move the window across two different screen, it'll resize to fit the screen size, so I'm sure there must be a way to resize, so I started spy++(x64) to check :
Edit 3: after tinkering abit with user32 api and no progress, I've tried to use a memory scanner to scan for the x and y position of tabtip and change it, however, it's not refreshing until a repaint is triggered, I'm wondering the feasibility going down that path.
Can you try to run your handle code in STA thread? I had a similar issue with native window, which I had resolved using STA thread.
var thread = new Thread(() => {
// Your code here
});
thread.TrySetApartmentState(ApartmentState.STA);
thread.Start();
I had a similar problem, and the reason I had it was that I started a program that needed to be run by an administrator with a non-administrative program, and it would pop up with WaitForInputIdle failed. This could be because the process does not have a graphical interface, so I assume you try starting your program with an administrator
good day guys, can anyone help me. I have a winforms that set TopMost = true. And I have a button that when I click it, it create a notepad. Now, I want my notepad to show at the top of my winforms without setting my winforms TopMost = false. Maybe I've miss something. I'm open for any suggestions. By the way I set my form to TopMost=true and BringToFront() because I don't want any user to select any program at the taskbar and bring it to front and minimize my winforms. Thanks in advance
public Form1()
{
InitializeComponent();
this.BringToFront();
this.TopMost = true;
}
// bunch of codes here...
private void button1_Click(object sender, EventArgs e)
{
Process process = new Process();
process.StartInfo.FileName = "notepad.exe";
process.StartInfo.WindowStyle = ProcessWindowStyle.Normal;
process.Start();
}
// some codes here
private void Form1_Load(object sender, EventArgs e)
{
FormBorderStyle = System.Windows.Forms.FormBorderStyle.None;
Left = Top = 0;
Width = Screen.PrimaryScreen.WorkingArea.Width;
Height = Screen.PrimaryScreen.WorkingArea.Height;
}
PInvoke solution:
using System.Runtime.InteropServices;
...
// Even if it is "user32.dll" it will do on IA64 as well
[DllImport("user32.dll", SetLastError = true)]
static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter,
int X, int Y, int cx, int cy, int uFlags);
...
// Since Process is IDisposable, put it into "using"
using (Process process = new Process()) {
process.StartInfo.FileName = "notepad.exe";
process.StartInfo.WindowStyle = ProcessWindowStyle.Normal;
process.Start();
// wait for main window be created
process.WaitForInputIdle();
// Insert (change Z-order) as the topmost - (IntPtr) (-1);
// NoMove, NoSize - 0x0002 | 0x0001
SetWindowPos(process.MainWindowHandle, (IntPtr) (-1), 0, 0, 0, 0, 0x0002 | 0x0001);
}
I basically have code that starts the keyboard, but it opens in alphanumeric portion and the box for editing is a NumericUpDown with numbers. So, I want to open tabtip.exe aka the onscreen keyboard in windows 8.1 with the numerpad focused. Here is my current code for opening tabtip, but again it does not open with numpad by default:
using System.Runtime.InteropServices; //added for keyboard closure
using System.Windows.Interop; //Keyboard closure - must add reference for WindowsBase
//Added for keyboard closure
[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 keyboard
void openKeyboard()
{
ProcessStartInfo startInfo = new ProcessStartInfo(#"C:\Program Files\Common Files\Microsoft Shared\ink\TabTip.exe");
startInfo.WindowStyle = ProcessWindowStyle.Hidden;
Process.Start(startInfo);
}
//close keyboard
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);
}
There's also seems to be some registry edit you can do, but I can't seem to get it to make the numpad portion of the taptip keyboard in windows 8.1 to show:
Windows 8 Desktop App: Open tabtip.exe to secondary keyboard (for numeric textbox)
Currently with Windows 8.1, not much functionality seems to be programmatically exposed.
The code below will cause the tabtip.exe to read the registry because the original process is killed.
It is not completely reliable, but is a way to for it to respond to some registry values.
The part about docking is optional, it forces it to dock each time via the registry change.
The process.Kill(); should be in a try/catch since it occasionally doesn't have permission and can throw an exception.
[DllImport("user32.dll")]
public static extern IntPtr FindWindow(string sClassName, string sAppName);
[DllImport("user32.dll")]
public static extern IntPtr PostMessage(int hWnd, uint msg, int wParam, int lParam);
private static void KillTabTip()
{
// Kill the previous process so the registry change will take effect.
var processlist = Process.GetProcesses();
foreach (var process in processlist.Where(process => process.ProcessName == "TabTip"))
{
process.Kill();
break;
}
}
public void ShowTouchKeyboard(bool isVisible, bool numericKeyboard)
{
if (isVisible)
{
const string keyName = "HKEY_CURRENT_USER\\Software\\Microsoft\\TabletTip\\1.7";
var regValue = (int) Registry.GetValue(keyName, "KeyboardLayoutPreference", 0);
var regShowNumericKeyboard = regValue == 1;
// Note: Remove this if do not want to control docked state.
var dockedRegValue = (int) Registry.GetValue(keyName, "EdgeTargetDockedState", 1);
var restoreDockedState = dockedRegValue == 0;
if (numericKeyboard && regShowNumericKeyboard == false)
{
// Set the registry so it will show the number pad via the thumb keyboard.
Registry.SetValue(keyName, "KeyboardLayoutPreference", 1, RegistryValueKind.DWord);
// Kill the previous process so the registry change will take effect.
KillTabTip();
}
else if (numericKeyboard == false && regShowNumericKeyboard)
{
// Set the registry so it will NOT show the number pad via the thumb keyboard.
Registry.SetValue(keyName, "KeyboardLayoutPreference", 0, RegistryValueKind.DWord);
// Kill the previous process so the registry change will take effect.
KillTabTip();
}
// Note: Remove this if do not want to control docked state.
if (restoreDockedState)
{
// Set the registry so it will show as docked at the bottom rather than floating.
Registry.SetValue(keyName, "EdgeTargetDockedState", 1, RegistryValueKind.DWord);
// Kill the previous process so the registry change will take effect.
KillTabTip();
}
Process.Start("c:\\Program Files\\Common Files\\Microsoft Shared\\ink\\TabTip.exe");
}
else
{
var win8Version = new Version(6, 2, 9200, 0);
if (Environment.OSVersion.Version >= win8Version)
{
const uint wmSyscommand = 274;
const uint scClose = 61536;
var keyboardWnd = FindWindow("IPTip_Main_Window", null);
PostMessage(keyboardWnd.ToInt32(), wmSyscommand, (int)scClose, 0);
}
}
}
You can call the above method from a custom version of a TextBox where OnTouchDown is overridden and an additional DependencyProperty is created to indicate if the field uses the NumericKeyboard:
#region NumericKeyboard
public static readonly DependencyProperty NumericKeyboardProperty = DependencyProperty.Register("NumericKeyboard", typeof(bool), typeof(CustomTextBox), new FrameworkPropertyMetadata(false));
/// <summary> Returns/set the "NumericKeyboard" state of the CustomTextBox. </summary>
public bool NumericKeyboard
{
get { return (bool)GetValue(NumericKeyboardProperty); }
set { SetValue(NumericKeyboardProperty, value); }
}
#endregion
protected override void OnTouchDown(TouchEventArgs e)
{
base.OnTouchDown(e);
Focus();
if (IsReadOnly == false)
ShowTouchKeyboard(true, NumericKeyboard);
}
Currently, I have not had any success using similar techniques to position the TabTip window around the screen when in a floating (non-docked) state.
I've WPF application which has one Main window on which I'm opening a Popup window. The problem I'm facing is the Popup window always stays on top. If I open some other application(any windows application) the main window goes into background but the Popup windows remain on top.However if I minimize the Mainwindow the Popup also minimizes.
Please help on overcoming this issue.
Update:
I am opening the Popup as below
myPopup.IsOpen = true;
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Controls.Primitives;
using System.Windows.Input;
using System.Windows.Interop;
/// <summary>
/// Popup with code to not be the topmost control
/// </summary>
public class NonTopmostPopup : Popup
{
/// <summary>
/// Is Topmost dependency property
/// </summary>
public static readonly DependencyProperty IsTopmostProperty = DependencyProperty.Register("IsTopmost", typeof(bool), typeof(NonTopmostPopup), new FrameworkPropertyMetadata(false, OnIsTopmostChanged));
private bool? _appliedTopMost;
private bool _alreadyLoaded;
private Window _parentWindow;
/// <summary>
/// Get/Set IsTopmost
/// </summary>
public bool IsTopmost
{
get { return (bool)GetValue(IsTopmostProperty); }
set { SetValue(IsTopmostProperty, value); }
}
/// <summary>
/// ctor
/// </summary>
public NonTopmostPopup()
{
Loaded += OnPopupLoaded;
Unloaded += OnPopupUnloaded;
}
void OnPopupLoaded(object sender, RoutedEventArgs e)
{
if (_alreadyLoaded)
return;
_alreadyLoaded = true;
if (Child != null)
{
Child.AddHandler(PreviewMouseLeftButtonDownEvent, new MouseButtonEventHandler(OnChildPreviewMouseLeftButtonDown), true);
}
_parentWindow = Window.GetWindow(this);
if (_parentWindow == null)
return;
_parentWindow.Activated += OnParentWindowActivated;
_parentWindow.Deactivated += OnParentWindowDeactivated;
}
private void OnPopupUnloaded(object sender, RoutedEventArgs e)
{
if (_parentWindow == null)
return;
_parentWindow.Activated -= OnParentWindowActivated;
_parentWindow.Deactivated -= OnParentWindowDeactivated;
}
void OnParentWindowActivated(object sender, EventArgs e)
{
Debug.WriteLine("Parent Window Activated");
SetTopmostState(true);
}
void OnParentWindowDeactivated(object sender, EventArgs e)
{
Debug.WriteLine("Parent Window Deactivated");
if (IsTopmost == false)
{
SetTopmostState(IsTopmost);
}
}
void OnChildPreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
Debug.WriteLine("Child Mouse Left Button Down");
SetTopmostState(true);
if (!_parentWindow.IsActive && IsTopmost == false)
{
_parentWindow.Activate();
Debug.WriteLine("Activating Parent from child Left Button Down");
}
}
private static void OnIsTopmostChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
var thisobj = (NonTopmostPopup)obj;
thisobj.SetTopmostState(thisobj.IsTopmost);
}
protected override void OnOpened(EventArgs e)
{
SetTopmostState(IsTopmost);
base.OnOpened(e);
}
private void SetTopmostState(bool isTop)
{
// Don’t apply state if it’s the same as incoming state
if (_appliedTopMost.HasValue && _appliedTopMost == isTop)
{
return;
}
if (Child == null)
return;
var hwndSource = (PresentationSource.FromVisual(Child)) as HwndSource;
if (hwndSource == null)
return;
var hwnd = hwndSource.Handle;
RECT rect;
if (!GetWindowRect(hwnd, out rect))
return;
Debug.WriteLine("setting z-order " + isTop);
if (isTop)
{
SetWindowPos(hwnd, HWND_TOPMOST, rect.Left, rect.Top, (int)Width, (int)Height, TOPMOST_FLAGS);
}
else
{
// Z-Order would only get refreshed/reflected if clicking the
// the titlebar (as opposed to other parts of the external
// window) unless I first set the popup to HWND_BOTTOM
// then HWND_TOP before HWND_NOTOPMOST
SetWindowPos(hwnd, HWND_BOTTOM, rect.Left, rect.Top, (int)Width, (int)Height, TOPMOST_FLAGS);
SetWindowPos(hwnd, HWND_TOP, rect.Left, rect.Top, (int)Width, (int)Height, TOPMOST_FLAGS);
SetWindowPos(hwnd, HWND_NOTOPMOST, rect.Left, rect.Top, (int)Width, (int)Height, TOPMOST_FLAGS);
}
_appliedTopMost = isTop;
}
#region P/Invoke imports & definitions
#pragma warning disable 1591 //Xml-doc
#pragma warning disable 169 //Never used-warning
// ReSharper disable InconsistentNaming
// Imports etc. with their naming rules
[StructLayout(LayoutKind.Sequential)]
public struct RECT
{
public int Left;
public int Top;
public int Right;
public int Bottom;
}
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool GetWindowRect(IntPtr hWnd, out RECT lpRect);
[DllImport("user32.dll")]
private static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X,
int Y, int cx, int cy, uint uFlags);
static readonly IntPtr HWND_TOPMOST = new IntPtr(-1);
static readonly IntPtr HWND_NOTOPMOST = new IntPtr(-2);
static readonly IntPtr HWND_TOP = new IntPtr(0);
static readonly IntPtr HWND_BOTTOM = new IntPtr(1);
private const UInt32 SWP_NOSIZE = 0x0001;
const UInt32 SWP_NOMOVE = 0x0002;
const UInt32 SWP_NOZORDER = 0x0004;
const UInt32 SWP_NOREDRAW = 0x0008;
const UInt32 SWP_NOACTIVATE = 0x0010;
const UInt32 SWP_FRAMECHANGED = 0x0020; /* The frame changed: send WM_NCCALCSIZE */
const UInt32 SWP_SHOWWINDOW = 0x0040;
const UInt32 SWP_HIDEWINDOW = 0x0080;
const UInt32 SWP_NOCOPYBITS = 0x0100;
const UInt32 SWP_NOOWNERZORDER = 0x0200; /* Don’t do owner Z ordering */
const UInt32 SWP_NOSENDCHANGING = 0x0400; /* Don’t send WM_WINDOWPOSCHANGING */
const UInt32 TOPMOST_FLAGS =
SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_NOSIZE | SWP_NOMOVE | SWP_NOREDRAW | SWP_NOSENDCHANGING;
// ReSharper restore InconsistentNaming
#pragma warning restore 1591
#pragma warning restore 169
#endregion
}
Popups do - as far as i know - not suppot such a behavior, their intended usage is for ComboxBox-dropdowns and the like as far as i can tell. To realize something like that you can use a normal Window and set its Owner to the main window on which it should be dependent. This will cause the popup-window to stay on top of its owner & to minimize together with the owner.
e.g.
public class ChildWindow: Window
{
public ChildWindow(Window owner)
{
this.Owner = owner;
}
}
var popup = new ChildWindow(mainWindow);
popup.Show();
(Windows cannot be re-opened once closed, so to reuse a window you just have to Hide() it when the user tries to close it (handle Closing event and cancel using event args))
H.B. is correct - WPF Popup control was not intended do be not top-most. On the other hand - check out the following blog post on using user32 for achieving your goal:
http://chriscavanagh.wordpress.com/2008/08/13/non-topmost-wpf-popup/
Are you creating the PopUp using the WPF control? The way this control paints popup leads to the behaviour you are describing.
Consider creating a new Window showing the content you´d like to display.
Check the Window.Topmost property whitch is indicates that window should be always on top.
PopupWindow wnd = new PopupWindow();
wnd.ShowDialog();
This solved my problem.
PopupForm pf = new PopupForm();
pf.Owner = this;
pf.Topmost = false;
pf.ShowDialog()
By default popups were designed to behaves as you comment, to be topmost always. Altering this behavior using hacks or things like that will complicate your life since you will have to lead with some other problems that for sure will arise.
Anyway, too late, but may be this can help you or others:
https://chriscavanagh.wordpress.com/2008/08/13/non-topmost-wpf-popup/
Note that this has some issues.... read all the comments published there. Among other issues, it makes all childs to be non-topmost so comboboxes are affected. Comboboxes will have problems with it as they rely on having a topmost popup for when they are drop down, so if you have any combobox inside your popup it will break.
Also there are other problems related to the popup position, when you resize and then move the main window where the popup is it, popup remains in the same position.
People there are proposing some approaches for these issues. I don't know if all are resolved but you can give it a try or try to resolve them if it is up to you.
Other people trying to face with this are explaining their proposals here also (although none of them seems to be the perfect solution, it looks like there is not a perfect one, all of them have drawnbacks, but again as I have said, trying to alter the default behavior for which a thing was designed for will lead into continuous problems) :
WPF Popup ZOrder
Create your PopUp window modal to the parent control only, as demonstrated by Billy Holli and Brad Leach. That way it will stay on top of the application/window which opened the popup control, but will not stay on top of other windows/applications any longer.