3

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;
10101
  • 2,232
  • 3
  • 26
  • 66

1 Answers1

1

However I have noticed that it does not react in case Google Chrome is opened and I am switching between Tabs.

Switching between tabs cause focus changes, so you can use EVENT_OBJECT_FOCUS (in addition) to cover this situation.

Rita Han
  • 9,574
  • 1
  • 11
  • 24
  • Thank you for the tip! I have now tried this approach but haven't got any success yet. Changed `EVENT_SYSTEM_FOREGROUND = 3` to `EVENT_OBJECT_FOCUS = 0x8005`. Is it the right way? – 10101 Oct 20 '20 at 09:31
  • @hatman This line (in C++, `SetWinEventHook(EVENT_OBJECT_FOCUS, EVENT_OBJECT_FOCUS, NULL, Wineventproc, 0, 0, WINEVENT_OUTOFCONTEXT);`) will get `EVENT_OBJECT_FOCUS` events for you when you clicking the tabs of Chrome. – Rita Han Oct 20 '20 at 10:11
  • @hatman Are you able to receive EVENT_OBJECT_FOCUS event? – Rita Han Oct 21 '20 at 07:59
  • I have already proceeded with Loop, but I think the proper way is to use solution provided by you above. I have to rewrite "couple" of lines in order to test your solution. I will try to do it this week and post a reply or accept the answer. Sorry for delay. Had no time yet to continue with this project. – 10101 Oct 21 '20 at 16:26
  • Looks like it should be `public const uint EVENT_OBJECT_FOCUS = 0x8005;`. This seems to be the right variable to use. Triggering the method with other processes. – 10101 Oct 22 '20 at 14:05