@@ -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;
0 commit comments