【问题标题】:How do I determine if any of the forms of may application is active?我如何确定任何可能的申请表是否有效?
【发布时间】:2020-03-27 13:39:52
【问题描述】:

我收到了一个客户的特殊请求,我为其制作了一个 vb.net 桌面应用程序。他需要记录使用该应用程序所花费的时间。使用“应用程序”是指它的任何形式(模态或其他形式)都是活动的,并且应用程序不仅仅是在后台运行或最小化。有没有办法判断属于该应用程序的任何表单是否处于活动状态?一旦我弄清楚这一点,我想我可以找到一种记录“活动”时间的方法。

【问题讨论】:

  • 没有魔法。处理适当的事件以了解表单状态何时发生变化并跟踪是否至少有一个表单处于活动状态。
  • ActivatedDeactivate 事件在表单显示或获得焦点以及关闭或焦点传递到另一个表单/应用程序时引发(通过直接用户操作或因为它已最小化)。不过,这并不意味着它被使用过
  • 因此,除了将代码放入每个表单中之外,别无他法。我希望我可以遍历 My.Application.OpenForms 对象集合并以某种方式判断其中是否有人处于活动状态。
  • 如果您不想轮询集合 - 但您可以检查 ActiveForm 属性 - 您可以使用如下内容:Add an event to all Forms in a Project。它在打开任何表单时将事件处理程序添加到任何表单,并在关闭表单时删除处理程序。它可以附加到任何现有应用程序,而无需更改应用程序中的一行代码。
  • 好吧,当然,您需要注入该代码。您可以使用 UI 自动化对监视另一个应用程序的外部应用程序执行相同的操作。或 UI 自动化帮助程序库。

标签: vb.net winforms


【解决方案1】:

可以通过使用实现IMessageFilter Interface 的类并使用Application.AddMessageFilter(IMessageFilter) Method 安装此类的实例来监视WinForm 项目中的键盘和鼠标活动。请注意,这种方式只能检测到发布到线程消息队列的消息;幸运的是,所有键盘和鼠标消息都发布到此队列。 (更多信息请见:About Messages and Message Queues

以下是IMessageFilter 的实现,旨在提供应用程序接收用户输入的时间(TimeUsed 属性)。类构造函数 (Sub New) 采用参数 idleSecondsToIgnore。该参数用于允许在没有用户输入的情况下累积使用时间;将其视为一个宽限期,以说明用户查看应用程序而不主动与其交互的时间。构造函数还处理注册过滤器。

Public Class UsageMonitor : Implements IMessageFilter

  Private timeUsedTicksAccumulator As Int64
  Private lastActiveTime As DateTime
  Private appIsRunning As Boolean
  Private ReadOnly idleTicksToIgnore As Int64

  Public Sub New(idleSecondsToIgnore As Int32)
    Me.idleTicksToIgnore = TimeSpan.TicksPerSecond * idleSecondsToIgnore
    StartTime = DateTime.Now
    lastActiveTime = StartTime
    Application.AddMessageFilter(Me)
    AddHandler Application.ApplicationExit, Sub(sender As Object, e As EventArgs)
                                              appIsRunning = False
                                              UpdateTimeUsed()
                                              Me._EndTime = DateAndTime.Now
                                            End Sub
    appIsRunning = True
  End Sub

  Public ReadOnly Property StartTime As DateTime
  Public ReadOnly Property EndTime As DateTime

  Public ReadOnly Property TotalRunTime As TimeSpan
    Get
      Return If(appIsRunning, DateTime.Now, EndTime) - StartTime
    End Get
  End Property

  Public ReadOnly Property TimeUsed As TimeSpan
    Get
      Dim ticks As Int64 = timeUsedTicksAccumulator
      If appIsRunning Then
        Dim now As DateTime = DateTime.Now
        Dim diff As TimeSpan = now - lastActiveTime
        If diff.Ticks < idleTicksToIgnore Then
          ticks += diff.Ticks
        Else
          ' give usage credit for only the idle time threshold
          ticks += idleTicksToIgnore
        End If
      End If
      Return New TimeSpan(ticks)
    End Get
  End Property

  Public Function PreFilterMessage(ByRef m As Message) As Boolean Implements IMessageFilter.PreFilterMessage
    ' Only messages posted to the thread message queue are received by this method.
    ' i.e. only messages that are processed by the message pump loop

    ' From: Message Routing, https://docs.microsoft.com/en-us/windows/win32/winmsg/about-messages-and-message-queues#message-routing
    '   A message that is posted to a message queue is called a queued message. 
    '   These are primarily the result of user input entered through the mouse or keyboard, 
    '   such as WM_MOUSEMOVE, WM_LBUTTONDOWN, WM_KEYDOWN, and WM_CHAR messages. 
    '   Other queued messages include the timer, paint, and quit messages: WM_TIMER, 
    '   WM_PAINT, and WM_QUIT. Most other messages, which are sent directly to a 
    '   window procedure, are called nonqueued messages.


    Const WM_KEYFIRST As Int32 = &H100
    Const WM_KEYLAST As Int32 = &H108

    Const WM_MOUSEFIRST As Int32 = &H200
    Const WM_MOUSELAST As Int32 = &H20E

    Const WM_NCMOUSEMOVE As Int32 = &HA0
    Const WM_NCLBUTTONDOWN As Int32 = &HA1
    Const WM_NCLBUTTONUP As Int32 = &HA2
    Const WM_NCLBUTTONDBLCLK As Int32 = &HA3
    Const WM_NCRBUTTONDOWN As Int32 = &HA4
    Const WM_NCRBUTTONUP As Int32 = &HA5
    Const WM_NCRBUTTONDBLCLK As Int32 = &HA6
    Const WM_NCMBUTTONDOWN As Int32 = &HA7
    Const WM_NCMBUTTONUP As Int32 = &HA8
    Const WM_NCMBUTTONDBLCLK As Int32 = &HA9

    Const WM_NCMOUSEHOVER As Int32 = &H2A0
    Const WM_NCMOUSELEAVE As Int32 = &H2A2

    Select Case m.Msg
      Case WM_KEYFIRST To WM_KEYLAST
        UpdateTimeUsed()
      Case WM_MOUSEFIRST To WM_MOUSELAST
        UpdateTimeUsed()
      Case WM_NCMOUSEMOVE, WM_NCLBUTTONDOWN, WM_NCLBUTTONUP, WM_NCLBUTTONDBLCLK, WM_NCRBUTTONDOWN, WM_NCRBUTTONUP, WM_NCRBUTTONDBLCLK, WM_NCMBUTTONDOWN, WM_NCMBUTTONUP, WM_NCMBUTTONDBLCLK
        UpdateTimeUsed()
      Case WM_NCMOUSEHOVER, WM_NCMOUSELEAVE
        UpdateTimeUsed()
      Case Else
        'ignore it
    End Select


    Return False ' always return false as we are not handling the message per se
  End Function

  Private Sub UpdateTimeUsed()
    Dim now As DateTime = DateTime.Now
    Dim diff As TimeSpan = now - lastActiveTime
    If diff.Ticks < idleTicksToIgnore Then
      timeUsedTicksAccumulator += diff.Ticks
    Else
      ' give usage credit for only the idle time threshold
      timeUsedTicksAccumulator += idleTicksToIgnore
    End If
    lastActiveTime = now
  End Sub

  Public Overrides Function ToString() As String
    Return $"Monitoring Started: {StartTime}{vbCrLf}Tot. Time: {Fmt(TotalRunTime)}{vbCrLf}Time Active: {Fmt(TimeUsed)}"
  End Function

  Private Shared Function Fmt(ts As TimeSpan) As String
    Return $"{ts:dd} Days {ts:hh}:{ts:mm}:{ts:ss}.{ts:fff}"
  End Function
End Class

创建此类实例的位置就在创建第一个表单并显示给用户之前。如果您使用的是 VB.Net 的应用程序框架,则可以将其添加到 MyApplication 类的定义中(可从打开 ApplicationEvents.vb 文件的项目菜单->项目属性->应用程序选项卡->查看应用程序事件按钮访问。

Imports Microsoft.VisualBasic.ApplicationServices

Namespace My
  Partial Friend Class MyApplication
    Public ReadOnly Property Usage As UsageMonitor = New UsageMonitor(600) ' 10 minute idle

    Private Sub MyApplication_Shutdown(sender As Object, e As EventArgs) Handles Me.Shutdown
      ' add your logging code here 
    End Sub
  End Class
End Namespace

在上面的代码中,实例是通过Usage 属性创建的。

如果您使用Sub Main 启动应用程序,请在调用Application.Run(mainform) 之前创建UsageMonitor 的实例。

要对此进行测试,请创建一个新的 WinForm 项目并在表单上放置一个标签(同时将上面显示的代码添加到项目中)。然后修改Form1.vb文件如下。

Public Class Form1
  Private WithEvents Timer1 As Timer

  Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
    Timer1 = New Timer
    Timer1.Interval = 2000
    Timer1.Start()
  End Sub

  Private Sub Timer1_Tick(sender As Object, e As EventArgs) Handles Timer1.Tick
    Label1.Text = My.Application.Usage.ToString()
  End Sub
End Class

请注意,您可能希望将 idleSecondsToIgnore 的值从 600 减少到 10 以进行测试,以便您可以观察到当您停止用户交互(将鼠标移到表单上)时活动时间值停止增加。

【讨论】:

  • 您好。这是否在 x86 机器上运行?也许您只需要Integer 而不是Int32Int64
  • @JQSOFT,Int64 是 TimeSpan(Int64) 构造函数的正确类型。 32 位和 64 位 .Net 都完全支持它。
  • 明白。谢谢。
  • 将看看我如何根据我的应用程序调整它。谢谢!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2018-06-07
  • 2012-06-08
  • 2010-09-19
  • 2020-10-12
  • 1970-01-01
  • 2012-01-25
  • 1970-01-01
相关资源
最近更新 更多