Проблемы с использованием ToastForms с System.Timers.Timer

В моем приложении я использовал класс ToastForms, чтобы показывать пользователю всплывающие уведомления для живых оповещений. Я пытался найти лучший метод опроса предупреждений из базы данных и представления их пользователю на регулярной основе. Я запрашиваю базу данных, нахожу все новые предупреждения и затем представляю их пользователю как всплывающее окно каждые 10 или около того секунд. Я пытался выбрать лучший метод / практику для этой процедуры, так как я не хочу вызывать высокую загрузку ЦП или зависание программы, поскольку я постоянно опрашиваю базу данных.

Немного поэкспериментировав, я решил перенести свои мысли на использование System.Timers.Timer и поместить свой код в процедуру Get_Alerts:

Private Sub frmNewDashboard_Load(sender As Object, e As EventArgs) Handles MyBase.Load
   ...
   tmr_GetAlerts = New System.Timers.Timer(10000)
   AddHandler tmr_GetAlerts.Elapsed, AddressOf GetAlerts
   tmr_GetAlerts.AutoReset = False
   tmr_GetAlerts.Start()
   ...
End Sub

Private Sub GetAlerts(source As Object, e As ElapsedEventArgs)
   ...
   'Query the database and populate a datatable
   'Determine all alert types
   'Handle Major Alerts, Minor Alerts, etc.
   If (Current_User.EnableNotifications = True) And (Current_User.NP_AMajor = True) Then
      If MajorCount > 1 Then
         Dim slice As ToastForm
         slice = New ToastForm((Current_User.Notify_Seconds * 1000), MajorCount & " New Major Alert(s) Detected")
         slice.Height = 100
         slice.Show()
      End If
   End If
   ...
   'Same code repeats to handle Minor Alerts
End Sub

Приведенный выше код прекрасно работал с обычным Forms.Timer, однако, поскольку я переместил его в System.Timers.Timer, я обнаружил, что ToastForm нормально всплывает, но затем кажется, что он зависает и никогда не закрывается:

Это не приводит к ошибкам, поэтому я не уверен, где лежит ошибка. Я предполагаю, что это как-то связано с открытием ToastForm в другом потоке к моему Таймеру, но я не уверен.

Любая помощь будет оценена. Благодарю.

Обновление Ниже приведен код, который запускает Toastform. Я импортировал класс из некоторого кода, который нашел в сети, так что это не мой код. Я просто передаю аргументы. Все работало нормально (и закрывалось), пока я не представил System.Timers.Timer.

Imports System.Runtime.InteropServices

Public Class ToastForm

    Private _item As ListViewItem = Nothing
    Private TooltipVisible As Boolean = False
    Private SelectedCallQueue As String = Nothing
    Private SelectedOverduePeriod As String
    Private OnlineUserCount As Integer

#Region " Variables "

    ''' <summary>
    ''' The list of currently open ToastForms.
    ''' </summary>
    Private Shared openForms As New List(Of ToastForm)

    ''' <summary>
    ''' Indicates whether the form can receive focus or not.
    ''' </summary>
    Private allowFocus As Boolean = False
    ''' <summary>
    ''' The object that creates the sliding animation.
    ''' </summary>
    Private animator As FormAnimator
    ''' <summary>
    ''' The handle of the window that currently has focus.
    ''' </summary>
    Private currentForegroundWindow As IntPtr

#End Region 'Variables

#Region " APIs "

    ''' <summary>
    ''' Gets the handle of the window that currently has focus.
    ''' </summary>
    ''' <returns>
    ''' The handle of the window that currently has focus.
    ''' </returns>
    <DllImport("user32")> _
    Private Shared Function GetForegroundWindow() As IntPtr
    End Function

    ''' <summary>
    ''' Activates the specified window.
    ''' </summary>
    ''' <param name="hWnd">
    ''' The handle of the window to be focused.
    ''' </param>
    ''' <returns>
    ''' True if the window was focused; False otherwise.
    ''' </returns>
    <DllImport("user32")> _
    Private Shared Function SetForegroundWindow(ByVal hWnd As IntPtr) As Boolean
    End Function

#End Region 'APIs

#Region " Constructors "

    ''' <summary>
    ''' Creates a new ToastForm object that is displayed for the specified length of time.
    ''' </summary>
    ''' <param name="lifeTime">
    ''' The length of time, in milliseconds, that the form will be displayed.
    ''' </param>
    Public Sub New(ByVal lifeTime As Integer, ByVal message As String)
        ' This call is required by the Windows Form Designer.
        InitializeComponent()

        ' Add any initialization after the InitializeComponent() call.


        'Set the time for which the form should be displayed and the message to display.
        Me.lifeTimer.Interval = lifeTime
        Me.messageLabel.BackColor = ColorTranslator.FromHtml(Current_User.NWC)
        Me.messageLabel.Text = message

        'Display the form by sliding up.
        Me.animator = New FormAnimator(Me, _
                                       FormAnimator.AnimationMethod.Slide, _
                                       FormAnimator.AnimationDirection.Up, _
                                       200)
    End Sub

#End Region 'Constructors

#Region " Methods "

    ''' <summary>
    ''' Displays the form.
    ''' </summary>
    ''' <remarks>
    ''' Required to allow the form to determine the current foreground window     before being displayed.
    ''' </remarks>
    Public Shadows Sub Show()
        Try
            'Determine the current foreground window so it can be reactivated each time this form tries to get the focus.
            Me.currentForegroundWindow = GetForegroundWindow()

            'Display the form.
            MyBase.Show()
            'Play a notification sound
            If Current_User.NotifySound = True Then NotificationSound.Play()

        Catch ex As Exception
            ErrorTrap(ex, "ToastForm: Show()")
        End Try
    End Sub

#End Region 'Methods

#Region " Event Handlers "

    Private Sub ToastForm_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        Try
            'Display the form just above the system tray.
            Me.Location = New Point(Screen.PrimaryScreen.WorkingArea.Width - Me.Width - 5, _
                                    Screen.PrimaryScreen.WorkingArea.Height - Me.Height - 5)

            'Move each open form upwards to make room for this one.
            For Each openForm As ToastForm In ToastForm.openForms
                openForm.Top -= Me.Height + 5
            Next

            'Add this form from the open form list.
            ToastForm.openForms.Add(Me)

            'Start counting down the form's liftime.
            Me.lifeTimer.Start()

        Catch ex As Exception
            ErrorTrap(ex, "ToastForm: ToastForm_Load()")
        End Try
    End Sub

    Private Sub ToastForm_Activated(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Activated
        Try
            'Prevent the form taking focus when it is initially shown.
            If Not Me.allowFocus Then
                'Activate the window that previously had the focus.
                SetForegroundWindow(Me.currentForegroundWindow)
            End If

        Catch ex As Exception
            ErrorTrap(ex, "ToastForm: ToastForm_Activated()")
        End Try
    End Sub

    Private Sub ToastForm_Shown(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Shown
        Try
            'Once the animation has completed the form can receive focus.
            Me.allowFocus = True

            'Close the form by sliding down.
            Me.animator.Direction = FormAnimator.AnimationDirection.Down

        Catch ex As Exception
            ErrorTrap(ex, "ToastForm: ToastForm_Shown()")
        End Try
    End Sub

    Private Sub ToastForm_FormClosed(ByVal sender As Object, ByVal e As FormClosedEventArgs) Handles MyBase.FormClosed
        Try
            'Move down any open forms above this one.
            For Each openForm As ToastForm In ToastForm.openForms
                If openForm Is Me Then
                    'The remaining forms are below this one.
                    Exit For
                End If

                openForm.Top += Me.Height + 5
            Next

            'Remove this form from the open form list.
            ToastForm.openForms.Remove(Me)

        Catch ex As Exception
            ErrorTrap(ex, "ToastForm: ToastForm_FormClosed()")
        End Try
    End Sub

    Private Sub lifeTimer_Tick(ByVal sender As Object, ByVal e As EventArgs) Handles lifeTimer.Tick
        Try
            'The form's lifetime has expired.
            Me.Close()

        Catch ex As Exception
            ErrorTrap(ex, "ToastForm: lifeTimer_Tick()")
        End Try
    End Sub

#End Region 'Event Handlers
End Class

1 ответ

Решение

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

Одним из простых вариантов для этого является использование Windows.Forms.Timer вместо Timers.Timer и позвонить RunWorkerAsync на BackgroundWorker в Tick обработчик события. Затем вы можете сделать запрос в DoWork обработчик событий, который выполняется во вторичном потоке и отображает уведомления в RunWorkerCompleted обработчик событий, который выполняется в потоке пользовательского интерфейса.

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