Download and Play Popfly Games Offline

Ever
have 800 items to keep straight and always wished you're todo list was always in sight? This application will show you a quick introduction to WPF and how to create a Desktop ToDo application. It shows how to do some basic pinvokes, transparent applications,
mixing WPF and Win32 controls, and how to know if a file has been modify.
Windows Presentation Foundation is an extremely powerful set of tools that lets you create richer applications with minimal effort. I went with WPF over Windows Form applications for the transparency and glow effect I get for free. I also have access to another control that did the layout for me too.
XAML is just like HTML or XML for the most part. You declare what you want and it renders how you want it to. The declarations I used here are pretty straight forward, if you have any questions, please make a comment so I can answer them.
<Window x:Class="ToDo_CSharp.MainWindow" xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml" Title="ToDo" Height="200" Width="300" Left="0" ShowInTaskbar="False" BorderBrush="Transparent" Icon="report.ico" Background="Transparent" ResizeMode="NoResize" BorderThickness="0" WindowStyle="None" AllowsTransparency="True" Loaded="Window_Loaded" StateChanged="Window_StateChanged" IsVisibleChanged="Window_IsVisibleChanged"> <WrapPanel Name="wpTodo" Orientation="Vertical"></WrapPanel> </Window>
So we have a few events we need to wire up, Loaded, StateChanged, and IsVisibleChanged.
On load, we need to do a few things.
C#
private void Window_Loaded(object sender, RoutedEventArgs e) { SetWindowLocation(); Win32.HideFromAltTab(handle); Width = SystemParameters.FullPrimaryScreenWidth; Top = SystemParameters.PrimaryScreenHeight - Height - 10; CreateNotifyIcon(); VerifyToDoFileLocation(); if (File.Exists(TodoFileFullPath)) fillWrapPanel(); createFileSystemWatcher(); }
VB
Private Sub Window_Loaded(ByVal sender As Object, ByVal e As RoutedEventArgs) SetWindowLocation() Win32.HideFromAltTab(Handle) Width = SystemParameters.FullPrimaryScreenWidth Top = SystemParameters.FullPrimaryScreenHeight - Height + 22 CreateNotifyIcon() VerifyToDoFileLocation() If File.Exists(TodoFileFullPath) Then fillWrapPanel() End If createFileSystemWatcher() End Sub
State change is fired when an end user does something like minimize or maximize. Here if the application is minimized in some edge case, we'll just force it back to being visible.
C#
if (WindowState == WindowState.Minimized)
WindowState = WindowState.Normal;
VB
If WindowState = WindowState.Minimized Then WindowState = WindowState.Normal End If
When I created this application a bit ago, WPF didn't have a system tray control, otherwise known as a NotifyIcon control. This may have changed but we'll just use the Windows Form NotifyIcon instead. Since this is a .Net application, gaining access is rather easy but we'll have to set all the properties by hand. We'll also have to create the context menu by hand that will be associated with this. We'll need to add in a reference to System.Windows.Forms but we'll rename it to forms when calling it to prevent namespace collisions. We have to do this as there are items called the same in other namespaces. Now this aspect of the application will be replaced with the Coding4Fun Util Runner with MEF in the future.
C#
private void CreateNotifyIcon() { _notifyIcon = new forms.NotifyIcon { Text = "ToDo", Icon = new Icon("report.ico"), Visible = true }; var contextMenu = new forms.ContextMenuStrip { Name = "contextMenu" }; var exitToolStripMenuItem = new forms.ToolStripMenuItem { Name = "exitToolStripMenuItem1", Text = "Exit" }; var configurationMenuItem = new forms.ToolStripMenuItem { Name = "configurationMenuItem", Text = "Options" }; var openMenuItem = new forms.ToolStripMenuItem { Name = "openMenuItem", Text = "Open ToDo" }; // contextMenu contextMenu.Items.AddRange(new forms.ToolStripItem[] { openMenuItem, configurationMenuItem, exitToolStripMenuItem }); contextMenu.Size = new Size(155, 114); exitToolStripMenuItem.Click += exitToolStripMenuItem_Click; configurationMenuItem.Click += configurationMenuItem_Click; openMenuItem.Click += openMenuItem_Click; _notifyIcon.ContextMenuStrip = contextMenu; }
VB
Private Sub CreateNotifyIcon() _notifyIcon = New _forms.NotifyIcon With {.Text = "ToDo", .Icon = New Icon("report.ico"), .Visible = True} Dim contextMenu = New Forms.ContextMenuStrip With {.Name = "contextMenu"} Dim exitToolStripMenuItem = New Forms.ToolStripMenuItem With {.Name = "exitToolStripMenuItem1", .Text = "Exit"} Dim configurationMenuItem = New Forms.ToolStripMenuItem With {.Name = "configurationMenuItem", .Text = "Options"} Dim openMenuItem = New Forms.ToolStripMenuItem With {.Name = "openMenuItem", .Text = "Open ToDo"} ' contextMenu contextMenu.Items.AddRange(New Forms.ToolStripItem() {openMenuItem, configurationMenuItem, exitToolStripMenuItem}) contextMenu.Size = New Size(155, 114) AddHandler exitToolStripMenuItem.Click, AddressOf exitToolStripMenuItem_Click AddHandler configurationMenuItem.Click, AddressOf configurationMenuItem_Click AddHandler openMenuItem.Click, AddressOf openMenuItem_Click _notifyIcon.ContextMenuStrip = contextMenu End Sub
While .Net can do tons of stuff, at times you'll need to do some magic with native APIs. You can find the extra pinvoke declarations at www.pinvoke.net. For this, we need to do a pinvoke call to hide from ALT-Tab along with forcing the application to the back of the desktop. I created a class that calls these in a friendly way so all my pinvokes are private methods.
For hiding from ALT-Tab, here is how you'd execute that call.
C#
[DllImport("user32.dll")] private static extern int SetWindowLong(IntPtr window, int index, int value); [DllImport("user32.dll")] private static extern int GetWindowLong(IntPtr window, int index); // alt tab code from http://bytes.com/forum/thread442047.html private const int GWL_EXSTYLE = -20; private const int WS_EX_TOOLWINDOW = 0x00000080; public static void HideFromAltTab(IntPtr Handle) { SetWindowLong(Handle, GWL_EXSTYLE, GetWindowLong(Handle, GWL_EXSTYLE) | WS_EX_TOOLWINDOW); }
VB
<DllImport("user32.dll")> _ Private Shared Function SetWindowLong(ByVal window As IntPtr, ByVal index As Integer, ByVal value As Integer) As Integer End Function <DllImport("user32.dll")> _ Private Shared Function GetWindowLong(ByVal window As IntPtr, ByVal index As Integer) As Integer End Function ' alt tab code from http://bytes.com/forum/thread442047.html Private Const GWL_EXSTYLE As Integer = -20 Private Const WS_EX_TOOLWINDOW As Integer = &H80 Public Shared Sub HideFromAltTab(ByVal Handle As IntPtr) SetWindowLong(Handle, GWL_EXSTYLE, GetWindowLong(Handle, GWL_EXSTYLE) Or WS_EX_TOOLWINDOW) End Sub Public Shared Sub SetProcessWorkingSetSize() SetProcessWorkingSetSize(Process.GetCurrentProcess().Handle, -1, -1) End Sub
Setting the window position requires a bit more work.
C#
// window positioning flags static readonly IntPtr HWND_TOPMOST = new IntPtr(-1); static readonly IntPtr HWND_NOTOPMOST = new IntPtr(-2); static readonly IntPtr HWND_TOP = new IntPtr(0); static readonly IntPtr HWND_BOTTOM = new IntPtr(1); public const uint SWP_NOMOVE = 0x2; public const uint SWP_NOSIZE = 0x1; public const uint SWP_SHOWWINDOW = 0x40; // http://www.developer.com/net/csharp/article.php/3347251 // and https://msdn.microsoft.com/en-us/library/ms633545(VS.85).aspx // AND http://pinvoke.net/default.aspx/user32.SetWindowPos [DllImport("user32.dll", EntryPoint = "SetWindowPos")] public static extern bool SetWindowPos( IntPtr hWnd, // window handle IntPtr hWndInsertAfter, // placement-order handle int X, // horizontal position int Y, // vertical position int cx, // width int cy, // height uint uFlags); public static void SetWindowInBack(IntPtr handle) { SetWindowPos(handle, HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW); SetWindowPos(handle, HWND_BOTTOM, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW); } public static void SetWindowInFront(IntPtr handle) { SetWindowPos(handle, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW); }
VB
' window positioning flags Shared ReadOnly HWND_TOPMOST As New IntPtr(-1) Shared ReadOnly HWND_NOTOPMOST As New IntPtr(-2) Shared ReadOnly HWND_TOP As New IntPtr(0) Shared ReadOnly HWND_BOTTOM As New IntPtr(1) Public Const SWP_NOMOVE As UInteger = &H2 Public Const SWP_NOSIZE As UInteger = &H1 Public Const SWP_SHOWWINDOW As UInteger = &H40 ' http://www.developer.com/net/csharp/article.php/3347251 ' and https://msdn.microsoft.com/en-us/library/ms633545(VS.85).aspx ' AND http://pinvoke.net/default.aspx/user32.SetWindowPos <DllImport("user32.dll", EntryPoint:="SetWindowPos")> _ Public Shared Function SetWindowPos(ByVal hWnd As IntPtr, ByVal hWndInsertAfter As IntPtr, ByVal X As Integer, ByVal Y As Integer, ByVal cx As Integer, ByVal cy As Integer, _ ByVal uFlags As UInteger) As Boolean End Function Public Shared Sub SetWindowInBack(ByVal Handle As IntPtr) SetWindowPos(Handle, HWND_NOTOPMOST, 0, 0, 0, 0, _ SWP_NOMOVE Or SWP_NOSIZE Or SWP_SHOWWINDOW) SetWindowPos(Handle, HWND_BOTTOM, 0, 0, 0, 0, _ SWP_NOMOVE Or SWP_NOSIZE Or SWP_SHOWWINDOW) End Sub Public Shared Sub SetWindowInFront(ByVal Handle As IntPtr) SetWindowPos(Handle, HWND_TOPMOST, 0, 0, 0, 0, _ SWP_NOMOVE Or SWP_NOSIZE Or SWP_SHOWWINDOW) End Sub
In .Net, there is a fantastic class called the FileSystemWatcher which will watch a folder or file for you and tells you when anything interesting happened. For this instance, we're interested if a file was created or altered. So when that file has been updated by someone adding or removing text from it, an event will be raised. Since event will occur on a non-UI thread, we have to tell the Dispatcher object on the form to update the by using the fillWrapPanel function. In a Win32 application, you could get away without doing this however in WPF, you cannot do that type of cross threading.
C#
private void createFileSystemWatcher() { todoWatcher = new FileSystemWatcher { Path = Settings.Default.ToDoFilePath, Filter = Settings.Default.ToDoFileName }; todoWatcher.Changed += todoWatcher_Changed; todoWatcher.Created += todoWatcher_Changed; todoWatcher.EnableRaisingEvents = true; } private void todoWatcher_Changed(object sender, FileSystemEventArgs e) { wpTodo.Dispatcher.Invoke(new NoArgDelegate(fillWrapPanel)); }
VB
Private Sub createFileSystemWatcher() todoWatcher = New FileSystemWatcher With {.Path = My.Settings.ToDoFilePath, .Filter = My.Settings.ToDoFileName} AddHandler TodoWatcher.Changed, AddressOf todoWatcher_Changed AddHandler TodoWatcher.Created, AddressOf todoWatcher_Changed TodoWatcher.EnableRaisingEvents = True End Sub Private Sub todoWatcher_Changed(ByVal sender As Object, ByVal e As FileSystemEventArgs) wpTodo.Dispatcher.Invoke(New NoArgDelegate(AddressOf fillWrapPanel)) End Sub
Updating the panel is pretty straight forward. You create new Label objects and just add them in. We'll split the todo file based off the new line character and then each line will be a Label. We'll also apply an OuterGlowBitmapEffect to make it so you can see the words easier.
C#
private void fillWrapPanel() { // does file exist and not in use? string todoFileText = string.Empty; if (File.Exists(TodoFileFullPath)) { while (FileInUse(TodoFileFullPath)) Thread.Sleep(50); todoFileText = File.ReadAllText(TodoFileFullPath); } wpTodo.Children.Clear(); string[] items = Regex.Split(todoFileText, Environment.NewLine); foreach (string item in items) wpTodo.Children.Add(CreateLabel(item)); } private Label CreateLabel(string item) { var txt = new Label { Background = new SolidColorBrush(System.Windows.Media.Color.FromArgb(0, 0, 0, 0)), Foreground = new SolidColorBrush(Settings.Default.FontForeColor), AllowDrop = false, Focusable = false, BorderThickness = new Thickness(0), Content = item }; txt.MouseDown += txt_MouseDown; if (Settings.Default.FontGlowColor.A != 0) txt.BitmapEffect = new OuterGlowBitmapEffect { GlowColor = Settings.Default.FontGlowColor, GlowSize = 7 }; return txt; } void txt_MouseDown(object sender, MouseButtonEventArgs e) { SetWindowLocation(); }
VB
Private Sub fillWrapPanel() ' does file exist and not in use? Dim todoFileText As String = String.Empty If File.Exists(TodoFileFullPath) Then While FileInUse(TodoFileFullPath) Thread.Sleep(50) End While todoFileText = File.ReadAllText(TodoFileFullPath) End If wpTodo.Children.Clear() Dim items As String() = Regex.Split(todoFileText, Environment.NewLine) For Each item As String In items wpTodo.Children.Add(CreateLabel(item)) Next End Sub Private Function CreateLabel(ByVal item As String) As Label Dim txt As New Label() With { _ .Background = New SolidColorBrush(System.Windows.Media.Color.FromArgb(0, 0, 0, 0)), _ .Foreground = New SolidColorBrush(My.Settings.FontForeColor), .AllowDrop = False, _ .Focusable = False, .BorderThickness = New Thickness(0), .Content = item} AddHandler txt.MouseDown, AddressOf txt_MouseDown If My.Settings.FontGlowColor.A <> 0 Then txt.BitmapEffect = New OuterGlowBitmapEffect() With {.GlowColor = My.Settings.FontGlowColor, .GlowSize = 7} End If Return txt End Function Sub txt_MouseDown(ByVal sender As Object, ByVal e As MouseButtonEventArgs) SetWindowLocation() End Sub
This application is just a quick and easy application to make my life saner along with learn WPF. If you plan on doing a lot more design work with WPF, I would suggest using Expression Blend instead of Visual Studio.
If you want to try this out, the download link for the source code is at the top of the article!
Clint Rutkas is an academic developer evangelist for Microsoft. His two primary development languages are C# and JavaScript. In his off time, he whips up other random weird projects and does twenty something activities with his friends. In the past he has built a computer controlled disco dance floor, an automated bartender and a skateboard segway all powered by c#. His blog http://www.betterthaneveryone.com talks about how these items were built and the reasons why they were built like they are. If you want, he is on twitter @ClintRutkas or you can email him at clint.rutkas@microsoft.com if you have any question.
I like it! Excellent job.
Is there a way to prevent Show Desktop or Windows Key + D from hiding the list?
Thanks!
A suggested minor enhancement ...
Adding the following line to CreateNotifyIcon
_notifyIcon.MouseDoubleClick += openMenuItem_Click;
allows double clicking on the icon to launch the editing of the todo list, saving having to bring up the menu each time.