Как определить, активна ли какая-либо из форм майского приложения? - PullRequest
0 голосов
/ 27 марта 2020

Я получил специфический запрос от клиента, для которого я создал настольное приложение vb. net. Ему нужно регистрировать, сколько времени уходит на использование приложения. Под «приложением» я подразумеваю, что любая из его форм (модальная или иная) активна и что приложение не просто работает в фоновом режиме или свернуто. Есть ли способ узнать, активна ли какая-либо из форм, принадлежащих приложению? Как только я это выясню, я думаю, что найду способ записать «активное» время.

1 Ответ

3 голосов
/ 28 марта 2020

Мониторинг активности клавиатуры и мыши в проекте 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 для тестирования, чтобы вы могли наблюдать прекращение увеличения активного значения времени, когда вы останавливаете взаимодействие с пользователем (перемещая мышь над формой).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...