Closing OpenFileDialog/SaveFileDialog - c#

We have a requirement to close child form as part of auto logoff. We can close the child forms by iterating Application.OpenForms from the timer thread. We are not able to close OpenFileDialog/SaveFileDialog using Application.OpenForms as the OpenFileDialog is not listed.
How can I close OpenFileDialog and CloseFileDialog?

This is going to require pinvoke, the dialogs are not Forms but native Windows dialogs. The basic approach is to enumerate all toplevel windows and check if their class name is "#32770", the class name for all dialogs owned by Windows. And force the dialog to close by sending the WM_CLOSE message.
Add a new class to your project and paste the code shown below. Call DialogCloser.Execute() when the logout timer expires. Then close the forms. The code will work for MessageBox, OpenFormDialog, FolderBrowserDialog, PrintDialog, ColorDialog, FontDialog, PageSetupDialog and SaveFileDialog.
using System;
using System.Text;
using System.Windows.Forms;
using System.Runtime.InteropServices;
static class DialogCloser {
public static void Execute() {
// Enumerate windows to find dialogs
EnumThreadWndProc callback = new EnumThreadWndProc(checkWindow);
EnumThreadWindows(GetCurrentThreadId(), callback, IntPtr.Zero);
GC.KeepAlive(callback);
}
private static bool checkWindow(IntPtr hWnd, IntPtr lp) {
// Checks if <hWnd> is a Windows dialog
StringBuilder sb = new StringBuilder(260);
GetClassName(hWnd, sb, sb.Capacity);
if (sb.ToString() == "#32770") {
// Close it by sending WM_CLOSE to the window
SendMessage(hWnd, 0x0010, IntPtr.Zero, IntPtr.Zero);
}
return true;
}
// P/Invoke declarations
private delegate bool EnumThreadWndProc(IntPtr hWnd, IntPtr lp);
[DllImport("user32.dll")]
private static extern bool EnumThreadWindows(int tid, EnumThreadWndProc callback, IntPtr lp);
[DllImport("kernel32.dll")]
private static extern int GetCurrentThreadId();
[DllImport("user32.dll")]
private static extern int GetClassName(IntPtr hWnd, StringBuilder buffer, int buflen);
[DllImport("user32.dll")]
private static extern IntPtr SendMessage(IntPtr hWnd, int msg, IntPtr wp, IntPtr lp);
}

i would not close all child forms in one thread but rather raise an event that every child form can/must subscribe to.
on raise your forms can decide what to do now. clean up something, persist state, send a message to the server
in the scope of your form you can access the openfiledialog and try to close that.

[workaround] here is example:
You should define fully transparent window ex. "TRANSP";
Every time you need to show dialog, you need to show TRANSP and pass TRANSP as a parameter to ShowDialog method.
When the application shuts down - call Close() Method of TRANSP window. Child dialogs will close.
public partial class MainWindow : Window
{
OpenFileDialog dlg;
TranspWnd transpWnd;
public MainWindow()
{
InitializeComponent();
Timer t = new Timer();
t.Interval = 2500;
t.Elapsed += new ElapsedEventHandler(t_Elapsed);
t.Start();
}
void t_Elapsed(object sender, ElapsedEventArgs e)
{
Dispatcher.BeginInvoke(new Action(() =>
{
transpWnd.Close();
}), null);
}
private void button1_Click(object sender, RoutedEventArgs e)
{
transpWnd = new TranspWnd();
transpWnd.Visibility = System.Windows.Visibility.Hidden; //doesn't works right
transpWnd.Show();
dlg = new OpenFileDialog();
dlg.ShowDialog(transpWnd);
}
}

My answer is conceptually similar to Hans Passant's answer.
However, using GetCurrentThreadId() as the tid parameter to EnumThreadWindows did not work for me since I was calling it from another thread. If you're doing that, then either enumerate the process' thread IDs and try each one until you find the windows you need:
ProcessThreadCollection currentThreads = Process.GetCurrentProcess().Threads;
foreach (ProcessThread thread in currentThreads) {
CloseAllDialogs(thread.Id);
}
Or save off the thread IDs that do the ShowDialog to open the CommonDialog:
threadId = GetCurrentThreadId();
threadIds.Add(threadId);
result = dialog.ShowDialog()
threadIds.Remove(threadId);
and then:
foreach (int threadId in threadIds) {
CloseAllDialogs(threadId);
}
Where CloseAllDialogs looks like:
public void CloseAllDialogs(int threadId) {
EnumThreadWndProc callback = new EnumThreadWndProc(checkIfHWNDPointsToWindowsDialog);
EnumThreadWindows(threadId, callback, IntPtr.Zero);
GC.KeepAlive(callback);
}
private bool checkIfHWNDPointsToWindowsDialog(IntPtr hWnd, IntPtr lp) {
StringBuilder sb = new StringBuilder(260);
GetClassName(hWnd, sb, sb.Capacity);
if (sb.ToString() == "#32770") {
SendMessage(hWnd, 0x0010, IntPtr.Zero, IntPtr.Zero);
}
return true;
}
private delegate bool EnumThreadWndProc(IntPtr hWnd, IntPtr lp);
[DllImport("user32.dll", SetLastError = true)]
private static extern bool EnumThreadWindows(int tid, EnumThreadWndProc callback, IntPtr lp);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern int GetCurrentThreadId();
[DllImport("user32.dll", SetLastError = true)]
private static extern int GetClassName(IntPtr hWnd, StringBuilder buffer, int buflen);
[DllImport("user32.dll", SetLastError = true)]
private static extern IntPtr SendMessage(IntPtr hWnd, int msg, IntPtr wp, IntPtr lp);

Related

C# .NET - Detecting and handling "extended" USB HID keyboard codes

I'm looking to detect and handle keycodes from a USB HID keyboard that fall outside the "normal" set of codes, i.e. codes above 100 (0x64) in a .NET Windows Forms application (.NET Framework 4.5).
Specifically, in my case I need to detect codes between 0x68 and 0x78, but I'd like to be able to detect anything up to 0xA4, which seems to be the upper limit of HID keyboard codes (aside from things like Ctrl, Alt, Win, etc.)
This question here seemed to be exactly what I was looking for, but I have had no success getting the advice on that answer to work. I have KeyPreview set to true for the form, and event handlers registered for KeyDown, KeyPress, and PreviewKeyDown, but none of them fire on reception of an 0x68 (F13) code. For now I'd just like to print the pressed key to a richtextbox control:
public mainFrm()
{
InitializeComponent();
this.KeyPreview = true;
this.KeyDown += new KeyEventHandler(KeyDownHandler);
this.KeyPress += new KeyPressEventHandler(KeyPressHandler);
this.PreviewKeyDown += new PreviewKeyDownEventHandler(PreviewKeyHandler);
}
private void KeyPressHandler(object sender, KeyPressEventArgs e)
{
rtb_hidLog.AppendText("Press: " + e.KeyChar.ToString() + "\r\n");
}
private void KeyDownHandler(object sender, KeyEventArgs e)
{
rtb_hidLog.AppendText("KeyDown: "+ e.KeyCode.ToString() + "\r\n");
}
private void PreviewKeyHandler(object sender, PreviewKeyDownEventArgs e)
{
rtb_hidLog.AppendText("Preview: " + e.KeyCode.ToString() + "\r\n");
}
I even tried overriding ProcessCmdKey (as per this question) and that also does not fire on 0x68:
protected override bool ProcessCmdKey(ref Message msg, Keys keyData)
{
rtb_hidLog.AppendText("CmdKey: " + keyData.ToString() + "\r\n");
return base.ProcessCmdKey(ref msg, keyData);
}
I have a USB HID Keyboard device connected (a PSoC microcontroller as a HID keyboard) that sends the 0x68 (F13) keycode when I press a button, but it doesn't fire the PreviewKeyHander. A standard 'A' code (0x04) from the PSoC device fires the KeyDownHandler and KeyPressHandler events with no problem. I have confirmed via USB Analyzer that the 0x68 code is being sent correctly, I just can't seem to force .NET to recognize it and fire an event. Is there something I'm missing or a trick I need to do to force my application to fire an event on these codes?
I've now also tried using Interop to use the win32 API (User32.dll) to hook into the keyboard input, and that also does not work. I get the same results; the hooked event will fire for all the keys on my keyboard, but anything not in that range does not fire a key pressed event.
My USB HID descriptor for the keyboard device, in case there is some issue there:
You can use a keyboard interceptor "in a separate DLL project that is referenced in your application" that is used by Form like this:
public delegate IntPtr KeyBoardHook( int nCode, IntPtr wParam, IntPtr lParam);
public class InterceptKeys : IDisposable
{
private const int WH_KEYBOARD_LL = 13;
private const int WM_KEYDOWN = 0x0100;
private KeyBoardHook _proc;
public event KeyBoardHook OnKeyBoardKeyClicked;
private static IntPtr _hookID = IntPtr.Zero;
public InterceptKeys()
{
_proc = HookCallback;
_hookID = SetHook(_proc);
if(_hookID == IntPtr.Zero)
{
throw new Exception($"Error Happened [{Marshal.GetLastWin32Error()}]");
}
}
public void Dispose()
{
UnhookWindowsHookEx(_hookID);
}
private IntPtr SetHook(KeyBoardHook proc)
{
using (Process curProcess = Process.GetCurrentProcess())
using (ProcessModule curModule = curProcess.MainModule)
{
return SetWindowsHookEx(WH_KEYBOARD_LL, proc,
GetModuleHandle(curModule.ModuleName), 0);
}
}
private IntPtr HookCallback(
int nCode, IntPtr wParam, IntPtr lParam)
{
OnKeyBoardKeyClicked?.Invoke(nCode, wParam, lParam);
//if (nCode >= 0 && wParam == (IntPtr)WM_KEYDOWN)
//{
// int vkCode = Marshal.ReadInt32(lParam);
// Console.WriteLine((char)vkCode);
//}
return CallNextHookEx(_hookID, nCode, wParam, lParam);
}
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern IntPtr SetWindowsHookEx(int idHook,
KeyBoardHook lpfn, IntPtr hMod, uint dwThreadId);
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool UnhookWindowsHookEx(IntPtr hhk);
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode,
IntPtr wParam, IntPtr lParam);
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern IntPtr GetModuleHandle(string lpModuleName);
}
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern IntPtr GetModuleHandle(string lpModuleName);
}

How to send a proper message to Console application using user32.dll's SendMessage

I'm trying to send text to the console when it requires Console.Readline() input, from a different thread (Windows form with a textbox). The problem is I can't seem to figure out how to send text and have it show up exactly as I wrote it.
Example: "test TEST 123" will show up in the console as: test test &é"
Image: http://puu.sh/gIjW8/e9f4d94bd5.png
using System;
using System.Windows.Forms;
using System.IO;
using System.Runtime.InteropServices;
using System.Diagnostics;
namespace ConsoleApplication1
{
public partial class Form1 : Form
{
[DllImport("User32.Dll", EntryPoint = "PostMessageA")]
private static extern bool PostMessage(IntPtr hWnd, uint msg, IntPtr wParam, int lParam);
[DllImport("user32.dll")]
static extern short VkKeyScan(char ch);
[DllImport("user32.dll", EntryPoint = "SendMessageA")]
public static extern int SendMessage(IntPtr hWnd, int uMsg, int wParam, string lParam);
[DllImport("kernel32.dll")]
internal static extern IntPtr GetConsoleWindow();
public static IntPtr hWnd = GetConsoleWindow();
public Form1()
{
InitializeComponent();
}
private void textBox1_KeyDown(object sender, KeyEventArgs e)
{
if (e.KeyData == Keys.Enter)
{
e.SuppressKeyPress = true;
TextBox tb = (sender as TextBox);
/*for (int i = 0; i < tb.Text.Length; i++)
{
PostMessage(hWnd, 0x100, (IntPtr)VkKeyScan(tb.Text[i]), 0); -> This crap doesn't support UPPERCASE and numbers <.<
}*/
PostMessage(hWnd, 0x100, (IntPtr)Keys.Enter, 0);
tb.Clear();
}
SendMessage(hWnd, 0x000C, 0, "HerpityDerp"); // -> This crap only changes the console title -_-
}
}
}
I tried using SendMessage, but it only changes the Console's title :/
I'd be very thankful if you guys could come up with a solution
A console app usually doesnt have a windows message pump, you might want to see how you can hook the console's stdout...

True minimizing for another application

I just want to do real minimized, all public codes are not minimizing in right way! It just minimize it as shown, but not minimize like if I click on Minimize button. How did I know that? Or what benefit will I get from that? When I press on minimize button, it reduce from CPU usage! (It's a game anyway.)
My code is :
[DllImport("User32.Dll", EntryPoint = "PostMessageA", SetLastError = true)]
public static extern bool PostMessage(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam);
List<int> ProcIDs = new List<int>();
private void timer1_Tick(object sender, EventArgs e)
{
foreach (Process process in Process.GetProcesses())
{
if (process.ProcessName == "League of Legends")
{
// MinimizeWindow((IntPtr)hProcess);
if (!ProcIDs.Contains(process.Id))
{
IntPtr hProcess = GetProcessWindow(process.Id);
ProcIDs.Add(process.Id);
PostMessage(hProcess, WM_SYSCOMMAND, (IntPtr)SC_MINIMIZE, IntPtr.Zero);
}
}
}
}
const int WM_SYSCOMMAND = 274;
const int SC_MINIMIZE = 0xF020;
I also tried other methods and it does the same, just minimize as show, but not real minimize! :)
You can try this
[DllImport("user32.dll")]
private static extern bool ShowWindow(IntPtr hWnd, WindowShowStyle nCmdShow);
with window style as ShowMinimized = 2,
http://www.pinvoke.net/default.aspx/user32.showwindow

send display to sleep mode in c#

it's a standard windows function that the display goes into sleep mode after the configured time. is it somehow possible to send the display into sleep mode immediately from a c# .net application in windows 7? i've already tried one thing i found but it didn't work for me.
[DllImport("user32.dll")]
static extern IntPtr SendMessage(IntPtr hWnd, UInt32 Msg, IntPtr wParam, IntPtr lParam);
[DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true)]
public static extern IntPtr GetDesktopWindow();
private const int SC_MONITORPOWER = 0xF170;
private const UInt32 WM_SYSCOMMAND = 0x0112;
private const int MONITOR_ON = -1;
private const int MONITOR_OFF = 2;
private const int MONITOR_STANBY = 1;
public static void DisplayToSleep()
{
var hWnd = GetDesktopWindow();
var ret = SendMessage(hWnd , Constants.WM_SYSCOMMAND, (IntPtr)Constants.SC_MONITORPOWER, (IntPtr)Constants.MONITOR_OFF);
}
hWnd seems to have a valid value but ret is always 0.
thx, kopi_b
This works fine in a WinForms application:
public partial class Form1 : Form
{
private int SC_MONITORPOWER = 0xF170;
private uint WM_SYSCOMMAND = 0x0112;
[DllImport("user32.dll")]
static extern IntPtr SendMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
SendMessage(this.Handle, WM_SYSCOMMAND, (IntPtr)SC_MONITORPOWER, (IntPtr)2);
}
}
The problem seems to come from the GetDesktopWindow function.
You need to use HWND_BROADCAST instead of the desktop window handle to ensure that the monitor powers off:
private const int HWND_BROADCAST = 0xFFFF;
var ret = SendMessage((IntPtr)HWND_BROADCAST, WM_SYSCOMMAND, (IntPtr)SC_MONITORPOWER, (IntPtr)MONITOR_OFF);
I have Visual Studio 2010 and Windows 7 and created a Windows Form Application with a 'Sleep' and 'Hibernate' button. The following worked for me:
private void Sleep_Click(object sender, EventArgs e)
{
bool retVal = Application.SetSuspendState(PowerState.Suspend, false, false);
if (retVal == false)
MessageBox.Show("Could not suspend the system.");
}
private void Hibernate_Click(object sender, EventArgs e)
{
bool retVal = Application.SetSuspendState(PowerState.Hibernate, false, false);
if (retVal == false)
MessageBox.Show("Could not hybernate the system.");
}
I found this here

C# Force Form Focus

So, I did search google and SO prior to asking this question. Basically I have a DLL that has a form compiled into it. The form will be used to display information to the screen. Eventually it will be asynchronous and expose a lot of customization in the dll. For now I just want it to display properly. The problem that I am having is that I use the dll by loading it in a Powershell session. So when I try to display the form and get it to come to the top and have focus, It has no problem with displaying over all the other apps, but I can't for the life of me get it to display over the Powershell window. Here is the code that I am currently using to try and get it to display. I am sure that the majority of it won't be required once I figure it out, this just represents all the things that I found via google.
CLass Blah
{
[DllImport("user32.dll", EntryPoint = "SystemParametersInfo")]
public static extern bool SystemParametersInfo(uint uiAction, uint uiParam, uint pvParam, uint fWinIni);
[DllImport("user32.dll", EntryPoint = "SetForegroundWindow")]
public static extern bool SetForegroundWindow(IntPtr hWnd);
[DllImport("User32.dll", EntryPoint = "ShowWindowAsync")]
private static extern bool ShowWindowAsync(IntPtr hWnd, int cmdShow);
private const int WS_SHOWNORMAL = 1;
public void ShowMessage(string msg)
{
MessageForm msgFrm = new MessageForm();
msgFrm.lblMessage.Text = "FOO";
msgFrm.ShowDialog();
msgFrm.BringToFront();
msgFrm.TopMost = true;
msgFrm.Activate();
SystemParametersInfo((uint)0x2001, 0, 0, 0x0002 | 0x0001);
ShowWindowAsync(msgFrm.Handle, WS_SHOWNORMAL);
SetForegroundWindow(msgFrm.Handle);
SystemParametersInfo((uint)0x2001, 200000, 200000, 0x0002 | 0x0001);
}
}
As I say I'm sure that most of that is either not needed or even flat out wrong, I just wanted to show the things that I had tried. Also, as I mentioned, I plan to have this be asynchronously displayed at some point which I suspect will wind up requiring a separate thread. Would splitting the form out into it's own thread make it easier to cause it to get focus over the Powershell session?
#Joel, thanks for the info. Here is what I tried based on your suggestion:
msgFrm.ShowDialog();
msgFrm.BringToFront();
msgFrm.Focus();
Application.DoEvents();
The form still comes up under the Powershell session. I'll proceed with working out the threading. I've spawned threads before but never where the parent thread needed to talk to the child thread, so we'll see how it goes.
Thnks for all the ideas so far folks.
Ok, threading it took care of the problem. #Quarrelsome, I did try both of those. Neither (nor both together) worked. I am curious as to what is evil about using threading? I am not using Application.Run and I have yet to have a problem. I am using a mediator class that both the parent thread and the child thread have access to. In that object I am using a ReaderWriterLock to lock one property that represents the message that I want displayed on the form that the child thread creates. The parent locks the property then writes what should be displayed. The child thread locks the property and reads what it should change the label on the form to. The child has to do this on a polling interval (I default it to 500ms) which I'm not real happy about, but I could not find an event driven way to let the child thread know that the proerty had changed, so I'm stuck with polling.
I also had trouble activating and bringing a window to the foreground. Here is the code that eventually worked for me. I'm not sure if it will solve your problem.
Basically, call ShowWindow() then SetForegroundWindow().
using System.Diagnostics;
using System.Runtime.InteropServices;
// Sets the window to be foreground
[DllImport("User32")]
private static extern int SetForegroundWindow(IntPtr hwnd);
// Activate or minimize a window
[DllImportAttribute("User32.DLL")]
private static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);
private const int SW_SHOW = 5;
private const int SW_MINIMIZE = 6;
private const int SW_RESTORE = 9;
private void ActivateApplication(string briefAppName)
{
Process[] procList = Process.GetProcessesByName(briefAppName);
if (procList.Length > 0)
{
ShowWindow(procList[0].MainWindowHandle, SW_RESTORE);
SetForegroundWindow(procList[0].MainWindowHandle);
}
}
Here is some code that I've used on one form or another for a few years. There are a few gotchas to making a window in another app pop up. Once you have the window handle do this:
if (IsIconic(hWnd))
ShowWindowAsync(hWnd, SW_RESTORE);
ShowWindowAsync(hWnd, SW_SHOW);
SetForegroundWindow(hWnd);
// Code from Karl E. Peterson, www.mvps.org/vb/sample.htm
// Converted to Delphi by Ray Lischner
// Published in The Delphi Magazine 55, page 16
// Converted to C# by Kevin Gale
IntPtr foregroundWindow = GetForegroundWindow();
IntPtr Dummy = IntPtr.Zero;
uint foregroundThreadId = GetWindowThreadProcessId(foregroundWindow, Dummy);
uint thisThreadId = GetWindowThreadProcessId(hWnd, Dummy);
if (AttachThreadInput(thisThreadId, foregroundThreadId, true))
{
BringWindowToTop(hWnd); // IE 5.5 related hack
SetForegroundWindow(hWnd);
AttachThreadInput(thisThreadId, foregroundThreadId, false);
}
if (GetForegroundWindow() != hWnd)
{
// Code by Daniel P. Stasinski
// Converted to C# by Kevin Gale
IntPtr Timeout = IntPtr.Zero;
SystemParametersInfo(SPI_GETFOREGROUNDLOCKTIMEOUT, 0, Timeout, 0);
SystemParametersInfo(SPI_SETFOREGROUNDLOCKTIMEOUT, 0, Dummy, SPIF_SENDCHANGE);
BringWindowToTop(hWnd); // IE 5.5 related hack
SetForegroundWindow(hWnd);
SystemParametersInfo(SPI_SETFOREGROUNDLOCKTIMEOUT, 0, Timeout, SPIF_SENDCHANGE);
}
I won't post the whole unit since since it does other things that aren't relevant
but here are the constants and imports for the above code.
//Win32 API calls necesary to raise an unowned processs main window
[DllImport("user32.dll")]
private static extern bool SetForegroundWindow(IntPtr hWnd);
[DllImport("user32.dll")]
private static extern bool ShowWindowAsync(IntPtr hWnd, int nCmdShow);
[DllImport("user32.dll")]
private static extern bool IsIconic(IntPtr hWnd);
[DllImport("user32.dll", SetLastError = true)]
private static extern bool SystemParametersInfo(uint uiAction, uint uiParam, IntPtr pvParam, uint fWinIni);
[DllImport("user32.dll", SetLastError = true)]
private static extern uint GetWindowThreadProcessId(IntPtr hWnd, IntPtr lpdwProcessId);
[DllImport("user32.dll")]
private static extern IntPtr GetForegroundWindow();
[DllImport("user32.dll")]
private static extern bool AttachThreadInput(uint idAttach, uint idAttachTo, bool fAttach);
[DllImport("user32.dll")]
static extern bool BringWindowToTop(IntPtr hWnd);
[DllImport("user32.dll")]
private static extern int GetWindowText(IntPtr hWnd, StringBuilder lpString, Int32 nMaxCount);
[DllImport("user32.dll")]
private static extern int GetWindowThreadProcessId(IntPtr hWnd, ref Int32 lpdwProcessId);
[DllImport("User32.dll")]
public static extern IntPtr GetParent(IntPtr hWnd);
private const int SW_HIDE = 0;
private const int SW_SHOWNORMAL = 1;
private const int SW_NORMAL = 1;
private const int SW_SHOWMINIMIZED = 2;
private const int SW_SHOWMAXIMIZED = 3;
private const int SW_MAXIMIZE = 3;
private const int SW_SHOWNOACTIVATE = 4;
private const int SW_SHOW = 5;
private const int SW_MINIMIZE = 6;
private const int SW_SHOWMINNOACTIVE = 7;
private const int SW_SHOWNA = 8;
private const int SW_RESTORE = 9;
private const int SW_SHOWDEFAULT = 10;
private const int SW_MAX = 10;
private const uint SPI_GETFOREGROUNDLOCKTIMEOUT = 0x2000;
private const uint SPI_SETFOREGROUNDLOCKTIMEOUT = 0x2001;
private const int SPIF_SENDCHANGE = 0x2;
Doesn't ShowDialog() have different window behavior than just Show()?
What if you tried:
msgFrm.Show();
msgFrm.BringToFront();
msgFrm.Focus();
TopMost = true;
.Activate() ?
Either of those any good?
Splitting it out into its own thread is a bit evil as it wont work properly if you don't call it with Application.Run and that will swallow up the thread. In the worst case scenario I guess you could separate it out into a different process and communicate via the disk or WCF.
The following solution should meet your requirements:
Assembly can be loaded into PowerShell and main class instantiated
When ShowMessage method on this instance is called, a new window is shown and activated
If you call ShowMessage multiple times, this same window updates its title text and is activated
To stop using the window, call Dispose method
Step 1: Let's create a temporary working directory (you can naturally use your own dir)
(powershell.exe)
mkdir C:\TEMP\PshWindow
cd C:\TEMP\PshWindow
Step 2: Now let's define class that we will be interacting with in PowerShell:
// file 'InfoProvider.cs' in C:\TEMP\PshWindow
using System;
using System.Threading;
using System.Windows.Forms;
namespace PshWindow
{
public sealed class InfoProvider : IDisposable
{
public void Dispose()
{
GC.SuppressFinalize(this);
lock (this._sync)
{
if (!this._disposed)
{
this._disposed = true;
if (null != this._worker)
{
if (null != this._form)
{
this._form.Invoke(new Action(() => this._form.Close()));
}
this._worker.Join();
this._form = null;
this._worker = null;
}
}
}
}
public void ShowMessage(string msg)
{
lock (this._sync)
{
// make sure worker is up and running
if (this._disposed) { throw new ObjectDisposedException("InfoProvider"); }
if (null == this._worker)
{
this._worker = new Thread(() => (this._form = new MyForm(this._sync)).ShowDialog()) { IsBackground = true };
this._worker.Start();
while (this._form == null || !this._form.Created)
{
Monitor.Wait(this._sync);
}
}
// update the text
this._form.Invoke(new Action(delegate
{
this._form.Text = msg;
this._form.Activate();
}));
}
}
private bool _disposed;
private Form _form;
private Thread _worker;
private readonly object _sync = new object();
}
}
As well as the Form that will be shown:
// file 'MyForm.cs' in C:\TEMP\PshWindow
using System;
using System.Drawing;
using System.Threading;
using System.Windows.Forms;
namespace PshWindow
{
internal sealed class MyForm : Form
{
public MyForm(object sync)
{
this._sync = sync;
this.BackColor = Color.LightGreen;
this.Width = 200;
this.Height = 80;
this.FormBorderStyle = FormBorderStyle.SizableToolWindow;
}
protected override void OnShown(EventArgs e)
{
base.OnShown(e);
this.TopMost = true;
lock (this._sync)
{
Monitor.PulseAll(this._sync);
}
}
private readonly object _sync;
}
}
Step 3: Let's compile the assembly...
(powershell.exe)
csc /out:PshWindow.dll /target:library InfoProvider.cs MyForm.cs
Step 4: ... and load the assembly in PowerShell to have fun with it:
(powershell.exe)
[System.Reflection.Assembly]::LoadFile('C:\TEMP\PshWindow\PshWindow.dll')
$a = New-Object PshWindow.InfoProvider
$a.ShowMessage('Hello, world')
A green-ish window with title 'Hello, world' should now pop-up and be active. If you reactivate the PowerShell window and enter:
$a.ShowMessage('Stack overflow')
The Window's title should change to 'Stack overflow' and the window should be active again.
To stop working with our window, dispose the object:
$a.Dispose()
This solution works as expected in both Windows XP SP3, x86 and Windows Vista SP1, x64. If there are question about how this solution works I can update this entry with detailed discussion. For now I'm hoping the code if self-explanatory.
Huge thanks people.
I think I've made it a bit shorter, here's what I put on a seperate thread and seems to be working ok.
private static void StatusChecking()
{
IntPtr iActiveForm = IntPtr.Zero, iCurrentACtiveApp = IntPtr.Zero;
Int32 iMyProcID = Process.GetCurrentProcess().Id, iCurrentProcID = 0;
IntPtr iTmp = (IntPtr)1;
while (bIsRunning)
{
try
{
Thread.Sleep(45);
if (Form.ActiveForm != null)
{
iActiveForm = Form.ActiveForm.Handle;
}
iTmp = GetForegroundWindow();
if (iTmp == IntPtr.Zero) continue;
GetWindowThreadProcessId(iTmp, ref iCurrentProcID);
if (iCurrentProcID == 0)
{
iCurrentProcID = 1;
continue;
}
if (iCurrentProcID != iMyProcID)
{
SystemParametersInfo(SPI_GETFOREGROUNDLOCKTIMEOUT, 0, IntPtr.Zero, 0);
SystemParametersInfo(SPI_SETFOREGROUNDLOCKTIMEOUT, 0, IntPtr.Zero, SPIF_SENDCHANGE);
BringWindowToTop(iActiveForm);
SetForegroundWindow(iActiveForm);
}
else iActiveForm = iTmp;
}
catch (Exception ex)
{
Definitions.UnhandledExceptionHandler(ex, 103106);
}
}
}
I don`t bother repasting the definitions...
You shouldn't need to import any win32 functions for this. If .Focus() isn't enough the form should also have a .BringToFront() method you can use. If that fails, you can set it's .TopMost property to true. You don't want to leave it true forever, so then call Application.DoEvents so the form can process that message and set it back to false.
Don't you just want the dialog to be a child of the calling form?
To do that you'll need the pass in the calling window and
use the ShowDialog( IWin32Window owner ) method.

Categories

Resources