I have a fairly simple C# WPF application using .NET 5. Basically it sits in the background and times specific events for the end user. The events are built from a xml file that is generated externally.
The application consists of 2 windows, one hidden that does all the thinking. If it detects that an event is due it raises a toast message which when clicked on opens the other window to show the event details to the user. All works fine and runs as expected except after a windows sleep/suspend and resume. We obviously don't want the events to add up upon sleep/suspend and so we close the hidden window and upon resume open it again. No problems there but once the system is resumed and an event is raised the visible window refuses to show. If the visible window is open when sleep/suspend happens then upon resume the whole window is frozen and refuses to respond (only way to close the window is kill the application and restart)
The APP code is as follows :-
public static Forms.NotifyIcon notifyIcon;
public static MainWindow mw;
public static ConfigWindow cw;
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
SystemEvents.PowerModeChanged = new PowerModeChangedEventHandler(SystemEvents_PowerModeChanged);
// Listen to notification activation
ToastNotificationManagerCompat.OnActivated = toastArgs =>
{
// Obtain the arguments from the notification
ToastArguments args = ToastArguments.Parse(toastArgs.Argument);
// Obtain any user input (text boxes, menu selections) from the notification
ValueSet userInput = toastArgs.UserInput;
// Need to dispatch to UI thread if performing UI operations
Application.Current.Dispatcher.Invoke(delegate
{
ToastControl.HandleToast(args);
});
};
ConfNotifyIcon();
OpenApp();
}
private void ConfNotifyIcon()
{
notifyIcon = new Forms.NotifyIcon();
notifyIcon.Icon = new System.Drawing.Icon("Images/Wellformation.ico");
notifyIcon.DoubleClick = OnClick;
notifyIcon.ContextMenuStrip = new Forms.ContextMenuStrip();
notifyIcon.ContextMenuStrip.Items.Add("Open", System.Drawing.Image.FromFile("Images/Wellformation.ico"), OnClick);
notifyIcon.ContextMenuStrip.Items.Add("Close", System.Drawing.Image.FromFile("Images/Wellformation.ico"), OnClose);
notifyIcon.ContextMenuStrip.Items.Add(new Forms.ToolStripSeparator());
notifyIcon.ContextMenuStrip.Items.Add("Exit", System.Drawing.Image.FromFile("Images/Wellformation.ico"), OnExit);
}
private void SystemEvents_PowerModeChanged(object sender, PowerModeChangedEventArgs e)
{
switch (e.Mode)
{
case PowerModes.Suspend:
this.Dispatcher.BeginInvoke((Action)(() =>
{
PrepareLock();
}), null);
break;
case PowerModes.Resume:
this.Dispatcher.BeginInvoke((Action)(() =>
{
PrepareAwake();
}), null);
break;
default:
break;
}
}
private void PrepareAwake()
{
OpenApp();
ConfNotifyIcon();
notifyIcon.Visible = true;
}
private void PrepareLock()
{
notifyIcon.Dispose();
cw.Close();
}
private void OnExit(object sender, EventArgs e)
{
Application.Current.Shutdown();
}
private void OnClose(object sender, EventArgs e)
{
mw.Close();
}
private void OnClick(object sender, EventArgs e)
{
OpenMain();
}
private void OpenMain()
{
mw = new();
mw.Show();
mw.Activate();
}
public static void OpenApp()
{
cw = new ConfigWindow();
}
The hidden Window XAML is as follows :-
<Window x:Class="WellformationDesktopApplication.ConfigWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WellformationDesktopApplication"
mc:Ignorable="d"
Title="ConfigWindow" Height="1" Width="1" Visibility="Hidden" WindowState="Minimized">
<Grid>
</Grid>
</Window>
with code as follows :-
Timer at = new();
public ConfigWindow()
{
BuildConfig();
InitializeComponent();
}
public void refreshconfig()
{
myObjects.Clear();
myObjects = NudgeManager.GetNudges();
NudgeHandler(myObjects);
}
public void BuildConfig()
{
myObjects.Clear();
myObjects = GetEvents(); // pulls a list of event names with intervals from the config file
EventHandler(myObjects); //Goes through the list of events and figures out when the next event is due based upon the interval in the configuration
ActionTimer();
}
private void ActionTimer()
{
at.Interval = 60000;
at.Elapsed = ChecktActions;
at.AutoReset = true;
at.Enabled = true;
}
private void ChecktActions(object sender, ElapsedEventArgs e)
{
//Go through the trigger times for all events and see if those time have passed, if they have raise a toast showing the event name.
//If an event is raised reset the trigger time for the event based upon the interval and reset that time.
}
and the visible window XAML is as follows :-
<Window x:Class="WellformationDesktopApplication.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WellformationDesktopApplication"
mc:Ignorable="d"
ResizeMode="NoResize"
WindowStyle="None"
Title="MainWindow" Height="500" Width="800" Background="{x:Null}" Foreground="{x:Null}" AllowsTransparency="True">
<Grid x:Name="BG">
<TextBlock x:Name="Display" HorizontalAlignment="Left" Margin="546,13,0,0" Text="Show event name and appropriate information about the event here..." VerticalAlignment="Top" FontSize="22"/>
</Grid>
</Window>
With Code as follows :-
public MainWindow()
{
InitializeComponent();
setstyles();
this.MouseLeftButtonDown = delegate { DragMove(); };
}
We know that everything to do with the ConfigWindow works fine, we know it is closed upon suspend and a new one is opened on resume with new timings set and all the appropriate alerts working.
The issue is with MainWindow as after a suspend and resume it cannot be interacted with at all. The open button on the icon does nothing, if the window is opened is is completely frozen and cannot be interacted with in any way, and if a toast is clicked on the window does not open but the rest of the toast handling code works fine around it. This happens on Win8, Win10 and Win11.
Any help out there as I am completely at a loss for how this is happening?
Thanks
CodePudding user response:
After much work and going through the code section by section commenting it out to see if it made a difference I have found the issue.
Buried deep inside the the code for the hidden window (4 calls to functions down the line) I found that the EventHandler() was also raising a listener for
SystemEvents.SessionSwitch = new SessionSwitchEventHandler(OnSessionSwitch);
With all the associated functions buried in a separate class that was not directly referenced from the window itself.
When this line was commented out everything worked fine, with it in and attached to the hidden window after Suspend/Resume of windows no UI changes would take place throughout the whole code (hence hidden window continued to working completely fine as it did not interact with the UI).
By lifting this code out into the APP space and handling it there rather than in a window the problem has gone away (though has revealed other issues that were not being handled upon Resume of Windows that I now have to fix).
So the answer is that for a WPF application listeners for SystemEvents of any type need to be housed int the APP code space and not within windows.