I am working on tool that will track activity and log it to database (will be added later, I am currently testing in TextBox). So it will read active window title and calculate time spent on each window.
I know there is a solution Detect active window changed using C# without polling
However I have noticed that it does not react in case Google Chrome is opened and I am switching between Tabs. I have to click on some other window and then on Chrome Tab, in this case active window switch is detected.
I was thinking maybe to approach this issue with running CheckForWindowTitle()
every X seconds and comparing to what is currently in GetActiveWindowTitle.GetActiveWindowTitleMethod()
and if they does not match, then change to CheckForWindowTitle()
. Might there be a better solution?
GetActiveWindowTitle.cs:
using System;
using System.Runtime.InteropServices;
using System.Text;
namespace Activitytracker
{
class GetActiveWindowTitle
{
public delegate void WinEventDelegate(IntPtr hWinEventHook, uint eventType, IntPtr hwnd, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime);
[DllImport("user32.dll")]
public static extern IntPtr SetWinEventHook(uint eventMin, uint eventMax, IntPtr hmodWinEventProc, WinEventDelegate lpfnWinEventProc, uint idProcess, uint idThread, uint dwFlags);
public const uint WINEVENT_OUTOFCONTEXT = 0;
public const uint EVENT_SYSTEM_FOREGROUND = 3;
[DllImport("user32.dll")]
static extern IntPtr GetForegroundWindow();
[DllImport("user32.dll")]
static extern int GetWindowText(IntPtr hWnd, StringBuilder text, int count);
public static string GetActiveWindowTitleMethod()
{
const int nChars = 256;
IntPtr handle = IntPtr.Zero;
StringBuilder Buff = new StringBuilder(nChars);
handle = GetForegroundWindow();
if (GetWindowText(handle, Buff, nChars) > 0)
{
return Buff.ToString();
}
return null;
}
}
}
MainWindow.xaml.cs:
GetActiveWindowTitle.WinEventDelegate dele = null;
Stopwatch stopwatch = new Stopwatch();
public MainWindow()
{
InitializeComponent();
dele = new GetActiveWindowTitle.WinEventDelegate(WinEventProc);
IntPtr m_hhook = GetActiveWindowTitle.SetWinEventHook(GetActiveWindowTitle.EVENT_SYSTEM_FOREGROUND,
GetActiveWindowTitle.EVENT_SYSTEM_FOREGROUND, IntPtr.Zero, dele, 0, 0, GetActiveWindowTitle.WINEVENT_OUTOFCONTEXT);
//_ = CheckForWindowTitle();
}
public async Task CheckForWindowTitle()
{
while (true)
{
var delayTask = Task.Delay(100);
TextBoxMain.Text = GetActiveWindowTitle.GetActiveWindowTitleMethod();
await delayTask;
}
}
public void WinEventProc(IntPtr hWinEventHook, uint eventType, IntPtr hwnd, int idObject,
int idChild, uint dwEventThread, uint dwmsEventTime)
{
stopwatch.Stop();
var milliSeocnds = stopwatch.ElapsedMilliseconds;
var timeSpan = stopwatch.Elapsed;
var CurrentDate = DateTime.Now;
TextBoxMain.Text += GetActiveWindowTitle.GetActiveWindowTitleMethod() + " " + timeSpan + " " + CurrentDate + "\r\n";
stopwatch.Start();
}
EDIT:
I am not sure about efficiency, but here is some progress if anybody is interested (it is tracking each window also in Chrome and other applications):
public static string WindowTitle;
GetActiveWindowTitle.WinEventDelegate dele = null;
Stopwatch stopwatch = new Stopwatch();
public MainWindow()
{
InitializeComponent();
MainProcess.CreateDataTable();
WindowTitle = GetActiveWindowTitle.GetActiveWindowTitleMethod();
_ = TrackActiveWindow();
}
public async Task TrackActiveWindow()
{
long milliSeocnds;
TimeSpan timeSpan;
DateTime CurrentDate;
while (true)
{
if (WindowTitle == GetActiveWindowTitle.GetActiveWindowTitleMethod())
{
stopwatch.Start();
}
else
{
stopwatch.Stop();
milliSeocnds = stopwatch.ElapsedMilliseconds;
timeSpan = stopwatch.Elapsed;
CurrentDate = DateTime.Now;
//TextBoxMain.Text += WindowTitle + " " + timeSpan + " " + CurrentDate + "\r\n";
MainProcess.AddRecordToDatatable(WindowTitle, timeSpan.ToString(), CurrentDate);
WindowTitle = GetActiveWindowTitle.GetActiveWindowTitleMethod();
}
var delayTask = Task.Delay(100);
WindowTitle = GetActiveWindowTitle.GetActiveWindowTitleMethod();
await delayTask; // wait until at least X seconds elapsed since delayTask created
}
}
EDIT 2:
I have now been trying to get working approach provided in answer below. I need to assign variable in order to use it in:
dele = new GetActiveWindowTitle.WinEventDelegate(WinEventProc);
IntPtr m_hhook = GetActiveWindowTitle.SetWinEventHook(GetActiveWindowTitle.EVENT_OBJECT_FOCUS,
GetActiveWindowTitle.EVENT_OBJECT_FOCUS, IntPtr.Zero, dele, 0, 0, GetActiveWindowTitle.WINEVENT_OUTOFCONTEXT);
I have tried:
public const uint EVENT_OBJECT_FOCUS = 3;
but none of these worked.
Here is the method:
public void WinEventProc(IntPtr hWinEventHook, uint eventType, IntPtr hwnd,
int idObject, int idChild, uint dwEventThread, uint dwmsEventTime)
{
GetActiveWindowTitle.stopwatch.Stop();
milliSeocnds = GetActiveWindowTitle.stopwatch.ElapsedMilliseconds;
timeSpan = GetActiveWindowTitle.stopwatch.Elapsed;
CurrentDate = DateTime.Now;
MainProcess.AddRecordToDatatable(GetActiveWindowTitle.GetActiveWindowTitleMethod(),
milliSeocnds / 1000, CurrentDate, MainProcess.AdminHoursCode, MainProcess.userName);
// Group DataTable records
MainProcess.OrganizeDataTable();
ActivityLogGrid.ItemsSource = MainProcess.ActivityLogGrouped.DefaultView;
GetActiveWindowTitle.stopwatch.Start();
}
After some testing, it should be
public const uint EVENT_OBJECT_FOCUS = 0x8005;