Ookii.Dialogs - making sure another dialog appears above Progress dialog - wpf

If you're unfamiliar with Ookii.Dialogs, I suggest you look at this web page first. It is open source and you can find source code, compiled binary, documentation and sample app as a download there.
In my application, I'm using Ookii.Dialogs.Wpf.ProgressDialog to ShowDialog(this) a progress dialog that does some processing on files (this is always the application's main window). As expected, the progress dialog takes about a second before it actually becomes visible (even if it is already doing the processing of my files).
In the DoWork thread of the progress dialog, I'm also checking whether the output files already exist and asking the user whether to overwrite each file or skip the output. I use Ookii.Dialogs.Wpf.TaskDialog to ShowDialog(this) a "Task Dialog with Command Links" (looks like this) and ask the user the overwrite question -- except when the OS doesn't support it, I fall back to a regular MessageBox (the problem applies to the message box as well).
The problem occurs when my application finds an existing file right at the beginning of the progress dialog's DoWork thread. When the task dialog appears asking user whether to overwrite:
Expected behavior: The task dialog must stay on top. When the progress dialog appears (after 1s delay), it must appear behind the task dialog.
Actual behavior: The task dialog does not stay on top. When the progress dialog appears after 1s delay), it appears on top of the task dialog.
The actual behavior does not occur in subsequent overwrite requests when the progress dialog is already visible. The task dialog appears correctly on top of the progress dialog for the subsequent ones, although the user can switch back and forth the two dialogs (just can't switch to the main window from either of them).
I'm looking for this:
Best solution: Make the task dialog appear modal on the progress dialog. If the user tries to switch to progress dialog, user must not be allowed to do so.
Second best: Make the first appearance of task dialog remain on top even when the progress dialog appears after the 1s delay.
I'm not looking for the following:
Set the always-on-top flag of task dialog. I don't want the task dialog to appear always-on-top of every window on user's computer.
Add a delay before task dialog's appearance. I have tried Thread.Sleep() even in a loop, it simply hangs the execution of everything and doesn't solve the problem.
Wait till the progress dialog appears before showing the task dialog. Could work in theory, except I didn't find a way to know if the progress dialog has appeared or not.
Make the dialogs modeless. I want them both to be modal. (Besides I have tried making them modeless; it doesn't help solve the problem.)
A solution that works only for the task dialog. It must work for the regular message box as well.

I know this may a little late coming but I just pre-empted a similar problem in my own program and it reminded me of this question.
I used the windows FindWindowEx API function against the task dialogues window title property to find the dialogues window handle, then used my own class to make that an Iwin32Window which can then be used as a parent for a message box or task-dialogue.
the appropriate code is below (VB.NET)
'allows us to use the handle as a window
class window
Implements IWin32Window
private _handle
public sub new(handle as intptr)
_handle = handle
end sub
Public ReadOnly Property Handle As System.IntPtr Implements System.Windows.Forms.IWin32Window.Handle
Get
Return _h
End Get
End Property
end class
'Declare the needed DLL import
class NativeMethods
<DllImport("user32.dll", SetLastError:=True, ThrowOnUnmappableChar:=True, CharSet:=CharSet.Unicode, bestFitMapping:=False)>
Public Shared Function FindWindowEx(hwndParent As IntPtr, hwndChildAfter As IntPtr, lpszClass As String, lpszWindow As String) As IntPtr
End Function
end class
'Make sure you've set a window title when you run your task
sub runTask()
dim myDlg as Ookii.Dialogs.ProgressDialog
'Do what you need to here
myDlg.WindowTitle = "make sure you set a title"
end sub
sub myTask(sender as object, e As System.ComponentModel.DoWorkEventArgs)
'Checks and balances here
if fileExists then
'this is the dialog that's running our task
Dim dlg As Ookii.Dialogs.ProgressDialog = DirectCast(sender, Ookii.Dialogs.ProgressDialog)
'Find the window handle
Dim dlgHandle As IntPtr = NativeMethods.FindWindowEx(IntPtr.Zero, IntPtr.Zero, Nothing, dlg.WindowTitle)
'make it an Iwin32Window
dim dlgWindow as new window(dlgHandle)
'That can then be used as a parent for the message box or the task dialog
MessageBox.Show(dlgWindow,"The file exists, overwrite?")
end if
end sub
I'm not really very good at commenting my code or making my explanations understood so If you have any questions about what's going on I'll try and help.

Today I've read the question and found a solution for me. Tried it for TaskDialog, and this ist working. Didn't try with other dialogs.
I wrote a DialogControler to deal with MVVM.
This DialogControlar has a property called 'Owner' which contains the WPF-MainWindow.
Then I use this for invoking (in my case I've been coming from a backgroundthread in the viewmodel) and also set this in ShowDialog.
My Code:
using (var dialog = new Ookii.Dialogs.Wpf.TaskDialog())
{
dialog.WindowTitle = "My title";
dialog.MainInstruction = "My text";
var okButton = new Ookii.Dialogs.Wpf.TaskDialogButton(Ookii.Dialogs.Wpf.ButtonType.Ok);
dialog.Buttons.Add(okButton);
Owner.Dispatcher.Invoke(new Action(() => dialog.ShowDialog(Owner)));
}
With this I got a topmost window.

Related

Update UI from another window BackgroundWorker vb.net WPF

I am working on a wpf project...I've faced similar issues in the past,but didn't get the answer to it and that's why i'm asking again :)
My WPF app has 2 windows,one works as a splash screen and the 2nd one is just a basic window with a canvas.The splash screen has a BackGroundWorker.Now,i have this code :
Dim h2 as new Window2
For Each fi As FileInfo In New DirectoryInfo(Application.StartupPath +
"\data\img\em_sml").GetFiles()
h2.canvas.children.add(new Button)
Now, my question is how do i use this code in the backgroundworker in Window1 ?? I tried this :
Dim method as Delegate
Private Sub BgWorker_DoWork(sender As Object, e As DoWorkEventArgs) Handles BgWorker.DoWork
If h2.Dispatcher.CheckAccess Then
For Each fi As FileInfo In New DirectoryInfo(Application.StartupPath +
"\data\img\em_sml").GetFiles()
h2.canvas.children.add(new Button)
Else
h2.Dispatcher.BeginInvoke(DispatcherPriority.Normal, method)
For Each fi As FileInfo In New DirectoryInfo(Application.StartupPath +
"\data\img\em_sml").GetFiles()
h2.canvas.children.add(new Button)
End if
A few things i'ld like to clear here :
• I got the sample code from another SO post(i converted it from c#)
• Dim method as Delegate,i don't really know how to use a delegate function
• The code above returns some exceptions like Parameter name:Method , Value can not be null(I know what this means but as i said,i don't know how i can use the delegate function in such a case)
Any help would be appreciated
The code you're using is trying to invoke to the UI thread via Dispatcher.BeginInvoke(). This is the way to do it in threads, but not in a BackgroundWorker. In a BGW you're supposed to call the ReportProgress() method combined with subscribing to the ProgressChanged event.
However for what you're trying to do, this is not good at all...
For starters: You should NEVER create controls in a background thread! All work related to the UI (User Interface) must always, always, always be done on the UI thread ONLY.
Secondly: There isn't really a good reason for trying to do this in a background thread. The way you're doing it now constantly updates the UI thread, causing it to lag/freeze anyway. What you could do to minimize the lag is to add the buttons in batches, but then as I said before you shouldn't be creating controls in a background thread at all.
Finally: The whole operation of iterating a few files and creating buttons for them really isn't very heavy. Unless you have thousands of files (in which case you should only display them in batches) this will not take that long to perform on the UI thread.
Conclusion: Skip the BackgroundWorker and run your code on the UI thread instead. If you have a really huge amount of files to load, store their paths in a list, only load them in batches and let the user decide when to load the next batch of files.

Stopping an Unloaded event in WPF

I think this isn't the best way to go about doing this but this is the only way I can think of.
I have this WPF UserControl and it is called via a menu item (click on the menu and open the UserControl).
There are a number of things the user can do on the UserControl and I keep track of the changes made (via EF and a variable that is set to true if the user makes any changes).
Now comes the part where I know I'm doing wrongly. I want the UserControl to check if there have been changes or additions made.
I have placed this in the Unloaded event trigger.
Private Sub TheUserControl_Unloaded(sender As Object, e As RoutedEventArgs) Handles Me.Unloaded
If (changesMade) Then
Dim answer = MessageBox.Show("Changes have been.....", "Alert", MessageBoxButton.OKCancel)
If (answer = MessageBoxResult.OK) Then
If (saveChanges() = False) Then
'stop unloading???
End If
Else
MessageBox.Show("Discarding changes.")
End if
End if
End Sub
I realised that I have placed this in the wrong EventControl but I have no idea which Event Control to place it in.
So now I'm asking, where can I put this "check" to ensure that if the user navigates away from this page (by clicking on another item in the menu) they will be asked to save the changes made before the UserControl closes and if an error occurs in the saving, it would stop the UserControl from Unloading.
Thanks.

Form closing immediately

Dim details As New frmDetails(ID, JobID, True)
details.ShowDialog()
The form flashes open and immediately closes. If I use Show() rather than ShowDialog() it stays open and look fine. Here are some things I've checked:
Breaking in FormClosing shows only
System.Windows.Forms.Form.OnFormClosing
System.Windows.Forms.Form.CheckCloseDialog
System.Windows.Forms.Application.ThreadContext.System.Windows.Forms.UnsafeNativeMethods.IMsoComponent.FContinueMessageLoop
System.Windows.Forms.Application.ComponentManager.System.Windows.Forms.UnsafeNativeMethods.IMsoComponentManager.FPushMessageLoop
System.Windows.Forms.Application.ThreadContext.RunMessageLoopInner
System.Windows.Forms.Application.ThreadContext.RunMessageLoop
System.Windows.Forms.Application.RunDialog
System.Windows.Forms.Form.ShowDialog
between the ShowDialog and the FormClosing.
CloseReason is "None"
Load runs to the end, as does VisibleChanged (though Activated never gets called).
There's no sign of any Exceptions being thrown.
Intellitrace doesn't show anything going on.
After the form closes, the DialogResult is "Cancel" (There's no reference to DialogResult in the form or its Designer)
I'm not doing any explicit threading
I'd appreciate any suggestions either as to what's going on or how to go about finding out.
Thanks.
In my case I was setting the DialogResult property on the load event to Cancel, and that was causing the dialog to close immediately after Load. I've set it to the default None and now I only set it to other value on the Click event of a button when I really need to close it.
Well, this will probably do nobody any good, but here's how I solved the problem:
There was a line in the Load method that read
Me.Text = ""
I have no idea what it was doing there (this isn't my code, thank goodness), especially since the value gets set again later on, but taking that line out stopped the form from mysteriously closing. Go figure.
I had a similar problem. In my case it was due to not specifying the parent window on the ShowDialog(). The dialog associated with the window that was topmost, which happened to be a combobox drop-down that was going away.
In my case, I changed the ShowDialog() call to use my application's main window as the parent, and problem solved.
Been debugging for couple of hours with the same problem. In my case the likely reason was that the parent form has setting ShowInTaskbar = false in Load event, while my form had this set to true in the designer. For some reason this caused the dialog result to be set to Cancel during initialization.

Close all open modal dialog windows

I have a WPF application that has several modal window used for various purposes. This is easily accomplished by using the ShowDialog function. However, in my application I have a timer to measure idle time (i.e. no mouse moves or key strokes) that will cause the user to be logged off. Is there a way (when this timer fires) to find and close all open modal windows without tracking each explicitly?
Update
I would also like to close any MessageBox.Show instances. Is this possible?
Thanks,
Matt
Have you tried to iterate the Application.Current.Windows collection, and close all these that are not the Application.Current.MainWindow?
Jogy
Is there a way (when this timer fires) to find and close all open modal windows without tracking each explicitly?
You could use ComponentDispatcher.IsThreadModal to check to see if you're UI thread is in a modal state. If it is, the Application.Current.Windows property will give you the list of opened Windows.
If you only have a single MainWindow, you could close any others (as they'd be your modal dialogs), but if you have multiple windows, you'd have to check each one.
Unfortunately, there's no direct API to determine whether a specific Window is modal - but there is a private variable in the Window class you could use to do this. For example, the following method uses reflection to determine whether a Window is modal:
public static bool IsModal(Window window)
{
Type type = typeof(Window);
var field = type.GetField("_showingAsDialog", BindingFlags.Instance | BindingFlags.NonPublic);
return field.GetValue(window);
}
This is, unfortunately, subject to change (since it's using undocumented private members).

In forms application, is there any Alternative to MsgBox?

I like how MsgBox stops all further processing until it's clicked but I don't like how it pops that little msgbox in the middle of the screen. Is there a way to do something like a msgbox but keep it on my Form (which is always in view) so I can go ahead and click a button on the form instead of having to bring the little msgbox window on top of windows that may be covering it.
I use the msgbox to inform me that a certain situation has happened, which I manually fix and when I'm done I click the MsgBox to continue processing. It'd be nice to have this button right on the form.
which I then have bring to the front if there is a window covering it
That shouldn't happen, but can happen if you display the message box from a thread in your program. The window has the desktop as the parent and has no Z-order relationship with the windows in your user interface. And yes, can easily disappear behind the window of another app, including your own.
There's a MessageBoxOptions option that isn't exposed in Winforms, MB_TOPMOST, which ensures the window is top-most. You'd use it like this:
MessageBox.Show("text", "caption", MessageBoxButtons.OK,
MessageBoxIcon.Information, MessageBoxDefaultButton.Button1,
(MessageBoxOptions)0x40000); // use MB_TOPMOST
But by far the best thing to do is to display the message box on your UI thread. Use Control.Invoke() to do so. That way the other windows of your app are disabled, no way for the user to not notice the box.
Still one problem with this, the user won't expect the box to show up since it is shown asynchronously from anything she does. Which means the box can easily get dismissed by accident when the user just happened to press the Enter or Space key. Or clicked at just the wrong spot. Nothing much you can do about that.
Centering the box in your main window is technically possible, but fugly to do. Check this answer.
Do you mean that the form shall exchange its contents with a message plus an OK button?
This would be similar to the way a text mode interface typically works.
You can make it happen by adding a disabled panel or UserControl with message and button topmost on the form and enable it when you wish to alert the user. However, I am puzzled how to implement the blocking behavior similar to MessageBox.Show() and Dialog.Show().

Resources