Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Keeping up with Messages

0.00/5 (No votes)
5 Oct 2004 1  
User defined control for displaying various messages.

Sample screenshot

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 - either osStatic or osRightToLeft. If osStatic, messages will change at the ChangeInterval event timeout; if RightToLeft, message is scrolled to the left, the number of ScrollSize (pixels) at the ChangeInterval event timeout.
  • MessageIsLink - if true will navigate to the document identified in the OSMessage.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 an OSMessages collection that you have built external to the control and displays these in order, if message is not ignored.
  • DisplayMessage - accepts an OSMessage 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!

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here