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:
Any ideas on how I could solve this?
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.