I am developing an outlook plugin in C#.
I want to be able to detect when outlook has finished the "send/receive" option after startup, so that I can then run operations on the received mail items.
What I have tried so far:
Manually calling Application.Session.SendAndReceive() on startup.
This runs fine, but the method returns before the send/receive is complete, which is the opposite of what I want
Overriding Application.NewMail and Application.NewMailEx - neither of these trigger as one might hope at startup (NewMailEx doesn't fire at all, NewMail is unreliable)
Calling NameSpace.SyncObjects.AppFolders.Start(); and registering the SyncObjects.AppFolders.SyncEnd event - this event fires before outlook has finished downloading mail
Iterating through NameSpace.SyncObjects, calling Start(), and registering SyncEnd - this method doesn't fire at all.
What is a solution here which will work depenably?
It seems that there is a hack to detect when syncing has finished; namely to override Application.Reminders.BeforeReminderShow as DWE suggests in this SO answer here
This event (in my testing) always fires after outlook syncing has finished.
Then, in order to make sure the reminder window fires, you add a new reminder at startup, and then hide the reminder again within Reminders_BeforeReminderShow
The code then being something like:
public partial class ThisAddIn
{
private ReminderCollectionEvents_Event reminders; //don't delete, or else the GC will break things
AppointmentItem hiddenReminder;
private void ThisAddIn_Startup(object sender, EventArgs e)
{
//other stuff
hiddenReminder = (AppointmentItem)Application.CreateItem(OlItemType.olAppointmentItem); //add a new silent reminder to trigger Reminders_BeforeReminderShow.This method will be triggered after send/receive is finished
hiddenReminder.Start = DateTime.Now;
hiddenReminder.Subject = "Autogenerated Outlook Plugin Reminder";
hiddenReminder.Save();
reminders = Application.Reminders;
reminders.BeforeReminderShow += Reminders_BeforeReminderShow;
}
private void Reminders_BeforeReminderShow(ref bool Cancel)
{
if (hiddenReminder == null) return;
bool anyVisibleReminders = false;
for (int i = Application.Reminders.Count; i >= 1; i--)
{
if (Application.Reminders[i].Caption == "Autogenerated Outlook Plugin Reminder") //|| Application.Reminders[i].Item == privateReminder
{
Application.Reminders[i].Dismiss();
}
else
{
if (Application.Reminders[i].IsVisible)
{
anyVisibleReminders = true;
}
}
}
Cancel = !anyVisibleReminders;
hiddenReminder?.Delete();
hiddenReminder = null;
//your custom code here
}
}
Yup, this is very kludgy, but that's the nature of working with outlook, and I haven't seen any free alternative which can actually claim to work reliably, whereas this works in all of the use cases I've tried it in. So that's a hit I'm willing to take to get a working solution.
Related
Win 10 - VS 2019 - Office365 - Exchange Online
My first post - woo hoo!!! (Although I have been lurking forever.)
I am writing my first Outlook Add-in with my first experience with C#. I have been hacking on computers since the 1980s and have had experience with about 10 different languages but am very new to this C# and .NET environment.
What I have will build without complaint and works as intended - most of the time.
namespace RoomReservations
{
public partial class ThisAddIn
{
private Outlook.Inspectors inspectors = null;
private Outlook.UserProperty objUserPropertyEventId = null;
private void ThisAddIn_Startup(object sender, System.EventArgs e)
{
inspectors = this.Application.Inspectors;
inspectors.NewInspector += new Microsoft.Office.Interop.Outlook.InspectorsEvents_NewInspectorEventHandler(Inspectors_NewInspector);
}
private void ThisAddIn_Shutdown(object sender, System.EventArgs e)
{
// Note: Outlook no longer raises this event. If you have code that
// must run when Outlook shuts down, see https://go.microsoft.com/fwlink/?LinkId=506785
}
void Inspectors_NewInspector(Microsoft.Office.Interop.Outlook.Inspector Inspector)
{
#pragma warning disable IDE0019 // Use pattern matching
Outlook.AppointmentItem appt = Inspector.CurrentItem as Outlook.AppointmentItem;
#pragma warning restore IDE0019 // Use pattern matching
// is it an AppointmentItem
if (appt != null)
{
// is it a meeting request
if (appt.MeetingStatus == Outlook.OlMeetingStatus.olMeeting)
{
appt.Save();
// save EventId as UserProperty
Outlook.AppointmentItem mtg = null;
mtg = (Outlook.AppointmentItem)Inspector.CurrentItem;
mtg.MeetingStatus = Outlook.OlMeetingStatus.olMeeting;
if (mtg != null)
{
if (mtg is Outlook.AppointmentItem)
{
string strEntryId = mtg.EntryID;
Outlook.UserProperties objUserProperties = mtg.UserProperties;
objUserPropertyEventId = objUserProperties.Add("MeetingEntryId", Outlook.OlUserPropertyType.olText, true, 1);
objUserPropertyEventId.Value = strEntryId;
}
}
if (mtg != null)
Marshal.ReleaseComObject(mtg);
}
if (appt != null)
Marshal.ReleaseComObject(appt);
}
// define event handler for ItemSend
Application.ItemSend += new Outlook.ApplicationEvents_11_ItemSendEventHandler(Application_ItemSend);
}
public void Application_ItemSend(object Item, ref bool Cancel)
{
// use EventId to identify current meeting request
var app = new Microsoft.Office.Interop.Outlook.Application();
var ns = app.Session;
Outlook.AppointmentItem meeting = ns.GetItemFromID(objUserPropertyEventId.Value);
// is ItemSend a request or a cancel
if (meeting.MeetingStatus != Outlook.OlMeetingStatus.olMeetingCanceled)
{
// ItemSend is a request
MessageBox.Show("Not Cancelled.");
if (meeting is Outlook.AppointmentItem)
{
if (meeting.MeetingStatus == Outlook.OlMeetingStatus.olMeeting)
{
// if a location was provided
if (meeting.Location != null)
{
Cancel = true;
bool blnTorF = false;
Outlook.Recipient recipConf;
// has the user included "JHP - Room Reservations" for placement
// on the shared calendar for all conference room availability
foreach (Outlook.Recipient objUser in meeting.Recipients)
{
if (objUser.Name == "JHP - Room Reservations")
blnTorF = true;
}
// add "JHP - Room Reservations" if not already included
if (blnTorF == false)
{
recipConf = meeting.Recipients.Add("JHP - Room Reservations");
recipConf.Type = (int)Outlook.OlMeetingRecipientType.olRequired;
if (recipConf != null)
Marshal.ReleaseComObject(recipConf);
}
// add reservarion to specific conference room calendar
String strLocation = meeting.Location;
Outlook.Recipient recipRoomUser;
if (strLocation.Contains("142"))
{
recipRoomUser = meeting.Recipients.Add("conference142#jhparch.com");
recipRoomUser.Type = (int)Outlook.OlMeetingRecipientType.olRequired;
}
if (strLocation.Contains("150"))
{
recipRoomUser = meeting.Recipients.Add("conference150#jhparch.com");
recipRoomUser.Type = (int)Outlook.OlMeetingRecipientType.olRequired;
}
if (strLocation.Contains("242"))
{
recipRoomUser = meeting.Recipients.Add("conference242#jhparch.com");
recipRoomUser.Type = (int)Outlook.OlMeetingRecipientType.olRequired;
}
if (strLocation.Contains("248"))
{
recipRoomUser = meeting.Recipients.Add("conference248#jhparch.com");
recipRoomUser.Type = (int)Outlook.OlMeetingRecipientType.olRequired;
}
// send request
meeting.Recipients.ResolveAll();
meeting.Send();
}
}
}
}
else
{
// ItemSend is a cancel
MessageBox.Show("Meeting is cancelled.");
}
}
#region VSTO generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InternalStartup()
{
this.Startup += new System.EventHandler(ThisAddIn_Startup);
this.Shutdown += new System.EventHandler(ThisAddIn_Shutdown);
}
#endregion
}
}
When working correctly, a meeting request will put 4 separate entries on 4 separate calendars. A meeting cancel will remove 4 separate entries from 4 separate calendars. The erratic behavior that occurs for a request includes placing the meeting event on only 2 calendars. Unusual behavior that occurs for a cancel includes deleting the meeting event from only 2 calendars. Or the meeting cancel has to be sent out 3 or 4 times before the meeting event is removed from all calendars. But then, I send another meeting request without changing any code and it is placed on all 4 calendars, as expected. And a cancel of that request will remove it from all 4 calendars on the first try. This odd behavior is common within our test group of three users, each running the identical add-in.
I feel like I understand how the native functions [if, foreach, etc.] work. But I don't know what I don't know. I have added some housekeeping [Marshal.ReleaseComObject()]. I'm not sure if it is everywhere it needs to be. I'm not sure if there are other means of housekeeping I am not aware of that could be causing this behavior. I am having a difficult time understanding why this will work, then not work, then work - all without changing any code.
Thanks for any leads that point in the direction of discovery,
Chris
The best way to understand how the code works is to run it under the debugger. Just set some breakpoints and run the solution hitting F5 in Visual Studio. When your breakpoints are hit you can go line-by-line and see intermediate results.
First of all, there is no need to subscribe to the ItemSend event in the NewInspector event handler. That is an Application-wide event, so you need to subscribe only once at startup.
private void ThisAddIn_Startup(object sender, System.EventArgs e)
{
inspectors = this.Application.Inspectors;
inspectors.NewInspector += new Microsoft.Office.Interop.Outlook.InspectorsEvents_NewInspectorEventHandler(Inspectors_NewInspector);
// define event handler for ItemSend
Application.ItemSend += new Outlook.ApplicationEvents_11_ItemSendEventHandler(Application_ItemSend);
}
Second, the item which is being sent is passed as a parameter to the ItemSend event handler, so you can use it instead of getting a separate one. So, the following code doesn't make sense:
public void Application_ItemSend(object Item, ref bool Cancel)
{
// use EventId to identify current meeting request
var app = new Microsoft.Office.Interop.Outlook.Application();
var ns = app.Session;
Outlook.AppointmentItem meeting = ns.GetItemFromID(objUserPropertyEventId.Value);
Instead, use the parameter:
public void Application_ItemSend(object Item, ref bool Cancel)
{
Outlook.AppointmentItem meeting = Item as Outlook.AppointmentItem;
Third, there is no need to create a new Outlook Application instance in the ItemSend event handler, use the Application property of your add-in class instead
Fourth, if you need to change the outgoing item there is no need to call the Send method in the ItemSend event handler.
meeting.Send();
Use the second parameter if you want to allow or cancel any further processing of that item. For example, if the event procedure sets this argument to true, the send action is not completed and the inspector is left open.
Fifth, I'd recommend using the try/catch blocks to log exceptions and release underlying COM objects in the finally, so you could be sure that everything went well and all objects were released. For example:
private void CreateSendItem(Outlook._Application OutlookApp)
{
Outlook.MailItem mail = null;
Outlook.Recipients mailRecipients = null;
Outlook.Recipient mailRecipient = null;
try
{
mail = OutlookApp.CreateItem(Outlook.OlItemType.olMailItem)
as Outlook.MailItem;
mail.Subject = "A programatically generated e-mail";
mailRecipients = mail.Recipients;
mailRecipient = mailRecipients.Add("Eugene Astafiev");
mailRecipient.Resolve();
if (mailRecipient.Resolved)
{
mail.Send();
}
else
{
System.Windows.Forms.MessageBox.Show(
"There is no such record in your address book.");
}
}
catch (Exception ex)
{
System.Windows.Forms.MessageBox.Show(ex.Message,
"An exception is occured in the code of add-in.");
}
finally
{
if (mailRecipient != null) Marshal.ReleaseComObject(mailRecipient);
if (mailRecipients != null) Marshal.ReleaseComObject(mailRecipients);
if (mail != null) Marshal.ReleaseComObject(mail);
}
}
You may find the How To: Create and send an Outlook message programmatically article helpful.
We have built a huge winforms project, already in progress for multiple years.
Sometimes, our users get an exception which looks like this one.
The resolution of this problem seems to be:
don't acces UI components from a background thread
.
But since our project is a very big project with a lot of different threads, we don't succeed in finding all these.
Is there a way to check (with some tool or debugging option) which components are called from a background thread?
To clarify:
I created a sample winforms project with a single Form, containing two Button
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
button1.Text = "Clicked!";
}
private void button2_Click(object sender, EventArgs e)
{
Task.Run(() =>
{
button2.BackColor = Color.Red; //this does not throw an exception
//button2.Text = "Clicked"; //this throws an exception when uncommented
});
}
}
The background color of button2 is set to red when the button is clicked. This happens in a background thread (which is considered bad behavior). However, it doesn't (immediately) throw an exception. I would like a way to detect this as 'bad behavior'. Preferably by scanning my code, but if it's only possible by debugging, (so pausing as soon as a UI component is accessed from a background thread) it's also fine.
I've got 2 recommendations to use together, the first is a Visual Studio Plugin called DebugSingleThread.
You can freeze all the threads and work on one at a time (obviously the non-main-UI threads) and see each threads access to controls. Tedious I know but not so bad with the second method.
The second method is to get the steps in order to reproduce the problem. If you know the steps to reproduce it, it will be easier to see whats causing it. To do this I made this User Action Log project on Github.
It will record every action a user makes, you can read about it here on SO: User Activity Logging, Telemetry (and Variables in Global Exception Handlers).
I'd recommend you also log the Thread ID, then when you have been able to reproduce the problem, go to the end of the log and work out the exact steps. Its not as painful as it seems and its great for getting application telemetry.
You might be able to customise this project, eg trap a DataSource_Completed event or add a dummy DataSource property that sets the real Grids DataSource property and raises an INotifyPropertyChanged event - and if its a non-main thread ID then Debugger.Break();.
My gut feeling is you're changing a control's (eg a grid) data source in a background thread (for that non-freeze feel) and thats causing a problem with synchronisation. This is what happened to the other DevExpress customer who experienced this. Its discussed here in a different thread to the one you referenced.
Is your app set to ignore cross threading intentionally?
Cross-thread operations should be blowing up all the time in winforms. It checks for them like crazy in just about every method. for a starting point check out https://referencesource.microsoft.com/#System.Windows.Forms/winforms/Managed/System/WinForms/Control.cs.
Somewhere in your app, somebody might have put this line of code:
Control.CheckForIllegalCrossThreadCalls = False;
Comment that out and run the app, then follow the exceptions.
(Usually you can fix the problem by wrapping the update in an invoke, e.g., in a worker thread if you see textbox1.text=SomeString; change it to `textbox.invoke(()=>{textbox1.text=SomeString;});.
You may also have to add checking for InvokeRequired, use BeginInvoke to avoid deadlocks, and return values from invoke, those are all separate topics.
this is assuming even a moderate refactor is out of the question which for even a medium sized enterprise app is almost always the case.
Note: it's not possible to guarantee successful discovery of this case thru static analysis (that is, without running the app). unless you can solve the halting problem ... https://cs.stackexchange.com/questions/63403/is-the-halting-problem-decidable-for-pure-programs-on-an-ideal-computer etc...
I did this to search for that specific situation but of course, need to adjust it to your needs, but the purpose of this is to give you at least a possibility.
I called this method SearchForThreads but since it's just an example, you can call it whatever you want.
The main idea here is perhaps adding this Method call to a base class and call it on the constructor, makes it somewhat more flexible.
Then use reflection to invoke this method on all classes deriving from this base, and throw an exception or something if it finds this situation in any class.
There's one pre req, that is the usage of Framework 4.5.
This version of the framework added the CompilerServices attribute that gives us details about the Method's caller.
The documentation for this is here
With it we can open up the source file and dig into it.
What i did was just search for the situation you specified in your question, using rudimentary text search.
But it can give you an insight about how to do this on your solution, since i know very little about your solution, i can only work with the code you put on your post.
public static void SearchForThreads(
[System.Runtime.CompilerServices.CallerMemberName] string memberName = "",
[System.Runtime.CompilerServices.CallerFilePath] string sourceFilePath = "",
[System.Runtime.CompilerServices.CallerLineNumber] int sourceLineNumber = 0)
{
var startKey = "this.Controls.Add(";
var endKey = ")";
List<string> components = new List<string>();
var designerPath = sourceFilePath.Replace(".cs", ".Designer.cs");
if (File.Exists(designerPath))
{
var designerText = File.ReadAllText(designerPath);
var initSearchPos = designerText.IndexOf(startKey) + startKey.Length;
do
{
var endSearchPos = designerText.IndexOf(endKey, initSearchPos);
var componentName = designerText.Substring(initSearchPos, (endSearchPos - initSearchPos));
componentName = componentName.Replace("this.", "");
if (!components.Contains(componentName))
components.Add(componentName);
} while ((initSearchPos = designerText.IndexOf(startKey, initSearchPos) + startKey.Length) > startKey.Length);
}
if (components.Any())
{
var classText = File.ReadAllText(sourceFilePath);
var ThreadPos = classText.IndexOf("Task.Run");
if (ThreadPos > -1)
{
do
{
var endThreadPos = classText.IndexOf("}", ThreadPos);
if (endThreadPos > -1)
{
foreach (var component in components)
{
var search = classText.IndexOf(component, ThreadPos);
if (search > -1 && search < endThreadPos)
{
Console.WriteLine($"Found a call to UI thread component at pos: {search}");
}
}
}
}
while ((ThreadPos = classText.IndexOf("Task.Run", ++ThreadPos)) < classText.Length && ThreadPos > 0);
}
}
}
I hope it helps you out.
You can get the Line number if you split the text so you can output it, but i didn't want to go through the trouble, since i don't know what would work for you.
string[] lines = classText.Replace("\r","").Split('\n');
Try that:
public static void Main(string[] args)
{
// Add the event handler for handling UI thread exceptions to the event.
Application.ThreadException += new ThreadExceptionEventHandler(exception handler);
// Set the unhandled exception mode to force all Windows Forms errors to go through the handler.
Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException);
// Add the event handler for handling non-UI thread exceptions to the event.
AppDomain.CurrentDomain.UnhandledException += // add the handler here
// Runs the application.
Application.Run(new ......);
}
Then you can log the message and the call stack and that should give you enough information to fix the issue.
I recommend you update your GUI to handle this situation automatically for your convenience. You instead use a set of inherited controls.
The general principle here is to override the property Set methods in a way to make them Thread Safe. So, in each overridden property, instead of a straight update of the base control, there's a check to see if an invoke is required (meaning we're on a separate thread the the GUI). Then, the Invoke call updates the property on the GUI thread, instead of the secondary thread.
So, if the inherited controls are used, the form code that is trying to update GUI elements from a secondary thread can be left as is.
Here is the textbox and button ones. You would add more of them as needed and add other properties as needed. Rather than putting code on individual forms.
You don't need to go into the designer, you can instead do a find/replace on the designer files only. For example, in ALL designer.cs files, you would replace System.Windows.Forms.TextBox with ThreadSafeControls.TextBoxBackgroundThread and System.Windows.Forms.Button with ThreadSafeControls.ButtonBackgroundThread.
Other controls can be created with the same principle, based on which control types & properties are being updated from the background thread.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
namespace ThreadSafeControls
{
class TextBoxBackgroundThread : System.Windows.Forms.TextBox
{
public override string Text
{
get
{
return base.Text;
}
set
{
if (this.InvokeRequired)
this.Invoke((MethodInvoker)delegate { base.Text = value; });
else
base.Text = value;
}
}
public override System.Drawing.Color ForeColor
{
get
{
return base.ForeColor;
}
set
{
if (this.InvokeRequired)
this.Invoke((MethodInvoker)delegate { base.ForeColor = value; });
else
base.ForeColor = value;
}
}
public override System.Drawing.Color BackColor
{
get
{
return base.BackColor;
}
set
{
if (this.InvokeRequired)
this.Invoke((MethodInvoker)delegate { base.BackColor = value; });
else
base.BackColor = value;
}
}
}
class ButtonBackgroundThread : System.Windows.Forms.Button
{
public override string Text
{
get
{
return base.Text;
}
set
{
if (this.InvokeRequired)
this.Invoke((MethodInvoker)delegate { base.Text = value; });
else
base.Text = value;
}
}
public override System.Drawing.Color ForeColor
{
get
{
return base.ForeColor;
}
set
{
if (this.InvokeRequired)
this.Invoke((MethodInvoker)delegate { base.ForeColor = value; });
else
base.ForeColor = value;
}
}
public override System.Drawing.Color BackColor
{
get
{
return base.BackColor;
}
set
{
if (this.InvokeRequired)
this.Invoke((MethodInvoker)delegate { base.BackColor = value; });
else
base.BackColor = value;
}
}
}
}
I am using VSTO to create an event when an email is sent. The goal is change Attachments.
I already have other addins that run in the ItemSend event, but the problem is, I want my addin to run first. As I've read, there is no execution order for the Outlook addins sent event, but there must be some order even if only by name or guid...
I tried this solution (the problem is, if I have 2 mail windows open, the first window doesn´t run the event ... :( there is some overwrite event problem)
private void ThisAddIn_Startup(object sender, System.EventArgs e)
{
this.Application.Inspectors.NewInspector += new InspectorsEvents_NewInspectorEventHandler(Custom_Inspector);
//This run in the end off all ItemSend Events.... :(
//this.Application.ItemSend += new Microsoft.Office.Interop.Outlook.ApplicationEvents_11_ItemSendEventHandler(MyFunction2);
}
private void Custom_Inspector(Inspector Inspector)
{
if (Inspector != null && Inspector.CurrentItem != null && Inspector.CurrentItem is Outlook.MailItem)
{
Outlook.MailItem mailItem = Inspector.CurrentItem as Outlook.MailItem;
if (mailItem.EntryID == null)
{
((ItemEvents_10_Event)mailItem).Send += new ItemEvents_10_SendEventHandler(MyFunction);
}
}
}
void MyFunction(ref bool Cancel)
{
MailItem mailItemContext = ((Inspector)this.Application.ActiveWindow()).CurrentItem as MailItem;
if (mailItemContext != null)
{
//my custom code here
}
}
this.Application.Inspectors.NewInspector += new InspectorsEvents_NewInspectorEventHandler(Custom_Inspector);
To get the NewInspector event of the Inspectors class fired you need to keep the source object alive, i.e. prevent it from swiping by the garbage collector. So, I'd recommend declaring the an instance of the Inspectors class at the global scope - at the class level.
The Outlook object model doesn't provide anything for changing the order of events. From my experience add-ins are loaded based on the ProgID value (sorted in the alphabetical order) and events are fired in the reverse order, i.e. a LIFO queue.
Eugene 100000 thanks! in reality Outlook Order Plugin Events by alphabetical reverse.
But by the way, how set NewInspector in top class? i need to define inside class ThisAddIn a prop call:
public partial class ThisAddIn
{
public Microsoft.Office.Interop.Outlook.Inspectors _inspector;
private void ThisAddIn_Startup(object sender, System.EventArgs e)
{
_inspector = this.Application.Inspectors;
_inspector.NewInspector += new InspectorsEvents_NewInspectorEventHandler(Custom_Inspector);
}
}
This question is specific to Windows Phone 8.1 (WinRT); it may also be applicable Windows 8.1. I am using Caliburn.Micro 2.0.1
In my ViewModel's OnActivate I check whether an item is a database, if it isn't, I want to navigate back to the previous page.
The simplist solution will be just to call GoBack in the OnActivate method (this works in Windows Phone 8.0):
INavigationService _navigationService;
protected override void OnActivate()
{
_item = GetItemFromDB();
if(_item == null)
{
_navigationService.GoBack()
}
}
To navigate to the view model I call:
_navigationService.NavigateToViewModel<MyViewModel>(_param);
But it does not work, it ignores the GoBack call and stays on the page which I do not want to view.
When stepping through the code you can see that the GoBack code is called inside the NavigateToViewModel method; I expect this is the reason why it does not work (something to do with a queuing issue maybe?).
I have a very "hacky" solution that involves a timer (that works), but I really despise it since it is prone to threading issues and has the possibility of being called during the NavigateToViewModel call (if it takes long to finish), which will then again not work:
protected override void OnActivate()
{
_item = GetItemFromDB();
if(_item == null)
{
DispatcherTimer navigateBackTimer = new DispatcherTimer();
navigateBackTimer.Interval = TimeSpan.FromMilliseconds(300);
navigateBackTimer.Tick += GoBackAfterNavigation;
navigateBackTimer.Start();
}
}
public void GoBackAfterNavigation(object sender, object e)
{
_navigationService.GoBack();
(sender as DispatcherTimer).Stop();
}
Is there a better way to navigate back? Why doesn't the GoBack work in OnActivate? Is there a way to get it to work in OnActivate?
You can use
Execute.OnUIThreadAsync(() => /* navigationCode */);
instead of a timer to queue the action immediately after the processing of the current stack has finished.
I am writing a C# .NET 3.5 Windows Service that needs to perform some actions whenever a user logon or unlock event occurs. I have tried registering the service by adding my event handler to Microsoft.Win32.SystemEvents.SessionSwitch:
using Microsoft.Win32;
SystemEvents.SessionSwitch += new SessionSwitchEventHandler(SystemEvents_SessionSwitch);
void SystemEvents_SessionSwitch(object sender, SessionSwitchEventArgs e)
{
if (e.Reason == SessionSwitchReason.SessionLogon)
{
// Do Task
}
else if (e.Reason == SessionSwitchReason.SessionUnlock)
{
// Do Task
}
}
Also I have tried to override the OnSessionChange(SessionChangeDescription changeDescription) {...} method inherited by the ServiceBase class:
protected override void OnSessionChange(SessionChangeDescription changeDescription)
{
if (changeDescription.Reason == SessionChangeReason.SessionLogon)
{
// Do Task
}
else if (changeDescription.Reason == SessionChangeReason.SessionUnlock)
{
// Do Task
}
base.OnSessionChange(changeDescription);
}
Many session events are handled by either of the methods described, unfortunately neither of these methods handle the event of a session unlock when fast user switching and terminal services are stopped and disabled. However the event is handled when both services are enabled and running. The work environment this will be deployed on will not have the services enabled.
Is there another way to accomplish this within the C# .NET managed code environment? Many questions I have seen answer the question with the methods described above, they do work correctly but not when both fast user switching and terminal services is disabled.
The CanHandleSessionChangeEvent property is set to False by default.
Insert the following code when initializing the components
public Service1()
{
InitializeComponent();
this.CanHandleSessionChangeEvent = true;
this.CanHandlePowerEvent = true;
this.CanPauseAndContinue = true;
this.CanShutdown = true;
this.CanStop = true;
this.AutoLog = true;
}
If you're having difficulty with SessionSwitchReason you can enable 'Other Logon/Logoff Events' in windows eventlog and monitor for these events yourself. It's a bit messy because you have to poll the logs in someway, but it does the trick.
See - https://stackoverflow.com/a/40675839/6715034