Skip to content

Commit 17df725

Browse files
committed
Add Worksheet provider
Start work on improved Watchers
1 parent 2e7820e commit 17df725

File tree

6 files changed

+222
-72
lines changed

6 files changed

+222
-72
lines changed

Source/ExcelDna.IntelliSense/FormattedText.cs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
1-
using System;
2-
using System.Collections;
1+
using System.Collections;
32
using System.Collections.Generic;
43
using System.Drawing;
5-
using System.Linq;
6-
using System.Text;
74

85
namespace ExcelDna.IntelliSense
96
{

Source/ExcelDna.IntelliSense/IntelliSenseDisplay.cs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ public class ArgumentInfo
2020
public string FunctionName;
2121
public string Description;
2222
public List<ArgumentInfo> ArgumentList;
23-
public string XllPath;
23+
public string SourcePath; // XllPath for .xll, Workbook Name for Workbook
2424
}
2525

2626
// CONSIDER: Revisit UI Automation Threading: http://msdn.microsoft.com/en-us/library/windows/desktop/ee671692(v=vs.85).aspx
@@ -39,6 +39,7 @@ class IntelliSenseDisplay : MarshalByRefObject, IDisposable
3939
WindowWatcher _windowWatcher;
4040
FormulaEditWatcher _formulaEditWatcher;
4141
PopupListWatcher _popupListWatcher;
42+
SelectDataSourceWatcher _selectDataSourceWatcher;
4243

4344
// Need to make these late ...?
4445
ToolTipForm _descriptionToolTip;
@@ -62,6 +63,7 @@ public IntelliSenseDisplay()
6263
public void SetXllOwner(string xllPath)
6364
{
6465
_threadAuto = new Thread(() => RunUIAutomation(xllPath));
66+
_threadAuto.SetApartmentState(ApartmentState.MTA);
6567
_threadAuto.Start();
6668
}
6769

@@ -80,6 +82,7 @@ void RunUIAutomation(string xllPath)
8082
_windowWatcher = new WindowWatcher(xllPath);
8183
_formulaEditWatcher = new FormulaEditWatcher(_windowWatcher, _syncContextAuto);
8284
_popupListWatcher = new PopupListWatcher(_windowWatcher, _syncContextAuto);
85+
_selectDataSourceWatcher = new SelectDataSourceWatcher(_windowWatcher, _syncContextAuto);
8386

8487
_windowWatcher.MainWindowChanged += OnMainWindowChanged;
8588
_popupListWatcher.SelectedItemChanged += OnSelectedItemChanged;
@@ -318,7 +321,7 @@ public void AddReference(string xllName)
318321
public void RemoveReference(string xllName)
319322
{
320323
List<string> functionsToRemove =
321-
_functionInfoMap.Where(p => p.Value.XllPath == xllName).Select(p => p.Key).ToList();
324+
_functionInfoMap.Where(p => p.Value.SourcePath == xllName).Select(p => p.Key).ToList();
322325

323326
foreach (string func in functionsToRemove)
324327
{

Source/ExcelDna.IntelliSense/IntelliSenseProvider.cs

Lines changed: 126 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,21 @@
33
using System.Diagnostics;
44
using System.IO;
55
using System.Linq;
6-
using System.Text;
76
using ExcelDna.Integration;
87

98
namespace ExcelDna.IntelliSense
109
{
11-
// An IntelliSenseProvider is a part of an IntelliSenseServer that provides IntelliSense info to the server.
12-
// The providers are built in to the ExcelDna.IntelliSense assembly - there are complications in making this a part that can be extended
10+
// An IntelliSenseProvider is the part of an IntelliSenseServer that provides the IntelliSense info gathered from add-ins to the server.
11+
// These providers are built in to the ExcelDna.IntelliSense assembly - there are complications in making this a part that can be extended
1312
// by a specific add-in (server activation, cross AppDomain loading etc.).
13+
//
1414
// Higher versions of the ExcelDna.IntelliSenseServer are expected to increase the number of providers
1515
// and/or the scope of some provider (e.g. add support for enums).
16+
// No provision is made at the moment for user-created providers or an external provider API.
1617

1718
// The server, upon activation and at other times (when? when ExcelDna.IntelliSense.Refresh is called ?) will call the provider to get the IntelliSense info.
18-
// Maybe the provider can also raise an Invalidate event, to prod the server into reloading the IntelliSense info for that provider.
19+
// Maybe the provider can also raise an Invalidate event, to prod the server into reloading the IntelliSense info for that provider
20+
// (a bit like the ribbon Invalidate works).
1921
// E.g. the XmlProvider might read from a file, and put a FileWatcher on the file so that whenever the file changes,
2022
// the server calls back to get the updated info.
2123

@@ -116,7 +118,7 @@ public IEnumerable<IntelliSenseFunctionInfo> GetFunctionInfos()
116118
FunctionName = functionName,
117119
Description = description,
118120
ArgumentList = argumentInfos,
119-
XllPath = _xllPath
121+
SourcePath = _xllPath
120122
};
121123
}
122124
}
@@ -163,15 +165,130 @@ IEnumerable<string> GetLoadedXllPaths()
163165
}
164166
}
165167

166-
// VBA might be easy, since the functions are scoped to the Workbook or add-in, and so might have the right name?
167-
// Excel4v(xlcRun, &xlRes, 1, TempStr(" myVBAAddin.xla!myFunc"));
168+
// For VBA code, (either in a regular workbook that is open, or in an add-in)
169+
// we allow the IntelliSense info to be put into a worksheet (possibly hidden or very hidden)
170+
// In the Workbook that contains the VBA.
171+
// Initially we won't scope the IntelliSense to the Workbook where the UDFs are defined,
172+
// but we should consider that.
168173

169-
class VbaIntelliSenseProvider
174+
class WorkbookIntelliSenseProvider : IIntelliSenseProvider
170175
{
176+
const string intelliSenseWorksheetName = "_IntelliSense_FunctionInfo_";
177+
class WorkbookRegistrationInfo
178+
{
179+
readonly string _name;
180+
DateTime _lastUpdate; // Version indicator to enumerate from scratch
181+
object[,] _regInfo = null; // Default value
182+
183+
public WorkbookRegistrationInfo(string name)
184+
{
185+
_name = name;
186+
}
187+
188+
// Called in a macro context
189+
public void Refresh()
190+
{
191+
dynamic app = ExcelDnaUtil.Application;
192+
var wb = app.Workbooks[_name];
193+
194+
try
195+
{
196+
var ws = wb.Sheets[intelliSenseWorksheetName];
197+
var info = ws.UsedRange.Value;
198+
_regInfo = info as object[,];
199+
}
200+
catch (Exception ex)
201+
{
202+
// We expect this if there is no sheet.
203+
// Another approach would be xlSheetNm
204+
Debug.Print("WorkbookIntelliSenseProvider.Refresh Error : " + ex.Message);
205+
_regInfo = null;
206+
}
207+
_lastUpdate = DateTime.Now;
208+
}
209+
210+
// Not in macro context - don't call Excel, could be any thread.
211+
public IEnumerable<IntelliSenseFunctionInfo> GetFunctionInfos()
212+
{
213+
// to avoid worries about locking and this being updated from another thread, we take a copy of _regInfo
214+
var regInfo = _regInfo;
215+
/*
216+
result[0, 0] = XlAddIn.PathXll;
217+
result[0, 1] = registrationInfoVersion;
218+
*/
219+
220+
if (regInfo == null)
221+
yield break;
222+
223+
// regInfo is 1-based: object[1..x, 1..y].
224+
for (int i = 1; i <= regInfo.GetLength(0); i++)
225+
{
226+
string functionName = regInfo[i, 1] as string;
227+
string description = regInfo[i, 2] as string;
228+
229+
List<IntelliSenseFunctionInfo.ArgumentInfo> argumentInfos = new List<IntelliSenseFunctionInfo.ArgumentInfo>();
230+
for (int j = 3; j <= regInfo.GetLength(1) - 1; j += 2)
231+
{
232+
var arg = regInfo[i, j] as string;
233+
var argDesc = regInfo[i, j + 1] as string;
234+
if (!string.IsNullOrEmpty(arg))
235+
{
236+
argumentInfos.Add(new IntelliSenseFunctionInfo.ArgumentInfo
237+
{
238+
ArgumentName = arg,
239+
Description = argDesc
240+
});
241+
}
242+
}
243+
244+
yield return new IntelliSenseFunctionInfo
245+
{
246+
FunctionName = functionName,
247+
Description = description,
248+
ArgumentList = argumentInfos,
249+
SourcePath = _name
250+
};
251+
}
252+
}
253+
}
254+
255+
Dictionary<string, WorkbookRegistrationInfo> _workbookRegistrationInfos = new Dictionary<string, WorkbookRegistrationInfo>();
256+
257+
public void Refresh()
258+
{
259+
foreach (var name in GetLoadedWorkbookNames())
260+
{
261+
WorkbookRegistrationInfo regInfo;
262+
if (!_workbookRegistrationInfos.TryGetValue(name, out regInfo))
263+
{
264+
regInfo = new WorkbookRegistrationInfo(name);
265+
_workbookRegistrationInfos[name] = regInfo;
266+
}
267+
regInfo.Refresh();
268+
}
269+
}
270+
271+
public IEnumerable<IntelliSenseFunctionInfo> GetFunctionInfos()
272+
{
273+
return _workbookRegistrationInfos.Values.SelectMany(ri => ri.GetFunctionInfos());
274+
}
275+
276+
// Called in macro context
277+
// Might be implemented by tracking Application events
278+
// Remember this changes when a workbook is saved, and can refer to the wrong workbook as they are closed / opened
279+
IEnumerable<string> GetLoadedWorkbookNames()
280+
{
281+
// TODO: Implement properly...
282+
dynamic app = ExcelDnaUtil.Application;
283+
foreach (var wb in app.Workbooks)
284+
{
285+
yield return wb.Name;
286+
}
287+
}
171288
}
172289

173290
// The idea is that other add-in tools like XLW or XLL+ could provide IntelliSense info with an xml file.
174-
// CONSIDER: How to find these files - can't jsut be relative to the IntelliSense add-in.
291+
// CONSIDER: How to find these files - can't just be relative to the IntelliSense add-in.
175292
// (Maybe next to the foreign .xll file (.xllinfo)?)
176293
class XmlIntelliSenseProvider
177294
{

Source/ExcelDna.IntelliSense/IntelliSenseServer.cs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,6 @@
33
using System.Diagnostics;
44
using System.Linq;
55
using System.Reflection;
6-
using System.Runtime.Remoting;
7-
using System.Text;
86
using System.Text.RegularExpressions;
97
using Microsoft.Win32;
108
using ExcelDna.Integration;
@@ -16,12 +14,13 @@ namespace ExcelDna.IntelliSense
1614
// Among different add-ins that are loaded into an Excel process, at most one IntelliSenseServer can be Active.
1715
// This should always be the IntelliSenseServer with the greatest version number among those registered.
1816
// At the moment the bookkeeping for registration and activation in the process is done with environment variables.
17+
// (An attractive alternative is the hidden Excel name space: http://www.cpearson.com/excel/hidden.htm )
1918
// This prevents cross-AppDomain calls, which are problematic because assemblies are then loaded into multiple AppDomains, and
2019
// since the mechanism is intended to cater for different assembly versions, this would be a problem. Also, we don't control
2120
// the CLR hosting configuration, so can't always set the MultiDomain flag on setup. COM mechanisms could work, but are complicated.
2221
// Another approach would be to use a hidden Excel function that the Active server provides, and have all server register with the active server.
2322
// When a new server should become active, it then tells the active server, and somehow gets all the other registrations...
24-
23+
2524
// Registered Servers also register a macro with Excel through which control calls are to be made.
2625
// This is against a unique GUID-based name for every registered server, so that the Activate call can be made on an inactive server.
2726
// (To be called in a macro context only, e.g. from AutoOpen.)

Source/ExcelDna.IntelliSense/IntellisenseHelper.cs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
using System;
2-
using System.Collections.Generic;
32
using ExcelDna.Integration;
43

54
namespace ExcelDna.IntelliSense
65
{
76
// TODO: This is to be replaced by the Provider / Server info retrieval mechanism
7+
// First version might run on a timer for updates.
88
public class IntelliSenseHelper : IDisposable
99
{
1010
private readonly IntelliSenseDisplay _id;
1111
private readonly IIntelliSenseProvider _excelDnaProvider = new ExcelDnaIntelliSenseProvider();
12+
private readonly IIntelliSenseProvider _workbookProvider = new WorkbookIntelliSenseProvider();
1213
// TODO: Others
1314

1415
public IntelliSenseHelper()
@@ -25,6 +26,12 @@ void RegisterIntellisense()
2526
{
2627
_id.RegisterFunctionInfo(fi);
2728
}
29+
30+
_workbookProvider.Refresh();
31+
foreach (var fi in _workbookProvider.GetFunctionInfos())
32+
{
33+
_id.RegisterFunctionInfo(fi);
34+
}
2835
}
2936

3037
public void Dispose()

0 commit comments

Comments
 (0)