Skip to content

Commit c9e20ee

Browse files
committed
Ensure call to LPenHelper is made from the main thread and add inactivity timeout of 100ms before calling it
1 parent 505616e commit c9e20ee

File tree

3 files changed

+104
-28
lines changed

3 files changed

+104
-28
lines changed

Source/ExcelDna.IntelliSense/ExcelDna.IntelliSense.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@
8888
<Compile Include="UIMonitor\UIState.cs" />
8989
<Compile Include="UIMonitor\WindowLocationWatcher.cs" />
9090
<Compile Include="UIMonitor\WindowWatcher.cs" />
91+
<Compile Include="Util\RenewableDelayExecutor.cs" />
9192
<Compile Include="Win32Helper.cs" />
9293
<Compile Include="Providers\LoaderNotification.cs" />
9394
<Compile Include="UIMonitor\WinEvents.cs" />

Source/ExcelDna.IntelliSense/UIMonitor/FormulaEditWatcher.cs

Lines changed: 36 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
using System;
1+
using ExcelDna.IntelliSense.Util;
2+
using System;
23
using System.Diagnostics;
34
using System.Threading;
45
using System.Windows;
@@ -71,11 +72,15 @@ public IntPtr FormulaEditWindow
7172
readonly SynchronizationContext _syncContextMain;
7273

7374
readonly WindowWatcher _windowWatcher; // Passed in
74-
WindowLocationWatcher _windowLocationWatcher; // Managed here
7575

76-
IntPtr _hwndFormulaBar;
77-
IntPtr _hwndInCellEdit;
78-
FormulaEditFocus _formulaEditFocus;
76+
WindowLocationWatcher _windowLocationWatcher; // Managed here
77+
readonly RenewableDelayExecutor _updateEditStateAfterTimeout;
78+
79+
IntPtr _hwndFormulaBar;
80+
IntPtr _hwndInCellEdit;
81+
FormulaEditFocus _formulaEditFocus;
82+
83+
const int DelayBeforeStateUpdateMilliseconds = 100;
7984

8085
public FormulaEditWatcher(WindowWatcher windowWatcher, SynchronizationContext syncContextAuto, SynchronizationContext syncContextMain)
8186
{
@@ -84,6 +89,7 @@ public FormulaEditWatcher(WindowWatcher windowWatcher, SynchronizationContext sy
8489
_windowWatcher = windowWatcher;
8590
_windowWatcher.FormulaBarWindowChanged += _windowWatcher_FormulaBarWindowChanged;
8691
_windowWatcher.InCellEditWindowChanged += _windowWatcher_InCellEditWindowChanged;
92+
_updateEditStateAfterTimeout = new RenewableDelayExecutor(DelayBeforeStateUpdateMilliseconds, () => UpdateEditState());
8793
}
8894

8995
// Runs on the Automation thread
@@ -95,13 +101,13 @@ void _windowWatcher_FormulaBarWindowChanged(object sender, WindowWatcher.WindowC
95101
if (e.ObjectId == WindowWatcher.WindowChangedEventArgs.ChangeObjectId.Self)
96102
{
97103
SetEditWindow(e.WindowHandle, ref _hwndFormulaBar);
98-
UpdateEditState();
104+
_updateEditStateAfterTimeout.Signal();
99105
}
100106
else if (e.ObjectId == WindowWatcher.WindowChangedEventArgs.ChangeObjectId.Caret)
101107
{
102108
// We expect this on every text change
103109
// NOTE: Not anymore after some Excel / Windows update
104-
UpdateEditStateDelayed();
110+
_updateEditStateAfterTimeout.Signal();
105111
}
106112
else
107113
{
@@ -126,29 +132,29 @@ void _windowWatcher_FormulaBarWindowChanged(object sender, WindowWatcher.WindowC
126132
SetEditWindow(e.WindowHandle, ref _hwndFormulaBar);
127133
}
128134
_formulaEditFocus = FormulaEditFocus.FormulaBar;
129-
UpdateEditState();
135+
_updateEditStateAfterTimeout.Signal();
130136
}
131137
break;
132138
case WindowWatcher.WindowChangedEventArgs.ChangeType.Unfocus:
133139
if (_formulaEditFocus == FormulaEditFocus.FormulaBar)
134140
{
135141
Logger.WindowWatcher.Verbose($"FormulaEdit - FormulaBar Unfocus");
136142
_formulaEditFocus = FormulaEditFocus.None;
137-
UpdateEditState();
143+
_updateEditStateAfterTimeout.Signal();
138144
}
139145
break;
140146
case WindowWatcher.WindowChangedEventArgs.ChangeType.Show:
141-
Logger.WindowWatcher.Verbose($"FormulaEdit - FormulaBar Show");
147+
Logger.WindowWatcher.Verbose($"FormulaEdit - FormulaBar Show");
142148
break;
143149
case WindowWatcher.WindowChangedEventArgs.ChangeType.Hide:
144-
Logger.WindowWatcher.Verbose($"FormulaEdit - FormulaBar Hide");
150+
Logger.WindowWatcher.Verbose($"FormulaEdit - FormulaBar Hide");
145151
break;
146152
case WindowWatcher.WindowChangedEventArgs.ChangeType.LocationChange:
147153
if (e.ObjectId == WindowWatcher.WindowChangedEventArgs.ChangeObjectId.Caret)
148154
{
149155
// We expect this on every text change in newer Excel versions
150156
Debug.Print($"-#-#-#- Text Changed ... ");
151-
UpdateEditStateDelayed();
157+
_updateEditStateAfterTimeout.Signal();
152158
}
153159
else
154160
{
@@ -172,14 +178,14 @@ void _windowWatcher_InCellEditWindowChanged(object sender, WindowWatcher.WindowC
172178
if (e.ObjectId == WindowWatcher.WindowChangedEventArgs.ChangeObjectId.Self)
173179
{
174180
SetEditWindow(e.WindowHandle, ref _hwndInCellEdit);
175-
UpdateEditState();
181+
_updateEditStateAfterTimeout.Signal();
176182
}
177183
else if (e.ObjectId == WindowWatcher.WindowChangedEventArgs.ChangeObjectId.Caret)
178184
{
179185
// We expect this on every text change
180186
// NOTE: Not anymore after some Excel / Windows update
181187
Debug.Print($"-#-#-#- Text Changed ... ");
182-
UpdateEditStateDelayed();
188+
_updateEditStateAfterTimeout.Signal();
183189
}
184190
else
185191
{
@@ -206,15 +212,15 @@ void _windowWatcher_InCellEditWindowChanged(object sender, WindowWatcher.WindowC
206212
Logger.WindowWatcher.Verbose($"FormulaEdit - InCellEdit Focus");
207213

208214
_formulaEditFocus = FormulaEditFocus.InCellEdit;
209-
UpdateEditState();
215+
_updateEditStateAfterTimeout.Signal();
210216
}
211217
break;
212218
case WindowWatcher.WindowChangedEventArgs.ChangeType.Unfocus:
213219
if (_formulaEditFocus == FormulaEditFocus.InCellEdit)
214220
{
215221
Logger.WindowWatcher.Verbose($"FormulaEdit - InCellEdit Unfocus");
216222
_formulaEditFocus = FormulaEditFocus.None;
217-
UpdateEditState();
223+
_updateEditStateAfterTimeout.Signal();
218224
}
219225
break;
220226
case WindowWatcher.WindowChangedEventArgs.ChangeType.Show:
@@ -228,7 +234,7 @@ void _windowWatcher_InCellEditWindowChanged(object sender, WindowWatcher.WindowC
228234
{
229235
// We expect this on every text change in newer Excel versions
230236
Debug.Print($"-#-#-#- Text Changed ... ");
231-
UpdateEditStateDelayed();
237+
_updateEditStateAfterTimeout.Signal();
232238
}
233239
else
234240
{
@@ -313,22 +319,20 @@ void _windowLocationWatcher_LocationChanged(object sender, EventArgs e)
313319
// UpdateFormula(textChangedOnly: true);
314320
//}
315321

316-
// TODO: Get rid of this somehow - added to make the mouse clicks in the in-cell editing work, by delaying the call to the PenHelper
317-
void UpdateEditStateDelayed()
322+
void UpdateEditState(bool moveOnly = false)
318323
{
319-
_syncContextAuto.Post(_ =>
320-
{
321-
Thread.Sleep(100);
322-
UpdateEditState();
323-
}, null);
324+
// Switches to our Main UI thread, updates current state and raises StateChanged event
325+
_syncContextMain.Post(_ =>
326+
{
327+
UpdateEditStateImpl(moveOnly);
328+
}, null);
324329
}
325330

326-
// Switches to our Automation thread, updates current state and raises StateChanged event
327-
void UpdateEditState(bool moveOnly = false)
331+
void UpdateEditStateImpl(bool moveOnly = false)
328332
{
329333
Logger.WindowWatcher.Verbose($"> FormulaEdit UpdateEditState - Thread {Thread.CurrentThread.ManagedThreadId}");
330334
Logger.WindowWatcher.Verbose($"FormulaEdit UpdateEditState - Focus: {_formulaEditFocus} Window: {(_formulaEditFocus == FormulaEditFocus.FormulaBar ? _hwndFormulaBar : _hwndInCellEdit)}");
331-
335+
332336
IntPtr hwnd = IntPtr.Zero;
333337
bool prefixChanged = false;
334338
if (_formulaEditFocus == FormulaEditFocus.FormulaBar)
@@ -410,6 +414,9 @@ public void Dispose()
410414
Debug.Assert(Thread.CurrentThread.ManagedThreadId == 1);
411415

412416
Logger.WindowWatcher.Verbose("FormulaEdit Dispose Begin");
417+
418+
_updateEditStateAfterTimeout.Dispose();
419+
413420
_windowWatcher.FormulaBarWindowChanged -= _windowWatcher_FormulaBarWindowChanged;
414421
_windowWatcher.InCellEditWindowChanged -= _windowWatcher_InCellEditWindowChanged;
415422

@@ -419,7 +426,8 @@ public void Dispose()
419426
{
420427
tempWatcher.Dispose();
421428
}
429+
422430
Logger.WindowWatcher.Verbose("FormulaEdit Dispose End");
423431
}
424432
}
425-
}
433+
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
using System;
2+
using System.Timers;
3+
4+
namespace ExcelDna.IntelliSense.Util
5+
{
6+
/// <summary>
7+
/// Upon a signal, executes the specified action after the specified delay.
8+
/// If any other signal arrives during the waiting period, the delay interval begins anew.
9+
/// </summary>
10+
internal class RenewableDelayExecutor : IDisposable
11+
{
12+
private readonly Timer _timer;
13+
private readonly Action _action;
14+
private readonly int _debounceIntervalMilliseconds;
15+
16+
public bool IsDisposed { get; private set; }
17+
18+
public RenewableDelayExecutor(int debounceIntervalMilliseconds, Action action)
19+
{
20+
_action = action;
21+
_debounceIntervalMilliseconds = debounceIntervalMilliseconds;
22+
_timer = new Timer
23+
{
24+
AutoReset = false,
25+
Interval = _debounceIntervalMilliseconds,
26+
};
27+
28+
_timer.Elapsed += OnTimerElapsed;
29+
}
30+
31+
private void OnTimerElapsed(object sender, ElapsedEventArgs e)
32+
{
33+
if (IsDisposed)
34+
{
35+
return;
36+
}
37+
38+
_action();
39+
}
40+
41+
public void Signal()
42+
{
43+
_timer.Stop();
44+
_timer.Start();
45+
}
46+
47+
private void Dispose(bool isDisposing)
48+
{
49+
IsDisposed = true;
50+
51+
_timer.Elapsed -= OnTimerElapsed;
52+
_timer.Dispose();
53+
54+
if (isDisposing)
55+
{
56+
GC.SuppressFinalize(this);
57+
}
58+
}
59+
60+
public void Dispose() => Dispose(true);
61+
62+
~RenewableDelayExecutor()
63+
{
64+
Dispose(false);
65+
}
66+
}
67+
}

0 commit comments

Comments
 (0)