That’s what’s up. We are going to stop windows from stealing focus with C#. No frills, no crap, no more of this nonsense. It’s time to stop.
Q: Why does Windows do this and why isn’t there a setting to fix this?
A: Remember Fred Johnson who lived down the street in the 70’s? You know… that slightly overweight jerk that bullied the smart kids? Well, one day on the way home from school he kicked Billy Gates dog and said something off color about his “parentage”. So, now we need to suffer for Fred’s sins. No need to Wiki this, it’s a true story.
Joking Aside
So, back in the early 2000’s we had the Windows XP PowerToy called TweakUI which allowed us to control how initialized windows interacted with the desktop environment. This involved changing the following registry key:
HKEY_CURRENT_USER\Control Panel\Desktop\Foreground\LockTimeout
In the modern versions of Windows, this override no longer works. For a long while, this left us SOL and drifting in space looking for the answer… that is until now.
The Solution
In my opinion, the C# community was right in thinking the best approach is to get on the P/Invoke track.
For this solution the key is LockSetForegroundWindow on a timer. For this solution we will use the following methods:
// Lock public static uint LSFW_LOCK = 1; public static uint LSFW_UNLOCK = 2; /// <summary> /// Locks the set foreground window. /// </summary> /// <param name="uLockCode">The u lock code.</param> /// <returns></returns> [DllImport("user32.dll")] public static extern bool LockSetForegroundWindow(uint uLockCode); /// <summary> /// Gets the foreground window. /// </summary> /// <returns></returns> [DllImport("user32.dll")] public static extern IntPtr GetForegroundWindow();
Now that we have identified the P/Invoke piece, let’s look at how we can implement this effectively with said timer in a WPF application. To do this we are going to create a controller to facilitate all our focus needs:
using System; using System.Diagnostics; using System.Linq; using System.Threading.Tasks; using System.Windows.Threading; using Win32Windows = Xcalibur.Win32.Win32ApiHelper.Windows; namespace Xcalibur.DontInterruptMe { /// <summary> /// Window focus controller. /// </summary> public class FocusController { #region Members private readonly DispatcherTimer _timer; private IntPtr _currentHandle; private bool _isRunning; private bool _isStarted; #endregion #region Constructors /// <summary> /// Initializes a new instance of the <see cref="FocusController"/> class. /// </summary> public FocusController() { this._isRunning = false; this._isStarted = false; // Set timer this._timer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(250) }; this._timer.Tick += (s, e) => this.EvaluateAsync(); // Start this.Start(); } #endregion #region Methods /// <summary> /// Starts this instance. /// </summary> public void Start() { if (this._isStarted) return; // Start timer this._timer.IsEnabled = true; this._timer.Start(); this._isStarted = true; } /// <summary> /// Stops this instance. /// </summary> public void Stop() { if (!this._isStarted) return; // Stop timer this._timer.Stop(); this._timer.IsEnabled = false; this._isStarted = false; // Unlock set foreground window LockSetForegroundWindow(LSFW_UNLOCK); } /// <summary> /// Evaluates this instance. /// </summary> private void Evaluate() { if (_isRunning) return; // Set as "running" _isRunning = true; // Get current var activeWindowHandle = GetForegroundWindow(); if (_currentHandle == activeWindowHandle) { _isRunning = false; return; } // Store current handle _currentHandle = activeWindowHandle; // Handle cannot be 0 if (activeWindowHandle == IntPtr.Zero) { _isRunning = false; return; } // Get related process var processes = Process.GetProcesses(); var currentProcess = processes.FirstOrDefault(x => x.MainWindowHandle == _currentHandle); // currentProcess must exist, and the MainWindowTitle must be valid. if (currentProcess == null || string.IsNullOrEmpty(currentProcess.MainWindowTitle)) { _isRunning = false; return; } // Lock set foreground window LockSetForegroundWindow(LSFW_LOCK); // Set as "not running" _isRunning = false; } /// <summary> /// Evaluates the asynchronous. /// </summary> /// <returns></returns> private async Task EvaluateAsync() { await Task.Run(() => this.Evaluate()); } #endregion #region P/Invoke // Lock public static uint LSFW_LOCK = 1; public static uint LSFW_UNLOCK = 2; /// <summary> /// Locks the set foreground window. /// </summary> /// <param name="uLockCode">The u lock code.</param> /// <returns></returns> [DllImport("user32.dll")] public static extern bool LockSetForegroundWindow(uint uLockCode); /// <summary> /// Gets the foreground window. /// </summary> /// <returns></returns> [DllImport("user32.dll")] public static extern IntPtr GetForegroundWindow(); #endregion } }
The last piece is the implementation of the FocusController from App.xaml:
using System; using System.Drawing; using System.IO; using System.Reflection; using System.Threading; using System.Windows; using System.Windows.Controls; using Hardcodet.Wpf.TaskbarNotification; using Microsoft.Win32; using Application = System.Windows.Application; namespace Xcalibur.DontInterruptMe { /// <summary> /// Interaction logic for App.xaml /// </summary> public partial class App : Application { #region Members private static Mutex _instanceMutex; private FocusController _focusController; #endregion #region Methods /// <summary> /// Checks if application is already running. /// </summary> /// <returns></returns> private static bool StartInstance() { // Set mutex _instanceMutex = new Mutex(true, Constants.ApplicationKey); // Check if already running bool isAlreadyInUse = false; try { isAlreadyInUse = !_instanceMutex.WaitOne(TimeSpan.Zero, true); } catch (AbandonedMutexException) { KillInstance(); isAlreadyInUse = false; } catch (Exception) { _instanceMutex.Close(); isAlreadyInUse = false; } return isAlreadyInUse; } /// <summary> /// Kills the instance. /// </summary> /// <param name="code">The code.</param> private static void KillInstance(int code = 0) { if (_instanceMutex == null) return; // Owning application should release mutex if (code == 0) { try { _instanceMutex.ReleaseMutex(); } catch (Exception) { } } _instanceMutex.Close(); } #endregion #region Events /// <summary> /// Raises the <see cref="E:System.Windows.Application.Startup" /> event. /// </summary> /// <param name="e">A <see cref="T:System.Windows.StartupEventArgs" /> that contains the /// event data.</param> protected override void OnStartup(StartupEventArgs e) { // Check if running if (StartInstance()) { // Already running, Exit Current.Shutdown(1); } // Invoke focus controller this._focusController = new FocusController(); // Base base.OnStartup(e); } /// <summary> /// Raises the <see cref="E:System.Windows.Application.Exit" /> event. /// </summary> /// <param name="e">An <see cref="T:System.Windows.ExitEventArgs" /> that contains the /// event data.</param> protected override void OnExit(ExitEventArgs e) { if (this._focusController != null) { // Gracefully exit this._focusController.Stop(); } // Kill instance KillInstance(e.ApplicationExitCode); // Base base.OnExit(e); } #endregion } }
You will notice I added the RunOnce Mutex Solution from our last post Restricting WPF Applications to run only once with a Mutex to avoid any potential issues with multiple instances.
Future Changes
In the next version of this application I will be tossing Process.GetProcesses() from the Evaluate method for a much faster P/Invoke solution I am planning to use in the next version of Astronomy.
If you want the complete, free product, download Don’t Interrupt Me! now.
Otherwise my friends, Happy Coding!