|
Yes, I added the line
return true; on purpose because I wanted to log all window texts. My goals was to find the particular window that has the window text "C:\DummyRepo - Switch/Checkout - TortoiseGit", but all I found was windows having window texts such as "Form1", "MSCTFIME UI" and "Default IME".
|
|
|
|
|
|
That's pretty much what I already do, I am able to get a bunch of windows but I can't tell which one corresponds to
Window 000C0678 "C:\DummyRepo - Switch/Checkout - TortoiseGit" #32770 (Dialog) because I can't find any window that has the heading "C:\DummyRepo - Switch/Checkout - TortoiseGit" using GetWindowText.
Now, on the other hand, the pure C# (no win32 pinvoke involved) property Process.MainWindowTitle seems to return the proper string, but I can only apply it to the de facto main window ("C:\DummyRepo - Log Messages - TortoiseGit"), not to the "secondary sibling"-window ("C:\DummyRepo - Switch/Checkout - TortoiseGit").
|
|
|
|
|
That's not what you're already doing. You must be looking at the first answer to the question.
Look at the SECOND answer to the question on that page.
The second answer uses the function EnumThreadWindows.
The difficult we do right away...
...the impossible takes slightly longer.
|
|
|
|
|
Ahhh, you mean this:
const uint WM_GETTEXT = 0x000D;
StringBuilder message = new StringBuilder(1000);
SendMessage(handle, WM_GETTEXT, message.Capacity, message); Yes, that works great!!! Thank you! This is the complete code:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
WindowWrapper parentForm = ProcessWindowsHelper.getHandleToAnotherProcessWindow("TortoiseGitProc", "Switch/Checkout");
MessageBoxEx.Show(parentForm, "Hello on top of TortoiseGit Switch/Checkout dialog");
}
}
public class ProcessWindowsHelper
{
private delegate bool EnumThreadWndProc(IntPtr hWnd, IntPtr lp);
[DllImport("user32.dll")]
private static extern bool EnumThreadWindows(int tid, EnumThreadWndProc callback, IntPtr lp);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
static extern IntPtr SendMessage(IntPtr hWnd, uint Msg, int wParam, StringBuilder lParam);
delegate bool EnumThreadDelegate(IntPtr hWnd, IntPtr lParam);
private const uint WM_GETTEXT = 0x000D;
public static WindowWrapper getHandleToAnotherProcessWindow(string processName, string substringInAnotherProcessWindow)
{
Process myProcess = findProcessByName(processName);
IEnumerable<IntPtr> allHandlesInMyProcess = EnumerateProcessWindowHandles(myProcess.Id);
foreach (IntPtr handle in allHandlesInMyProcess)
{
StringBuilder message = new StringBuilder(1000);
SendMessage(handle, WM_GETTEXT, message.Capacity, message);
if (message.ToString().Contains(substringInAnotherProcessWindow))
{
Debug.WriteLine(message);
return new WindowWrapper(handle);
}
}
return null;
}
private static IEnumerable<IntPtr> EnumerateProcessWindowHandles(int processId)
{
List<IntPtr> handles = new List<IntPtr>();
ProcessThreadCollection processThreadCollection = Process.GetProcessById(processId).Threads;
for (int i = 0; i < processThreadCollection.Count; i++)
{
ProcessThread thread;
thread = processThreadCollection[i];
EnumThreadWindows(thread.Id,
delegate(IntPtr hWnd, IntPtr lParam)
{
handles.Add(hWnd);
return true;
},
IntPtr.Zero);
}
return handles;
}
private static Process findProcessByName(string processName)
{
Process[] allProcesses = Process.GetProcesses();
for (int j = 0; j < allProcesses.Length; j++)
{
if (allProcesses[j].ProcessName.Equals(processName))
{
return allProcesses[j];
}
}
return null;
}
}
public class WindowWrapper : IWin32Window
{
public WindowWrapper(IntPtr handle)
{
_hwnd = handle;
}
public IntPtr Handle
{
get { return _hwnd; }
}
private IntPtr _hwnd;
} The class MessageBoxEx can be found here: Parent centered MessageBox dialog in C# ยท GitHub[^]
modified 1-Aug-20 9:49am.
|
|
|
|
|
Glad you got it sorted! ๐
The difficult we do right away...
...the impossible takes slightly longer.
|
|
|
|
|
Well, I guess no joy lasts forever... In case somebody is using bash.exe instead of TortoiseGit to Switch/Checkout, then I wanted to do the same thing with bash.exe as the parent form. But when I call
parentForm = ProcessWindowsHelper.getHandleToAnotherProcessWindow("bash", "MING") then it seems the handles list inside the EnumerateProcessWindowHandles method doesn't get any elements.
private static IEnumerable<IntPtr> EnumerateProcessWindowHandles(int processId)
{
List<IntPtr> handles = new List<IntPtr>();
ProcessThreadCollection processThreadCollection = Process.GetProcessById(processId).Threads;
for (int i = 0; i < processThreadCollection.Count; i++)
{
ProcessThread thread;
thread = processThreadCollection[i];
EnumThreadWindows(thread.Id,
delegate(IntPtr hWnd, IntPtr lParam)
{
handles.Add(hWnd);
return true;
},
IntPtr.Zero);
}
return handles;
}
The call to Process.GetProcessById(processId).Threads returns 3 threads, but nothing gets added to the handles list. Does anybody know why?
When I look at the information in Spy++ I see the following:
Window OOOA9B72 "MINGW64:/c/dummyRepo" mintty Windows Properties, General tab:
Window Caption: MINGW64:/c/dummyRepo
Window Handle: OOOA9B72
Window Proc: (Unavailable)(Unicode)
Rectangle: (86, 89)-(681, 466), 595x377
Restored Rect: (86, 89)-(681, 466), 595x377
Client Rect: (8, 31)-(570, 369), 562x338
Instance Handle 00400000
Menu Handle 00000000
User Data 00000000
Windows Bytes:
modified 1-Aug-20 10:15am.
|
|
|
|
|
Have you stepped through it in the debugger?
Does it find the correct process?
Which line of code is failing?
The difficult we do right away...
...the impossible takes slightly longer.
|
|
|
|
|
Yes, I stepped it. I first rewrote the method EnumerateProcessWindowHandles according to this:
private static List<IntPtr> handles;
private static IEnumerable<IntPtr> EnumerateProcessWindowHandles(int processId)
{
handles = new List<IntPtr>();
ProcessThreadCollection processThreadCollection = Process.GetProcessById(processId).Threads;
for (int i = 0; i < processThreadCollection.Count; i++)
{
ProcessThread thread;
thread = processThreadCollection[i];
EnumThreadWindows(thread.Id, myDelegate, IntPtr.Zero);
}
return handles;
}
private static bool myDelegate(IntPtr hWnd, IntPtr lParam)
{
handles.Add(hWnd);
return true;
} So, something seems to go wrong inside the call to EnumThreadWindows, but that's a Win32 function and I don't know how to put a breakpoint inside it or how to step through it.
|
|
|
|
|
You need to put a breakpoint inside the "myDelegate" callback to see if ANY windows are being found, not just ones that match your substring.
But first, verify that it is indeed finding the correct process. Compare the ProcessId to the one shown by Task Manager.
EDIT: I'm sorry I didn't see that you had already tried the breakpoint inside the callback.
The only thing I can think of at this moment is to make sure it is finding the correct process.
The difficult we do right away...
...the impossible takes slightly longer.
modified 1-Aug-20 11:49am.
|
|
|
|
|
It is finding the right bash.exe process (there is only 1 running) and it successfully finds its 3 threads. As you said, the filtering with the substrings happens later and by then there are no handles at all to filter.
|
|
|
|
|
I have one last thing. I'm not familiar with bash, is it a command line program?
The difficult we do right away...
...the impossible takes slightly longer.
|
|
|
|
|
Yes, I think it's some kind of command prompt that is standard for Unix systems, but it exists for Windows also. I've seen some of my colleagues (those that prefer typing over interacting with GUI:s) type
$ git.exe checkout develop when they want to checkout in Git.
|
|
|
|
|
Bingo! That must be why you're not finding any Windows.
I can think of two possibilities at this point.
1. It might be that you need to find bash's parent process and enumerate the windows of that process instead.
2. It might be that the Linux Subsystem is interfering somehow.
The difficult we do right away...
...the impossible takes slightly longer.
|
|
|
|
|
When I closed my bash.exe command prompt, I could see that the following processes disappeared: backgroundTaskHost, bash, conhost, git-bash, mintty and RuntimeBroker. When I tried mintty instead of bash, then it worked great! Again, thanks for your help!
|
|
|
|
|
Happy to help!
The difficult we do right away...
...the impossible takes slightly longer.
|
|
|
|
|
Hi all I have this method in a class which tries to get an access token from Azure Active Directory - it works if I use it in a Net Core app but hangs forever when I try to use it in a Winforms app
private async Task GetAccessToken()
{
string Instance = Utils.GetAppConfigSetting("Instance");
string TenantID = Utils.GetAppConfigSetting("TenantID");
string ClientID = Utils.GetAppConfigSetting("ClientID");
string ClientSecret = Utils.GetAppConfigSetting("ClientSecret");
string BaseAddress = Utils.GetAppConfigSetting("BaseAddress");
string ResourceID = Utils.GetAppConfigSetting("ResourceID");
string APIServer = Utils.GetAppConfigSetting("APIServer");
string Authority = String.Format(CultureInfo.InvariantCulture,Instance, TenantID);
this.App = ConfidentialClientApplicationBuilder.Create(ClientID)
.WithClientSecret(ClientSecret)
.WithAuthority(new Uri(Authority))
.Build();
this.ResourceIds = new string[] { ResourceID };
this.Result = await this.App.AcquireTokenForClient(this.ResourceIds).ExecuteAsync();
this.AccessToken = this.Result.AccessToken;
this.Client.Authenticator = new JwtAuthenticator(this.AccessToken);
}
// I call it with this code
this.GetAccessToken().GetAwaiter().GetResult();
there is no error it just hangs - any ideas ?
Edit
It must be the UI thread in Winforms as I just tried the same code in a Console App and it worked perfectly
"We can't stop here - this is bat country" - Hunter S Thompson - RIP
modified 31-Jul-20 9:51am.
|
|
|
|
|
pkfox wrote: It must be the UI thread in Winforms
Correct. Your async method tries to return to the current "execution context" after the await . In a WinForms app, when the async method was called from the UI thread, it tries to return to the UI thread. But you've blocked the UI thread by trying to execute the async method synchronously:
this.GetAccessToken().GetAwaiter().GetResult(); Therefore, the async method can never complete.
If you're not accessing the UI from your GetAccessToken method, you can add .ConfigureAwait(false) to your ExecuteAsync call, so that the rest of the method can complete on a thread-pool thread:
this.Result = await this.App.AcquireTokenForClient(this.ResourceIds).ExecuteAsync().ConfigureAwait(false); Otherwise, you'll need to find a way to avoid blocking the UI thread. You'd need to make the calling method async . However, you want to avoid async void methods[^]. A simple solution is to move the code into a task-returning async method, and assign the returned task to a "discard" variable to avoid the compiler warning.
Old code, don't use:
private void ButtonClick(object sender, EventArgs e)
{
this.GetAccessToken().GetAwaiter().GetResult();
} Bad fix, don't use:
private async void ButtonClick(object sender, EventArgs e)
{
await this.GetAccessToken();
} Better fix:
private void ButtonClick(object sender, EventArgs e)
{
_ = SomeMethodAsync();
}
private async Task SomeMethodAsync()
{
await this.GetAccessToken();
}
ConfigureAwait FAQ | .NET Blog[^]
"These people looked deep within my soul and assigned me a number based on the order in which I joined."
- Homer
|
|
|
|
|
Thanks Richard should I leave the code as is in GetAccessToken() ? I got around it by using a BackGroundWorker but I think maybe your way is better
"We can't stop here - this is bat country" - Hunter S Thompson - RIP
|
|
|
|
|
If you're moving the code to a task-returning async method, and getting rid of the .GetAwaiter().GetResult() call in favour of await ing the operation, then the GetAccessToken code should be fine.
"These people looked deep within my soul and assigned me a number based on the order in which I joined."
- Homer
|
|
|
|
|
How will I know when the job has finished ?
"We can't stop here - this is bat country" - Hunter S Thompson - RIP
|
|
|
|
|
That depends what you're trying to do.
If you just want to run some code immediately after GetAccessToken has finished, you can put it in the new async method after the await this.GetAccessToken(); line.
"These people looked deep within my soul and assigned me a number based on the order in which I joined."
- Homer
|
|
|
|
|
Except GetAccessToken() is in a separate class from the form
"We can't stop here - this is bat country" - Hunter S Thompson - RIP
|
|
|
|
|
That doesn't matter. You have a task-returning async method which calls it, and put any code you need to run after it's returned after the await .
If you need to run code in the form after the async method in another class returns, you also use a task-returning async method, and put the code after the await .
class SomeClass
{
private async Task GetAccessToken() { ... }
public async Task DoSomething()
{
await GetAccessToken();
}
}
class YourForm
{
private SomeClass _someClass;
private void FormLoad(object sender, EventArgs e)
{
_someClass = new SomeClass();
_ = DoSomethingInTheForm();
}
private async Task DoSomethingInTheForm()
{
await _someClass.DoSomething();
}
}
"These people looked deep within my soul and assigned me a number based on the order in which I joined."
- Homer
|
|
|
|
|
Thanks Richard that's pretty much what I've ended up with - brilliant article in the link you posted
"We can't stop here - this is bat country" - Hunter S Thompson - RIP
|
|
|
|