使用带有 System.Timers.Timer 的 ToastForms 的问题

Posted

技术标签:

【中文标题】使用带有 System.Timers.Timer 的 ToastForms 的问题【英文标题】:Issues Using ToastForms With System.Timers.Timer 【发布时间】:2014-07-22 02:08:56 【问题描述】:

在我的应用程序中,我使用了 ToastForms 类来向用户显示弹出通知以获取实时警报。我一直在尝试从数据库中轮询警报并经常将它们呈现给用户的最佳方法。我查询数据库,找到所有新警报,然后每隔 10 秒左右将这些警报作为弹出窗口呈现给用户。我一直在尝试为此过程确定最佳方法/实践,因为我不想在不断轮询数据库时导致任何高 CPU 使用率或程序挂起。

经过一些试验后,我决定将我的想法转移到使用 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

【问题讨论】:

您没有向我们展示应该如何关闭表单的任何信息。我假设Current_User.Notify_Seconds * 1000 是生命周期,但它是如何使用的?您是在 UI 线程以外的线程上创建该表单,因此如果 toast 表单尝试访问另一个表单上的某些内容(该表单将在另一个线程上创建),则可能会出现问题。不过没有具体的证据。 感谢 jmcilhinney。我可以发布 toast 表单的代码,但它是相当多的代码。我应该如何处理这个?这比单纯的关闭事件要复杂一些。 如果表单没有关闭,那么很明显关闭它的代码存在问题。是什么促使它首先关闭?是Timer.Tick 还是别的什么? 我已经用用于 ToastForm 的代码更新了我的线程。我看不到它在哪里引用了我的 MainForm 或超出其范围的任何内容,尽管它确实调用了动画师来处理表单转换的动画。 我认为您在网上找到的代码可能是我的。它是从这里来的吗(vbforums.com/…)? 【参考方案1】:

我认为这里的解决方案可能是在 UI 线程上创建通知表单。您仍然可以执行查询以在辅助线程上获取数据,然后将方法调用编组到 UI 线程以显示通知。

一个简单的选择是使用Windows.Forms.Timer 而不是Timers.Timer,并在Tick 事件处理程序中对BackgroundWorker 调用RunWorkerAsync。然后,您可以在辅助线程上执行的DoWork 事件处理程序中进行查询,并在 UI 线程上执行的RunWorkerCompleted 事件处理程序中显示通知。

【讨论】:

具有讽刺意味的是,这就是我最初设置它的方式。因为我正在运行许多不同的进程,它们都访问数据库以获得不同的东西,所以我不想将 UI 与我的所有处理联系起来。因此,为什么我认为我会将所有内容移至不同的线程。我担心的是从 timer_tick 事件中调用后台工作人员是否是一种好习惯。 我想不出这样做有什么问题。不过,最好先测试 IsBusy 属性。

以上是关于使用带有 System.Timers.Timer 的 ToastForms 的问题的主要内容,如果未能解决你的问题,请参考以下文章

使用System.Timers.Timer类实现程序定时执行

为啥 xmlignore 不能与 System.Timers.Timer 类一起使用

如何在 C# 中创建计时器而不使用 System.Timers.Timer 或 System.Threading.Timer

System.Timers.Timer 严重不准确

System.Timers.Timer

为啥 System.Timers.Timer 能在 GC 中存活,而 System.Threading.Timer 不能?