Introduction
The aim of this library is to provide a new and simple solution to represent the notifier message of an application, instead of using the classic MessageBox
.
MessageBox
is a very powerful message notifier, but it shows a lack of style and practice. A lot of customers require a UI that is in line with today's market. This idea was born in 2008, during a University work: from the v3 of this library is published a WPF version also, which is only a simple porting from the Windows Form (.NET 2.0) version (thus it needs more time to be concluded). However you can use it, especially for debug purposes of your app.
Another aim is to get a notification that is easy to update in its content: sometimes, it is necessary to put a MessageBox
in a loop, to get (for instance) a fast error report. This often causes a waterfall of message notifications. In this case, we need a notification that can stay opened, on top, in a comfortable position and with a updatable content, thus it is possible to update a hypothetical error counter.
The Features
- Up to four simple notifier types with respective colors:
Info
Error
Warning
Confirm
- Draggable Notifier
- DialogStyle Notifier
- Positionable Notifier
- Message with the same content/title are now handled as multi-notes: you will see an update counter in the title of the note
- Full Screen faded background (experimental)
- Added
SimpleLogger
: a custom logger to VS Console or File
Using the Code
To use this code, simply insert a reference to the Notifier.dll library to your project:
using System.Data;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using Notify;
To use the WPF version, use:
using WPFNotify;
Then, to create a note, simply:
Notifier.Show("Note text", Notifier.Type.INFO, "TitleBar");
You have to indicate the text of the note, the type and the title. Also, it is possible to use a simple call, with only the text ("Info
" as the default type).
You can choose between 4 styles:
Warning
Ok
Error
Info
The notifications are stacked to the right bottom corner of the active screen.
In the above image, you can see a key-feature of this notification library: you can now handle the same note as one note with a counter updates.
Also, the creation call returns the ID
of the note. We can use this ID
to change the notification content:
short ID = Notifier.Show("Note text", Notifier.Type.INFO, "TitleBar");
Then, it is possible to use this ID
to refer to the opened notification to update its content:
Notifier.Update(ID, "New Note text", Notifier.Type.OK, "New TitleBar");
Briefly:
short ID = Notifier.Show("Hello World");
Notifier.Update(ID, "Hello Mars");
It also introduced the "Close All
" function: to access it, open the notification menu by pressing the menu icon left to the close icon:
In the opened menu, select "Close All
" to close all the opened notifications; this will also reset the ID
counter.
Watching the Code
The Notifier.dll library is made of a Windows Form and its resource file. In the resource file are stored the background image of the note and the icons.
The WPFNotifier.dll library is made of a Windows Presentation Foundation and its resource file. In the resources are stored the background image of the note and the icons.
The below code descriptions refer to the Windows Form version.
GLOBALS
Let's start from the GLOBALS
part:
#region GLOBALS
public enum Type { INFO, WARNING, ERROR, OK }
class NoteLocation
{
internal int X;
internal int Y;
internal Point initialLocation;
internal bool mouseIsDown = false;
public NoteLocation(int x, int y)
{
this.X = x;
this.Y = y;
}
}
static List<Notifier> notes = new List<Notifier>();
private NoteLocation noteLocation;
private short ID = 0;
private string description = "";
private string title = "Notifier";
private Type type = Type.INFO;
private bool isDialog = false;
private BackDialogStyle backDialogStyle = BackDialogStyle.None;
private Form myCallerApp;
private Color Hover = Color.FromArgb(0, 0, 0, 0);
private Color Leave = Color.FromArgb(0, 0, 0, 0);
private int timeout_ms = 0;
private AutoResetEvent timerResetEvent = null;
private Form inApp = null;
#endregion
That part includes the helper class used to save the position of the note and its information.
The...
class NoteLocation
{
internal int X;
internal int Y;
internal Point initialLocation;
internal bool mouseIsDown = false;
public NoteLocation(int x, int y)
{
this.X = x;
this.Y = y;
}
}
...is used to store the positions of all the notifications, because every time a note is created, it is necessary to check the available position to show it in an empty space. To save the notifications, we use a static
container and a static
ID
counter to be sure that each Notifier
has its ID
.
static List<Notifier> notes = new List<Notifier>();
CREATE & DRAW
In the OnLoad
method, called on the form creation:
BackColor = Color.Blue;
TransparencyKey = Color.FromArgb(128, 128, 128);
FormBorderStyle = FormBorderStyle.None;
We set the transparency of the form, so we can use a custom background to achieve a better smooth effect and a transparent border.
this.Tag = "__Notifier|" + ID.ToString("X4");
setNotifier(description, type, title);
This TAG
is used to identify the notification type of the form. It is helpful for the "Update
" operation.
To set the content of the note, this function is used:
private void setNotifier(string description,
Type noteType,
string title,
bool isUpdate = false)
{
this.title = title;
this.description = description;
this.type = noteType;
noteTitle.Text = title;
noteContent.Text = description;
noteDate.Text = DateTime.Now + "";
#region ADJUST COLORS
switch (noteType)
{
case Type.ERROR:
icon.Image = global::Notify.Properties.Resources.ko;
Leave = Color.FromArgb(200, 60, 70);
Hover = Color.FromArgb(240, 80, 90);
break;
case Type.INFO:
icon.Image = global::Notify.Properties.Resources.info;
Leave = Color.FromArgb(90, 140, 230);
Hover = Color.FromArgb(110, 160, 250);
break;
case Type.WARNING:
icon.Image = global::Notify.Properties.Resources.warning;
Leave = Color.FromArgb(200, 200, 80);
Hover = Color.FromArgb(220, 220, 80);
break;
case Type.OK:
icon.Image = global::Notify.Properties.Resources.ok;
Leave = Color.FromArgb(80, 200, 130);
Hover = Color.FromArgb(80, 240, 130);
break;
}
buttonClose.BackColor = Leave;
buttonMenu.BackColor = Leave;
noteTitle.BackColor = Leave;
this.buttonClose.MouseHover += (s, e) =>
{
this.buttonClose.BackColor = Hover;
this.buttonMenu.BackColor = Hover;
this.noteTitle.BackColor = Hover;
};
this.buttonMenu.MouseHover += (s, e) =>
{
this.buttonMenu.BackColor = Hover;
this.buttonClose.BackColor = Hover;
this.noteTitle.BackColor = Hover;
}; this.noteTitle.MouseHover += (s, e) =>
{
this.buttonMenu.BackColor = Hover;
this.buttonClose.BackColor = Hover;
this.noteTitle.BackColor = Hover;
};
this.buttonClose.MouseLeave += (s, e) =>
{
this.buttonClose.BackColor = Leave;
this.buttonMenu.BackColor = Leave;
this.noteTitle.BackColor = Leave;
};
this.buttonMenu.MouseLeave += (s, e) =>
{
this.buttonMenu.BackColor = Leave;
this.buttonClose.BackColor = Leave;
this.noteTitle.BackColor = Leave;
};
this.noteTitle.MouseLeave += (s, e) =>
{
this.buttonMenu.BackColor = Leave;
this.buttonClose.BackColor = Leave;
this.noteTitle.BackColor = Leave;
};
#endregion
#region DIALOG NOTE
if (isDialog)
{
Button ok_button = new Button();
ok_button.FlatStyle = FlatStyle.Flat;
ok_button.BackColor = Leave;
ok_button.ForeColor = Color.White;
Size = new Size(Size.Width,
Size.Height + 50);
ok_button.Size = new Size(120, 40);
ok_button.Location = new Point(Size.Width / 2 - ok_button.Size.Width / 2,
Size.Height - 50);
ok_button.Text = DialogResult.OK.ToString();
ok_button.Click += onOkButtonClick;
Controls.Add(ok_button);
noteDate.Location = new Point(noteDate.Location.X,
noteDate.Location.Y + 44);
noteLocation = new NoteLocation(Left, Top);
}
#endregion
#region NOTE LOCATION
if (!isDialog && !isUpdate)
{
NoteLocation location = adjustLocation(this);
Left = location.X;
Top = location.Y;
}
#endregion
}
That set all the notification elements with the desired content. We have to handle different things:
- ADJUST STYLE
- ADJUST LOCATIONS
- HANDLE THE DIALOG STYLE NOTE
Dialog style is quite different from the simple docked notification: it requires a button to check the user event acquire as well as the close button. Also, the dialog style locks the application GUI until the note is closed.
Optionally, it will have a faded black background (which is currently not possible with a simple messageBox
) over the application or over the whole screen (try it in the demo).
In the #region ADJUST LOCATION
, we find a place for our notification:
private NoteLocation adjustLocation(Notifier note)
{
Rectangle notesArea;
int nMaxRows = 0,
nColumn = 0,
nMaxColumns = 0,
xShift = 25;
bool add = false;
if (inApp != null && inApp.WindowState == FormWindowState.Normal)
{
notesArea = new Rectangle(inApp.Location.X,
inApp.Location.Y,
inApp.Size.Width,
inApp.Size.Height);
}
else
{
notesArea = new Rectangle(Screen.GetWorkingArea(note).Left,
Screen.GetWorkingArea(note).Top,
Screen.GetWorkingArea(note).Width,
Screen.GetWorkingArea(note).Height);
}
nMaxRows = notesArea.Height / Height;
nMaxColumns = notesArea.Width / xShift;
noteLocation = new NoteLocation(notesArea.Width +
notesArea.Left -
Width,
notesArea.Height +
notesArea.Top -
Height);
while (nMaxRows > 0 && !add)
{
for (int nRow = 1; nRow <= nMaxRows; nRow++)
{
noteLocation.Y = notesArea.Height +
notesArea.Top -
Height * nRow;
if (!isLocationAlreadyUsed(noteLocation, note))
{
add = true; break;
}
if (nRow == nMaxRows)
{
nColumn++;
nRow = 0;
noteLocation.X = notesArea.Width +
notesArea.Left -
Width - xShift * nColumn;
}
if (nColumn >= nMaxColumns)
{
add = true; break;
}
}
}
noteLocation.initialLocation = new Point(noteLocation.X,
noteLocation.Y);
return noteLocation;
}
First, we check if is an update of the note: in this case, it is not needed to evaluate a position because the updated note already is displayed.
Then, we see if it's a dialog note: the dialog uses the location defined in the bottom part, while the docked note uses the first branch:
- First, we get the screen area and calculate the available grid dimension (row and columns).
- Then, we start the position cycle: if a column is full, we shift the notification across the X axis of the screen by a custom value (with or not notifications overlay - uncomment the line if you do not want overlay).
- If the entire screen is full, then create the notifications in the last used place.
In the end, we set the note position.
Let's see the interesting part, the Update
function.
UPDATE
To update the notification, as previously said, we need its ID
.
With the note ID
, it is simple to find the needed notification and update it:
public static void Update(short ID,
string desc,
Type noteType,
string title)
{
foreach (var note in notes)
{
if (note.Tag != null &&
note.Tag.Equals("__Notifier|" + ID.ToString("X4")))
{
if (note.timerResetEvent != null)
note.timerResetEvent.Set();
Notifier myNote = (Notifier)note;
myNote.setNotifier(desc, noteType, title, true);
}
}
}
We cycle in the notifications list and check for the ID
set in the TAG
properties: then is called the setNotifier
part that handles the update changing the note content (or type).
TEMPORARY NOTE
A very useful function: we can create a note and tell if it's an autoclose note or not. I use this for some volatile information.
Technically, this is achieved using a BackgroundWorker
and a AutoResetEvent
In the Show
call, we define it:
if (not.timeout_ms >= 500)
{
not.timerResetEvent = new AutoResetEvent(false);
BackgroundWorker timer = new BackgroundWorker();
timer.DoWork += timer_DoWork;
timer.RunWorkerCompleted += timer_RunWorkerCompleted;
timer.RunWorkerAsync(not);
}
Then, it is easy to close not after the specified ms timeout is elapsed:
#region TIMER
private static void timer_DoWork(object sender, DoWorkEventArgs e)
{
Notifier not = (Notifier)e.Argument;
bool timedOut = false;
while (!timedOut)
{
if (!not.timerResetEvent.WaitOne(not.timeout_ms))
timedOut = true;
}
e.Result = e.Argument;
}
private static void timer_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
Notifier not = (Notifier) e.Result;
not.closeMe();
}
#endregion
The background worker handles the background part of the timeout
: the timeout
event is an auto reset event: it is useful for the update part. Let's consider a note that displays the same message, as we know, it will be handled using the note counter: but what if this note is a temp note? For every new notification the timer is reset, so the note can display at least the counter for the desired times.
IN APP NOTE
The new feature introduced in the v4 release (for both WinForm and WPF) is the ability to insert the note snapped to the container application. You can try it in the demo to better understand this feature. Usually the notes are snapped to the bottom right corner of the screen, but if you enable the IN APP feature you can snap the notes to the bottom right corner of a specified Form
or Window
.
Simple Logger
To get more power regarding the notification to the user, it is possible to use the included logger
, called SimpleLogger
.
A very basilar logger for .NET. It is possible to choose a filename for the log and the level of logging message.
In your class, include the logger
:
Logger logger = new Logger();
...or specify a filename:
Logger logger = new Logger("myManager.log");
OPTIONAL: Set the log level (Remember: CRITICAL
< ERROR
< WARNING
< INFO
< VERBOSE
) so if you set the level to WARNING
, you will have in the log the CRITICAL
, ERROR
and WARNING
if you set the level to CRITICAL
, you will have only CRITICAL
in the log Default value is VERBOSE
.
logger.setLoggingLevel(Logger.LEVEL.INFO);
Use it:
logger.log(Logger.LEVEL.ERROR, e.StackTrace);
Points of Interest
Some of the future developments are as follows:
- Configurable startup position of the note (X, Y, Corner of the screen)
- WPF: some effects (es: fade)
- Fix the redraw procedure that causes multiple redraw for each mouse drag
- Logger: log to console
History
- 05/08/2018: v1.4 - Added the InApp features and a better multi screen support (WinForm and WPF)
- 12/01/2018: v1.3 - Added the WPF version - added the temporary note support
- 10/07/2017: v1.2 - Added the features as
showDialog
, draggable Notifier. Improved the graphics part, update counters of the same note. Added the SimpleLogger
- 01/09/2016: v1.1 - Changed the caller style in a static way, added the Close All function and the notification ID: it is possible now to recall and change the content of an opened notification
- 25/08/2016: v1.0 - First version release