Comment Feed for Channel 9 - Todo: You stuff on your desktophttp://video.ch9.ms/ecn/c4f/images/9651495_100.jpgChannel 9 - Todo: You stuff on your desktop
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.
A WPF application? Why?
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.
XAMLing up the application
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.
&lt;Window x:Class=&quot;ToDo_CSharp.MainWindow&quot;
xmlns=&quot;http://schemas.microsoft.com/winfx/2006/xaml/presentation&quot;
xmlns:x=&quot;http://schemas.microsoft.com/winfx/2006/xaml&quot;
Title=&quot;ToDo&quot; Height=&quot;200&quot; Width=&quot;300&quot; Left=&quot;0&quot;
ShowInTaskbar=&quot;False&quot; BorderBrush=&quot;Transparent&quot; Icon=&quot;report.ico&quot;
Background=&quot;Transparent&quot; ResizeMode=&quot;NoResize&quot;
BorderThickness=&quot;0&quot; WindowStyle=&quot;None&quot; AllowsTransparency=&quot;True&quot;
Loaded=&quot;Window_Loaded&quot; StateChanged=&quot;Window_StateChanged&quot; IsVisibleChanged=&quot;Window_IsVisibleChanged&quot;&gt;
&lt;WrapPanel Name=&quot;wpTodo&quot; Orientation=&quot;Vertical&quot;&gt;&lt;/WrapPanel&gt;
&lt;/Window&gt;
Wiring the WPF stuff up
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 &#43; 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
Time to use some Windows Forms
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 = &quot;ToDo&quot;, Icon = new Icon(&quot;report.ico&quot;), Visible = true };
var contextMenu = new forms.ContextMenuStrip { Name = &quot;contextMenu&quot; };
var exitToolStripMenuItem = new forms.ToolStripMenuItem { Name = &quot;exitToolStripMenuItem1&quot;, Text = &quot;Exit&quot; };
var configurationMenuItem = new forms.ToolStripMenuItem { Name = &quot;configurationMenuItem&quot;, Text = &quot;Options&quot; };
var openMenuItem = new forms.ToolStripMenuItem { Name = &quot;openMenuItem&quot;, Text = &quot;Open ToDo&quot; };
// contextMenu
contextMenu.Items.AddRange(new forms.ToolStripItem[] { openMenuItem, configurationMenuItem, exitToolStripMenuItem });
contextMenu.Size = new Size(155, 114);
exitToolStripMenuItem.Click &#43;= exitToolStripMenuItem_Click;
configurationMenuItem.Click &#43;= configurationMenuItem_Click;
openMenuItem.Click &#43;= openMenuItem_Click;
_notifyIcon.ContextMenuStrip = contextMenu;
}
VB
Private Sub CreateNotifyIcon()
_notifyIcon = New _forms.NotifyIcon With {.Text = &quot;ToDo&quot;, .Icon = New Icon(&quot;report.ico&quot;), .Visible = True}
Dim contextMenu = New Forms.ContextMenuStrip With {.Name = &quot;contextMenu&quot;}
Dim exitToolStripMenuItem = New Forms.ToolStripMenuItem With {.Name = &quot;exitToolStripMenuItem1&quot;, .Text = &quot;Exit&quot;}
Dim configurationMenuItem = New Forms.ToolStripMenuItem With {.Name = &quot;configurationMenuItem&quot;, .Text = &quot;Options&quot;}
Dim openMenuItem = New Forms.ToolStripMenuItem With {.Name = &quot;openMenuItem&quot;, .Text = &quot;Open ToDo&quot;}
' 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
PInvoking to the back of the class
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(&quot;user32.dll&quot;)]
private static extern int SetWindowLong(IntPtr window, int index, int value);
[DllImport(&quot;user32.dll&quot;)]
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
&lt;DllImport(&quot;user32.dll&quot;)&gt; _
Private Shared Function SetWindowLong(ByVal window As IntPtr, ByVal index As Integer, ByVal value As Integer) As Integer
End Function
&lt;DllImport(&quot;user32.dll&quot;)&gt; _
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 = &amp;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 http://msdn.microsoft.com/en-us/library/ms633545(VS.85).aspx
// AND http://pinvoke.net/default.aspx/user32.SetWindowPos
[DllImport(&quot;user32.dll&quot;, EntryPoint = &quot;SetWindowPos&quot;)]
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 = &amp;H2
Public Const SWP_NOSIZE As UInteger = &amp;H1
Public Const SWP_SHOWWINDOW As UInteger = &amp;H40
' http://www.developer.com/net/csharp/article.php/3347251
' and http://msdn.microsoft.com/en-us/library/ms633545(VS.85).aspx
' AND http://pinvoke.net/default.aspx/user32.SetWindowPos
&lt;DllImport(&quot;user32.dll&quot;, EntryPoint:=&quot;SetWindowPos&quot;)&gt; _
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
Watching files for doing some sneaky stuff
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 &#43;= todoWatcher_Changed;
todoWatcher.Created &#43;= 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
Filling Panels with glowing goodness
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 &#43;= 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 &lt;&gt; 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
Conclusion
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!
About The Author
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.
enSun, 02 Aug 2015 18:46:22 GMTSun, 02 Aug 2015 18:46:22 GMTRev9Re: Todo: You stuff on your desktop
I like it! Excellent job.

Is there a way to prevent Show Desktop or Windows Key + D from hiding the list?