55using System . Linq ;
66using System . Threading ;
77using ExcelDna . Integration ;
8+ using Microsoft . Office . Interop . Excel ;
89
910namespace ExcelDna . IntelliSense
1011{
@@ -33,14 +34,15 @@ namespace ExcelDna.IntelliSense
3334 // TODO: Consider interaction with Application.MacroOptions. (or not?)
3435
3536 // TODO: We might relax the threading rules, to say that Refresh runs on the same thread as Invalidate
36- // TODO: We might get rid of Refresh (since that runs in the Invalidate context)
37+ // TODO: We might get rid of Refresh (since that runs in the Invalidate context)
38+ // TODO: The two providers have been refactored to work very similarly - maybe be can extract out a base class...
3739 interface IIntelliSenseProvider : IDisposable
3840 {
3941 void Initialize ( ) ; // Executed in a macro context, on the main Excel thread
4042 void Refresh ( ) ; // Executed in a macro context, on the main Excel thread
4143 event EventHandler Invalidate ; // Must be raised in a macro context, on the main thread
4244
43- IEnumerable < IntelliSenseFunctionInfo > GetFunctionInfos ( ) ; // Called from a worker thread - no Excel or COM access (probably an MTA thread involved in the UI Automation)
45+ IList < IntelliSenseFunctionInfo > GetFunctionInfos ( ) ; // Called from a worker thread - no Excel or COM access (probably an MTA thread involved in the UI Automation)
4446 }
4547
4648 // Provides IntelliSense info for all Excel-DNA based .xll add-ins, using the built-in RegistrationInfo helper function.
@@ -137,53 +139,14 @@ public IEnumerable<IntelliSenseFunctionInfo> GetFunctionInfos()
137139 public event EventHandler Invalidate ;
138140
139141 public ExcelDnaIntelliSenseProvider ( )
140- {
141- var threadRefresh = new Thread ( RunRefreshWatch ) ;
142- threadRefresh . Start ( ) ;
143- }
144-
145- // DANGER: Still subject to LoaderLock problem...
146- void loaderNotification_LoadNotification ( object sender , LoaderNotification . NotificationEventArgs e )
147- {
148- Debug . Print ( $ "@>@>@>@> LoadNotification: { e . Reason } - { e . FullDllName } ") ;
149- if ( e . FullDllName . EndsWith ( ".xll" , StringComparison . OrdinalIgnoreCase ) )
150- _syncContextExcel . Post ( ProcessLoadNotification , e ) ;
151- }
152-
153- // Runs on the main thread, in a macro context
154- void ProcessLoadNotification ( object state )
155- {
156- Debug . Assert ( Thread . CurrentThread . ManagedThreadId == 1 ) ;
157- // we might want to introduce a delay here, so that the .xll can complete loading...
158- var notification = ( LoaderNotification . NotificationEventArgs ) state ; ;
159- var xllPath = notification . FullDllName ;
160- lock ( _xllRegistrationInfos )
161- {
162- XllRegistrationInfo regInfo ;
163- if ( ! _xllRegistrationInfos . TryGetValue ( xllPath , out regInfo ) )
164- {
165- if ( notification . Reason == LoaderNotification . Reason . Loaded )
166- {
167- regInfo = new XllRegistrationInfo ( xllPath ) ;
168- _xllRegistrationInfos [ xllPath ] = regInfo ;
169- regInfo . Refresh ( ) ;
170- OnInvalidate ( ) ;
171- }
172- }
173- else if ( notification . Reason == LoaderNotification . Reason . Unloaded )
174- {
175- _xllRegistrationInfos . Remove ( xllPath ) ;
176- }
177- }
178- }
179-
180- void RunRefreshWatch ( )
181142 {
182143 _loaderNotification = new LoaderNotification ( ) ;
183144 _loaderNotification . LoadNotification += loaderNotification_LoadNotification ;
184145 _syncContextExcel = new ExcelSynchronizationContext ( ) ;
185146 }
186147
148+ #region IIntelliSenseProvider implementation
149+
187150 // Must be called on the main Excel thread
188151 public void Initialize ( )
189152 {
@@ -193,10 +156,9 @@ public void Initialize()
193156 {
194157 foreach ( var xllPath in GetLoadedXllPaths ( ) )
195158 {
196- XllRegistrationInfo regInfo ;
197- if ( ! _xllRegistrationInfos . TryGetValue ( xllPath , out regInfo ) )
159+ if ( ! _xllRegistrationInfos . ContainsKey ( xllPath ) )
198160 {
199- regInfo = new XllRegistrationInfo ( xllPath ) ;
161+ XllRegistrationInfo regInfo = new XllRegistrationInfo ( xllPath ) ;
200162 _xllRegistrationInfos [ xllPath ] = regInfo ;
201163 regInfo . Refresh ( ) ;
202164 }
@@ -219,14 +181,53 @@ public void Refresh()
219181 }
220182
221183 // May be called from any thread
222- public IEnumerable < IntelliSenseFunctionInfo > GetFunctionInfos ( )
184+ public IList < IntelliSenseFunctionInfo > GetFunctionInfos ( )
223185 {
224186 lock ( _xllRegistrationInfos )
225187 {
226188 return _xllRegistrationInfos . Values . SelectMany ( ri => ri . GetFunctionInfos ( ) ) . ToList ( ) ;
227189 }
228190 }
229191
192+ #endregion
193+
194+ // DANGER: Still subject to LoaderLock problem...
195+ // TODO: Consider Load/Unload done when AddIns is enumerated...
196+ void loaderNotification_LoadNotification ( object sender , LoaderNotification . NotificationEventArgs e )
197+ {
198+ Debug . Print ( $ "@>@>@>@> LoadNotification: { e . Reason } - { e . FullDllName } ") ;
199+ if ( e . FullDllName . EndsWith ( ".xll" , StringComparison . OrdinalIgnoreCase ) )
200+ _syncContextExcel . Post ( ProcessLoadNotification , e ) ;
201+ }
202+
203+ // Runs on the main thread, in a macro context
204+ void ProcessLoadNotification ( object state )
205+ {
206+ Debug . Assert ( Thread . CurrentThread . ManagedThreadId == 1 ) ;
207+ // we might want to introduce a delay here, so that the .xll can complete loading...
208+ var notification = ( LoaderNotification . NotificationEventArgs ) state ; ;
209+ var xllPath = notification . FullDllName ;
210+ lock ( _xllRegistrationInfos )
211+ {
212+ XllRegistrationInfo regInfo ;
213+ if ( ! _xllRegistrationInfos . TryGetValue ( xllPath , out regInfo ) )
214+ {
215+ if ( notification . Reason == LoaderNotification . Reason . Loaded )
216+ {
217+ regInfo = new XllRegistrationInfo ( xllPath ) ;
218+ _xllRegistrationInfos [ xllPath ] = regInfo ;
219+ //regInfo.Refresh(); // Rather not.... so that we don't even try during the AddIns enumeration... OnInvalidate will lead to Refresh()
220+ OnInvalidate ( ) ;
221+ }
222+ }
223+ else if ( notification . Reason == LoaderNotification . Reason . Unloaded )
224+ {
225+ _xllRegistrationInfos . Remove ( xllPath ) ;
226+ // OnInvalidate();
227+ }
228+ }
229+ }
230+
230231 // Called in macro context
231232 // Might be implemented by COM AddIns check, or checking loaded Modules with Win32
232233 // Application.AddIns2 also lists add-ins interactively loaded (Excel 2010+) and has IsOpen property.
@@ -278,7 +279,7 @@ public WorkbookRegistrationInfo(string name)
278279 {
279280 _name = name ;
280281 }
281-
282+
282283 // Called in a macro context
283284 public void Refresh ( )
284285 {
@@ -349,38 +350,97 @@ public IEnumerable<IntelliSenseFunctionInfo> GetFunctionInfos()
349350 Dictionary < string , WorkbookRegistrationInfo > _workbookRegistrationInfos = new Dictionary < string , WorkbookRegistrationInfo > ( ) ;
350351
351352 public event EventHandler Invalidate ;
352-
353+
354+ #region IIntelliSenseProvider implementation
355+
356+ public WorkbookIntelliSenseProvider ( )
357+ {
358+ }
359+
353360 public void Initialize ( )
354361 {
355362 Logger . Provider . Info ( "WorkbookIntelliSenseProvider.Initialize" ) ;
356363
364+ // The events are just to keep track of the set of open workbooks,
365+ var xlApp = ( Application ) ExcelDnaUtil . Application ;
366+ xlApp . WorkbookOpen += Excel_WorkbookOpen ;
367+ xlApp . WorkbookBeforeClose += Excel_WorkbookBeforeClose ;
368+ //xlApp.WorkbookAddinInstall += Excel_WorkbookAddinInstall;
369+ //xlApp.WorkbookAddinUninstall += Excel_WorkbookAddinUninstall;
370+
357371 //var app = ExcelDnaUtil.Application;
358372 // app.WorkbookLoaded...
359- foreach ( var name in GetLoadedWorkbookNames ( ) )
373+ lock ( _workbookRegistrationInfos )
360374 {
361- WorkbookRegistrationInfo regInfo ;
362- if ( ! _workbookRegistrationInfos . TryGetValue ( name , out regInfo ) )
375+ foreach ( var name in GetLoadedWorkbookNames ( ) )
363376 {
364- regInfo = new WorkbookRegistrationInfo ( name ) ;
365- _workbookRegistrationInfos [ name ] = regInfo ;
377+ if ( ! _workbookRegistrationInfos . ContainsKey ( name ) )
378+ {
379+ WorkbookRegistrationInfo regInfo = new WorkbookRegistrationInfo ( name ) ;
380+ _workbookRegistrationInfos [ name ] = regInfo ;
381+ regInfo . Refresh ( ) ;
382+ }
366383 }
367- regInfo . Refresh ( ) ;
368384 }
369385 }
370386
387+ // Runs on the main thread
371388 public void Refresh ( )
372389 {
373390 Logger . Provider . Info ( "WorkbookIntelliSenseProvider.Refresh" ) ;
391+ lock ( _workbookRegistrationInfos )
392+ {
393+ foreach ( var regInfo in _workbookRegistrationInfos . Values )
394+ {
395+ regInfo . Refresh ( ) ;
396+ }
397+ }
374398 }
375399
376- public IEnumerable < IntelliSenseFunctionInfo > GetFunctionInfos ( )
400+ // May be called from any thread
401+ public IList < IntelliSenseFunctionInfo > GetFunctionInfos ( )
377402 {
378- return _workbookRegistrationInfos . Values . SelectMany ( ri => ri . GetFunctionInfos ( ) ) ;
403+ lock ( _workbookRegistrationInfos )
404+ {
405+ return _workbookRegistrationInfos . Values . SelectMany ( ri => ri . GetFunctionInfos ( ) ) . ToList ( ) ;
406+ }
379407 }
380408
409+ #endregion
410+
411+ void Excel_WorkbookOpen ( Workbook Wb )
412+ {
413+ var regInfo = new WorkbookRegistrationInfo ( Wb . Name ) ;
414+ lock ( _workbookRegistrationInfos )
415+ {
416+ _workbookRegistrationInfos [ Wb . Name ] = regInfo ;
417+ OnInvalidate ( ) ;
418+ }
419+ }
420+
421+ void Excel_WorkbookBeforeClose ( Workbook Wb , ref bool Cancel )
422+ {
423+ // Do we have to worry about renaming / Save As?
424+ lock ( _workbookRegistrationInfos )
425+ {
426+ _workbookRegistrationInfos . Remove ( Wb . Name ) ;
427+ }
428+ }
429+
430+ //private void Excel_WorkbookAddinInstall(Workbook Wb)
431+ //{
432+ // throw new NotImplementedException();
433+ //}
434+
435+ //private void Excel_WorkbookAddinUninstall(Workbook Wb)
436+ //{
437+ // throw new NotImplementedException();
438+ //}
439+
381440 // Called in macro context
382441 // Might be implemented by tracking Application events
383442 // Remember this changes when a workbook is saved, and can refer to the wrong workbook as they are closed / opened
443+ // CONSIDER: Check AddIns2 ?
384444 IEnumerable < string > GetLoadedWorkbookNames ( )
385445 {
386446 // TODO: Implement properly...
@@ -391,6 +451,11 @@ IEnumerable<string> GetLoadedWorkbookNames()
391451 }
392452 }
393453
454+ void OnInvalidate ( )
455+ {
456+ Invalidate ? . Invoke ( this , EventArgs . Empty ) ;
457+ }
458+
394459 public void Dispose ( )
395460 {
396461 }
0 commit comments