Question

What I want is a small notification message that is shown in the lower right corner when there are any messages to be shown. If there are none the notification message will not be shown. The notification message should not steal focus or block the main application.

What I have is an application that runs a Task as a kind of messageservice. This application contains multiple dialogs that opens as modal dialogs.

When a message arrives to the application it is added to a observable list. This fires an eventhandler in the form showing the notification message and it is redrawn to show the first item in the list. When a message is read/closed it is removed from the list which fires the event again and the form is updated with the information from the first item in the list. If the list is empty the form is hidden.

My problem is that if i get a message and the notification message form is shown, and before I close it a modal dialog is opened in the main application, my form with the notification message is still on top of everything, even the modal dialog, but it's not clickable.

I've searched and read several forums for an answer but haven't been able to come up with an answer.

A small testapplication that simulates this behaviour can be found at Github. https://github.com/Oneleg/NotificationMessage

Some fast info:

The NotificationMessage form has:

  • FormBorderStyle = None
  • Topmost = False
  • Is shown with Show()
  • Overloads ShowWithoutActivation()
  • Overloads CreateParams with WS_EX_NOACTIVATE WS_EX_TOOLWINDOW WS_EX_TOPMOST

Any ideas on how I could solve this?

Was it helpful?

Solution

Looks like I'll be able to answer my own question.

The answer is to create the NotificationMessage as an application withs it's own messagepump.

Application.Run(New NotificationMessage(_messageList))

After some modifications my Main now looks like this:

Imports System.Threading
Imports System.Threading.Tasks

Public Class frmMain

    Private _notificationMessage As NotificationMessage
    Private _task As Task
    Private _messageList As ObservableGenericList(Of String) = New ObservableGenericList(Of String)
    Private ReadOnly _cancelMessages As CancellationTokenSource = New CancellationTokenSource()

    Private Sub btnModal_Click(sender As System.Object, e As System.EventArgs) Handles btnModal.Click
        frmModal.ShowDialog()
    End Sub

    Private Sub frmMain_Load(sender As System.Object, e As System.EventArgs) Handles MyBase.Load
        AddHandler _messageList.Changed, AddressOf MessageListChanged
    End Sub

    Private Sub NotificationMessageLoop(mess As String)
        _notificationMessage = New NotificationMessage(_messageList)
        _messageList.Add(mess)
        Application.Run(_notificationMessage)
    End Sub

    Private Sub btnMessage_Click(sender As System.Object, e As System.EventArgs) Handles btnMessage.Click

        Dim newMessage = String.Format("Message no {0}", _messageList.Count + 1)

        If _task Is Nothing Then
            _task = Task.Factory.StartNew(Sub() NotificationMessageLoop(newMessage), _cancelMessages.Token)
        Else
            _messageList.Add(newMessage)
        End If
    End Sub

    Private Sub MessageListChanged()
        If Not _messageList.Any Then
            _cancelMessages.Cancel()
        End If
    End Sub
End Class

And the NotificationMessage looks like this:

Imports System.Runtime.InteropServices

Public Class NotificationMessage
    Public Sub New(messages As ObservableGenericList(Of String))

        InitializeComponent()
        _messages = messages
        AddHandler _messages.Changed, AddressOf ListChanged

    End Sub

    Private ReadOnly _messages As ObservableGenericList(Of String)
    Private Delegate Sub ListChangedDelegate()

    Private Sub ListChanged()
        If InvokeRequired Then
            BeginInvoke(New ListChangedDelegate(AddressOf ListChanged))
            Return
        End If

        If _messages.Any Then
            Dim message As String = _messages.First
            txtMessage.Text = message
            lblCounter.Text = String.Format("({0} messages)", _messages.Count)
            Show()
        Else
            Hide()
        End If
    End Sub

    Private Sub MessageLoad(sender As System.Object, e As EventArgs) Handles MyBase.Load
        Left = Screen.PrimaryScreen.WorkingArea.Width - Width
        Top = Screen.PrimaryScreen.WorkingArea.Height - Height
    End Sub

    Private Sub btnClose_Click(sender As System.Object, e As System.EventArgs) Handles btnClose.Click
        _messages.RemoveFirst()
    End Sub

#Region "Overrides"

    Private Const WS_EX_NOACTIVATE = &H8000000 ' Do not steal focus
    Private Const WS_EX_TOOLWINDOW = &H80 ' Makes form hidden from Alt + Tab window
    Private Const WS_EX_TOPMOST = &H8 ' Makes window topmost

    ''' <summary> Indicates whether the window will be activated when it is shown. </summary>
    ''' <remarks> http://msdn.microsoft.com/en-us/library/system.windows.forms.form.showwithoutactivation.aspx </remarks>
    Protected Overrides ReadOnly Property ShowWithoutActivation() As Boolean
        Get
            Return True
        End Get
    End Property

    ''' <summary> Override for creation parameters that are set when control handle is created. </summary>
    Protected Overrides ReadOnly Property CreateParams() As CreateParams
        Get
            Dim params As CreateParams = MyBase.CreateParams
            params.ExStyle = params.ExStyle Or WS_EX_NOACTIVATE Or WS_EX_TOOLWINDOW Or WS_EX_TOPMOST
            Return params
        End Get
    End Property

#End Region

End Class

I now have a notification message that is only visible when there are any messages to show, doesn't steal focus when a new message arrives, is always on top and is clickable even after a modal form is opened in the main application.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top