We are using NUnit to test a WPF control.
The text fixture basically opens a test window containing the control to be tested on a new thread. Then the Microsoft UI Automation (UIA) is used to interact with the control.
The new thread shows the window and starts the dispatcher. Things work as expected.
The issue we are running into is that this control can launch a dialog. Once the dialog is launched we need to interact with it and close it. I have been unable to get a reference to this dialog to accomplish this task.
One solution that does not work is to use Application.Current.Windows to get all the windows and then iterate through them until the dialog is found. This does not work because during unit testing Application.Current = null. Now if we only care about this test we can just instantiate an Application. This will however interfere with other tests, because the Application will automatically enter shutting down mode when our Application variable goes out of scope (at the end of the test). As a result other tests will fail (most notably because InitializeComponent typically calls System.Windows.Application.LoadComponent which can't be called during shutting down mode).
I suppose what we need is an alternative to Application.Current.Windows.
I found a working solution to my problem.
UIA fires a number of events. One of them indicates that a new window has opened.
Subscribe a handler to the WindowOpenedEvent:
Automation.AddAutomationEventHandler(WindowPattern.WindowOpenedEvent, AutomationElement.RootElement, TreeScope.Children, new AutomationEventHandler(NewWindowHandler));
public void NewWindowHandler(Object sender, AutomationEventArgs e)
{
AutomationElement element = (AutomationElement)sender;
if (element.Current.Name == "PUT YOUR NAME HERE")
{
HwndSource hSource = HwndSource.FromHwnd(new IntPtr(element.Current.NativeWindowHandle));
MyWindow = hSource.RootVisual as WavefrontToolkit.FormulaEditor.FormulaEditor;
Assert.IsNotNull(_MyWindow );
}
}
}
In the handler you do not have a reference to the window that was opened. You can however get it from the Win32 handle.
The other issue I encountered is that the test will continue on as the Window is opening. Some of the test might be depended on that window. To deal with that I cause a delay until the Window is ready.
while (MyWindow == null)
{
System.Threading.Thread.Sleep(10);
}
Related
So I have a program that runs a simple "Plugin Selection". For ease of use, when the Plugin loads up, I make the original application form invisible.
The problem is that when the Plugin closes, I can't think of a way to pass a trigger back to the original application to check if the Plugin closes, and then make the Main application visible again (or for simplicity's sake, just close).
My first attempt didn't go so well:
// Looks for the instance of the plugin, and loads the instance, and opens the Form in that instance
if (LoadPlugin(plugin))
{
// While the instanced plugin Form is visible
while (Global.Plugins.AvailablePlugins.Find(plugin).Instance.PluginForm.Visible)
{
// Makes this form invisible, to maintain the parent Application instance to host the Plugin
this.Visible = false;
}
// When the plugin form closes, close this form, and end the program.
this.Close();
}
Out of curiosity, I wonder why I can't show two different instances of FolderBrowserDialog one after the other in the constructor of a Window, but can do it in the Window's Loaded event.
The Example 1 just shows the first dialog (fbd1), and doesn't show the next one.
The Example 2 shows the two dialogs.
Example 1 :
public partial class MainWindow : Window {
public MainWindow() {
InitializeComponent();
using (var fbd1 = new FolderBrowserDialog()) {
fbd1.ShowDialog();
}
using (var fbd2 = new FolderBrowserDialog()) {
fbd2.ShowDialog();
}
}
}
Example 2 :
public partial class MainWindow : Window {
public MainWindow() {
InitializeComponent();
}
private void Window_Loaded(object sender, RoutedEventArgs e) {
using (var fbd1 = new FolderBrowserDialog()) {
fbd1.ShowDialog();
}
using (var fbd2 = new FolderBrowserDialog()) {
fbd2.ShowDialog();
}
}
}
By the way, I've also tested with WinForms, and this is almost the same.
It doesn't work in the Form's constructor and in the Form's Load event, but works in the Shown event.
The answer you like is not in fact the correct answer, it actually does activate the second dialog. Activation state and Z-order are distinct windows properties. You just can't see the dialog because you lost the foreground. One you can only ever keep when you have a window that can stay in the foreground.
A program gets ~6 seconds to steal the foreground with its own window after it starts up. That timeout is easy to see, Windows displays the Cursors.AppStarting cursor (small arrow with hourglass). That worked to get the 1st dialog into the foreground. What happens next is however doomed to go wrong. When the user closes the dialog then your app has no window left that can be moved into the foreground. Windows now goes hunting for another window to put in the foreground, inevitably one that's owned by another process. Pretty likely to be the VS main window when you debug for example. And the 6 seconds has expired. The 2nd dialog will show up and get activated but of course it is overlapped by that window.
Cold hard fact is that a dialog must always have an owner. FolderBrowserDialog is a bit too forgiving about that, providing you with a ShowDialog() overload without an owner argument. Very convenient, not always correct. It uses GetActiveWindow() under the hood to find an owner. If there isn't one then the desktop window becomes the owner, trouble ahead,
otherwise without throwing an exception.
As Reza Aghaei said in his 2nd comment :
When you close the first dialog, the second one appears, but since
your Form is not visible at the moment and is not visible in task-bar,
it doesn't activate the second dialog, while it's open behind other
windows. Just press Alt+Tab to see open windows and you will see the
second dialog too. But when your Form is visible (for example when run
code in Shown) you will not have this issue.
This is the answer to my curiosity.
I have an ActiveX control written in C# which operates a scanner from the browser using WIA. Everything works fine except the WIA CommonDialog pops under the browser window. How can I get it to show up on top of the browser?
wiaDialog = new WIA.CommonDialog();
wiaImage = wiaDialog.ShowAcquireImage(WiaDeviceType.ScannerDeviceType, WiaImageIntent.UnspecifiedIntent, WiaImageBias.MaximizeQuality, wiaFormatJPEG, false, false, false);
[Edit]
Thanks very much to Noseratio for putting me onto the right track. The suggestion to use BringWindowToTop invoked via a timer before popping up the dialog does not quite work. Instead the function to use is SetForegroundWindow. The code is as follows (invoked from a System.Timer.Timer prior to opening the scan dialog):
public static void scanDialogToTop(Object caller, EventArgs theArgs) {
scanner.theTimer.Stop();
foreach (Process p in Process.GetProcesses()) {
if (p.MainWindowTitle.StartsWith("Scan using")) {
SetForegroundWindow(p.MainWindowHandle);
break;
}
}
}
See this article for a more complete discussion.
It does not look like you can specify a parent window for ShowAcquireImage. If the caption of the popup window is static, you could use FindWindow to find the popup's handle. If ShowAcquireImage is a blocking call (doesn't return until the popup window is closed), before calling it you'd need to setup a timer and call FindWindow upon a timer event. I also suspect the WIA popup is created on a different thread (you could check that with Spy++). If that's the case, you could use the following hack to give the WIA popup window focus. Otherwise you just do BringWindowToTop.
I'm using coded ui to do my automation I need to count how many windows open for a test case but have no idea how to do it. Have tried using find and getting the applicationundertest and walking the childrens but seems like the windows don't belong to it.
Anyone got experience with this?
Most UI frameworks have a collection of the open windows.
Winforms:
int count = Application.OpenForms.Count;
A FormCollection containing all the currently open forms owned by this
application.
WPF:
int count = Application.Current.Windows.Count;
A Window reference is automatically added to Windows as soon as a
window is instantiated on the user interface (UI) thread; windows that
are created by worker threads are not added. A Window reference is
automatically removed after its Closing event has been handled and
before its Closed event is raised.
Simply create a static int in your main instance and count it up in all forms constructors and down in FormClosing event
Maybe this short code will be helpfull
this.Load += delegate { mainInstance.myCount++; };
this.FormClosing += delegate { mainInstance.myCount--; };
I have came across a problem while developing a WPF application.
The application is based on Prism.
The application boots using the prism bootstraper and
before loading any Window, the app opens a modal dialog on a different thread (STA),
and then a bunch of stuff are loaded (services and etc.)
The dialog is open during that time and allows to notify the user on the progress of the application start-up process (using event aggregator to pass the updates).
When loading is done, bootstraper closes the dialog and opens the main application window.
So far so good...
Then when closing the application, same thing is going on.
the Main window is closed, a dialog box is opened (again on a new STA thread), to allow notifications.
But now, when hitting the ShowDialog call (which occurs inside the new STA thread),
an exception is raised:
"Cannot use a DependencyObject that belongs to a different thread than its parent Freezable".
After long long hours of debugging I have figured out the cause for the exception was the background of the window which is a brush/image taken from a merged dictionary at the application level (instantiated on the wpf UI thread).
If loading the image without a ResouceDictionary - everything goes well.
To summaries:
The exception is observed only when using a resourceDictionary and only on the second call to a new STA thread which in turn loads up a DialogBox and raise an exception exactly when calling ShowDialog
If you have only one dialog (for example no dialog at boot time and only dialog at the shutdown process), then the exception will not occur.
My question is then: what is the reason for that? what exactly this exception mean in this case?
(I understand that in general there is some kind of UI thread updates form other threads, but then I do not understand why this happens only on the second instance of the dialgo+thread).
Thanks :)
As you correctly mentioned, your background object was created on main UI thread. Your background is actually a Brush object and Brush is a DependencyObject.
When a DependencyObject is created it "depends" on the STA thread it was created on. So like other dependency objects it can only be used on its own thread. This means that STA and kind of compatibility to old COM objects model.
So when you try use it on the other STA thread you get an appropriate exception.
P.S I have the same problem with images that have been defined as resources.
I had a similar issue with this. I'm not sure how are you are implementing the background. I can try to explain my situation and maybe you can get something out of it. I created my own base Window let's call it MyWindow which is inheriting from Window. Ie.
public class MyWindow : Window
{
}
What I was going for is applying the background from a dynamic resource from the applications resource dictionary.
I first went for this answer
public class MyWindow : Window
{
public MyWindow()
{
this.SetResourceReference(BackgroundProperty, "MyResourceKey");
}
}
Now this worked for me when the Resource was a set color ie.
<SolidColorBrush x:Key="MyResourceKey" Color="White"/>
I found that when I set the resource reference to a system color I would get the issue you are getting.
<SolidColorBrush x:Key="MyResourceKey" Color="{DynamicResource {x:Static SystemColors.WindowColorKey}}"/>
It would work the first time but the 2nd time I'd get the parent freezable error. So my initial thought was, oh it's a threading issue I just need to invoke the dispatcher. Now this is where I got in a snag because I thought I needed to invoke it on the window. Not true. You need to invoke it on the dependency object of that resource.
The problem. You don't have the object using SetResourceReference because it looks for the resource and creates a reference to it. So what we need is the actual dependency object. To get the object from the resource you can do this.
object temp = this.TryFindResource("MyResourceKey");
Now you have the object but this needs to be a dependency object. I haven't tried this but you may just be able to do this
DependencyObject temp = (DependencyObjet)this.TryFindResource("MyResourceKey");
Now you have the dependency object! This is what is causing our threading issue with a freezable parent. Now we invoke the dispatcher on this object. I eventually ended up with something like this. This worked for me but I might try to clean it up a bit.
public class MyWindow: Window
{
public MyWindow()
{
SetResources();
}
private void SetResources()
{
DependencyObject dependencyObject;
object temp;
temp = this.TryFindResource("MyResourceKey");
if (temp != null)
{
if (temp is DependencyObject)
{
dependencyObject = (DependencyObject)temp;
if (!dependencyObject.CheckAccess())
{
dependencyObject.Dispatcher.BeginInvoke(new System.Action(() => { this.SetResources(); }));
}
else
{
this.SetValue(BackgroundProperty, temp);
}
}
}
}
}
Now this is just setting the background property. I believe this should work the same for a style. So you can do
this.SetValue(StyleProperty, temp)
Took a little bit to figure it out. But once I got it working I was exhilarated. It looks like the dependency object our resource is using is encountering a threading issue hence it's loading the first time but not the 2nd. The first time it's on the correct thread, but somewhere along the way another thread is set off. Yet to figure this one out. If someone has a better solution to this I would love to see it.