Форма сверху, но не кликабельная, когда отображается модальное диалоговое окно

То, что я хочу, - это небольшое уведомление, которое отображается в правом нижнем углу, когда есть какие-либо сообщения, которые будут показаны. Если их нет, уведомление не будет отображаться. Уведомление не должно красть фокус или блокировать основное приложение.

У меня есть приложение, которое запускает Задачу как своего рода службу сообщений. Это приложение содержит несколько диалогов, которые открываются как модальные диалоги.

Когда сообщение поступает в приложение, оно добавляется в наблюдаемый список. Это запускает обработчик событий в форме, показывающей сообщение уведомления, и он перерисовывается, чтобы показать первый элемент в списке. Когда сообщение прочитано / закрыто, оно удаляется из списка, который снова запускает событие, и форма обновляется информацией из первого элемента в списке. Если список пуст, форма скрыта.

Моя проблема в том, что, если я получаю сообщение и отображается форма сообщения уведомления, и, прежде чем я его закрываю, в главном приложении открывается модальное диалоговое окно, моя форма с уведомлением по-прежнему находится поверх всего, даже модального диалога. но это не кликабельно.

Я искал и прочитал несколько форумов для ответа, но не смог найти ответ.

Небольшое тестовое приложение, имитирующее это поведение, можно найти на Github. https://github.com/Oneleg/NotificationMessage

Немного быстрой информации:

Форма NotificationMessage имеет:

  • FormBorderStyle = Нет
  • Topmost = Ложь
  • Показывается с помощью Show()
  • Перегрузки ShowWithoutActivation()
  • Перегружает CreateParams с помощью WS_EX_NOACTIVATE WS_EX_TOOLWINDOW WS_EX_TOPMOST

Любые идеи о том, как я мог бы решить это?

1 ответ

Решение

Похоже, я смогу ответить на свой вопрос.

Ответ заключается в том, чтобы создать NotificationMessage как приложение с собственным сообщением.

Application.Run(New NotificationMessage(_messageList))

После некоторых модификаций мой Main теперь выглядит так:

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

И сообщение NotificationMessage выглядит так:

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

Теперь у меня есть уведомление, которое видно только тогда, когда есть какие-либо сообщения для показа, не крадет фокус, когда приходит новое сообщение, всегда вверху и доступно для нажатия даже после открытия модальной формы в главном приложении.

Другие вопросы по тегам