Skip to content

Commit b26d490

Browse files
committed
Improve Focus/Unfocus tracking
1 parent 6d911af commit b26d490

File tree

4 files changed

+130
-33
lines changed

4 files changed

+130
-33
lines changed

Source/ExcelDna.IntelliSense/UIMonitor/FormulaEditWatcher.cs

Lines changed: 21 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -118,23 +118,27 @@ void _windowWatcher_FormulaBarWindowChanged(object sender, WindowWatcher.WindowC
118118
case WindowWatcher.WindowChangedEventArgs.ChangeType.Focus:
119119
if (_formulaEditFocus != FormulaEditFocus.FormulaBar)
120120
{
121-
Logger.WindowWatcher.Verbose($"FormulaEdit - FormulaBar focus");
121+
Logger.WindowWatcher.Verbose($"FormulaEdit - FormulaBar Focus");
122122
_formulaEditFocus = FormulaEditFocus.FormulaBar;
123123
UpdateFormulaPolling();
124124
UpdateEditState();
125125
}
126126
break;
127-
case WindowWatcher.WindowChangedEventArgs.ChangeType.Show:
128-
break;
129-
case WindowWatcher.WindowChangedEventArgs.ChangeType.Hide:
127+
case WindowWatcher.WindowChangedEventArgs.ChangeType.Unfocus:
130128
if (_formulaEditFocus == FormulaEditFocus.FormulaBar)
131129
{
132-
Logger.WindowWatcher.Verbose($"FormulaEdit - FormulaBar hidden");
130+
Logger.WindowWatcher.Verbose($"FormulaEdit - FormulaBar Unfocus");
133131
_formulaEditFocus = FormulaEditFocus.None;
134132
UpdateFormulaPolling();
135133
UpdateEditState();
136134
}
137135
break;
136+
case WindowWatcher.WindowChangedEventArgs.ChangeType.Show:
137+
Logger.WindowWatcher.Verbose($"FormulaEdit - FormulaBar Show");
138+
break;
139+
case WindowWatcher.WindowChangedEventArgs.ChangeType.Hide:
140+
Logger.WindowWatcher.Verbose($"FormulaEdit - FormulaBar Hide");
141+
break;
138142
default:
139143
throw new ArgumentOutOfRangeException("Unexpected Window Change Type", "e.Type");
140144
}
@@ -161,23 +165,26 @@ void _windowWatcher_InCellEditWindowChanged(object sender, WindowWatcher.WindowC
161165
case WindowWatcher.WindowChangedEventArgs.ChangeType.Focus:
162166
if (_formulaEditFocus != FormulaEditFocus.InCellEdit)
163167
{
164-
Logger.WindowWatcher.Verbose($"FormulaEdit - InCellEdit focus");
168+
Logger.WindowWatcher.Verbose($"FormulaEdit - InCellEdit Focus");
165169
_formulaEditFocus = FormulaEditFocus.InCellEdit;
166170
UpdateFormulaPolling();
167171
UpdateEditState();
168172
}
169173
break;
174+
case WindowWatcher.WindowChangedEventArgs.ChangeType.Unfocus:
175+
if (_formulaEditFocus == FormulaEditFocus.InCellEdit)
176+
{
177+
Logger.WindowWatcher.Verbose($"FormulaEdit - InCellEdit Unfocus");
178+
_formulaEditFocus = FormulaEditFocus.None;
179+
UpdateFormulaPolling();
180+
UpdateEditState();
181+
}
182+
break;
170183
case WindowWatcher.WindowChangedEventArgs.ChangeType.Show:
184+
Logger.WindowWatcher.Verbose($"FormulaEdit - InCellEdit Show");
171185
break;
172186
case WindowWatcher.WindowChangedEventArgs.ChangeType.Hide:
173-
// NOTE: Very confusing - under Excel 2010 InCellEdit gets hidden immediately, and never shown again....
174-
//if (_formulaEditFocus == FormulaEditFocus.InCellEdit)
175-
//{
176-
// Logger.WindowWatcher.Verbose($"FormulaEdit - InCellEdit hidden");
177-
// _formulaEditFocus = FormulaEditFocus.None;
178-
// UpdateFormulaPolling();
179-
// UpdateEditState();
180-
//}
187+
Logger.WindowWatcher.Verbose($"FormulaEdit - InCellEdit Hide");
181188
break;
182189
default:
183190
throw new ArgumentOutOfRangeException("Unexpected Window Change Type", "e.Type");

Source/ExcelDna.IntelliSense/UIMonitor/PopupListWatcher.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,10 @@ void _windowWatcher_PopupListWindowChanged(object sender, WindowWatcher.WindowCh
8585
IsVisible = false;
8686
UpdateSelectedItem(_selectedItem);
8787
break;
88+
case WindowWatcher.WindowChangedEventArgs.ChangeType.Focus:
89+
case WindowWatcher.WindowChangedEventArgs.ChangeType.Unfocus:
90+
Logger.WindowWatcher.Verbose($"PopupList unexpected focus event!?");
91+
break;
8892
default:
8993
break;
9094
}

Source/ExcelDna.IntelliSense/UIMonitor/WindowWatcher.cs

Lines changed: 61 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -19,20 +19,25 @@ class WindowWatcher : IDisposable
1919
{
2020
public class WindowChangedEventArgs : EventArgs
2121
{
22-
23-
// CONSIDER: This mapping is trivial - just get rid of it?
2422
public enum ChangeType
2523
{
2624
Create = 1,
2725
Destroy = 2,
2826
Show = 3,
2927
Hide = 4,
30-
Focus = 5
28+
Focus = 5,
29+
Unfocus = 6
3130
}
3231

3332
public readonly IntPtr WindowHandle;
3433
public readonly ChangeType Type;
3534

35+
internal WindowChangedEventArgs(IntPtr windowHandle, ChangeType changeType)
36+
{
37+
WindowHandle = windowHandle;
38+
Type = changeType;
39+
}
40+
3641
internal WindowChangedEventArgs(IntPtr windowHandle, WinEventHook.WinEvent winEvent)
3742
{
3843
WindowHandle = windowHandle;
@@ -78,6 +83,11 @@ internal static bool IsSupportedWinEvent(WinEventHook.WinEvent winEvent)
7883

7984
WinEventHook _windowStateChangeHook;
8085

86+
// These track keyboard focus for Windows in the Excel process
87+
// Used to synthesize the 'Unfocus' change events
88+
IntPtr _focusedWindowHandle;
89+
string _focusedWindowClassName;
90+
8191

8292
// public IntPtr SelectDataSourceWindow { get; private set; }
8393
// public bool IsSelectDataSourceWindowVisible { get; private set; }
@@ -107,18 +117,47 @@ public WindowWatcher(SynchronizationContext syncContextAuto)
107117
public void TryInitialize()
108118
{
109119
// Debug.Print("### WindowWatcher TryInitialize on thread: " + Thread.CurrentThread.ManagedThreadId);
110-
AutomationElement focused;
111-
try
120+
var focusedWindowHandle = Win32Helper.GetFocusedWindowHandle();
121+
string className = null;
122+
if (focusedWindowHandle != IntPtr.Zero)
123+
className = Win32Helper.GetClassName(_focusedWindowHandle);
124+
125+
UpdateFocus(focusedWindowHandle, className);
126+
}
127+
128+
bool UpdateFocus(IntPtr windowHandle, string windowClassName)
129+
{
130+
if (windowHandle == _focusedWindowHandle)
112131
{
113-
focused = AutomationElement.FocusedElement;
114-
var className = focused.GetCurrentPropertyValue(AutomationElement.ClassNameProperty);
115-
Debug.Print($"WindowWatcher.TryInitialize - Focused: {className}");
132+
Debug.Assert(_focusedWindowClassName == windowClassName);
133+
return false;
116134
}
117-
catch (ArgumentException aex)
135+
136+
Logger.WindowWatcher.Verbose($"Focus lost by {_focusedWindowHandle} ({_focusedWindowClassName})");
137+
// It has changed - raise an event for the old window
138+
switch (_focusedWindowClassName)
118139
{
119-
Debug.Print($"!!! ERROR: Failed to get Focused Element: {aex}");
120-
return;
140+
case _popupListClass:
141+
PopupListWindowChanged?.Invoke(this, new WindowChangedEventArgs(_focusedWindowHandle, WindowChangedEventArgs.ChangeType.Unfocus));
142+
break;
143+
case _inCellEditClass:
144+
InCellEditWindowChanged?.Invoke(this, new WindowChangedEventArgs(_focusedWindowHandle, WindowChangedEventArgs.ChangeType.Unfocus));
145+
break;
146+
case _formulaBarClass:
147+
FormulaBarWindowChanged?.Invoke(this, new WindowChangedEventArgs(_focusedWindowHandle, WindowChangedEventArgs.ChangeType.Unfocus));
148+
break;
149+
//case _nuiDialogClass:
150+
default:
151+
// Not one of our watched window, so we don't care
152+
break;
121153
}
154+
155+
// Set the new focus info
156+
// Event will be raised by WinEventReceived handler itself
157+
_focusedWindowHandle = windowHandle;
158+
_focusedWindowClassName = windowClassName;
159+
Logger.WindowWatcher.Verbose($"Focus changed to {windowHandle} ({windowClassName})");
160+
return true;
122161
}
123162

124163
// This runs on the Automation thread, via SyncContextAuto passed in to WinEventHook when we created this WindowWatcher
@@ -130,8 +169,18 @@ void _windowStateChangeHook_WinEventReceived(object sender, WinEventHook.WinEven
130169
if (!WindowChangedEventArgs.IsSupportedWinEvent(e.EventType))
131170
return;
132171

133-
// Debug.Print("### Thread receiving WindowStateChange: " + Thread.CurrentThread.ManagedThreadId);
134172
var className = Win32Helper.GetClassName(e.WindowHandle);
173+
if (e.EventType == WinEventHook.WinEvent.EVENT_OBJECT_FOCUS)
174+
{
175+
// Might raise change event for Unfocus
176+
if (!UpdateFocus(e.WindowHandle, className))
177+
{
178+
// We already have the right focus
179+
return;
180+
}
181+
}
182+
183+
// Debug.Print("### Thread receiving WindowStateChange: " + Thread.CurrentThread.ManagedThreadId);
135184
switch (className)
136185
{
137186
//case _sheetWindowClass:
@@ -182,10 +231,6 @@ void _windowStateChangeHook_WinEventReceived(object sender, WinEventHook.WinEven
182231
// }
183232
// break;
184233
default:
185-
if (e.EventType == WinEventHook.WinEvent.EVENT_OBJECT_FOCUS)
186-
{
187-
Logger.WindowWatcher.Verbose($"FOCUS! on {className}");
188-
}
189234
//InCellEditWindowChanged(this, EventArgs.Empty);
190235
break;
191236
}
@@ -201,9 +246,6 @@ public void Dispose()
201246
}
202247
}
203248

204-
205-
206-
207249
//class SelectDataSourceWatcher : IDisposable
208250
//{
209251
// SynchronizationContext _syncContextAuto;

Source/ExcelDna.IntelliSense/Win32Helper.cs

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System;
2+
using System.ComponentModel;
23
using System.Drawing;
34
using System.Runtime.InteropServices;
45
using System.Text;
@@ -47,6 +48,49 @@ enum WM : uint
4748
[DllImport("user32.dll")]
4849
static extern bool ScreenToClient(IntPtr hWnd, ref Point lpPoint);
4950

51+
[DllImport("user32.dll", SetLastError = true)]
52+
static extern bool GetGUIThreadInfo(uint idThread, ref GUITHREADINFO lpgui);
53+
54+
struct GUITHREADINFO
55+
{
56+
public int cbSize;
57+
public int flags;
58+
public IntPtr hwndActive;
59+
public IntPtr hwndFocus;
60+
public IntPtr hwndCapture;
61+
public IntPtr hwndMenuOwner;
62+
public IntPtr hwndMoveSize;
63+
public IntPtr hwndCaret;
64+
public Rectangle rcCaret;
65+
}
66+
67+
[DllImport("user32.dll", SetLastError=true)]
68+
static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);
69+
70+
// Returns the WindowHandle of the focused window, if that window is in our process.
71+
public static IntPtr GetFocusedWindowHandle()
72+
{
73+
var info = new GUITHREADINFO();
74+
info.cbSize = Marshal.SizeOf(info);
75+
if (!GetGUIThreadInfo(0, ref info))
76+
throw new Win32Exception();
77+
78+
var focusedWindow = info.hwndFocus;
79+
if (focusedWindow == IntPtr.Zero)
80+
return focusedWindow;
81+
82+
uint processId;
83+
uint threadId = GetWindowThreadProcessId(focusedWindow, out processId);
84+
if (threadId == 0)
85+
throw new Win32Exception();
86+
87+
uint currentProcessId = GetCurrentProcessId();
88+
if (processId == currentProcessId)
89+
return focusedWindow;
90+
91+
return IntPtr.Zero;
92+
}
93+
5094
public static Point GetClientCursorPos(IntPtr hWnd)
5195
{
5296
Point pt;

0 commit comments

Comments
 (0)