Why does this code run twice? - c#

I'm trying to get the active title bar and test it if it equals certain value, I open a new browser window. I used the code below inside a timer tick event
The problem is that on each timer tick, the Process.Start will run twice, which means I see two windows of firefox opening the page www.googlepromo.com.
public static void timerTick()
{
foreach (string s in ttlkeywords) {
if ((GetActiveWindowTitle().Contains(s)) && s.Length>2)
{
System.Diagnostics.Process.Start("http://www.googlepromo.com");
Thread.Sleep(60000);
}
}
}
The function below is the one I use to get the active title bar. I don't think the problem is in the function below but I write it anyway:
// Detect active windows Title Bar
[DllImport("user32.dll")]
static extern IntPtr GetForegroundWindow();
[DllImport("user32.dll")]
static extern int GetWindowText(IntPtr hWnd, StringBuilder text, int count);
private static string GetActiveWindowTitle()
{
const int nChars = 256;
IntPtr handle = IntPtr.Zero;
StringBuilder Buff = new StringBuilder(nChars);
handle = GetForegroundWindow();
if (GetWindowText(handle, Buff, nChars) > 0)
{
return Buff.ToString();
}
return null;
}
The timer :
Timer timer = new Timer();
timer.Interval = 5000;
timer.OnTimerTick += timerTick;
Thread timerThread = timer.Start();
timer.Run();
Can someone please explain why the browser windows run twice instead of just a single time ?

Related

Progress Bar Value > Integer doesn't work in if statement?

I have an application I am testing out where audio is played and an action is performed when the audio is played. I have the audio captured in a progress bar which works flawlessly however after I start the program everything will work but the if statement below. Is there something I am missing? I am using NAudio to capture the audio etc as well.
All audio is going to the progress bar when selecting any audio source and will detect at the right level. When I click on a button in the Windows Form Application it right clicks fine, but when I have audio coming through past a certain level it doesn't right click again which is what I am trying to accomplish.
Here is the main code which gets the audio and the mouse buttons:
[DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
public static extern void mouse_event(uint dwFlags, uint dx, uint dy, uint cButtons, uint dwExtraInfo);
private const int MOUSEEVENTF_RIGHTDOWN = 0x08;
private const int MOUSEEVENTF_RIGHTUP = 0x10;
public void DoMouseClick()
{
uint X = (uint)Cursor.Position.X;
uint Y = (uint)Cursor.Position.Y;
mouse_event(MOUSEEVENTF_RIGHTDOWN | MOUSEEVENTF_RIGHTUP, X, Y, 0, 0);
}
private WaveIn recorder;
public Form1()
{
InitializeComponent();
recorder = new WaveIn();
recorder.StartRecording();
MMDeviceEnumerator enumerator = new MMDeviceEnumerator();
var devices = enumerator.EnumerateAudioEndPoints(DataFlow.All, DeviceState.Active);
audioSourcesList.Items.AddRange(devices.ToArray());
}
private void timer1_Tick(object sender, EventArgs e)
{
if (audioSourcesList.SelectedItem != null)
{
var device = (MMDevice)audioSourcesList.SelectedItem;
device.AudioEndpointVolume.Mute = false;
Label.Text = (Math.Round(device.AudioMeterInformation.MasterPeakValue * 100)).ToString();
progressBar.Value = (int)(device.AudioMeterInformation.MasterPeakValue * 100);
}
}
Here is the block that starts the main program and where it seems to not work:
private void test()
{
Thread.Sleep(2500);
DoMouseClick();
if (progressBar.Value >= 5)
{
DoMouseClick();
Thread.Sleep(2000);
DoMouseClick();
}
}
I did not work with NAudio but set up the threads you need.
If the program stops when it reaches the if condition, it is because the control is for another thread and you are using it for another thread. With the BeginInvoke command, that allowed two threads to run asynchronously so that both threads had access to the required controls.
If you want more information, refer to this link Calling Synchronous Methods Asynchronously
change Form1 constructor to this
public Form1()
{
Thread thread = new Thread(delegate ()
{
recorder = new WaveIn();
recorder.StartRecording();
});
System.Timers.Timer t = new System.Timers.Timer();
t.Interval = 500;
t.Elapsed += T_Elapsed;
t.Start();
MMDeviceEnumerator enumerator = new MMDeviceEnumerator();
var devices = enumerator.EnumerateAudioEndPoints(DataFlow.All, DeviceState.Active);
audioSourcesList.Items.AddRange(devices.ToArray());
}
Delete the timer1_Tick method and add the following lines to the program
public delegate void InvokeDelegate();
private void T_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
this.BeginInvoke(new InvokeDelegate(InvokeMethod));
}
void InvokeMethod()
{
if (audioSourcesList.SelectedItem != null)
{
var device = (MMDevice)audioSourcesList.SelectedItem;
device.AudioEndpointVolume.Mute = false;
Label.Text = (Math.Round(device.AudioMeterInformation.MasterPeakValue * 100)).ToString();
progressBar.Value = (int)(device.AudioMeterInformation.MasterPeakValue * 100);
}
}

Bring calculator window to front in Windows 10

I would like to ask you a question about bringing calculator window to front in Windows 10. I have tested a lot of codes, but nothing is really working. I think the main problem is, that Calculator is part of "ApplicationFrameHost".
In my application (C# WinForm), I would like to start system Calculator, if it's not running. If it's running bring the window to front even if it's minimized or not.
public static class WindowHelper
{
[DllImport("user32.dll")]
private static extern void keybd_event(byte bVk, byte bScan, uint dwFlags, int dwExtraInfo);
[DllImport("user32.dll")]
private static extern bool SetForegroundWindow(IntPtr hWnd);
[DllImport("user32.dll")]
private static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);
private const int ALT = 0xA4;
private const int EXTENDEDKEY = 0x01;
private const int KEYUP = 0x02;
private const int SW_MINIMIZE = 0x06;
private const int SW_RESTORE = 0x09;
public static void BringProcessToFront(IntPtr mainWindowHandle)
{
// check if window has focus already
//if (mainWindowHandle == GetForegroundWindow()) return;
ShowWindow(mainWindowHandle, SW_RESTORE);
// simulate ALT key down
keybd_event((byte)ALT, 0x45, EXTENDEDKEY | 0, 0);
// simulate ALT key up
keybd_event((byte)ALT, 0x45, EXTENDEDKEY | KEYUP, 0);
// bring window into foreground
SetForegroundWindow(mainWindowHandle);
}
}
private void btnCalc_Click(object sender, EventArgs e)
{
// get all processes
System.Diagnostics.Process[] processes = System.Diagnostics.Process.GetProcesses();
// get ApplicationFrameHost for win10
System.Diagnostics.Process[] appFH = System.Diagnostics.Process.GetProcessesByName("ApplicationFrameHost");
IntPtr mWHandle = IntPtr.Zero;
foreach (System.Diagnostics.Process proc in processes)
{
if (proc.ProcessName == "calc" || proc.ProcessName == "Calculator" || proc.ProcessName == "win32calc")
{
// non-ApplicationFrameHost case
mWHandle = proc.MainWindowHandle;
if (appFH.Length > 0)
{
// if ApplicationFrameHost is running, find calculator MainWindowHandle
foreach (System.Diagnostics.Process app in appFH)
{
if ((app.MainWindowTitle == proc.MainWindowTitle) || (proc.MainWindowTitle.Length == 0))
mWHandle = app.MainWindowHandle;
}
}
// bring window to front
WindowHelper.BringProcessToFront(mWHandle);
return;
}
}
// calculator was not found, starts new one
System.Diagnostics.Process.Start("calc");
}
This code is working on Windows 7 as well, but not for 10 with no-english localization. There is a problem, when Calculator is minimized, it can't be restored.
As I said, I have tried lot of codes like:
public static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
public static extern IntPtr FindWindowEx(IntPtr hwndParent, IntPtr hwndChildAfter, string lpszClass, string lpszWindow);
private static extern bool GetWindowPlacement(IntPtr hWnd, ref WINDOWPLACEMENT lpwndpl);
// this was working to bring focus on already displayed window
WindowHelper.FindWindowEx(app.MainWindowHandle, IntPtr.Zero, "Windows.UI.Core.CoreWindow", null);
The main problem is, that I didn't find the solution, which could unminimize window on non-english localized windows.
Do you have some example, how to deal with that?
Thank you.
EDIT:
After some testing, I have identified, why is not possible to bring this window to front. Win7 and Win10 with en (Maybe it isn't related to locale) has Calculator still activated even if it's minimized or not. On the other hand a second Win10 has process suspended when it's minimized (see picture attachment). So now the question is, how to un-suspend it. Then hopefully window could be bring to front.
You cannot force the foreground window if you are not the foreground window. There are actually a bunch of conditions that determine when you can force the foreground window. From the docs: (bottom one applies to you)
The system restricts which processes can set the foreground window.
A process can set the foreground window only if one of the following
conditions is true:
The process is the foreground process.
The process was started by the foreground process.
The process received the last input event.
There is no foreground process.
The process is being debugged.
The foreground process is not a Modern Application or the Start Screen. The foreground is not locked (see LockSetForegroundWindow)
The foreground lock time-out has expired (see SPI_GETFOREGROUNDLOCKTIMEOUT in SystemParametersInfo).
No menus are active.
An application cannot force a window to the foreground while the user is
working with another window. Instead, Windows flashes the taskbar button of the
window to notify the user.
After some time (little bit occupied by another projects) I have founded the solution, which is working for me.
public static class WindowHelper
{
public static Dictionary<IntPtr, String> appWins;
public static bool ThreadWindows(IntPtr handle, IntPtr param)
{
int size = WindowHelper.GetWindowTextLength(handle);
if (size > 0)
{
StringBuilder strbTitle = new StringBuilder(size + 1);
WindowHelper.GetWindowText(handle, strbTitle, strbTitle.Capacity);
if (strbTitle.Length > 0)
{
appWins.Add(handle, strbTitle.ToString());
return true;
}
}
return false;
}
public static void BringProcessToFront(IntPtr mainWindowHandle)
{
// check if window has focus already
//if (mainWindowHandle == GetForegroundWindow()) return;
ShowWindow(mainWindowHandle, SW_RESTORE);
// simulate ALT key down
keybd_event((byte)ALT, 0x45, EXTENDEDKEY | 0, 0);
// simulate ALT key up
keybd_event((byte)ALT, 0x45, EXTENDEDKEY | KEYUP, 0);
// bring window into foreground
SetForegroundWindow(mainWindowHandle);
}
//[DllImport("user32.dll")]
//private static extern IntPtr GetForegroundWindow();
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
public static extern int GetWindowTextLength(IntPtr hWnd);
[DllImport("user32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern int GetWindowText(IntPtr hWnd, StringBuilder lpString, int nMaxCount);
public delegate bool EnumThreadDelegate(IntPtr hWnd, IntPtr lParam);
[DllImport("user32.dll")]
public static extern bool EnumThreadWindows(int dwThreadId, EnumThreadDelegate lpfn, IntPtr lParam);
[DllImport("User32.dll")]
public static extern IntPtr FindWindowEx(IntPtr hwndParent, IntPtr hwndChildAfter, string lpszClass, string lpszWindow);
[DllImport("user32.dll")]
private static extern void keybd_event(byte bVk, byte bScan, uint dwFlags, int dwExtraInfo);
[DllImport("user32.dll")]
private static extern bool SetForegroundWindow(IntPtr hWnd);
[DllImport("user32.dll")]
private static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);
private const int ALT = 0xA4;
private const int EXTENDEDKEY = 0x01;
private const int KEYUP = 0x02;
private const int SW_RESTORE = 0x09;
}
private void btnCalc_Click(object sender, EventArgs e)
{
// get all processes
System.Diagnostics.Process[] processes = System.Diagnostics.Process.GetProcesses();
// get ApplicationFrameHost for win10
System.Diagnostics.Process[] appFH = System.Diagnostics.Process.GetProcessesByName("ApplicationFrameHost");
IntPtr mWHandle = IntPtr.Zero;
foreach (System.Diagnostics.Process proc in processes)
{
if (proc.ProcessName == "calc" || proc.ProcessName == "Calculator" || proc.ProcessName == "win32calc")
{
// save first handle
mWHandle = proc.MainWindowHandle;
// if ApplicationFrameHost is running, find calculator MainWindowHandle
foreach (System.Diagnostics.Process app in appFH)
{
// calculator is already running
if (mWHandle == (IntPtr)0x00)
{
mWHandle = WindowHelper.FindWindowEx(app.MainWindowHandle, IntPtr.Zero, "Windows.UI.Core.CoreWindow", null);
} else
{
// create new windows dictionary
WindowHelper.appWins = new Dictionary<IntPtr, String>();
// enumerate all windows in all AFH threads
foreach (System.Diagnostics.ProcessThread thread in app.Threads)
WindowHelper.EnumThreadWindows(thread.Id, new WindowHelper.EnumThreadDelegate(WindowHelper.ThreadWindows), IntPtr.Zero);
// check if proc window was found
if (WindowHelper.appWins.ContainsValue(proc.MainWindowTitle))
{
IntPtr hwnd;
// get key from value
if ((hwnd = WindowHelper.appWins.First(x => x.Value == proc.MainWindowTitle).Key) != (IntPtr)0)
mWHandle = hwnd;
}
// clear list
WindowHelper.appWins.Clear();
}
}
if (mWHandle != (IntPtr)(0x00))
{
// bring already running calc to front
WindowHelper.BringProcessToFront(mWHandle);
return;
}
// do not search for other processes
break;
}
}
// start new calc instance
System.Diagnostics.Process.Start("calc");
}
This solution is locale independent.
Thanks to everyone who pointing me to the right direction.

C# - Wait for process to exit and close 'ShowDialog()'

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();

Closing OpenFileDialog/SaveFileDialog

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);

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