Click here to Skip to main content
Click here to Skip to main content

WPF: Sticky Notes ListBox

, 4 Nov 2009 CPOL
Rate this:
Please Sign up or sign in to vote.
A notes listbox that you could use in your own app.


A while ago, one of my colleagues from WPF Disciples showed me a video about a WPF app that Billy Hollis had put together. This app was written in VB.NET, and had a very nice notes listbox. There was supposed to be some source code published somewhere, but I couldn't find it. As such, I tried the task myself and am pretty pleased with the results. This article represents the fruits of my investigation/trials.

It is a re-usable Notes ListBox that can be applied to your own project with not too much bother (I hope).


Here is what I will be covering in this article:

What it All Looks Like

Well, it looks like this, by default, but as Josh Smith will probably point out, this may not suit people's schemes/tastes. I say, thanks Josh, but they have the code, they can change these within the XAML. The problem with Mr. Smith is that he's just a lot brighter than the rest of us. I personally am quite happy about that, he is normally right.

The red bounding shape is the main focus of this article. This is the part I intended to be re-used; the rest of the UI is really just to demo the re-usable notes listbox. Though, there are one or two details that I will have to go through with you that you need to know before you are able to re-use the attached notes listbox in your own app. There is like one or two rules you need to adhere to.

The Design of the Code

What I wanted to create was a nice looking notes system that could be re-used within someone else's app. I think I have managed to do this (OK, you may have to change colors etc.). What the attached NotesListBox allows is as follows:

  1. Intended to work with a Type that has a ObservableCollection<Note> Notes property.
  2. Auto focus when you type in a note.
  3. Add/Removal of note.
  4. Mouse navigate through notes.
  5. Designed to be placed in the AdornerLayer using the NoteAdorner. This preserves your existing screen space.

NotesListBox is just a listbox, but I think it's a pretty funky one, that looks as follows:

There are a couple of things worth a mention. So I will stroll on and mention them. One of the things that I like is how each item gets its own rotation. This is achieved using a ValueConverter (I use this for several index based binding conversions) that works with the current ListBoxItem index. Here is an example:

In XAML, I have a Style for a ListBoxItem that looks like:

<!-- ListBoxItem -->
<Style TargetType="ListBoxItem">
    <Setter Property="Canvas.Left" Value="0"/>
    <Setter Property="FocusVisualStyle" Value="{x:Null}"/>

    <Setter Property="Canvas.Top">
            <Binding RelativeSource="{RelativeSource Self}" 
                Converter="{StaticResource myListIndexConverter}"
    <Setter Property="Canvas.ZIndex">
            <Binding RelativeSource="{RelativeSource Self}" 
                Converter="{StaticResource myListIndexConverter}"
    <Setter Property="Template">
            <ControlTemplate TargetType="{x:Type ListBoxItem}">
                <Grid x:Name="gridItem" 
                         Width="100" Height="100">
                    <Border Background="LemonChiffon" Margin="2">
                            SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" 
                        <RotateTransform CenterX="0.5" CenterY="0.5" 
                            Angle="{Binding RelativeSource=
                            {RelativeSource Mode=FindAncestor, 
                            AncestorType={x:Type ListBoxItem}, AncestorLevel=1}, 
                            Converter={StaticResource myListIndexConverter}, 
                    <Trigger Property="IsSelected" Value="true">
                        <Setter TargetName="gridItem" Property="RenderTransform">
                                <ScaleTransform CenterX="0.5" CenterY="0.5" 
                                                ScaleX="1.5" ScaleY="1.5"/>
                        <Setter Property="Canvas.ZIndex" Value="99999"/>

Notice the use of the ValueConverter. Let's have a look at that, shall we?

using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Data;
using System.ComponentModel;
using System.Windows.Controls;

namespace NotesListBox
    /// <summary>
    /// Provides a OneWay converter that can provide a Top/ZIndex or Rotate
    /// value for a give ListBoxItem, based on the source ListBoxItem index
    /// </summary>
    public class ListIndexConverter : IValueConverter
        #region IValueConverter Members

        public object Convert(object value, Type targetType, object parameter, 
            System.Globalization.CultureInfo culture)

            ListBoxItem item = (ListBoxItem)value;
            ListBox listBox =
                ItemsControl.ItemsControlFromItemContainer(item) as ListBox;
            String paramValue = parameter.ToString();
            Int32 index = listBox.ItemContainerGenerator.IndexFromContainer(item);

            switch (paramValue)
                case "Top":
                    return index * 80;
                case "ZIndex":
                    return listBox.Items.Count - index;
                case "Rotate":
                    return RotateAngle(index);
            return value;


        public object ConvertBack(object value, Type targetType, 
            object parameter, System.Globalization.CultureInfo culture)
            throw new NotImplementedException("can not convert back");

        #region Private Methods
        private int RotateAngle(int index)
            if (index == 0)
                return -5;
            if (index % 3 == 0)
                return -15;
            if (index % 2 == 0)
                return 10;
            if (index % 1 == 0)
                return 6;
                return 3;

It can be seen that this one ValueConverter is used for a number of purposes associated with indexes within the associated ListBox. For example, it provides a Top/ZIndex and a Rotate binding value based on the current ListBoxItem index within the original ListBox.

Other than that, it's all about Styles/Templates. So, I shall leave that as an exercise for the reader. We will now go on to look at the NoteAdorner object and what it does for us. The NoteAdorner is an Adorner that holds a single instance of a NotesListBox. For those of you who have not heard of AdornerLayer, you can think of it as a special layer that is on top of the current content.

MSDN states: "An Adorner is a custom FrameworkElement that is bound to a UIElement. Adorners are rendered in an AdornerLayer, which is a rendering surface that is always on top of the adorned element or a collection of adorned elements. Rendering of an adorner is independent from rendering of the UIElement that the adorner is bound to. An adorner is typically positioned relative to the element to which it is bound, using the standard 2D coordinate origin located at the upper-left of the adorned element.

So we can take advantage of this and use this layer to overlay items which don't affect the layout of anything else. This is what the NoteAdorner does. Its only job is to receive a ObservableCollection<Note> Notes from the current object (the one you want to store notes with), which it passes on to the hosted NotesListBox. The hosted NotesListBox actually then takes ownership of the ObservableCollection<Note> Notes that was passed to it, and will raise events when the user either adds/removes/changes a note. This gives the end user the opportunity to be alerted when one of these actions happens. This will be explained a bit further in the next section.

What You Need to Be Aware of to Use this in Your Own Apps

There is actually very little you need to be aware of when using this code, but you must follow the following two items if you wish to use this NotesListBox in your own code.

Provide a Custom AdornerDecorator

As the NotesListBox is intended to work with the AdornerLayer, you must use the custom AdornerDecorator (NotesAdornerDecorator) that I have made. This article had originally required the user to create an inline XAML AdornerDecorator which wrapped their original content, and the user had to put code in their own application to manage the Adorner, but Josh told me it would be better if you created a subclass of AdornerDecorator where it managed itself, and all the user had to do was put in their XAML and provide a property to it and wire up its events. So this is what I have now done. The result is that the NotesListBox is very easy to use in your own code now. You simply do the following:

Create a NotesAdornerDecorator somewhere in your main content element (this is normally a Grid):

<Window x:Class="NotesListBoxTest.Window1"
    Title="Window1" Height="450" Width="650" 
    MinHeight="450" MinWidth="650"

            <!-- Here is the actual content-->
            <DockPanel LastChildFill="True" 

        <!-- Here is my custom AdornerDecorator-->
        <notes:NotesAdornerDecorator x:Name="notesAdornerDecorator" />

This allows NotesListBox to manage its own AdornerLayer. All you have to do then is set the NotesAdornerDecorator.DisplayNotes property and wire up the NotesAdornerDecorator events. This is shown below:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using NotesListBox;
using System.Collections.ObjectModel;

namespace NotesListBoxTest
    public partial class Window1 : Window
        #region Ctor
        public Window1()

            #region Wire up Routed Events
            //Wire up the Note Added Event, which will come from the 
            //NotesListBoxControl on the AdornerLayer
                new NoteEventHandler(
                    (s, ea) =>

            //Wire up the Note Removed Event, which will come from the 
            //NotesListBoxControl on the AdornerLayer
                new NoteEventHandler(
                    (s, ea) =>

            //Wire up the Note Changed Event, which will come from the 
            //NotesListBoxControl on the AdornerLayer
                new NoteEventHandler(
                    (s, ea) =>
            this.Loaded +=new RoutedEventHandler(Window1_Loaded);



        private void lstPeople_SelectionChanged(object sender, SelectionChangedEventArgs e)
            notesAdornerDecorator.DisplayNotes = (lstPeople.SelectedItem as Person).Notes;

It's that easy.

I think the bit of advise Josh Smith gave me has improved the re-usability a lot. So thanks for the idea Josh.

That's it

That's all I wanted to say this time, I hope it helps some of you. Could I just ask, if you liked this article, please vote for it.


This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


About the Author

Sacha Barber
Software Developer (Senior)
United Kingdom United Kingdom
I currently hold the following qualifications (amongst others, I also studied Music Technology and Electronics, for my sins)
- MSc (Passed with distinctions), in Information Technology for E-Commerce
- BSc Hons (1st class) in Computer Science & Artificial Intelligence
Both of these at Sussex University UK.

I am lucky enough to have won a few awards for Zany Crazy code articles over the years

  • Microsoft C# MVP 2015
  • Codeproject MVP 2015
  • Microsoft C# MVP 2014
  • Codeproject MVP 2014
  • Microsoft C# MVP 2013
  • Codeproject MVP 2013
  • Microsoft C# MVP 2012
  • Codeproject MVP 2012
  • Microsoft C# MVP 2011
  • Codeproject MVP 2011
  • Microsoft C# MVP 2010
  • Codeproject MVP 2010
  • Microsoft C# MVP 2009
  • Codeproject MVP 2009
  • Microsoft C# MVP 2008
  • Codeproject MVP 2008
  • And numerous codeproject awards which you can see over at my blog

Comments and Discussions

QuestionAwesomeness PinmemberMember 951654129-Nov-12 8:25 
GeneralMy vote of 5 PinmemberPolinia25-Apr-12 2:09 
GeneralWow Sacha ! PinmemberMazen el Senih14-Mar-12 10:19 
GeneralRe: Wow Sacha ! PinmvpSacha Barber14-Mar-12 12:09 
QuestionMy vote of 5 PinmemberFilip D'haene5-Aug-11 23:09 
AnswerRe: My vote of 5 PinmvpSacha Barber6-Aug-11 0:39 
Cheers man, old but good
Sacha Barber
  • Microsoft Visual C# MVP 2008-2011
  • Codeproject MVP 2008-2011
Open Source Projects

Your best friend is you.
I'm my best friend too. We share the same views, and hardly ever argue
My Blog :

GeneralAdding Scrollbars Pinmemberaaroncampf18-May-11 10:19 
GeneralRe: Adding Scrollbars PinmvpSacha Barber18-May-11 10:24 
GeneralMy vote of 5 Pinmemberahsan sarfraz24-Jan-11 2:26 
GeneralMy vote of 5 Pinmembersachinkarche29-Aug-10 5:58 
GeneralSending events to NotesListBox Pinmemberdream3n15-Nov-09 10:40 
GeneralRe: Sending events to NotesListBox PinmvpSacha Barber15-Nov-09 22:54 
GeneralRe: Sending events to NotesListBox Pinmemberdream3n16-Nov-09 4:40 
GeneralRe: Sending events to NotesListBox PinmvpSacha Barber16-Nov-09 4:53 
GeneralMy first vote on CP PinmemberMidnight Run14-Nov-09 13:49 
GeneralRe: My first vote on CP PinmvpSacha Barber14-Nov-09 21:50 
GeneralRe: My first vote on CP PinmemberMidnight Run15-Nov-09 10:18 
GeneralRe: My first vote on CP PinmvpSacha Barber15-Nov-09 22:53 
GeneralMy vote of 4 [modified] PinmemberMohammad Dayyan4-Nov-09 7:37 
GeneralRe: My vote of 1 PinmvpPete O'Hanlon4-Nov-09 10:59 
GeneralRe: My vote of 4 Pinmember_Mohammad_13-Jan-10 1:33 
GeneralCannot wire up events Pinmemberdream3n3-Nov-09 22:56 
GeneralRe: Cannot wire up events PinmvpSacha Barber4-Nov-09 0:32 
GeneralRe: Cannot wire up events Pinmemberdream3n4-Nov-09 3:34 
GeneralRe: Cannot wire up events PinmvpSacha Barber4-Nov-09 4:26 
GeneralRe: Cannot wire up events Pinmemberdream3n4-Nov-09 6:59 
GeneralYou are the man PinmemberAlomgir Miah A23-Apr-09 5:19 
GeneralRe: You are the man PinmvpSacha Barber23-Apr-09 5:39 
Generalcool Pingroupzhujinlong1984091317-Apr-09 1:11 
GeneralRe: cool PinmvpSacha Barber17-Apr-09 1:21 
GeneralCool project PinmemberDr.Luiji11-Dec-08 2:08 
GeneralRe: Cool project PinmvpSacha Barber11-Dec-08 3:10 
QuestionHow hard would it be to port to Silverlight PinmemberDewey24-Nov-08 16:57 
AnswerRe: How hard would it be to port to Silverlight PinmvpSacha Barber25-Nov-08 11:03 
Generalgood Pinmembergodspeed_yjx23-Nov-08 21:40 
GeneralRe: good PinmvpSacha Barber23-Nov-08 22:49 
GeneralNever ends PinmemberDr.Luiji21-Nov-08 2:54 
GeneralRe: Never ends PinmvpSacha Barber21-Nov-08 3:26 
QuestionExcellent, one question though PinmemberFantmx4-Nov-08 11:00 
AnswerRe: Excellent, one question though PinmvpSacha Barber4-Nov-08 11:17 
GeneralAamer's three words! Pinmemberfinal_zero21-Oct-08 2:25 
GeneralRe: Aamer's three words! PinmvpSacha Barber21-Oct-08 3:09 
GeneralNice one.. PinmemberRajesh Pillai18-Oct-08 2:28 
GeneralSacha You Rock PinmemberAlomgir Abdul Miah16-Oct-08 16:29 
GeneralRe: Sacha You Rock PinmvpSacha Barber16-Oct-08 22:51 
Generalwhats the difference between Decorator, Adorner, AdornerDecorator PinmemberLeblanc Meneses15-Oct-08 7:55 
GeneralRe: whats the difference between Decorator, Adorner, AdornerDecorator PinmvpSacha Barber15-Oct-08 10:50 
GeneralXcelent PinmemberAbhijit Jana13-Oct-08 3:30 
GeneralRe: Xcelent PinmvpSacha Barber13-Oct-08 3:36 
GeneralHere's the link to original Sticky Notes - Written in VB.NET PinmemberKeia12-Oct-08 11:23 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web04 | 2.8.150327.1 | Last Updated 4 Nov 2009
Article Copyright 2008 by Sacha Barber
Everything else Copyright © CodeProject, 1999-2015
Layout: fixed | fluid