I have a WPF application I like to keep quietly running when the user closes the main window. I do this using a NotifyIcon in the task status area, and use it as such in my App.xaml.cs:
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
_notifyIcon = new NotifyIcon();
_notifyIcon.DoubleClick += (sender, args) => ShowMainWindow();
_notifyIcon.Icon = Wpf.Properties.Resources.QDrive;
_notifyIcon.Visible = true;
CreateContextMenu();
new Bootstrapper().Run();
Debug.Assert(Current.MainWindow != null, "Application.Current.MainWindow != null");
Current.MainWindow.Closing += MainWindowOnClosing;
}
private void CreateContextMenu()
{
_notifyIcon.ContextMenuStrip = new ContextMenuStrip();
_notifyIcon.ContextMenuStrip.Items.Add("Open Q-Drive...").Click += (sender, args) => ShowMainWindow();
_notifyIcon.ContextMenuStrip.Items.Add("Exit").Click += (sender, args) => ExitApplication();
}
private void ExitApplication()
{
_isExit = true;
Debug.Assert(Current.MainWindow != null, "Application.Current.MainWindow != null");
Current.MainWindow.Close();
_notifyIcon.Visible = false;
_notifyIcon.Dispose();
_notifyIcon = null;
}
Yet after closing and restarting the app a few times while debugging in VS2017, I have multiple icons visible, of which all but the active one vanish on mouse-over. I notice this is a bug with a few other applications I use that I have not developed myself.
How can I prevent this?
NotifyIcon leaves its icon behind if you exit the program without hiding the icon first.
You're hiding it in ExitApplication, of course. I suspect that while debugging, though, you're not always exiting the program by selecting the Exit item on the menu, but simply by stopping Visual Studio. That's why the orphaned icon gets left behind.
This isn't unusual in development, but it won't affect your users unless they use the Task Manager to force an immediate halt to your program.
If it bothers you, though, you could write a global exception handler (something you should probably do anyway) and in that handler you could hide the icon, taking care first to make sure it still exists.
Of course, if you break on exceptions in Visual Studio and you abruptly terminate the program, even that global exception handler won't hide the NotifyIcon.
Related
Hi I had posted a question along these lines recently but this is now a little more specific to my requirements. So, I have an Application where the user needs to log in. The log in process can take some time so I decided to put up a little animated GIF to show it is doing something. Sounds simple...!!??
I noticed soon that the login process was freezing the animation so I thought, I will put the login process on its own thread. I had countless instances of it referencing objects on the UI Thread so thought I would try the other way round and have the Image display on a new thread. Same issue - so I decided to create a new window containing the image, format it accordingly and display this as a new thread! Simple! That (bit) worked... I click to login, animation appears and disappears onces login is complete. So the Thread variable is set as global one:
Friend g_thLoading As Thread
And when the Login button is clicked I have the following:
g_thLoading = New Thread(AddressOf LoginSplashScreen)
g_thLoading.SetApartmentState(ApartmentState.STA)
g_thLoading.IsBackground = True
g_thLoading.Name = "LoginThread"
g_thLoading.Start()
VerifyLogin() 'Process that takes a while...
g_thLoading.Abort()
Then the method that is called in the new thread:
Sub LoginSplashScreen()
Dim SplashScreenWin As New SplashScreen()
Try
SplashScreenWin.ShowDialog()
System.Windows.Threading.Dispatcher.Run()
Catch ex As Exception
SplashScreenWin.Close()
SplashScreenWin = Nothing
End Try
End Sub
This works - but not if I have to click the button more than once. However If (for example) the user enters the wrong credentials, clicks login (the above processes and completes) they are prompted to re-enter - click the login button again... but this time, the window doesnt display (but oddly does appear in the Task Bar)... Then the application is forced to close (nothing in debug on why that is).
I am confident that the Dialogue Window is closing correctly after the first instance as i) it is no longer in the Task Bar and secondly I have put some checks on the Windows Close event. I am fairly confident that the created Thread is closed after the first instance as I can see it drop off from the Thread Window in Visual Studio... So - I am at a total loss. I have also tried the Join function on the thread but this just hangs the process before it gets to g_thLoading.Abort()
I am open to any advice on how I can go about achieving my end goal... whether it is expanding on what I have done here or another suggestion altogether. I have messed around with the Background Worker but not had much more luck there.
Use the BackgroundWorker class to implement your long running processes. The class allows you to specify code that will run on a background thread (in the DoWork event handler), code that will run during "updates" on the thread that created the BackgroundWorker in the ProgressChanged event handler, and code that will run when the process completes, again on the thread that created the BackgroundWorker in the RunWorkerCompleted event handler.
Using it goes something like this:
private class LoginParameters {
public string Name { get; set; }
public string Password { get; set; }
// Any other properties needed
}
// Make this a property of your form.
BackgroundWorker LoginWorker { get; set; }
// Somewhere in your UI code after the user clicks the "login button"
LoginWorker = new BackgroundWorker();
LoginWorker.WorkerReportsProgress = true;
LoginWorker.WorkerSupportsCancellation = true; // Can set to false if you don't allow the operation to be cancelled.
LoginWorker.DoWorker += DoLogin;
LoginWorker.ProgressChanged += ReportProgress;
LoginWorker.RunWorkerCompleted += LoginFinished;
LoginParameters login = new LoginParameters {
// Code to initialize everything here
};
LoginWorker.RunWorkerAsync(login);
// Put this in the click event handler for the Cancel button, if you have one
if ( LoginWorker != null )
LoginWorker.CancelAsync();
private void DoLogin(object sender, DoWorkEventArgs e) {
BackgroundWorker worker = (BackgroundWorker) sender;
LoginParameters login = (LoginParameters) e.Argument;
// Your logic to process the login goes here. It should periodically do the following to check to see if the user clicked the cancel button:
if ( worker.CancellationPending ) {
e.Cancel = true;
return;
}
// When you want to update the UI, do this:
worker.ReportProgress( percentComplete, objectWithOtherDataToWriteToTheUI );
// When you're done, just return.
}
private void ReportProgress(object sender, ProgressChangedEventArgs e) {
// Your code to extract the data you need to update the display from the arguments & to then update the display goes here. Remember, this runs on the UI thread
}
private void LoginFinished( object sender, RunWorkerCompletedEventArgs e ) {
if (e.Cancelled == true)
// Your code to inform the user of the cancellation here
else if (e.Error != null)
// All unhadgled exceptions throws by the DoWork event handler end up here
// Your code to inform the user of the error here
else {
// Your code to inform the user of the success goes here.
// Remember, this runs on the UI thread.
// I recommend you set the form BackgroundWorker property to null after its finished, as you can't reuse it after its finished.
LoginWorker = null;
}
}
Sorry this is in C# if you're looking for VB.NET, but it shouldn't be hard to translate.
I want to run integration UI tests on my WPF application, and I'm not sure how to detect when the current test has finished so that I can proceed to the next one.
Simplifying, suppose I have a button on my window. When the button is clicked I disable it, I modify the model, and I re-enable the button. Once it detects that the model has changed, WPF changes the view on the screen.
Now I want to run a test that simulates clicking the button again and again. To click the button I’ll use automation, as described in this SO question. But how do I know when the work is finished and the display updated, so as to "click" the button again? Do I hook the botton’s IsEnabledChanged, or is there some global indication that the current cycle of processing has finished?
Edit: What was missing in my description is that I want the user to see the interim results on the screen. For example, if the test has 10 phases I want the user to see something like a Step Counter label with values 1 .. 10 appearing on the screen, and not just the number changing immediately from 1 to 10. See my answer below.
how do I know when the work is finished and the display updated, so as to "click" the button again?
According to your description, you said When the button is clicked I disable it, I modify the model, and I re-enable the button.
Therefore, I can only assume that when the model has changed, the Button will be re-enabled. So you could either attach a handler to the model's NotifyPropertyChanged event, or as you suggested, add a handler for the IsEnabledChanged event.
Here is how I managed to get it working. This might be trivial - I'm a novice with GUI. I'm just posting it here in the hope it'll help other novices like me :)
Anyhow, what I used is a two button solutions: Test and Step. Test starts the testing sequence, Step runs each step of the tests. The Step buttons interact with an Integration Tester By Steps helper.
The helper receives an Init with the Number Of Commands as parameter, (currently the helper generates random commands by itself, so it just needs to know how many commands to generate). The helpe provides a Step method to execute the next command, and a Needs More Steps property to indicate whether testing should continue.
The helper derives form INotifyPropertyChanged and has a Counter dependency property that is displayed on the main window.
The states of the Test and Step buttons are controlled by three helper methods: SetButtonsFor_OutsideTesting, SetButtonsFor_InsideTestingOutsideAnyStep and SetButtonsFor_InsideTestingInsideAStep.
First, I verified that everything is working manually, and then I added a timer and automated the process using the Stack Overflow suggestions on how to programmatically click a button in WPF and how to make a WPF Timer Like C# Timer.
Now, here's the Main Window's code:
private void Test_Click(object sender, RoutedEventArgs e)
{
SetButtonsFor_InsideTestingOutsideAnyStep();
RunTheTestBySteps();
}
public readonly IntegrationTesterBySteps _integrationTesterBySteps =
new IntegrationTesterBySteps();
void RunTheTestBySteps()
{
SetButtonsFor_InsideTestingOutsideAnyStep();
IntegrationTesterBySteps.Init(10);
StartTheTimer();
}
private void StartTheTimer()
{
DispatcherTimer = new DispatcherTimer();
DispatcherTimer.Tick += DispatcherTimer_Tick;
DispatcherTimer.Interval = new TimeSpan(0, 0, 1);
DispatcherTimer.Start();
}
private void StopTheTimer()
{
DispatcherTimer.Stop();
DispatcherTimer.Tick -= DispatcherTimer_Tick;
}
private DispatcherTimer DispatcherTimer { get; set; }
private void DispatcherTimer_Tick(object sender, EventArgs e)
{
if (!BtnStep.IsEnabled) return;
ClickTheStepButton();
CommandManager.InvalidateRequerySuggested();
}
private void BtnStep_Click(object sender, RoutedEventArgs e)
{
SetButtonsFor_InsideTestingInsideAStep();
IntegrationTesterBySteps.Step();
if (this.IntegrationTesterBySteps.NeedsMoreSteps)
SetButtonsFor_InsideTestingOutsideAnyStep();
else
{
SetButtonsFor_OutsideTesting();
StopTheTimer();
}
}
private void ClickTheStepButton()
{
var peer = new ButtonAutomationPeer(BtnStep);
var invokeProv = peer.GetPattern(PatternInterface.Invoke)
as IInvokeProvider;
if (invokeProv != null)
invokeProv.Invoke();
}
void SetButtonsFor_InsideTestingInsideAStep()
{
BtnTest.IsEnabled = false;
BtnStep.IsEnabled = false;
}
void SetButtonsFor_InsideTestingOutsideAnyStep()
{
BtnTest.IsEnabled = false;
BtnStep.IsEnabled = true;
}
void SetButtonsFor_OutsideTesting()
{
BtnTest.IsEnabled = true;
BtnStep.IsEnabled = false;
}
As part of my App's startup procedure, it checks data integrity, and if it finds a problem it pops up a message to the user telling them that it might take a while to repair things.
I'm showing the message using MessageBox.Show. Because the data check is done from a worker thread, I'm switching over to the UI thread to make that call, and then setting a ManualResetEvent to tell the worker thread when the user has acknowledged the message.
I kick off the data check/load very early in the app's lifecycle from the constructor in the main Application class, by spinning off a worker thread (using the ThreadPool).
When I run with the debugger, and the message is displayed, the app just waits for input. When I run without the debugger, the app terminates after displaying the dialog for 10 seconds.
That 10 seconds is a big clue - it tells me that the OS thinks the app took too long to initialize (the OS kills apps that take too long to start up).
I think that my MessageBox.Show is blocking the UI thread before the App.RootFrameNavigating has a chance to be invoked.
My questions:
Does my diagnosis sound right?
I'd prefer to kick off my data load early, because it is almost entirely IO, except for this Message Box, and the sooner I can get my Model loaded, the better, but do you normally delay your data load until later in the app lifecycle?
Any other ideas/suggestions? I can't guarantee which page will be the start page, because the app could be resuming to any page. I'm also thinking of having the MessageBox.Show delay itself until the app has initialized, perhaps polling away for a flag set by App.RootFrameNavigating - does that make sense?
I think your problem is a result of kicking off the worker thread in the Application constructor. You should use the appropriate life-cycle event, in this case: PhoneApplicationService.Activated Event
So, the solution I've come up with is to still kick off the data load in a worker-thread from the Application's constructor, but in my PhoneService's class ShowDialog method that I invoke to invoke MessageBox.Show, I check to see if the initial navigation has occurred:
private readonly ManualResetEvent _appInitialized = new ManualResetEvent(false);
public void AppInitialized()
{
_appInitialized.Set();
}
public void ShowDialog(string caption, string text, Action<MessageBoxResult> callback, MessageBoxButton button = MessageBoxButton.OKCancel)
{
_appInitialized.WaitOne();
DispatcherHelper.CheckBeginInvokeOnUI(() =>
{
var result = MessageBox.Show(text, caption, button);
if (callback != null)
{
callback(result);
}
});
}
Then in my Application class:
private bool _firstNavigate = true;
private void RootFrameNavigating(object sender, NavigatingCancelEventArgs e)
{
if (_firstNavigate)
{
_firstNavigate = false;
var navigationService = (NavigationService) sender;
navigationService.Navigated += NavigationServiceNavigated;
}
....
private void NavigationServiceNavigated(object sender, NavigationEventArgs e)
{
var navigationService = (NavigationService)sender;
navigationService.Navigated -= NavigationServiceNavigated;
PhoneServices.Current.AppInitialized();
}
Anyone see any issues with this approach? Anyone come up with a better way?
Recently I needed to implement please wait dialog in wpf application. i found below code. it's really good but it always open an window in saprate thread and hold the position. is there any other alter for below code. while my request of code is non threaded.
private void NewWindowThread<T,P>(Func<P, T> constructor, P param) where T : Window
{
Thread thread = new Thread(() =>
{
T w = constructor(param);
w.Show();
w.Closed += (sender, e) => w.Dispatcher.InvokeShutdown();
System.Windows.Threading.Dispatcher.Run();
});
thread.SetApartmentState(ApartmentState.STA);
thread.Start();
}
to call above method use below lines. where loading window is you window which you want to show at dialog (please wait. windows)
string t = "Please Wait…";
NewWindowThread<LoadingWindow, string>(c => new LoadingWindow(c), t);
Blocking the ui thread was never a good idea, but it is increasingly more a bad idea.
Windows will tell the user that your app stopped responding. This may incite them to force your appliccations. If you render progress bars, they will lose the animation effects, and they may render incorrect. In WPF the gui animations will stop.
Use background threads for the heavy processing, and if you need to write data back in the objects used by your main thread, marshall them back to the gui thread. BackgroundWorker can be useful there.
this might help you out.
public partial class Splash : Window
{
private static Splash splash = new Splash();
// To refresh the UI immediately
private delegate void RefreshDelegate();
private static void Refresh(DependencyObject obj)
{
obj.Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Render,
(RefreshDelegate)delegate { });
}
public Splash()
{
InitializeComponent();
}
public static void BeginDisplay()
{
splash.Show();
}
public static void EndDisplay()
{
splash.Close();
}
public static void Loading(string test)
{
splash.statuslbl.Content = test;
Refresh(splash.statuslbl);
}
}
using above code
Splash.BeginDisplay();
// Setting the status to show the application is still loading data
Splash.Loading("Connecting...");
// Set to sleep to simulate long running process
Thread.Sleep(1500);
Splash.Loading("Retrieving....");
Thread.Sleep(1500);
Splash.Loading("Success....");
Thread.Sleep(1500);
Splash.EndDisplay();
I started a new WPF project in VS2008 and then added some code to trap DispatcherUnhandledException. Then I added a throw exception to Window1
but the error is not trapped by the handler. Why?
public App()
{
this.DispatcherUnhandledException += new DispatcherUnhandledExceptionEventHandler(App_DispatcherUnhandledException);
}
void App_DispatcherUnhandledException(object sender, DispatcherUnhandledExceptionEventArgs e)
{
System.Windows.MessageBox.Show(string.Format("An error occured: {0}", e.Exception.Message), "Error");
e.Handled = true;
}
void Window1_MouseDown(object sender, MouseButtonEventArgs e)
{
throw new NotImplementedException();
}
This can happen because of the way you have the debugger handling exceptions -- Debug/Exceptions... should allow you to configure exactly how you want it handled.
Look at following msdn link http://msdn.microsoft.com/en-us/library/system.windows.application.dispatcherunhandledexception.aspx
Following is Relevant here
If an exception is not handled on either a background user interface (UI) thread (a thread with its own Dispatcher) or a background worker thread (a thread without a Dispatcher), the exception is not forwarded to the main UI thread. Consequently, DispatcherUnhandledException is not raised. In these circumstances, you will need to write code to do the following:
Handle exceptions on the background thread.
Dispatch those exceptions to the main UI thread.
Rethrow them on the main UI thread without handling them to allow DispatcherUnhandledException to be raised.
This is how I handle it. This isn't pretty but keep in mind that this type of error should never make it past debugging as a dev. Those errors should be long resolved before you go to production (so its okay that this isn't pretty). In the Startup project, in the App.xaml (App.xaml.cs) code behind, I put the following code.
OnStartup, create a DispatcherUnhandledException event handler
In the handler, use a MessageBox to display the message. Note that its likely the startup window has not yet been created so don't try to put it in a window.
e.Handle the error
I like to see when there are additional internal errors so I continue to call the display window until the internal error is null.
I'm not sure why the code block special characters aren't formatting this correctly. Sorry about that.
protected override void OnStartup(StartupEventArgs e)
{
// define application exception handler
Application.Current.DispatcherUnhandledException +=
AppDispatcherUnhandledException;
// defer other startup processing to base class
base.OnStartup(e);
}
private void AppDispatcherUnhandledException(object sender, DispatcherUnhandledExceptionEventArgs e)
{
runException(e.Exception);
e.Handled = true;
}
void runException(Exception ex)
{
MessageBox.Show(
String.Format(
"{0} Error: {1}\r\n\r\n{2}",
ex.Source, ex.Message, ex.StackTrace,
"Initialize Error",
MessageBoxButton.OK,
MessageBoxImage.Error));
if (ex.InnerException != null)
{
runException(ex.InnerException);
}
}
At first, even outside the debugging environment, my handler does not seem to be triggering.....then I realized I forget to set e.Handled = true.
In truth it was working but because e.Handled was still false the standard exception handler still kicked in and did its thing.
Once I set e.Handled = true, then everything was hunky dory. So if its not working for you, make sure you've done that step.
For those interested
It seems that the IDE is still breaking on exceptions and that if you click continue in the IDE it call the error handler.