Form on top but not clickable when modal dialog is shown - vb.net

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?

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.

Related

VB.NET - How to block user input on a windows form without graying it out?

I am trying to block any sort of user input on a modal dialog that can be used to perform a slow operation that takes a few minutes to finish. The only solution for this which I found so far is to simply set the form's Enabled property to false, however that also grays out the entire form. In addition to that it also for some reason prevents me from showing a wait cursor because that only seems to work while the form is not disabled.
How can I block any user input on my form while the operation is not finished without graying the form out and while still allowing me to set a wait cursor?
I am trying to block any sort of user input
User input is sent to the application in the form of window's messages. You can install a filter to block the messages related to keyboard and mouse input. This is done by defining a class that implements the IMessageFilter Interface and the filter is installed by calling Application.AddMessageFilter and passing an instance of the class.
The following defines a class that implements IMessageFilter. If the application recieves a message defined in the FilteredMessages enum, it will block further processing of that message. The class is self contained and exposes two static methods:
Install
Uninstall
Public Class InputFilter : Implements IMessageFilter
Private Shared sharedInstance As InputFilter
Private handledMessages As FilteredMessages() = CType([Enum].GetValues(GetType(FilteredMessages)), FilteredMessages())
Private Sub New()
End Sub
Private Enum FilteredMessages
#Region "Keyboard"
WM_KEYDOWN = &H100
WM_KEYUP = &H101
WM_SYSKEYDOWN = &H104
WM_SYSKEYUP = &H105
#End Region
#Region "Mouse"
WM_LBUTTONDOWN = &H201
WM_LBUTTONUP = &H202
WM_LBUTTONDBLCLK = &H203
WM_RBUTTONDOWN = &H204
WM_RBUTTONUP = &H205
WM_RBUTTONDBLCLK = &H206
'prevent interaction with title bar
WM_NCLBUTTONDOWN = &HA1
WM_NCLBUTTONUP = &HA2
WM_NCLBUTTONDBLCLK = &HA3
WM_NCRBUTTONDOWN = &HA4
WM_NCRBUTTONUP = &HA5
WM_NCRBUTTONDBLCLK = &HA6
' prevent mouse hover/enter/move events
WM_MOUSEHOVER = &H2A1
WM_MOUSEMOVE = &H200
#End Region
End Enum
Public Function PreFilterMessage(ByRef m As Message) As Boolean Implements IMessageFilter.PreFilterMessage
Return handledMessages.Contains(CType(m.Msg, FilteredMessages))
End Function
Public Shared Sub Install()
If sharedInstance Is Nothing Then
sharedInstance = New InputFilter
Application.AddMessageFilter(sharedInstance)
End If
End Sub
Public Shared Sub Uninstall()
If sharedInstance IsNot Nothing Then
Application.RemoveMessageFilter(sharedInstance)
sharedInstance = Nothing
End If
End Sub
End Class
To test this class create a new WinForm project and add two Buttons, a Label, and a Textbox to the form in the designer. Then replace the form's code with the following code.
Public Class Form1
Private tmr As New Timer() With {.Interval = 10000}
Private tickHandler As EventHandler = Sub(s, e)
tmr.Stop()
RemoveHandler tmr.Tick, tickHandler
InputFilter.Uninstall()
Label1.Text = "filter off"
Application.UseWaitCursor = False
End Sub
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
InputFilter.Install()
Application.UseWaitCursor = True
AddHandler tmr.Tick, tickHandler
tmr.Start()
Label1.Text = "filter on"
Label1.Refresh()
TextBox1.Select()
End Sub
Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
MessageBox.Show("clicked")
End Sub
End Class
Run the project and click on Button1. The Button1_Click handler installs the filter, activates the WaitCursor and starts a timer. The timer will elapse after ten seconds and will uninstall the filter. While the filter is active, you will not be able to interact with the form using either the keytboard or mouse.
For information on the filtered messages, see Mouse Input Notifications and Keyboard Input Notifications.

VB.NET Custom Menu in Panel

Hi I'm implementing a custom menu in a panel like this image (link) below..
heres the link http://i.imgur.com/5OlRk9c.png
My question is, how can I detect that the user clicks on another part of my form excepts the menu panel and buttons(inside the red circle).
I already used the LostFocus event but nothing happens.
Please help.
You can trap mouse messages before they get routed to the controls via IMessageFilter. Then you can check to see if the cursor position is inside or outside the bounds of your panel. Here's a simple example with Panel1:
Public Class Form1
Private WithEvents filter As New MyFilter
Private Sub Form1_Load(sender As System.Object, e As System.EventArgs) Handles MyBase.Load
Application.AddMessageFilter(filter)
End Sub
Private Sub filter_LeftClick() Handles filter.LeftClick
Dim rc As Rectangle = Panel1.RectangleToScreen(Panel1.ClientRectangle)
If Not rc.Contains(Cursor.Position) Then
Debug.Print("Click outside of Panel1")
End If
End Sub
Private Class MyFilter
Implements IMessageFilter
Public Event LeftClick()
Private Const WM_LBUTTONDOWN As Integer = &H201
Public Function PreFilterMessage(ByRef m As System.Windows.Forms.Message) As Boolean Implements System.Windows.Forms.IMessageFilter.PreFilterMessage
Select Case m.Msg
Case WM_LBUTTONDOWN
RaiseEvent LeftClick()
End Select
Return False
End Function
End Class
End Class

Cancelling the button click event in its derived class

Very recently, I was working on a custom button, in which I had to capture the event of the button, and depending on the certain conditions, either I'll block the event or pass it on the form containing my custom button.
Here is prototype of the code I was working on:
Public Class MyCustomButton
Inherits Windows.Forms.Button
Private Sub Me_Clicked(ByVal sender As Object, ByVal e As EventArgs) Handles MyBase.Click
Dim i As Integer = MsgBox("Are you sure you want to perform the operation?", MsgBoxStyle.YesNoCancel, "MyApp")
If Not i = 6 Then
'Cancel the event
Else
'Continue with the event
End If
End Sub
End Class
Now, I do not have any idea how to block the event and not allow it to pass if the users opts for "No" in the given example.
Any suggestions?
You need to override the OnClick event:
Public Class MyCustomButton
Inherits Button
Protected Overrides Sub OnClick(ByVal e As System.EventArgs)
Dim i As Integer = MsgBox("Are you sure you want to perform the operation?", MsgBoxStyle.YesNoCancel, "MyApp")
If Not i = 6 Then
'Cancel the event
Else
MyBase.OnClick(e)
End If
End Sub
End Class

Generate dialog result from a custom message box class

I am developing a custom messagebox class like the following-
Public Class MyCustomMsgBox
Private MyForm As Form = New Form
Private lblHeadline As Label = New Label
Private lblMessageBody As Label = New Label
Private btnNo As Button = New Button
Private btnOk As Button = New Button
Private btnYes As Button = New Button
Public Sub New(ByVal Message As String)
With MyForm
.Width = 438
.Height = 214
.Controls.AddRange(New Control() {lblHeadline, lblMessageBody, btnNo, btnYes, btnOk})
End With
End Sub
Public Shared Function ShowErrorMsg(ByVal ErrorMessage As String) As Windows.Forms.DialogResult
Dim obj As MyCustomMsgBox = New MyCustomMsgBox(ErrorMessage)
obj.MyForm.ShowDialog()
End Sub
Public Shared function ShowSuccessMsg(ByVal SuccessMessage As String) As Windows.Forms.DialogResult
'some code
End Sub
Public Shared Function AskQuestions(ByVal Questions As String) As Windows.Forms.DialogResult
'some code
End Sub
Public Shared Function ShowExceptions(ByVal ExMessage As String) As Windows.Forms.DialogResult
'some code
End Sub
'Private Sub btnNo_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnNo.Click
' Windows.Forms.DialogResult.No()
'End Sub
End Class
Those functions are designed with related graphics, color, title and heading.
btnOk will return DialogResult.Ok, btnNo will return DialogResult.No and btnYes will return DialogResult.Yes
How do i return the dialog result with those functions?
How do i know that which button is pressed?
i dont know how to handle a button click event in a formless class.
Would you give me the idea?
Thank you in advance.
SKPaul
Start with the easy one. You will have to manually wire-up the events by using the AddHandler and RemoveHandler keywords this
AddHandler btnNo.Click, AddressOf btnNo_Click
btnNo is the button object. The ".Click" is the event you want captured. The AddressOf gets a pointer to the function (basically, it tells the compiler where the function is. Think of it as a different type of "handles".)
You'll have to remvoe the handler when your done, by doing this.
RemoveHandler btnNo.Click, AddressOf btnNo_Click
To set the Dialog Results, the form must be called via ShowDialog. You Simple set the DialogResults property of the form. I'd do it in the form.closing event.
me.DialogResult = Windows.Forms.DialogResult.OK
Me.DialogResult = Windows.Forms.DialogResult.Abort
Me.Close()
and it will return result Abort

vb.net how do I make an application that only has a notifyicon and no windows form?

I want to make an application that only has a notifyicon and doesn't have any visible window form when it starts up. I see some example sort of like what I want to do for c#, but I don't see how to do this in vb.net project.
A form is not strictly necessary. You can instantiate a NotifyIcon and use that without creating a form:
Public Class AppContext
Inherits ApplicationContext
Private notifyIcon As NotifyIcon
Private appActive As Boolean
Public Sub New()
AddHandler Application.ApplicationExit, AddressOf OnApplicationExit
notifyIcon = New NotifyIcon()
notifyIcon.Icon = My.Resources.ActiveIcon
notifyIcon.Text = "The app is active."
AddHandler notifyIcon.MouseClick, AddressOf OnIconMouseClick
appActive = True
notifyIcon.Visible = True
End Sub
Private Sub OnApplicationExit(ByVal sender As Object, ByVal e As EventArgs)
If notifyIcon IsNot Nothing Then
notifyIcon.Dispose()
End If
End Sub
Private Sub OnIconMouseClick(ByVal sender As Object, ByVal e As MouseEventArgs)
If e.Button = MouseButtons.Left Then
appActive = Not appActive
notifyIcon.Icon = If(appActive, My.Resources.ActiveIcon, My.Resources.InactiveIcon)
notifyIcon.Text = If(appActive, "The app is active.", "The app is not active.")
Else
If MsgBox("Do you want to Exit?", MsgBoxStyle.YesNo) = MsgBoxResult.Yes Then
notifyIcon.Visible = False
ExitThread()
End If
End If
End Sub
End Class
And then start your app from a Sub Main:
Public Module EntryPoint
Public Sub Main()
Dim ctx As New AppContext()
Application.Run(ctx)
End Sub
End Module
Just put the form transparent and resize it to 1x1..
And add a notifyicon..
And on the Form Load Event do this:
NotifyIcon.Visible = True
Then make what ever you want..
You can create a context menu strip (A Menu when you right click on it )
PS: If you do it, You need to go on the NotifyIcon properties and set the Context Menu Strip to that you created..
Hope it helped you..