I made an on screen keyboard with c# Windows Forms. I use Sendkeys.Send() function to send the keystrokes. All letters but the letter i works fine. When I press the letter i on the keyboard when Microsoft Word is open, it sends Ctrl + Alt + I and opens the print dialog. It is same in Notepad++ as well. But it works fine when I try to type in notepad.
In my code I send the keys with SendKeys.Send(value); where value is the text of the button which is pressed. I get the text with the following code:
string s = ((Button)sender).Text;
Any comments about why it does not work properly?
Edit: I have created a new windows forms project with just a button and the whole code is below. Still not working. Any idea would be appreciated.
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
SendKeys.Send("i");
}
// Prevent form being focused
const int WS_EX_NOACTIVATE = 0x8000000;
protected override CreateParams CreateParams
{
get
{
CreateParams ret = base.CreateParams;
ret.ExStyle |= WS_EX_NOACTIVATE;
return ret;
}
}
}
The overridden function is to prevent the form being focused. So that I can send the keystrokes to the other application which has the focus.
Two alternatives:
1-
Simulates a keypress, see http://msdn2.microsoft.com/en-us/library/system.windows.forms.sendkeys(VS.71).aspx
Sample:
public static void ManagedSendKeys(string keys)
{
SendKeys.SendWait(keys);
SendKeys.Flush();
}
2- Sends a key to a window, pressing the button for x seconds
[DllImport("user32.dll")]
public static extern void keybd_event(byte bVk, byte bScan, uint dwFlags, UIntPtr dwExtraInfo);
public static void KeyboardEvent(Keys key, IntPtr windowHandler, int delay)
{
const int KEYEVENTF_EXTENDEDKEY = 0x1;
const int KEYEVENTF_KEYUP = 0x2;
keybd_event((byte)key, 0x45, KEYEVENTF_EXTENDEDKEY, (UIntPtr)0);
Thread.Sleep(delay);
keybd_event((byte)key, 0x45, KEYEVENTF_EXTENDEDKEY | KEYEVENTF_KEYUP, (UIntPtr)0);
}
Your aren't calling the "SetForegroundWindow" Win32 API method. Therefore, your "SendKeys" call is likely sending the keys to your app, not the target app as intended.
Here's an example on MSDN:
How to: Simulate Mouse and Keyboard Events in Code
Also, here's the code from the example:
using System;
using System.Runtime.InteropServices;
using System.Drawing;
using System.Windows.Forms;
namespace SimulateKeyPress
{
class Form1 : Form
{
private Button button1 = new Button();
[STAThread]
public static void Main()
{
Application.EnableVisualStyles();
Application.Run(new Form1());
}
public Form1()
{
button1.Location = new Point(10, 10);
button1.TabIndex = 0;
button1.Text = "Click to automate Calculator";
button1.AutoSize = true;
button1.Click += new EventHandler(button1_Click);
this.DoubleClick += new EventHandler(Form1_DoubleClick);
this.Controls.Add(button1);
}
// Get a handle to an application window.
[DllImport("USER32.DLL", CharSet = CharSet.Unicode)]
public static extern IntPtr FindWindow(string lpClassName,
string lpWindowName);
// Activate an application window.
[DllImport("USER32.DLL")]
public static extern bool SetForegroundWindow(IntPtr hWnd);
// Send a series of key presses to the Calculator application.
private void button1_Click(object sender, EventArgs e)
{
// Get a handle to the Calculator application. The window class
// and window name were obtained using the Spy++ tool.
IntPtr calculatorHandle = FindWindow("CalcFrame","Calculator");
// Verify that Calculator is a running process.
if (calculatorHandle == IntPtr.Zero)
{
MessageBox.Show("Calculator is not running.");
return;
}
// Make Calculator the foreground application and send it
// a set of calculations.
SetForegroundWindow(calculatorHandle);
SendKeys.SendWait("111");
SendKeys.SendWait("*");
SendKeys.SendWait("11");
SendKeys.SendWait("=");
}
// Send a key to the button when the user double-clicks anywhere
// on the form.
private void Form1_DoubleClick(object sender, EventArgs e)
{
// Send the enter key to the button, which raises the click
// event for the button. This works because the tab stop of
// the button is 0.
SendKeys.Send("{ENTER}");
}
}
}
Related
Being new to .net, i am not able to get how to close the show dialog modal window once its open. As i have learnt we cannot close that automatically until explicitly its to be called. Here is my code:
//process - notepad.exe
Process p = Process.Start(process);
frm_Save fsave = new frm_Save();
Using (p)
{
do
{
if(!p.HasExited)
{
p.Refresh();
fsave.ShowDialog(); // it just stuck here and doesn't go to next line
}
}
while(!p.WaitForExit(1000));
}
//frm_Save.cs
public frm_Save()
{
InitializeComponent();
}
private void frm_Save_Load(...,....)
{
//
}
private void frm_Save_Shown(...,...)
{
Sleep(100);
Forms.Application.DoEvents();
Close();
}
As you have explained, you want to show a dialog with icon that you are saving a video in the background and prevent the user to do something. One regular way to do that is with a BackgroundWorker in your Dialog. Here is the code how it would work:
public class frm_Save : Form
{
public FrmProgress(List<TransferOptions> transferOptions)
{
InitializeComponent();
BackgroundWorker BgrdWorker = new System.ComponentModel.BackgroundWorker();
this.BgrdWorker.DoWork += new System.ComponentModel.DoWorkEventHandler(this.BgrdWorker_DoWork);
this.BgrdWorker.RunWorkerCompleted += new System.ComponentModel.RunWorkerCompletedEventHandler(this.BgrdWorker_RunWorkerCompleted);
}
private void FrmProgress_Load(object sender, EventArgs e)
{
// Show image and message...
}
private void BgrdWorker_DoWork(object sender, DoWorkEventArgs e)
{
// Call your video Process start Function
// after that
var stopWatch = new StopWatch();
stopWatch.Start()
while (true)
{
if (stopWatch.ElapsedMilliseconds >1000 || videoProcessHasReturnedSuccessfully)
{
break
}
}
}
private void BgrdWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
// inform the user the video processing is finished
this.Close();
}
}
Then in the main form of your console app when you want to start the whole process, you call:
frm_Save fsave = new frm_Save();
fsave.ShowDialog()
Tip: You can also use BgrdWorker.ProgressChanged to show the progress of background task to the user by communicating between the background task and the UI if necessary, but you have not requested that in your question.
This approach may work for you, note the use of TopMost.
using System.Runtime.InteropServices;
private static readonly IntPtr HWND_TOPMOST = new IntPtr(-1);
private const UInt32 SWP_NOSIZE = 0x0001;
private const UInt32 SWP_NOMOVE = 0x0002;
private 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);
....
frm_Save fsave = new frm_Save();
fsave.Show();
SetWindowPos(frm_Save.Handle, HWND_TOPMOST, 0, 0, 0, 0, TOPMOST_FLAGS);
Process p = Process.Start(process);
using (p)
{
while (!p.WaitForExit(1000))
{
fsave.Refresh();
}
}
fsave.Close();
I am trying to draw my own tooltip. But I cant get rid of the standard shadow.
Its a standard WinForm application, with lots of forms. So therefore is
Application.EnableVisualStyles();
called, and needed, when the application starts. If I comment out this line, it works. I made a minimal WinForm app below. If EnableVisualStyles is commented out, it draws a red rectangle only. When I uncomment it, it draws a red rectangle with a shadow.
Does anyone know ho to solve this? How to have Application.EnableVisualStyles(), and have a tooltip 100% OwnerDrawn, without any standard shadows?
Minimal WinForm app is here:
using System;
using System.Drawing;
using System.Windows.Forms;
namespace ToolTipExample
{
public class MainForm : Form
{
[STAThread]
static void Main()
{
// Comment out below line and it works.
Application.EnableVisualStyles();
Application.Run(new MainForm());
}
private ToolTip toolTip;
private Button button;
public MainForm()
{
toolTip = new ToolTip();
toolTip.OwnerDraw = true;
toolTip.Draw += new DrawToolTipEventHandler(toolTip1_Draw);
toolTip.Popup += new PopupEventHandler(toolTip1_Popup);
button = new Button();
button.Location = new Point(25, 25);
button.Text = "Button";
toolTip.SetToolTip(button, "Button tip text");
Controls.AddRange(new Control[] { button });
}
private void toolTip1_Popup(object sender, PopupEventArgs e)
{
e.ToolTipSize = new Size(100, 100);
}
private void toolTip1_Draw(System.Object sender, DrawToolTipEventArgs e)
{
e.Graphics.FillRectangle(new SolidBrush(Color.Red), e.Bounds);
}
}
}
You can get the class style of the ToolTip using GetClassLong and then remove CS_DROPSHADOW style from it and set the class style for the ToolTip again:
//using System.Runtime.InteropServices;
public const int GCL_STYLE = -26;
public const int CS_DROPSHADOW = 0x20000;
[DllImport("user32.dll", EntryPoint = "GetClassLong")]
public static extern int GetClassLong(IntPtr hWnd, int nIndex);
[DllImport("user32.dll", EntryPoint = "SetClassLong")]
public static extern int SetClassLong(IntPtr hWnd, int nIndex, int dwNewLong);
private void toolTip1_Popup(object sender, PopupEventArgs e)
{
e.ToolTipSize = new Size(100, 100);
var hwnd = (IntPtr)typeof(ToolTip).GetProperty("Handle",
System.Reflection.BindingFlags.NonPublic |
System.Reflection.BindingFlags.Instance).GetValue(toolTip);
var cs = GetClassLong(hwnd, GCL_STYLE);
if ((cs & CS_DROPSHADOW) == CS_DROPSHADOW)
{
cs = cs & ~CS_DROPSHADOW;
SetClassLong(hwnd, GCL_STYLE, cs);
}
}
I've written a WPF touchscreen application. Within this application I've written a user control which is a touch screen keyboard.
I have all the keys working apart from the backspace. Obviously all the keys are buttons and on the backspace button click I'd like to send the back space key to the focused textbox.
I've tried the following but this just inserts a square character in my textbox:
private void buttonBackSpace_Click(object sender, RoutedEventArgs e)
{
PresentationSource source = PresentationSource.FromDependencyObject(CurrentControl);
//CurrentControl is my Textbox
KeyEventArgs ke = new KeyEventArgs(Keyboard.PrimaryDevice, source, 0, Key.Back)
{
RoutedEvent = UIElement.KeyDownEvent
};
System.Windows.Input.InputManager.Current.ProcessInput(ke);
}
Any ideas? I'm using Visual Studio 2012 & Windows 8.
I'm not sure if this is a typo or not, but I think your issue is that
KeyEventArgs ke = new KeyEventArgs(Keyboard.PrimaryDevice, source, 0, Key.Back)
Should actually be:
KeyEventArgs ke = new KeyEventArgs(Keyboard.PrimaryDevice, source, 0, Keys.Back)
I think the Keys enumeration is what you're actually looking for here.
EDIT: The above answer does not work. The below answer should, however, solve the problem.
private void buttonBackSpace_Click(object sender, RoutedEventArgs e)
{
CurrentControl.Focus();
keybd_event(BACK, 0, KEYEVENTF_KEYDOWN, 0);
keybd_event(BACK, 0, KEYEVENTF_KEYUP, 0);
}
// Virtual Keypress Function
[DllImport("user32.dll", SetLastError = true)]
static extern void keybd_event(byte bVk, byte bScan, int dwFlags, int dwExtraInfo);
public const int KEYEVENTF_KEYDOWN = 0x0001; //Key down flag
public const int KEYEVENTF_KEYUP = 0x0002; //Key up flag
public const int BACK = 0x08; // Backspace keycode.
My C# application consists of a taskbar icon (NotifyIcon) and an overhead window initially hidden. I want the user to be able to toggle the window visibility by clicking on the NotifyIcon (left, single click). Also the window is being hidden when loosing focus.
This is what I have so far, a subclassed System.Windows.Forms.Form:
Initialization:
this.ControlBox = false;
this.ShowIcon = false;
this.ShowInTaskbar = false;
// Instance variables: bool allowVisible;
// System.Windows.Forms.NotifyIcon notifyIcon;
this.allowVisible = false;
this.notifyIcon = new NotifyIcon();
this.notifyIcon.MouseUp += new MouseEventHandler(NotifyIconClicked);
this.Deactivate += new EventHandler(HideOnEvent);
Instance methods:
private void NotifyIconClicked(object sender, MouseEventArgs e)
{
if (e.Button == System.Windows.Forms.MouseButtons.Left)
{
if (this.Visible)
this.Hide();
else
this.Show();
}
}
new public void Show()
{
this.allowVisible = true;
this.Visible = true;
this.Activate();
}
new public void Hide()
{
this.allowVisible = false;
this.Visible = false;
}
private void HideOnEvent(object sender, EventArgs e)
{
this.Hide();
}
protected override void SetVisibleCore(bool visible)
{
base.SetVisibleCore(this.allowVisible ? visible : this.allowVisible);
}
Clicking the icon reveals the window like it should. But clicking it again hides it for as long as the mouse is being pressed, then resets it to visible.
My guess is that the mouse down event steals the focus from the window so it disappears. Then the mouse up event is triggered, showing the window as it is hidden.
My next idea was to read the window visibility at mouse down event, so I tested three events and logged the UNIX time as they are called:
notifyIcon.MouseDown
notifyIcon.MouseUp
this.LostFocus
The result is pretty weird: Let's say the window is visible. This happens when I click the icon: Focus lost is called immediately. Mouse down is called as soon as I release the mouse, right before the mouse up event.
1312372231 focus lost
1312372235 mouse down
1312372235 mouse up
Why is the mouse down event delayed?
How can I toggle the window?
I think this may work for you.
I found an expert exchange post which contains a class which provides a method for checking whether the cursor is currently over the tray.
NotifyIcon - Detect MouseOut
Using this class I modified your HideOnEvent method like so:
private void HideOnEvent(object sender, EventArgs e)
{
if (!WinAPI.GetTrayRectangle().Contains(Cursor.Position))
{
this.Hide();
}
}
Which seems to do what you need.
I have included the class below:
using System.Runtime.InteropServices;
using System.Drawing;
public class WinAPI
{
public struct RECT
{
public int left;
public int top;
public int right;
public int bottom;
public override string ToString()
{
return "(" + left + ", " + top + ") --> (" + right + ", " + bottom + ")";
}
}
[DllImport("user32.dll", CharSet = CharSet.Auto)]
public static extern IntPtr FindWindow(string strClassName, string strWindowName);
[DllImport("user32.dll", SetLastError = true)]
public static extern IntPtr FindWindowEx(IntPtr parentHandle, IntPtr childAfter, string className, IntPtr windowTitle);
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool GetWindowRect(IntPtr hWnd, out RECT lpRect);
public static IntPtr GetTrayHandle()
{
IntPtr taskBarHandle = WinAPI.FindWindow("Shell_TrayWnd", null);
if (!taskBarHandle.Equals(IntPtr.Zero))
{
return WinAPI.FindWindowEx(taskBarHandle, IntPtr.Zero, "TrayNotifyWnd", IntPtr.Zero);
}
return IntPtr.Zero;
}
public static Rectangle GetTrayRectangle()
{
WinAPI.RECT rect;
WinAPI.GetWindowRect(WinAPI.GetTrayHandle(), out rect);
return new Rectangle(new Point(rect.left, rect.top), new Size((rect.right - rect.left) + 1, (rect.bottom - rect.top) + 1));
}
}
It is not a perfect solution but I hope this helps.
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.