Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ensure call to LPenHelper is made from the main thread and add inactivity timeout of 100ms before calling it #98

Merged
merged 1 commit into from
Oct 21, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Source/ExcelDna.IntelliSense/ExcelDna.IntelliSense.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@
<Compile Include="UIMonitor\UIState.cs" />
<Compile Include="UIMonitor\WindowLocationWatcher.cs" />
<Compile Include="UIMonitor\WindowWatcher.cs" />
<Compile Include="Util\RenewableDelayExecutor.cs" />
<Compile Include="Win32Helper.cs" />
<Compile Include="Providers\LoaderNotification.cs" />
<Compile Include="UIMonitor\WinEvents.cs" />
Expand Down
64 changes: 36 additions & 28 deletions Source/ExcelDna.IntelliSense/UIMonitor/FormulaEditWatcher.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using ExcelDna.IntelliSense.Util;
using System;
using System.Diagnostics;
using System.Threading;
using System.Windows;
Expand Down Expand Up @@ -71,11 +72,15 @@ public IntPtr FormulaEditWindow
readonly SynchronizationContext _syncContextMain;

readonly WindowWatcher _windowWatcher; // Passed in
WindowLocationWatcher _windowLocationWatcher; // Managed here

IntPtr _hwndFormulaBar;
IntPtr _hwndInCellEdit;
FormulaEditFocus _formulaEditFocus;
WindowLocationWatcher _windowLocationWatcher; // Managed here
readonly RenewableDelayExecutor _updateEditStateAfterTimeout;

IntPtr _hwndFormulaBar;
IntPtr _hwndInCellEdit;
FormulaEditFocus _formulaEditFocus;

const int DelayBeforeStateUpdateMilliseconds = 100;

public FormulaEditWatcher(WindowWatcher windowWatcher, SynchronizationContext syncContextAuto, SynchronizationContext syncContextMain)
{
Expand All @@ -84,6 +89,7 @@ public FormulaEditWatcher(WindowWatcher windowWatcher, SynchronizationContext sy
_windowWatcher = windowWatcher;
_windowWatcher.FormulaBarWindowChanged += _windowWatcher_FormulaBarWindowChanged;
_windowWatcher.InCellEditWindowChanged += _windowWatcher_InCellEditWindowChanged;
_updateEditStateAfterTimeout = new RenewableDelayExecutor(DelayBeforeStateUpdateMilliseconds, () => UpdateEditState());
}

// Runs on the Automation thread
Expand All @@ -95,13 +101,13 @@ void _windowWatcher_FormulaBarWindowChanged(object sender, WindowWatcher.WindowC
if (e.ObjectId == WindowWatcher.WindowChangedEventArgs.ChangeObjectId.Self)
{
SetEditWindow(e.WindowHandle, ref _hwndFormulaBar);
UpdateEditState();
_updateEditStateAfterTimeout.Signal();
}
else if (e.ObjectId == WindowWatcher.WindowChangedEventArgs.ChangeObjectId.Caret)
{
// We expect this on every text change
// NOTE: Not anymore after some Excel / Windows update
UpdateEditStateDelayed();
_updateEditStateAfterTimeout.Signal();
}
else
{
Expand All @@ -126,29 +132,29 @@ void _windowWatcher_FormulaBarWindowChanged(object sender, WindowWatcher.WindowC
SetEditWindow(e.WindowHandle, ref _hwndFormulaBar);
}
_formulaEditFocus = FormulaEditFocus.FormulaBar;
UpdateEditState();
_updateEditStateAfterTimeout.Signal();
}
break;
case WindowWatcher.WindowChangedEventArgs.ChangeType.Unfocus:
if (_formulaEditFocus == FormulaEditFocus.FormulaBar)
{
Logger.WindowWatcher.Verbose($"FormulaEdit - FormulaBar Unfocus");
_formulaEditFocus = FormulaEditFocus.None;
UpdateEditState();
_updateEditStateAfterTimeout.Signal();
}
break;
case WindowWatcher.WindowChangedEventArgs.ChangeType.Show:
Logger.WindowWatcher.Verbose($"FormulaEdit - FormulaBar Show");
Logger.WindowWatcher.Verbose($"FormulaEdit - FormulaBar Show");
break;
case WindowWatcher.WindowChangedEventArgs.ChangeType.Hide:
Logger.WindowWatcher.Verbose($"FormulaEdit - FormulaBar Hide");
Logger.WindowWatcher.Verbose($"FormulaEdit - FormulaBar Hide");
break;
case WindowWatcher.WindowChangedEventArgs.ChangeType.LocationChange:
if (e.ObjectId == WindowWatcher.WindowChangedEventArgs.ChangeObjectId.Caret)
{
// We expect this on every text change in newer Excel versions
Debug.Print($"-#-#-#- Text Changed ... ");
UpdateEditStateDelayed();
_updateEditStateAfterTimeout.Signal();
}
else
{
Expand All @@ -172,14 +178,14 @@ void _windowWatcher_InCellEditWindowChanged(object sender, WindowWatcher.WindowC
if (e.ObjectId == WindowWatcher.WindowChangedEventArgs.ChangeObjectId.Self)
{
SetEditWindow(e.WindowHandle, ref _hwndInCellEdit);
UpdateEditState();
_updateEditStateAfterTimeout.Signal();
}
else if (e.ObjectId == WindowWatcher.WindowChangedEventArgs.ChangeObjectId.Caret)
{
// We expect this on every text change
// NOTE: Not anymore after some Excel / Windows update
Debug.Print($"-#-#-#- Text Changed ... ");
UpdateEditStateDelayed();
_updateEditStateAfterTimeout.Signal();
}
else
{
Expand All @@ -206,15 +212,15 @@ void _windowWatcher_InCellEditWindowChanged(object sender, WindowWatcher.WindowC
Logger.WindowWatcher.Verbose($"FormulaEdit - InCellEdit Focus");

_formulaEditFocus = FormulaEditFocus.InCellEdit;
UpdateEditState();
_updateEditStateAfterTimeout.Signal();
}
break;
case WindowWatcher.WindowChangedEventArgs.ChangeType.Unfocus:
if (_formulaEditFocus == FormulaEditFocus.InCellEdit)
{
Logger.WindowWatcher.Verbose($"FormulaEdit - InCellEdit Unfocus");
_formulaEditFocus = FormulaEditFocus.None;
UpdateEditState();
_updateEditStateAfterTimeout.Signal();
}
break;
case WindowWatcher.WindowChangedEventArgs.ChangeType.Show:
Expand All @@ -228,7 +234,7 @@ void _windowWatcher_InCellEditWindowChanged(object sender, WindowWatcher.WindowC
{
// We expect this on every text change in newer Excel versions
Debug.Print($"-#-#-#- Text Changed ... ");
UpdateEditStateDelayed();
_updateEditStateAfterTimeout.Signal();
}
else
{
Expand Down Expand Up @@ -313,22 +319,20 @@ void _windowLocationWatcher_LocationChanged(object sender, EventArgs e)
// UpdateFormula(textChangedOnly: true);
//}

// 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
void UpdateEditStateDelayed()
void UpdateEditState(bool moveOnly = false)
{
_syncContextAuto.Post(_ =>
{
Thread.Sleep(100);
UpdateEditState();
}, null);
// Switches to our Main UI thread, updates current state and raises StateChanged event
_syncContextMain.Post(_ =>
{
UpdateEditStateImpl(moveOnly);
}, null);
}

// Switches to our Automation thread, updates current state and raises StateChanged event
void UpdateEditState(bool moveOnly = false)
void UpdateEditStateImpl(bool moveOnly = false)
{
Logger.WindowWatcher.Verbose($"> FormulaEdit UpdateEditState - Thread {Thread.CurrentThread.ManagedThreadId}");
Logger.WindowWatcher.Verbose($"FormulaEdit UpdateEditState - Focus: {_formulaEditFocus} Window: {(_formulaEditFocus == FormulaEditFocus.FormulaBar ? _hwndFormulaBar : _hwndInCellEdit)}");

IntPtr hwnd = IntPtr.Zero;
bool prefixChanged = false;
if (_formulaEditFocus == FormulaEditFocus.FormulaBar)
Expand Down Expand Up @@ -410,6 +414,9 @@ public void Dispose()
Debug.Assert(Thread.CurrentThread.ManagedThreadId == 1);

Logger.WindowWatcher.Verbose("FormulaEdit Dispose Begin");

_updateEditStateAfterTimeout.Dispose();

_windowWatcher.FormulaBarWindowChanged -= _windowWatcher_FormulaBarWindowChanged;
_windowWatcher.InCellEditWindowChanged -= _windowWatcher_InCellEditWindowChanged;

Expand All @@ -419,7 +426,8 @@ public void Dispose()
{
tempWatcher.Dispose();
}

Logger.WindowWatcher.Verbose("FormulaEdit Dispose End");
}
}
}
}
67 changes: 67 additions & 0 deletions Source/ExcelDna.IntelliSense/Util/RenewableDelayExecutor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
using System;
using System.Timers;

namespace ExcelDna.IntelliSense.Util
{
/// <summary>
/// Upon a signal, executes the specified action after the specified delay.
/// If any other signal arrives during the waiting period, the delay interval begins anew.
/// </summary>
internal class RenewableDelayExecutor : IDisposable
{
private readonly Timer _timer;
private readonly Action _action;
private readonly int _debounceIntervalMilliseconds;

public bool IsDisposed { get; private set; }

public RenewableDelayExecutor(int debounceIntervalMilliseconds, Action action)
{
_action = action;
_debounceIntervalMilliseconds = debounceIntervalMilliseconds;
_timer = new Timer
{
AutoReset = false,
Interval = _debounceIntervalMilliseconds,
};

_timer.Elapsed += OnTimerElapsed;
}

private void OnTimerElapsed(object sender, ElapsedEventArgs e)
{
if (IsDisposed)
{
return;
}

_action();
}

public void Signal()
{
_timer.Stop();
_timer.Start();
}

private void Dispose(bool isDisposing)
{
IsDisposed = true;

_timer.Elapsed -= OnTimerElapsed;
_timer.Dispose();

if (isDisposing)
{
GC.SuppressFinalize(this);
}
}

public void Dispose() => Dispose(true);

~RenewableDelayExecutor()
{
Dispose(false);
}
}
}