vb.net - 我如何确定任何可能的申请表是否有效?
问题描述
我收到了一个客户的特殊请求,我为其制作了一个 vb.net 桌面应用程序。他需要记录使用该应用程序所花费的时间。使用“应用程序”是指它的任何形式(模态或其他形式)都是活动的,并且应用程序不仅仅是在后台运行或最小化。有没有办法判断属于该应用程序的任何表单是否处于活动状态?一旦我弄清楚这一点,我想我可以找到一种记录“活动”时间的方法。
解决方案
可以通过使用实现IMessageFilter 接口的类并使用Application.AddMessageFilter(IMessageFilter) 方法安装此类的实例来监视 WinForm 项目中的键盘和鼠标活动。请注意,这种方式只能检测到发布到线程消息队列的消息;幸运的是,所有键盘和鼠标消息都发布到此队列。(有关详细信息,请参阅:关于消息和消息队列。
以下是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
属性创建的。
如果您使用 aSub Main
来启动应用程序,请UsageMonitor
在调用之前创建一个实例Application.Run(mainform)
。
要对此进行测试,请创建一个新的 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 以进行测试,以便您可以观察到当您停止用户交互(将鼠标移到表单上)时活动时间值停止增加。
推荐阅读
- python - 为什么 numpy.nan 与 math.nan 不同?
- spring - Spring Boot 批处理框架
- metadata - 从谷歌驱动器选择文件后如何获取文件的最后修改日期
- php - Laravel 块方法返回 bool 而不是数据
- python - 使用knn算法进行数据分类
- flutter - 颤振错误:此小部件已卸载,因此状态不再具有上下文(应视为已失效)
- java - 我如何为按钮列表创建 GWT clickHandler
- mongodb - 从 mongodb 文档中获取值作为 List
- javascript - JS:函数执行超时
- c# - ItemsControl 组合框 selecteditem C# WPF MVVM