I have an application that at its heart is a keyboard hook. This hook allows me to turn on and off some features on a Tablet that the company I work for manufactures. The program started off as a WinForms application and the developer who first made it is no longer working with this company. Long story short there were some bugs in the program that needed to get fixed so I've been spending some time cleaning up the code base, and updating the UI to have a more fresh feeling to it. One of the WinForms window was to display at the bottom center of the screen for 500ms with a picture of the option that you just modified. For instance if you just disabled bluetooth a grayed out bluetooth symbol would show up on the bottom of the screen.
Well I've been on a kick lately to stop using Winforms all together and I wanted the challenge of updating the UI to use WPF. I came up with this code to replace the on screen window that was shown before (actual xaml not shown as that isn't the problem)
public partial class OnScreenDisplayDialog : Window
{
public static void ShowUserUpdate(Enums.OnScreenDisplayOptions target)
{
var f = new OnScreenDisplayDialog();
var imageSource = GetPictureFromOptions(target);
f.targetImage.Source = new BitmapImage(new Uri(imageSource, UriKind.Relative));
f.Show();
f.Top = SystemParameters.PrimaryScreenHeight - f.ActualHeight;
f.StartCloseTimer();
}
private OnScreenDisplayDialog()
{
InitializeComponent();
}
private void StartCloseTimer()
{
DispatcherTimer timer = new DispatcherTimer();
timer.Interval = TimeSpan.FromMilleseconds(500);
timer.Tick += TimerTick;
timer.Start();
}
private void TimerTick(object sender, EventArgs e)
{
DispatcherTimer timer = (DispatcherTimer)sender;
timer.Stop();
timer.Tick -= TimerTick;
var sb = new Storyboard();
var fadeout = new DoubleAnimation(0, new Duration(TimeSpan.FromSeconds(1)));
Storyboard.SetTargetProperty(fadeout, new System.Windows.PropertyPath("Opacity"));
sb.Children.Add(fadeout);
sb.Completed += closeStoryBoard_Completed;
this.BeginStoryboard(sb);
}
private void closeStoryBoard_Completed(object sender, EventArgs e)
{
this.Close();
}
}
So the idea is that I only want to make a single call to this Window like this
Forms.OnScreenDisplayDialog.ShowUserUpdate(Enums.OnScreenDisplayOptions.BluetoothOn);
The problem that I appear to be having is that with a simple test like the code below I get some unexpected results
Forms.OnScreenDisplayDialog.ShowUserUpdate(Enums.OnScreenDisplayOptions.BluetoothOn);
System.Threading.Thread.Sleep(1000);
Forms.OnScreenDisplayDialog.ShowUserUpdate(Enums.OnScreenDisplayOptions.BrightnessDown);
System.Threading.Thread.Sleep(1000);
Forms.OnScreenDisplayDialog.ShowUserUpdate(Enums.OnScreenDisplayOptions.BrightnessUp);
So i would expect that first window to show for 500ms then dissappear. 500ms later the next window shows (repeat). but instead all 3 windows remain visible, and even after about 10 seconds the windows are still visible. If I click elsewhere (so that they loose focus) they close. And I can't for the life of me figure out why. I put a break point on the Storyboard completed method and all 3 complete at the same time (which makes me think the timer is static, instead of dynamic).
Can someone please help me figure out what I am doing wrong here?
EDIT
So as my comment mentioned I wanted to show what I put in my code to test. There are 2 things here. One is from the main entry point in my program for quick and easy testing. The second is ran after my keyboard hook is setup and running. Normally I filter out the keys being pressed and act accordingly, but in this case if a key is pressed or released I open this window. So here is the main entry point portion of code
[STAThread]
static void Main()
{
InstantiateProgram();
EnsureApplicationResources();
if (true)
{
new System.Threading.Thread(Test).Start();
System.Threading.Thread.Sleep(1000);
new System.Threading.Thread(Test).Start();
System.Threading.Thread.Sleep(1000);
new System.Threading.Thread(Test).Start();
}
singleton = new System.Threading.Mutex(true, "Keymon");
if (!singleton.WaitOne(System.TimeSpan.Zero, true)) return;
AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;
System.Windows.Forms.Application.Run(main);
}
private static void Test()
{
Console.Beep(1000, 200);
System.Windows.Application.Current.Dispatcher.Invoke(new Action(delegate
{
Forms.OnScreenDisplayDialog.ShowUserUpdate((Enums.OnScreenDisplayOptions)r.Next(0,7));
}));
//System.Threading.Thread.Sleep(1000);
}
static Random r = new Random();
public static void EnsureApplicationResources()
{
if (System.Windows.Application.Current == null)
{
// create the Application object
new System.Windows.Application();
System.Windows.Application.Current.ShutdownMode = ShutdownMode.OnExplicitShutdown;
// merge in your application resources
System.Windows.Application.Current.Resources.MergedDictionaries.Add(
System.Windows.Application.LoadComponent(
new Uri("/Themes/Generic.xaml",
UriKind.Relative)) as ResourceDictionary);
}
}
private static void InstantiateProgram()
{
System.Windows.Forms.Application.EnableVisualStyles();
System.Windows.Forms.Application.SetCompatibleTextRenderingDefault(false);
main = new frmMain();
}
Here is the code I mentioned that is used after the main form is initialized
public frmMain()
{
InitializeComponent();
theHook = new KeyboardHookLibrary.KeyboardHook();
theHook.KeyDown += theHook_KeyDown;
theHook.KeyUp += theHook_KeyUp;
theHook.Start();
}
~frmMain()
{
theHook.Dispose();
}
void theHook_KeyUp(int key)
{
Forms.OnScreenDisplayDialog.ShowUserUpdate(Enums.OnScreenDisplayOptions.BrightnessDown);
}
void theHook_KeyDown(int key)
{
Forms.OnScreenDisplayDialog.ShowUserUpdate(Enums.OnScreenDisplayOptions.BluetoothOn);
}
and just in case I'll include xaml of the On screen display
<Window x:Class="XPKbdMon.Forms.OnScreenDisplayDialog"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
Height="200"
Width="200"
WindowStyle="None"
AllowsTransparency="True"
Background="Black"
ShowInTaskbar="False">
<!--WindowStartupLocation="CenterScreen"-->
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Image x:Name="targetImage" />
<ProgressBar x:Name="brightnessBar" Grid.Row="1" Visibility="Collapsed" Height="30"/>
</Grid>
</Window>