Мониторинг активности клавиатуры и мыши в проекте WinForm может быть достигнут с помощью класса, реализующего интерфейс IMessageFilter , и установки экземпляра этого класса с помощью метода Application.AddMessageFilter (IMessageFilter) . Обратите внимание, что таким образом могут быть обнаружены только сообщения, отправленные в очередь сообщений потока; К счастью, все сообщения от клавиатуры и мыши отправляются в эту очередь. (Для получения дополнительной информации см .: О сообщениях и очередях сообщений .
Ниже приведена реализация 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
Место для создания экземпляра этого класса находится непосредственно перед созданием и показом пользователю первой формы. Если вы используете Application Framework 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
property.
Если вы используете Sub 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 для тестирования, чтобы вы могли наблюдать прекращение увеличения активного значения времени, когда вы останавливаете взаимодействие с пользователем (перемещая мышь над формой).