I am calling a thread every hour to take a screenshot.
but the issue is with activating the window or moving the window to the front.
if there are other apps in the front then while taking the screenshot they appear in the front.
Whats going on?
<Runtime.InteropServices.DllImport("user32.dll")>
Private Function SetForegroundWindow(ByVal hWnd As IntPtr) As Integer
End Function
<Runtime.InteropServices.DllImport("user32.dll")>
Private Function ShowWindow(ByVal hWnd As IntPtr, ByVal nCmdShow As Integer) As IntPtr
End Function
Public Sub TakeScreenshot()
Dim thread As New Threading.Thread(AddressOf TakeScreenShotThread)
thread.Start()
End Sub
Public Function TakeScreenShotThread() As Integer
Dim proc As Process = Process.GetCurrentProcess
Call SetForegroundWindow(proc.MainWindowHandle)
Call ShowWindow(proc.MainWindowHandle, 5)
'GIVE IT TIME TO DISPLAY THE APP AND ACTIVATE WINDOW
Dim t = Threading.Tasks.Task.Run(Async Function()
Await Threading.Tasks.Task.Delay(TimeSpan.FromMilliseconds(200))
Return 1
End Function)
t.Wait()
'SAVE SCREENSHOT IMAGE CODE HERE
End Function
Unfortunately(or if one thinks from the user's point of view fortunately) SetForegroundWindow is not an unconditional "make this window the foremost/active one" command.
It has a set of restrictions:
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.
So while there are some not recommended ways to overcome this restriction, under normal circumstances, unless one of those conditions is true, SetForegroundWindow won't work.
P.S.: Also, recommended reading - https://devblogs.microsoft.com/oldnewthing/20090220-00/?p=19083
P.S.1: While this restriction is probably the reason why the original solution fails, I'd also like to point that there is a chance that proc.MainWindowHandle can be IntPtr.Zero or be a stale handle (from some starting window or etc.) and that you do not check return values from the called WIN API functions, that is both very important and may actually assist in troubleshooting the issue...
Related
I am trying to start another program from my code and set it to be the size of the screen. No matter what I try, I can't seem to get the correct handle of the window, so SetWindowPos() fails. This is on a Raspberry Pi, and the window I am trying to resize is LXTerminal running a Python script.
Code:
Process p = Process.Start ("bash", b.command);
p.WaitForInputIdle (100);
Process[] procs = Process.GetProcessesByName ("/usr/bin/lxterminal");
p.Refresh ();
IntPtr handle = procs [0].MainWindowHandle;
Console.WriteLine (handle);
SetWindowPos (handle, 0, 0, 0, 1920, 1080, 0x0040);
This always prints '0' for the handle, and "SetWindowPos (nil) (nil) to [0,0x1920,1080] 64" after the SetWindowPos has been run.
b.command is equal to -c "lxterminal -e 'python3 ../../../../GameV1.py'" (but only in this case.) This code for lxterminal is an example and the program may launch other processes.
You are waiting for input idle in a maximum amount of time of 0.1s. I sincerely doubt that the process is going to both start AND have it's main message loop created in such short manner.
I suggest you use the parameterless overload instead, so that you'll wait until the process's message loop has entered an idle state - which is when its window should've been created:
p.WaitForInputIdle()
Quoting the documentation:
A process is said to be in an idle state when it is waiting for messages inside of a message loop. This state is useful, for example, when your application needs to wait for a starting process to finish creating its main window before the application communicates with that window.
I'm using mikeobriens HIDLibrary for communicating with a sensor we make.
It's all working, and I have it so it re-enumerates if the device is unplugged then reconnected.
The problem I have is that each time it's reconnected, a new HidLibrary.HidDeviceEventMonitor.DeviceEventMonitor Worker Thread is created, and if the system is run like this for a long period, many threads are created. How can I kill these threads when the device is disconnected?
I believe there is a kind of bug if the HidLibrary.
If you look closer at the HidDevice class you may found that HidDevice.CloseDevice doesn't actually stop event monitoring.
There are two ways to fix it.
Either call Dispose instead of CloseDevice in your application:
//_device.CloseDevice(); - dispose will close device automatically
_device.Dispose();
Or set the flag MonitorDeviceEvents to false after CloseDevice call:
_device.CloseDevice();
_device.MonitorDeviceEvents = false;
I would prefer the first one.
N.B. Also be sure that you don't keep a reference to the disconnected device.
This didn't work for me because I'm using the blocking call Device.ReadReport(AddressOf OnReport). If I simply set the device to NULL (to get rid of the reference), then the next scan throws an exception.
To get around this, here's what I did:
Public Class myDevice
Private Device As HidDevice
Private isConnected as Boolean
Public Sub Close()
If Not Device Is Nothing Then
Device.CloseDevice()
Device.MonitorDeviceEvents = False
End If
isConnected = False
End Sub
Sub OnReport(Report As HidReport)
If isConnected = False Then
Device.Dispose()
Device = Nothing
Return
End If
' Do stuff with the data
' ...
' ...
' Wait for more data.
Device.ReadReport(AddressOf OnReport)
End Sub
End Class
So after closing a device, the thread is left in a state where it is still waiting for input. BUT ... I've set a flag to indicate that the device has been closed (Device.isConnected did not work for me, so I created my own flag). The next event received by the closed thread detects that the device is no longer "connected", so it disposes the device, de-references the device, and returns without calling ReadReport.
The requirements I'm up against
About 12 people are using this application, but we only want to allow 4 to close the application through traditional methods (Alt+F4, File > Exit, Close)
If any other method is used (TaskManager, WindowsShutdown) or one of the allowed users close the application, we need to perform some clean up (Closing out some connection channels)
The Code I've used to satisfy said requirements
private void formClosing(object sender, FormClosingEventArgs e)
{
// If a user is allowed to close the application, an empty file (filename)
// will be in the root directory of the application.
if(e.CloseReason == CloseReason.UserClosing && !File.Exists("filename"))
{
e.Cancel = true;
return;
}
// Cleanup
}
The Problem
If a user (not allowed to close) attempts to close the application through traditional methods, then attempts to close using Task Manager the CloseReason enum doesn't seem to reset itself, thus causing Task Manager to pop the prompt to force close, preventing the application from cleaning up.
The Question
Is this a bug, or am I missing something, something that will reset the CloseReason after the FormClosing event has been cancelled.
.NET Reflector is your friend when working out how WinForms is operating.
The Form class has an internal field called closeReason and this is used when generating the event parameter that you examine in the Closing event. This internal field is set in four different places that I can find. These are...
1, The Form.Close() method sets the closeReason = UserClosing.
This makes sense as making a manual call to the Form.Close() method is usually the result of some user action, such as a File->Exit menu option being selected by the user. Clearly this is a user action.
2, The WM_SYSCOMMAND (SC_CLOSE) sets the closeReason = UserClosing.
The WndProc of the Form processes the SC_CLOSE system command by setting the closeReason to UserClosing and the lets the default window proc execute and close the application. This makes sense as this SC_CLOSE is sent when the user presses the window close chrome button or selected the close option from right clicking the title bar. Both are user actions and so setting the closeReason to UserClosing appears correct.
3, WndProc processes message WM_CLOSE (0x10) with closeReason = TaskManagerClosing
WM_CLOSE is sent by task manager and other applications to close a window and if the closeReason is currently equal to None it updates it to TaskManagerClosing. Note this issue with it being updated only if it is None as I think this is a problem for you.
4, WndProc processes messages 0x11 and 0x16 with closeReason = WindowsShutDown
This is not very interesting as you do not care about this scenario but it is just standard processing of shut down messages.
So the core problem you are having is that at no point is the closeReason being reset back to None when you cancel the Closing event. Therefore point number 3 above will never correctly update the value to TaskManagerClosing if that occurs after your cancel. As the closeReasson is an internal field you cannot update it directly. But you can cheat and this is an approach I have used myself in the past. You need to use reflection to get access to the internal field and then reset it to None when you set Cancel=true in your event handler.
I have not tested this code but you need something along the lines of...
PropertyInfo pi = typeof(Form).GetProperty("CloseReason",
BindingFlags.Instance |
BindingFlags.SetProperty |
BindingFlags.NonPublic);
pi.SetValue(this, CloseReason.None, null);
I think you cannot keep your process from shutting down if it's initiated by task manager (that is, OS... he's the 'big boss', it doesn't make sense that you can deny it something like closing your program).
The next best thing is to record the state of the application, and then instantiate another instance of your process with some startup options to take over the state you left. The OS would kill your process, but you will start another one immediately.
Also, if the user clicks in TaskManager "go to process" in the app list, and from there ends process, I don't think you'll be receiving any event at all...
Maybe it would be best if you had a windows service that's running behind the scenes and that keeps track that an instance is running. This way, users probably won't be aware that such process exists since it's not their application, and you can use that keep track of application shutdown.
I'm trying to find a reliable way to activate / set focus to a window of an external application using C#. Currently I'm attempting to achieve this using the following Windows API calls:
SetActiveWindow(handle);
SwitchToThisWindow(handle, true);
Previously I also had ShowWindow(handle, SW_SHOWMAXIMIZED); executing before the other 2, but removed it because it was causing strange behavior.
The problem I'm having with my current implementation is that occasionally the focus will not be set correctly. The window will become visible but the top part of it will still appear grayed out as if it wasn't in focus.
Is there a way to reliably do this which works 100% of the time, or is the inconsistent behavior a side-effect I can't escape? Please let me know if you have any suggestions or implementations that always work.
You need to use AttachThreadInput
Windows created in different threads typically process input independently of each other. That is, they have their own input states (focus, active, capture windows, key state, queue status, and so on), and they are not synchronized with the input processing of other threads. By using the AttachThreadInput function, a thread can attach its input processing to another thread. This also allows threads to share their input states, so they can call the SetFocus function to set the keyboard focus to a window of a different thread. This also allows threads to get key-state information. These capabilities are not generally possible.
I am not sure of the ramifications of using this API from (presumably) Windows Forms. That said, I've used it in C++ to get this effect. Code would be something like as follows:
DWORD currentThreadId = GetCurrentThreadId();
DWORD otherThreadId = GetWindowThreadProcessId(targetHwnd, NULL);
if( otherThreadId == 0 ) return 1;
if( otherThreadId != currentThreadId )
{
AttachThreadInput(currentThreadId, otherThreadId, TRUE);
}
SetActiveWindow(targetHwnd);
if( otherThreadId != currentThreadId )
{
AttachThreadInput(currentThreadId, otherThreadId, FALSE);
}
targetHwnd being the HWND of the window you want to set focus to. I assume you can work out the P/Invoke signature(s) since you're already using native APIs.
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool SetForegroundWindow(IntPtr hWnd);
This one worked for me
If it's all internal in your application then you can get the parent window or that window, and in this way activate it (vb sorry):
Public Class Form1 : Inherits Form
Protected Overrides Sub OnLoad(e As EventArgs)
Dim form2 As New Form2
form2.Show()
End Sub
End Class
Class Form2 : Inherits Form
Protected Overrides Sub OnLoad(e As EventArgs)
MyBase.OnLoad(e)
Me.Owner.Activate()
End Sub
End Class
hwnd_WhoRecvFocus.ShowWindow( SW_MINIMIZE )
hwnd_WhoRecvFocus.ShowWindow( SW_RESTORE )
Kind of a special case problem:
I start a process with System.Diagnostics.Process.Start(..)
The process opens a splash screen -- this splash screen becomes the main window.
The splash screen closes and the 'real' UI is shown. The main window (splash screen) is now invalid.
I still have the Process object, and I can query its handle, module, etc. But the main window handle is now invalid.
I need to get the process's UI (or UI handle) at this point. Assume I cannot change the behavior of the process to make this any easier (or saner).
I have looked around online but I'll admit I didn't look for more than an hour. Seemed like it should be somewhat trivial :-(
If you don't mind using the Windows API, you could use EnumWindowsProc, and check each of the handles that that turns up using GetWindowThreadProcessId (to see that it's in your process), and then maybe IsWindowVisible, GetWindowCaption and GetWindowTextLength to determine which hWnd in your process is the one you want.
Though if you haven't used those functions before that approach will be a real pain, so hopefully there's a simpler way.
#ageektrapped is on the right track, however FindWindow will not search child windows.
For that you will need to use FindWindowEx
Thank you for your answers. Thanks to you here, I figured out how to know if the main window of a process is in front or not:
N.B : of course this needs System.Diagnostic and System.Runtime.Interrop
public bool IsWindowActive(Int32 PID)
{
return IsWindowActive(Process.GetProcessById(PID));
}
[DllImport("user32.dll")]
private static extern
IntPtr GetForegroundWindow();
public bool IsWindowActive(Process proc)
{
proc.Refresh();
return proc.MainWindowHandle.Equals(GetForegroundWindow());
}
You may find that if you call .Refresh() that you get the new top-level window.
If you know the window's title, you can use the Win32 call, FindWindow, through P/Invoke.
You can find the signature here on pinvoke.net
From what I understand MainWindowHandle property of the process you are starting is not valid. If that's the case, you can use FindWindow function (from Win32 SDK) which returns the window handle you need. All you need is the class name of target application's main window. You can obtain it using Spy++ or Winspector. You also need to ensure you have the right window by checking that window's process id using GetWindowThreadProcessId.
At last, I have to say I am not an expert on Win32 and there might be a better solution for your case.
Use Process.GetProcessById(proc.Id); where proc was your splash screen.
Works for me.
Now, how do you get to main window properties in System.Windows.Forms to give it focus w/o using win32?
After all .net is supposed to be a one-stop solution - is it not?
Somewhere in the code, the "real" main window is created. You can just save the window handle at that time and then after the splash screen closes you can set Application.MainWindow to the real window.
The MainWindowHandle property is cached after it is first accessed which is why you don't see it changing even after the handle becomes invalid. GregUzelac's information is correct. Calling Proces.Refresh will causes the next call to Process.MainWindowHandle to re-do the logic to find a new main window handle. Michael's logic also works because the new Process doesn't have a cached version of the MainWindowHandle.