For example, in the application I'm testing, I know that if I execute the CloseCommand routed command on the transmittal management view model it will display a confirmation message box like this. It has a known caption and I know the buttons it will display. With this information I can find the window and close it.
I will have to use unmanaged code to do this.
Step one is to use EnumWindows to find all open windows.
Step two is to use GetWindowText to get each window's caption.
Step three is to use EndDialog to close the target window.
My UI thread is busy waiting for the window to close so I need to find and close it on a different thread. BackgroundWorker is the obvious solution.
I originally thought I would solve this problem using SendKey, but that proved to be a dead-end. Nevertheless, the project is called SendKey.
Start a new C# WPF project called SendKey. I'm using Visual Studio 2019. You can target any framework you want,
Add a new window called TestWindow. MainWindow will launch TestWindow's view model and execute a routed command. That command will display a dialog box. At the same time MainWindow will launch a background worker that monitors windows until it finds the dialog box. It will then close the dialog box and exit.
TestWindow.xaml simply defines a routed command.
<Window x:Class="SendKey.TestWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:SendKey"
mc:Ignorable="d"
Title="TestWindow" Height="450" Width="800">
<Window.Resources>
<RoutedCommand x:Key="PromptCommand"/>
</Window.Resources>
<Window.CommandBindings>
<CommandBinding Command="{StaticResource PromptCommand}" Executed="CommandBinding_Executed"/>
</Window.CommandBindings>
<Grid/>
</Window>
using System.Windows;
using System.Windows.Input;
namespace SendKey
{
public partial class TestWindow : Window
{
public TestWindow()
{
InitializeComponent();
}
private void CommandBinding_Executed(object sender, ExecutedRoutedEventArgs e)
{
MessageBoxResult result;
result = MessageBox.Show("Are you sure?", "Test", MessageBoxButton.YesNo);
if (result != MessageBoxResult.Yes)
MessageBox.Show(result.ToString());
}
}
}
<Window x:Class="SendKey.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:SendKey"
mc:Ignorable="d"
Loaded="Window_Loaded"
Title="MainWindow" Height="450" Width="800">
</Window>
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Text;
using System.Windows;
using System.Windows.Input;
namespace SendKey
{
public partial class MainWindow : Window
{
protected delegate bool EnumWindowsProc(IntPtr hWnd, IntPtr
lParam);
[DllImport("user32.dll", CharSet =
CharSet.Unicode)]
protected static extern int GetWindowText(IntPtr hWnd, StringBuilder strText, int maxCount);
[DllImport("user32.dll", CharSet =
CharSet.Unicode)]
protected static extern int GetWindowTextLength(IntPtr hWnd);
[DllImport("user32.dll")]
protected static extern bool EnumWindows(EnumWindowsProc enumProc, IntPtr lParam);
[DllImport("user32.dll")]
protected static extern bool IsWindowVisible(IntPtr hWnd);
[DllImport("user32.dll", SetLastError
= true)]
public static extern bool EndDialog(IntPtr hWnd, int Result);
Dictionary<IntPtr, String>
WindowList = new
Dictionary<IntPtr, string>();
public MainWindow()
{
InitializeComponent();
}
private void
Window_Loaded(object sender,
RoutedEventArgs e)
{
// Create the
background worker and start it
BackgroundWorker bw = new BackgroundWorker();
bw.DoWork += Bw_DoWork;
bw.RunWorkerAsync();
// Instantiate
the TestWindow and test the PromptCommand functionality
TestWindow tw = new TestWindow();
RoutedCommand rc =
(RoutedCommand)tw.FindResource("PromptCommand");
rc.Execute(null, tw);
// The
PromptCommand returned - which is a good thing
this.Content = "Done";
}
// Bw_DoWork
and EnumTheWindows would normally be in a core library somewhere
private void
Bw_DoWork(object sender, DoWorkEventArgs e)
{
Stopwatch sw = new Stopwatch();
bool IsClosed = false;
sw.Start();
while (sw.ElapsedMilliseconds < 60000 && !IsClosed)
{
// Get a
list of all windows
WindowList.Clear();
EnumWindows(new EnumWindowsProc(EnumTheWindows),
IntPtr.Zero);
// Look
for the window with the desired caption
foreach (KeyValuePair<IntPtr, String> oKVP in WindowList)
{
if (oKVP.Value == "Test")
{
// and try to close it in the desired way
if (EndDialog(oKVP.Key, (int)System.Windows.Forms.DialogResult.Yes))
{
IsClosed = true;
return;
}
}
}
// Didn't
find the window? Wait one second and try again
System.Threading.Thread.Sleep(1000);
}
// Couldn't
close the window in one minute? Give up
Console.WriteLine("Gave up");
}
//
EnumTheWindows has an odd design. It calls this method for each window found
// I grab the
caption and hWnd and store them in a global dictionary for later evaluation
private bool
EnumTheWindows(IntPtr hWnd, IntPtr lParam)
{
int size = GetWindowTextLength(hWnd);
if (size++ > 0 && IsWindowVisible(hWnd))
{
StringBuilder sb = new StringBuilder(size);
GetWindowText(hWnd, sb, size);
WindowList.Add(hWnd,
sb.ToString());
}
return true;
}
}
}
Here's the application in action.
No comments:
Post a Comment