I am developing an Add-in for Autodesk Revit, I have created a WPF Window using XAML and C# as shown in image 1. User of my add-in needs to switch between my Window and Revit and my window needs to maintain its state.
Being it is a modal dialog user cannot interact with Revit until my window is opened. So, in this situation I use Hide instead of close window. And when the user needs it again the add-in uses ShowDialog to display the window again.
But the problem is when I show the window again, it appears white washed (no controls are visible) as shown in image 2. And as soon as I resize my window, all the controls appear back and window starts working normally.
Image 1:
Image 2:
public partial class NavigationWindow : Window, INotifyPropertyChanged
{
...
}
using this code to show,
NavigationWindow navigationWindow = new NavigationWindow();
navigationWindow.ShowDialog ();
To Hide Window, I am using,
Application.Current.Dispatcher.Invoke (() =>
{
this.Visibility = System.Windows.Visibility.Hidden;
});
Any ideas on how can this be fixed?
I would recommend to go with a modeless window instead. This is the proper way to keep the window up while interacting with Revit. The way to go about this is to use the IdleEvent or ExternalEvent. Here's a simple scenario with an idle event. Just switch the window.ShowDialog() to window.Show() that will make it modeless.
First you need to add an idle event queue and handler to the IExternalApplication so something like this:
public class AppCommand : IExternalApplication
{
private static Queue<Action<UIApplication>> Tasks;
public Result OnStartup(UIControlledApplication application)
{
Tasks = new Queue<Action<UIApplication>>();
application.Idling += OnIdling;
return Result.Succeeded;
}
private static void OnIdling(object sender, IdlingEventArgs e)
{
var app = (UIApplication)sender;
lock (Tasks)
{
if (Tasks.Count <= 0) return;
var task = Tasks.Dequeue();
task(app);
}
}
public static void EnqueueTask(Action<UIApplication> task)
{
lock (Tasks)
{
Tasks.Enqueue(task);
}
}
}
So, Idling event is going to fire when Revit is not doing anything. That's a perfect time to interact with Revit from another thread (UI). So when you are in a modless dialog and want to send a task to Revit, you can just use the EnquuqTask utility to put a task into a queue and then Revit when it's not busy will fire Idling event, dequeue the task and execute it. This guarantees that you are not executing anything outside of Revit's scope, and will let you keep the window in modeless state allowing for interaction with Revit while the window is open.
Here's how to add a task to queue from anywhere:
EnqueueTask(app =>
{
//do something
//use app object to interact with Revit
});
Related
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 a WPF project and from the main window i am creating and loading some bunch of user controls, there is some large data i am loading in background and then updating a built-in control throw the dispatcher, that works fine, the problem is that some of the user controls loads a lot of data, for example the very first thing i load in the main area of my main window, what i want is to put a loading label instead, load the main window as fast as possible so the user see this label and run in background the creation of that user control and when is done add it as a child of my main container area on my main window while i remove the loading label, if i follow the same philosophy i run into the same error like when i run a task and then try to update the window without using the dispatcher. i want to be able of create the user control asynchronous then update the main window.
Code:
User Control:
public partial class CustomUserControlGallery : UserControl
{
public CustomUserControlGallery()
{
InitializeComponent();
}
...
}
On the backend class of the main window:
public partial class MainWindow : Window
{
CustomUserControlGallery _customUserControlGallery;
public MainWindow()
{
InitializeComponent();
Task t = new Task({
//Can't use the _customUserControlGallery's Dispatcher because object is uninitialized and this.Dispatcher not working either.
_customUserControlGallery = new CustomUserControlGallery(); //Error Here.
_gridContainer.Dispatcher.Invoke(new Action(() => _gridContainer.Children.Add(_customUserControlGallery)));
_loadingLabel.Visbility = Visibility.Collapse;
});
t.Start();
}
...
}
I don't know how to handle this situation with the thread associated to the user control and the main thread.
Error:
{"The calling thread must be STA, because many UI components require this."}
You're doing this wrong. All controls must be created & operate on the UI Thread. That said, you can use the BackgroundWorker class to load the data.
You typically do this by disabling the control whose data is being loaded in the background or hiding it & displaying a progress indicator in its place. Then, you start your BackgroundWorker. That can communicate how far along it is using the ReportProgress method. Finally, when it's finished running, the RunWorkerCompleted event is fired, and you use that to either enable the control, or to hide the progress indicator & show the control.
Some quick & dirty (untested) code:
Place this in your Initialize() or control constructor:
private BackgroundWorker loadData = new BackgroundWorker();
loadData.DoWork += loadData_DoWork;
loadData.ProgressChanged += loadData_ProgressChanged; // Only do this if you are going to report progress
loadData.WorkerReportsProgress = true;
loadData.WorkerSupportsCancellation = false; // You can set this to true if you provide a Cancel button
loadData.RunWorkerCompleted += loadData_RunWorkerCompleted;
private void DoWork( object sender, DoWorkEventArgs e ) {
BackgroundWorker worker = sender as BackgroundWorker;
bool done = false;
while ( !done ) {
// If you want to check for cancellation, include this if statement
if ( worker.CancellationPending ) {
e.Cancel = true;
return;
}
// Your code to load the data goes here.
// If you wish to display progress updates, compute how far along you are and call ReportProgress here.
}
}
private void loadData_ProgressChanged( object sender, ProgressChangedEventArgs e ) {
// You code to report the progress goes here.
}
private void loadData_RunWorkerCompleted( object sender, RunWorkerCompletedEventArgs e ) {
// Your code to do whatever is necessary to put the UI into the completed state goes here.
}
What you are essentially saying (I think) is that Your app becomes sluggish while your control renders a large amount of data.
This is a problem that needs to be solved via virtualisation. You cannot create a control on a background thread, have it render its data behind the scenes and then pop it into existence. You can create controls on separate dispatchers, but they cannot share the same visual and logical tree, so you will not be able to have one as a child of the other.
Virtualisation is what you need to focus on. Depending on the control you can use a variety of virtualisation settings. Try googleing the subject as there is a lot of information on how to achieve this effectively. Most likely you will want to use things like virtualizing stackpanels and container recycling.
You cannot create UI controls with different Dispatchers and use them with each other. It's just not allowed. What you want to do is on your Task you do the heavy lifting work without UI updates and when it is done you push it back to the Dispatcher to update the UI.
In your case, I wouldn't even use Dispatcher.Invoke. Since you are using Task, it has a TaskScheduler.FromCurrentSynchronizationContext() that you can pass in the constructor.
What is the purpose of instantiating controls in a different thread if you're just going to put it back to the Main dispatcher? It's not expensive to do that.
I have a options window and a window that displays color based on these options and Kinect data. So far everything's on one thread (as far as I know; I haven't done any threading).
Now, I'm adding an option to open a viewer window that will need to be updated with lowest possible latency. All this entails is creating a window and showing it:
viewer = new SkeletalViewer.MainWindow();
viewer.Show();
When this event fires, the color window stops displaying colors (i.e. the event that fires 30 times a second on the main thread stops firing), but the viewer is displayed perfectly. I want the viewer and the color window to both be updated.
From reading other questions, it sounds like the solution is to create the viewer on a new thread. I'm encountering a lot of problems with this, though.
This fires when I click the button to open the viewer:
private void launchViewerThread_Click(object sender, RoutedEventArgs e)
{
Thread viewerThread = new Thread(delegate()
{
viewer = new SkeletalViewer.MainWindow();
viewer.Dispatcher.Invoke(new Action(delegate()
{
viewer.Show();
}));
});
viewerThread.SetApartmentState(ApartmentState.STA); // needs to be STA or throws exception
viewerThread.Start();
}
Regardless of if I just call viewer.Show() or Invoke() it as above, the line throws an exception: Cannot use a DependencyObject that belongs to a different thread than its parent Freezable. Here's how I understand Invoke(): it accesses viewer's dispatcher, which knows what thread the object is running on, and can then call methods from that thread.
Should I be trying to put this viewer on a new thread? Is the problem even a question of threads? The user will not be interacting with the viewer.
Anyone know why this doesn't work? Thanks for the help.
You need to call Show() on the same thread that the window is created on - that's why you are getting the error. Then you also need to start a new Dispatcher instance to get the runtime to manage the window.
private void launchViewerThread_Click(object sender, RoutedEventArgs e)
{
Thread viewerThread = new Thread(delegate()
{
viewer = new SkeletalViewer.MainWindow();
viewer.Show();
System.Windows.Threading.Dispatcher.Run();
});
viewerThread.SetApartmentState(ApartmentState.STA); // needs to be STA or throws exception
viewerThread.Start();
}
See the Multiple Windows/Multiple Threads example at: http://msdn.microsoft.com/en-us/library/ms741870.aspx
So I was running into a similar issue where a new window failed to open on a new thread. The exception was "cannot use a dependencyobject that belongs to a different thread".
The issue ended up being that the window was using a global resource (Background brush). Once I froze the brush resource, the window loaded just fine.
I am not sure if this will solve your problem but can you try creating a thread proc (to open a viewer window) which is executed on a different thread and then have a dispatcher.beginInvoke to update the main window ,
Here is some code-
in the constructor register this
public MainWindow()
{
UpdateColorDelegate += UpdateColorMethod;
}
// delegate and event to update color on mainwindow
public delegate void UpdateColorDelegate(string colorname);
public event UpdateColorDelegate updateMainWindow;
// launches a thread to show viewer
private void launchViewerThread_Click(object sender, RoutedEventArgs e)
{
Thread t = new Thread(this.ThreadProc);
t.Start();
}
// thread proc
public void ThreadProc()
{
// code for viewer window
...
// if you want to access any main window elements then just call DispatchToMainThread method
DispatchToUiThread(color);
}
//
private void DispatchToUiThread(string color)
{
if (updateMainWindow != null)
{
object[] param = new object[1] { color};
Dispatcher.BeginInvoke(updateMainWindow, param);
}
}
// update the mainwindow control's from this method
private void UpdateColorMethod(string colorName)
{
// change control or do whatever with main window controls
}
With this you can update the main window controls without freezing it, Let me know if you have any questions
I have a C# application which runs into trouble when it comes to multi-threads / backgroundworkers when I'm using a splash screen before I load the main window.
my code look something like this:
[STAThread]
private static void Main()
{
.. do some stuff
ShowSplash(); // where i show a splash screen and load some stuff
...
As the last step of ShowSplash, I do the following:
new MyCabApplication<MyMainWorkItem, MDIParentForm>().Run(); -- where i load the form through cab.
The problem is that when I do that I get the following exception:
Starting a second message loop on a single thread is not a valid operation. Use Form.ShowDialog instead
Any idea what can I do?
Here is my showsplash function:
private static DialogResult ShowSplash(AutoResetEvent controller)
{
// create and register splash screen
splashScreen = new PointSplashScreen();
Application.Run(splashScreen);
return DialogResult.OK;
}
Two solutions:
Instead of using Application.Run, just create a new instance of the form and then call ShowDialog. Move new MyCabApplication<MyMainWorkItem, MDIParentForm>().Run(); outside of the Splash Screen after the call to ShowDialog(). You can check properties of the Splash screen if this code should not always be run.
Instead of using Application.Run(Form), use Application.Run(ApplicationContext). You will need to create a new ApplicationContext and move your code there.
Solution 1 is easier.
It sounds like MyCabApplication extends Application. The Run method starts a WinForm application by starting a message loop that handles window messages.
Because you are already showing UI, there is already a message loop running, so you cannot start another. To get your main form to show up, make a new instance of it and call Show():
var form = new MainForm();
form.Show();
Using Windows Forms, .NET 3.5 framework, language: c#
I would like to show a popup window for 1 second to notify users of actions that are performed. For example, when I copy a file X I want to show a notification like "Copied file X to File X-copy". Should be shown for a second, then autohide.
You can use a timer. Something along the lines of the following where ShowFloating does the initial display and HideFloating does, you know.
public void ShowFloatingForXMilliSeconds(int milliSeconds) {
ShowFloating();
if (_autoOffTimer == null) {
_autoOffTimer = new System.Timers.Timer();
_autoOffTimer.Elapsed += OnAutoOffTimerElapsed;
_autoOffTimer.SynchronizingObject = this;
}
_autoOffTimer.Interval = milliSeconds;
_autoOffTimer.Enabled = true;
}
void OnAutoOffTimerElapsed(Object sender, System.Timers.ElapsedEventArgs ea) {
if ((_autoOffTimer != null) && _autoOffTimer.Enabled) {
_autoOffTimer.Enabled = false;
HideFloating();
}
}
Also detach the timer handler and dispose the timer in Dispose.
This topic will help you to make topmost window without stealing focus from currently active window.
To complete your solution, in simple case you need to add a timer on your form to make sure the form auto-closes after 1 second and locate your notification window properly (you probably want it in the bottom right part of the screen? - that's a simple arithmetic exercise).
For more advanced solution, you should create NotificationManager class and manage lifetime of your notification message forms there.
Dispite the answers given. I think a message that pops up is somewhat not user friendly. What about using a statusbar link? It's not that evasive (and you can show the progress)