Keeping up with Messages






2.56/5 (5 votes)
User defined control for displaying various messages.
Introduction
I like to keep my users informed. I know I'm probably way out of line here, but hey, I'm a rebel.
We have a lot of systems we deal with, don't we? Email, servers, message queues, servlets, web services, etc. And more and more, we are integrating these into the applications we write. Some applications may require all of these and more. If a part is down, it may be inconvenient to the users. For instance, how about this scenario: as part of your application, a user receives messages from a message queue, manipulates the data with your application, then posts the resulting data to a remote web service, and at the same time, notifies the next department by email that will receive the data that it is complete.
We put exception handling into our apps to handle such situations, right? What about the user...it's frustrating for them if they go to submit something to the remote web service only to find the corporate web connection is down. Or if they try to send the notification email, only to find that the mail server is down for maintenance. It would be nice for them to know prior to this so they can be prepared. After all, we want our users to be happy, right? (I'm kinda sappy that way.)
The Control
In our environment, the systems gurus have graciously done this by creating a web page notifying the entire organization of any problems that are currently occurring, any scheduled outages, and any special circumstances they feel the need to communicate. The problem is, you have to open up a browser, navigate to the page, and refresh the page before getting the current status.
I've included in my application this OSNotify
control to display messages about system status or anything else I want the user to pay attention to. It works as follows:
The control contains an OSMessage
class that defines several properties; Key
, Index
, Text
, Importance
, Link
, and Ignore
. It also has a collection class, OSMessages
, that can be passed to the control to display. Messages can be marked at three importance levels, LOW
, MEDIUM
, HIGH
, and different user-defined icons will be displayed for each. The Ignore
property can be used to skip a message if the user doesn't want to see it anymore.
Many properties control the display of the messages; these are described below:
BorderStyle
,BackColor
,ForeColor
- you know what these are.ChangeInterval
,ScrollSize
- in combination, sets scrolling speed.LowPriorityIcon
,MediumPriorityIcon
,HighPriorityIcon
- self explanatory.MessageDisplayType
- eitherosStatic
orosRightToLeft
. IfosStatic
, messages will change at theChangeInterval
event timeout; ifRightToLeft
, message is scrolled to the left, the number ofScrollSize
(pixels) at theChangeInterval
event timeout.MessageIsLink
- if true will navigate to the document identified in theOSMessage.Link
property.PauseOnMouseOver
- if true, stops scrolling when mouse is over the control so user can read message.
Methods of the control are described below.
DisplayMessages
- accepts anOSMessages
collection that you have built external to the control and displays these in order, if message is not ignored.DisplayMessage
- accepts anOSMessage
object. This is the way you can manually display messages without providing a collection of messages; in essence, the developer can control the display of messages.NextMessage
- immediately goes to the next un-ignored message.StopDisplay
- stops scrolling.
If a collection is provided, after the last message is displayed, it starts over again at the beginning. This is where I would update the messages so that the user has the most up-to-date information.
The Code
I won't display all the code here, just some of the interesting parts. Here's my Paint
event:
Private Sub OSNotifier_Paint(ByVal sender As Object, _
ByVal e As System.Windows.Forms.PaintEventArgs) Handles MyBase.Paint
'use some double-buffering techniques
Dim bmp As New Bitmap(Me.Width, Me.Height, _
Imaging.PixelFormat.Format24bppRgb)
Dim g As Graphics = Graphics.FromImage(bmp)
'repaint background
g.FillRectangle(New SolidBrush(_BackColor), 0, 0, _bmp.Width, bmp.Height)
'this is where our graphic will go
Dim PicRectangle As New Rectangle(3, 3, Me.Height - 6, Me.Height - 6)
g.SmoothingMode = Drawing2D.SmoothingMode.AntiAlias
'draw border
Select Case Me._BorderStyle
Case BorderStyle.Fixed3D
Dim p1 As New Pen(SystemColors.ControlLightLight, 1)
Dim P2 As New Pen(SystemColors.ControlDark, 1)
Dim P3 As New Pen(SystemColors.ControlDarkDark, 1)
g.DrawRectangle(p1, New Rectangle(0, 0, Me.Width - 1, Me.Height - 1))
g.DrawRectangle(P2, New Rectangle(0, 0, Me.Width, Me.Height))
g.DrawRectangle(P3, New Rectangle(1, 1, Me.Width - 2, Me.Height - 2))
Case BorderStyle.FixedSingle
Dim p1 As New Pen(SystemColors.ControlLightLight, 1)
g.DrawRectangle(p1, New Rectangle(0, 0, Me.Width - 1, Me.Height - 1))
Case Else
'Console.WriteLine("no border")
End Select
If Me.DesignMode Then
'draw graphic is it exists
If Not IsNothing(Me._LowPriorityIcon) Then
g.DrawIcon(Me._LowPriorityIcon, PicRectangle)
ElseIf Not IsNothing(Me._MediumPriorityIcon) Then
g.DrawIcon(Me._MediumPriorityIcon, PicRectangle)
ElseIf Not IsNothing(Me._HighPriorityIcon) Then
g.DrawIcon(Me._HighPriorityIcon, PicRectangle)
End If
'persist the bitmap to the control
e.Graphics.DrawImage(bmp, 0, 0)
Else
'only draw the graphic if we have a message
If Not IsNothing(_CurrentMessage) Then
Select Case _CurrentMessage.Importance
Case OSMessage.MessageImportanceConstants.osIMPORT_LOW
If Not IsNothing(Me._LowPriorityIcon) Then
g.DrawIcon(Me._LowPriorityIcon, PicRectangle)
End If
Case OSMessage.MessageImportanceConstants.osIMPORT_MEDIUM
If Not IsNothing(Me._MediumPriorityIcon) Then
g.DrawIcon(Me._MediumPriorityIcon, PicRectangle)
End If
Case OSMessage.MessageImportanceConstants.osIMPORT_HIGH
If Not IsNothing(Me._HighPriorityIcon) Then
g.DrawIcon(Me._HighPriorityIcon, PicRectangle)
End If
Case Else
If Me._MessageDisplayType = MessageDisplayTypeConstants.osStatic Then
Me._CurrentLeftPosition = 3
End If
End Select
'persist the bitmap to the control
e.Graphics.DrawImage(bmp, 0, 0)
'create new bitmap for the text area
bmp = New Bitmap(Me.pnlTextArea.Width, Me.pnlTextArea.Height, _
Imaging.PixelFormat.Format24bppRgb)
g = Graphics.FromImage(bmp) 'Me.pnlTextArea.CreateGraphics
g.SmoothingMode = Drawing2D.SmoothingMode.AntiAlias
'determine the left position
If Me._MessageDisplayType = MessageDisplayTypeConstants.osFromRightToLeft Then
Me._CurrentLeftPosition -= _ScrollSize
Else
Me._CurrentLeftPosition = 3
End If
If Me._CurrentLeftPosition + g.MeasureString(_CurrentMessage.Text, _
Me.pnlTextArea.Font).Width < 0 Then
If Me._DisplayingMultipleMessages Then
Me._CurrentMessage = GetNextMessage()
RaiseEvent MessageChange(Me._CurrentMessage)
End If
Me._CurrentLeftPosition = Me.pnlTextArea.Width
End If
g.FillRectangle(New SolidBrush(_BackColor), -1, -1, _
bmp.Width + 1, bmp.Height + 1)
If Not IsNothing(Me._CurrentMessage) Then
Dim dTop As Double = (Me.pnlTextArea.Height - _
g.MeasureString(Me._CurrentMessage.Text, _
Me.pnlTextArea.Font).Height) / 2
Dim newPoint As New PointF(Me._CurrentLeftPosition, dTop)
g.DrawString(Me._CurrentMessage.Text, Me.pnlTextArea.Font, _
New SolidBrush(_ForeColor), newPoint)
Me.pnlTextArea.CreateGraphics.DrawImage(bmp, 0, 0)
End If
Else
'persist the bitmap to the control
e.Graphics.DrawImage(bmp, 0, 0)
End If
End If
End Sub
Notice double-buffering of the graphics. .NET does a good job of double buffering using the following style settings for your user control, which I have in my InitializeComponent
method.
Me.SetStyle(ControlStyles.UserPaint, True)
Me.SetStyle(ControlStyles.AllPaintingInWmPaint, True)
Me.SetStyle(ControlStyles.DoubleBuffer, True)
I found, with the drawing interrupts so small (it looks good interrupting every 20ms), that I was still getting some flickering, so I employed some old double-buffering techniques (creating a bitmap and then drawing that to the graphics object) which worked out just fine.
Hope you enjoy the control!