├── .gitattributes
├── .gitignore
├── README.md
├── Screenshots
├── ExcelSelection.png
├── RefEditUI.png
├── RefEditUI2.png
├── Ribbon.png
└── WholeForm.png
└── Source
├── DotNetRefEdit.sln
├── DotNetRefEdit
├── DotNetRefEdit.csproj
├── DotNetRefEdit.dna
├── DotNetRefEdit.xll
├── DotNetRefEdit64.dna
├── DotNetRefEdit64.xll
├── ExcelHelper.cs
├── ExcelSelectionTracker.cs
├── Properties
│ └── AssemblyInfo.cs
├── RefEditForm.Designer.cs
├── RefEditForm.cs
├── RefEditForm.resx
├── RefEditWindow.xaml
├── RefEditWindow.xaml.cs
├── Ribbon.cs
└── WindowsInterop.cs
└── ExcelDna.Integration.dll
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 |
4 | # Custom for Visual Studio
5 | *.cs diff=csharp
6 |
7 | # Standard to msysgit
8 | *.doc diff=astextplain
9 | *.DOC diff=astextplain
10 | *.docx diff=astextplain
11 | *.DOCX diff=astextplain
12 | *.dot diff=astextplain
13 | *.DOT diff=astextplain
14 | *.pdf diff=astextplain
15 | *.PDF diff=astextplain
16 | *.rtf diff=astextplain
17 | *.RTF diff=astextplain
18 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.suo
2 | Source/DotNetRefEdit/bin
3 | Source/DotNetRefEdit/obj
4 | Source/DotNetRefEdit/DotNetRefEdit.csproj.user
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # DotNetRefEdit
2 | Examples of RefEdit like controls for Excel add-ins using C# and ExcelDna
3 |
4 | Overview
5 | --------
6 | This project is a proof of concept. This is not a library to be reused in other solutions.
7 |
8 | The purpose is to show how to build a .Net UI using WinForm or WPF, within an Excel add-in, where the user can select a range in Excel in order to see the range address appear in the UI control.
9 |
10 | Illustration
11 | --------
12 |
13 | - Step 1: open a form and focus to a "RefEdit" control
14 |
15 | 
16 |
17 | - Step 2: select a range into Excel
18 |
19 | 
20 |
21 | - Result: the range address is populated automatically into the "RefEdit" control into the form
22 |
23 | 
24 |
25 | Inventory
26 | --------
27 | Several projects can be found on the internet which propose to implement the equivalent of the RefEdit control for .Net programs. But as far as I know, none of these projects show how to manage the window itself. Here is a list of issues I faced in the past:
28 | - If the UI runs in the Excel thread, then it will freeze when Excel is busy.
29 | - If the UI runs in the Excel thread, then in some conditions it may be impossible for the user to manually edit a control within the UI because Excel will activate and put the focus to the last selected cell. The conditions to reproduce this are quite unclear to me though.
30 | - If the UI runs in its own thread, then the user will need to click twice into Excel in order to select a range. Actually, the first click will activate the window and then Excel will discard the message. The second click is to select the range into the activated window.
31 |
32 | Links:
33 | - http://blogs.msdn.com/b/gabhan_berry/archive/2008/06/12/net-refedit-control.aspx
34 | - http://www.codeproject.com/Articles/32805/RefEdit-Emulation-for-NET
35 | - http://www.codeproject.com/Articles/34425/VS-NET-Excel-Addin-Refedit-Control
36 |
37 | Solution
38 | --------
39 | The best option, according to me, is to run the UI in its own thread. The "must-click-twice" issue described above can be resolved by hooking the WH_CALLWNDPROC messages: if the message is of type "WM_MOUSEACTIVATE" and if the handle is an Excel workbook window (in which case, the window name will be "EXCEL7") then it is possible to set the focus to that window before Excel processes the message.
40 |
41 | 1. Hook
42 | ```C#
43 | _hHookCwp = SetWindowsHookEx(HookType.WH_CALLWNDPROC, _procCwp, (IntPtr)0, excelThreadId);
44 | ```
45 |
46 | 2. Check the class name
47 | ```C#
48 | GetClassNameW(cwpStruct.hwnd, cname, cname.Capacity);
49 | if (cname.ToString() == "EXCEL7")
50 | ```
51 |
52 | 3. Set the focus
53 | ```C#
54 | SetFocus(cwpStruct.hwnd);
55 | ```
56 |
57 | Bonus: using the same hook, it is possible to notify the UI when the user clicks on the same cell as before. In fact, the UI is notified of a new selection by the Excel event "SheetSelectionChange", but this event is not triggered if the user points to the same cell again. By adding a special notification to the UI inside the hook method, it is possible to resolve that issue.
58 |
59 | Code
60 | --------
61 | The solution available along with this project proposes 4 examples, all accessible from the Excel ribbon. These examples have been tested with Excel 2010 32bit, Excel 2013 32bit and Excel 2013 64bit, running on Windows 7 64bit.
62 |
63 | 
64 |
65 | The "Excel Thread" buttons launch a WinForm and a WPF window running in the Excel main thread. The "Separate Thread" buttons launch the same UIs in their own threads.
66 |
67 | When the UI is launched, it will subscribe to the "SheetSelectionChange" event and hook the WH_CALLWNDPROC messages. For more details on how to hook Windows messages, please refer to https://msdn.microsoft.com/en-us/library/windows/desktop/ms644959%28v=vs.85%29.aspx
68 |
69 | The hook method "CwpProc" will apply a specific treatment to WM_MOUSEACTIVATE messages.
70 |
71 | 1. Verify the window name. If the name is not "EXCEL7" then skip the treatment and call next hook.
72 |
73 | https://msdn.microsoft.com/en-us/library/windows/desktop/ms633582%28v=vs.85%29.aspx
74 |
75 | 2. Set the focus to the window using SetFocus.
76 |
77 | https://msdn.microsoft.com/en-us/library/windows/desktop/ms646312%28v=vs.85%29.aspx
78 |
79 | 3. Notify the UI so it can populate the current selection's address into the "RefEdit" control.
80 |
81 | I also included some features like the "F4" shortcut to convert the address, but this is not the main purpose of this project so I will not run into the details.
82 |
83 | Demonstration
84 | --------
85 |
86 | Note: DotNetRefEdit.xll is for Excel 32bit, DotNetRefEdit64.xll is for Excel 64bit.
87 |
88 | 1. Open the add-in and set "12" in A1 inside the active worksheet. Set "1", "2", 3" and "4" respectively in B1, B2, C1 and C2.
89 |
90 | 2. Click on button "WinForm" in the "Separate Thread" section. Then click on the first text box, next to "Augend".
91 |
92 | 
93 |
94 | 3. Select A1 in the worksheet. **This is possible in one click.** Finally "[Book1]Sheet1!A1" shall appear in the text box.
95 |
96 | 4. Now focus on the second text box, next to "Addend". Then select "B1:C2" in the worksheet. Once again **this is possible in one click**. Finally, "[Book1]Sheet1!B1:C2" will appear in the text box.
97 |
98 | 5. The form will evaluate the sum automatically and "22" will appear in the box next to "Evaluation".
99 |
100 | 6. Now select the output, let's say "A5", in the destination box. Finally click on the "Insert" button and this will insert the formula into A5: "=SUM(Sheet1!A1,Sheet1!B1:C2 )"
101 |
102 | 
103 |
104 | 7. Focus to the first box again, remove the address. Now go to the worksheet, edit A1 and copy the value. Without escaping from the cell edition, return to the UI and paste the text into the text box. It works because the UI runs in its own thread so **it is not frozen when Excel is busy**.
105 |
106 | 8. Focus to the "Augend" box and select A1. This will populate "[Book1]Sheet1!A1" into that box.
107 |
108 | 9. Now focus on the "Addend" box and select A1 again. **The address will appear into the box, despite the "SheetSelectionChange" event was not raised.**
109 |
110 | Conclusion
111 | --------
112 |
113 | First, I want to thank Govert from the ExcelDna project, for all the help he provided in order to resolve these issues. Big thanks also for the ExcelDna project itself, which is amazing.
114 |
115 | https://github.com/Excel-DNA/ExcelDna
116 |
117 | The **DotNetRefEdit** project demonstrates that it is possible to create .Net add-ins for Excel where UIs can behave like function wizards and allow range selections into Excel, in an user-friendly way.
118 |
119 | The "RefEdit" control itself is not hard to implement. The main difficulty resides in the window management. The solution is quite simple once you know it: hook the WH_CALLWNDPROC messages using the SetWindowsHookEx function ; when the hooked message is a WM_MOUSEACTIVATE and the underlying window is a workbook, then call SetFocus.
120 |
--------------------------------------------------------------------------------
/Screenshots/ExcelSelection.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ron-Ldn/DotNetRefEdit/1d8d58ec830cf93683467872e93540fdd5ee1f47/Screenshots/ExcelSelection.png
--------------------------------------------------------------------------------
/Screenshots/RefEditUI.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ron-Ldn/DotNetRefEdit/1d8d58ec830cf93683467872e93540fdd5ee1f47/Screenshots/RefEditUI.png
--------------------------------------------------------------------------------
/Screenshots/RefEditUI2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ron-Ldn/DotNetRefEdit/1d8d58ec830cf93683467872e93540fdd5ee1f47/Screenshots/RefEditUI2.png
--------------------------------------------------------------------------------
/Screenshots/Ribbon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ron-Ldn/DotNetRefEdit/1d8d58ec830cf93683467872e93540fdd5ee1f47/Screenshots/Ribbon.png
--------------------------------------------------------------------------------
/Screenshots/WholeForm.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ron-Ldn/DotNetRefEdit/1d8d58ec830cf93683467872e93540fdd5ee1f47/Screenshots/WholeForm.png
--------------------------------------------------------------------------------
/Source/DotNetRefEdit.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 11.00
3 | # Visual Studio 2010
4 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotNetRefEdit", "DotNetRefEdit\DotNetRefEdit.csproj", "{FF741B2C-EC4D-47A3-8222-913A82CF2340}"
5 | EndProject
6 | Global
7 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
8 | Debug|Any CPU = Debug|Any CPU
9 | Release|Any CPU = Release|Any CPU
10 | EndGlobalSection
11 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
12 | {FF741B2C-EC4D-47A3-8222-913A82CF2340}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
13 | {FF741B2C-EC4D-47A3-8222-913A82CF2340}.Debug|Any CPU.Build.0 = Debug|Any CPU
14 | {FF741B2C-EC4D-47A3-8222-913A82CF2340}.Release|Any CPU.ActiveCfg = Release|Any CPU
15 | {FF741B2C-EC4D-47A3-8222-913A82CF2340}.Release|Any CPU.Build.0 = Release|Any CPU
16 | EndGlobalSection
17 | GlobalSection(SolutionProperties) = preSolution
18 | HideSolutionNode = FALSE
19 | EndGlobalSection
20 | EndGlobal
21 |
--------------------------------------------------------------------------------
/Source/DotNetRefEdit/DotNetRefEdit.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Debug
5 | AnyCPU
6 | 8.0.30703
7 | 2.0
8 | {FF741B2C-EC4D-47A3-8222-913A82CF2340}
9 | Library
10 | Properties
11 | DotNetRefEdit
12 | DotNetRefEdit
13 | v4.0
14 | 512
15 |
16 |
17 |
18 | true
19 | full
20 | false
21 | bin\Debug\
22 | DEBUG;TRACE
23 | prompt
24 | 4
25 |
26 |
27 | pdbonly
28 | true
29 | bin\Release\
30 | TRACE
31 | prompt
32 | 4
33 |
34 |
35 |
36 | ..\ExcelDna.Integration.dll
37 |
38 |
39 |
40 | True
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 | Form
63 |
64 |
65 | RefEditForm.cs
66 |
67 |
68 |
69 |
70 | RefEditWindow.xaml
71 |
72 |
73 |
74 |
75 |
76 |
77 | RefEditForm.cs
78 |
79 |
80 |
81 |
82 | PreserveNewest
83 |
84 |
85 | PreserveNewest
86 |
87 |
88 | PreserveNewest
89 |
90 |
91 | PreserveNewest
92 |
93 |
94 |
95 |
96 | MSBuild:Compile
97 | Designer
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
116 |
--------------------------------------------------------------------------------
/Source/DotNetRefEdit/DotNetRefEdit.dna:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/Source/DotNetRefEdit/DotNetRefEdit.xll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ron-Ldn/DotNetRefEdit/1d8d58ec830cf93683467872e93540fdd5ee1f47/Source/DotNetRefEdit/DotNetRefEdit.xll
--------------------------------------------------------------------------------
/Source/DotNetRefEdit/DotNetRefEdit64.dna:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/Source/DotNetRefEdit/DotNetRefEdit64.xll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ron-Ldn/DotNetRefEdit/1d8d58ec830cf93683467872e93540fdd5ee1f47/Source/DotNetRefEdit/DotNetRefEdit64.xll
--------------------------------------------------------------------------------
/Source/DotNetRefEdit/ExcelHelper.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Runtime.InteropServices;
3 | using Microsoft.Office.Interop.Excel;
4 |
5 | namespace DotNetRefEdit
6 | {
7 | ///
8 | /// Common functions used by the form and the WPF window
9 | ///
10 | static class ExcelHelper
11 | {
12 | ///
13 | /// Make Excel evaluate the formula.
14 | /// To be run in Excel thread.
15 | ///
16 | ///
17 | ///
18 | ///
19 | public static object EvaluateFormula(string formula, Application application)
20 | {
21 | try
22 | {
23 | object formulaResult = application.Evaluate(formula);
24 |
25 | // Check the Excel error codes
26 | if (formulaResult is int)
27 | {
28 | switch ((int)formulaResult)
29 | {
30 | case -2146826288:
31 | case -2146826281:
32 | case -2146826265:
33 | case -2146826259:
34 | case -2146826252:
35 | case -2146826246:
36 | case -2146826273:
37 | return "Could not evaluate function";
38 | }
39 | }
40 |
41 | return formulaResult;
42 | }
43 | catch
44 | {
45 | return "Could not evaluate function";
46 | }
47 | }
48 |
49 | ///
50 | /// Insert formula into Excel range.
51 | /// To be run in Excel thread.
52 | ///
53 | ///
54 | ///
55 | ///
56 | public static void InsertFormula(string formula, Application application, string destination)
57 | {
58 | Range rg = null;
59 |
60 | try
61 | {
62 | rg = application.Range[destination];
63 | rg.Formula = formula;
64 | }
65 | finally
66 | {
67 | if (rg != null)
68 | {
69 | Marshal.ReleaseComObject(rg);
70 | }
71 | }
72 | }
73 |
74 | ///
75 | /// Try to switch the address format, following this sequence:
76 | /// 1. RowAbsolute=False, ColumnAbsolute=False
77 | /// 2. RowAbsolute=True, ColumnAbsolute=True
78 | /// 3. RowAbsolute=True, ColumnAbsolute=False
79 | /// 4. RowAbsolute=False, ColumnAbsolute=True
80 | /// This shall reproduce the behaviour of the Excel "Function Arguments" form when the user hits F4.
81 | /// To be run in Excel thread.
82 | ///
83 | ///
84 | ///
85 | ///
86 | ///
87 | public static bool TryF4(string text, Application application, out string newAddress)
88 | {
89 | try
90 | {
91 | object formulaResult = application.Evaluate(text);
92 |
93 | if (formulaResult is Range)
94 | {
95 | string relativePart = text;
96 |
97 | if (text.Contains("!"))
98 | {
99 | relativePart = text.Substring(text.IndexOf("!") + 1, text.Length - text.IndexOf("!") - 1);
100 | }
101 |
102 | Range range = (Range) formulaResult;
103 |
104 | List addresses = new List
105 | {
106 | range.Address[false, false, XlReferenceStyle.xlA1, false],
107 | range.Address[true, true, XlReferenceStyle.xlA1, false],
108 | range.Address[true, false, XlReferenceStyle.xlA1, false],
109 | range.Address[false, true, XlReferenceStyle.xlA1, false]
110 | };
111 |
112 | bool found = false;
113 | for (int i = 0; i < addresses.Count; i++)
114 | {
115 | if (addresses[i] == relativePart)
116 | {
117 | relativePart = addresses[i + 1 == addresses.Count ? 0 : i + 1];
118 | found = true;
119 | break;
120 | }
121 | }
122 |
123 | if (!found)
124 | {
125 | newAddress = range.Address[false, false, XlReferenceStyle.xlA1, true];
126 | return true;
127 | }
128 |
129 | newAddress = text.Contains("!")
130 | ? string.Concat(text.Substring(0, text.IndexOf("!") + 1), relativePart)
131 | : relativePart;
132 |
133 | return true;
134 | }
135 |
136 | newAddress = null;
137 | return false;
138 | }
139 | catch
140 | {
141 | newAddress = null;
142 | return false;
143 | }
144 | }
145 | }
146 | }
147 |
--------------------------------------------------------------------------------
/Source/DotNetRefEdit/ExcelSelectionTracker.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Diagnostics;
3 | using System.Runtime.InteropServices;
4 | using System.Text;
5 | using ExcelDna.Integration;
6 | using Microsoft.Office.Interop.Excel;
7 | using Application = Microsoft.Office.Interop.Excel.Application;
8 |
9 | namespace DotNetRefEdit
10 | {
11 | public class RangeAddressEventArgs : EventArgs
12 | {
13 | public string Address { get; set; }
14 | }
15 |
16 | public class ExcelSelectionTracker
17 | {
18 | private readonly Application _application;
19 |
20 | public event EventHandler NewSelection;
21 |
22 | private readonly int _hHookCwp;
23 | private readonly WindowsInterop.HookProc _procCwp; // Note: do not make this delegate a local variable within the ExcelSelectionTracker constructor because it must not be collected by the GC before the unhook
24 |
25 | public ExcelSelectionTracker(int excelThreadId)
26 | {
27 | _application = (Application)ExcelDnaUtil.Application;
28 | _application.SheetSelectionChange += OnNewSelection;
29 |
30 | _procCwp = CwpProc;
31 |
32 | _hHookCwp = WindowsInterop.SetWindowsHookEx(HookType.WH_CALLWNDPROC, _procCwp, (IntPtr)0, excelThreadId);
33 | if (_hHookCwp == 0)
34 | {
35 | throw new Exception("Failed to hook WH_CALLWNDPROC");
36 | }
37 | }
38 |
39 | public void Stop()
40 | {
41 | _application.SheetSelectionChange -= OnNewSelection;
42 |
43 | if (!WindowsInterop.UnhookWindowsHookEx(_hHookCwp))
44 | {
45 | Debug.Print("Error: Failed to unhook WH_CALLWNDPROC");
46 | }
47 | }
48 |
49 | private int CwpProc(int nCode, IntPtr wParam, IntPtr lParam)
50 | {
51 | CwpStruct cwpStruct = (CwpStruct)Marshal.PtrToStructure(lParam, typeof(CwpStruct));
52 |
53 | if (nCode < 0)
54 | {
55 | return WindowsInterop.CallNextHookEx(_hHookCwp, nCode, wParam, lParam);
56 | }
57 |
58 | if (cwpStruct.message == WindowsInterop.WM_MOUSEACTIVATE)
59 | {
60 | // We got a WM_MOUSEACTIVATE message. Now we will check that the target handle is a workbook window.
61 | // Workbook windows have the name "EXCEL7".
62 | bool isWorkbookWindow = false;
63 |
64 | try
65 | {
66 | StringBuilder cname = new StringBuilder(256);
67 | WindowsInterop.GetClassNameW(cwpStruct.hwnd, cname, cname.Capacity);
68 | if (cname.ToString() == "EXCEL7")
69 | {
70 | isWorkbookWindow = true;
71 | }
72 | }
73 | catch (Exception e)
74 | {
75 | Debug.Print("Could not get the window name: {0}", e);
76 | }
77 |
78 | if (isWorkbookWindow)
79 | {
80 | // If the window is not activated, then Excel will activate it and then discard the message. That's why the user cannot select a range at the same time.
81 | // The following statement will activate the window before Excel treats the message, thus it will not activate the window and it will keep proceeding the message.
82 | // In that way, it is possible to select the range.
83 | try
84 | {
85 | WindowsInterop.SetFocus(cwpStruct.hwnd);
86 | }
87 | catch (Exception e)
88 | {
89 | Debug.Print("Failed to set the focus: {0}", e);
90 | }
91 |
92 | // If the user chooses a cell which was already selected, then the event SheetSelectionChange will not be raised.
93 | // A workaround is to send the current selection when the Excel window gets the focus.
94 | // Note that if the user selects a different range, then 2 events will be raised: a first one with the current selection,
95 | // and a second one with the new selection.
96 | try
97 | {
98 | OnNewSelection(null, (Range) _application.Selection);
99 | }
100 | catch
101 | {
102 | }
103 | }
104 | }
105 |
106 | return WindowsInterop.CallNextHookEx(_hHookCwp, nCode, wParam, lParam);
107 | }
108 |
109 | private void OnNewSelection(object sh, Range target)
110 | {
111 | try
112 | {
113 | var newSelection = NewSelection;
114 | if (newSelection != null)
115 | {
116 | newSelection(this, new RangeAddressEventArgs { Address = target.Address[false, false, XlReferenceStyle.xlA1, true] });
117 | }
118 | }
119 | catch
120 | {
121 | }
122 | }
123 | }
124 | }
125 |
--------------------------------------------------------------------------------
/Source/DotNetRefEdit/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.CompilerServices;
3 | using System.Runtime.InteropServices;
4 |
5 | // General Information about an assembly is controlled through the following
6 | // set of attributes. Change these attribute values to modify the information
7 | // associated with an assembly.
8 | [assembly: AssemblyTitle("DotNetRefEdit")]
9 | [assembly: AssemblyDescription("")]
10 | [assembly: AssemblyConfiguration("")]
11 | [assembly: AssemblyCompany("")]
12 | [assembly: AssemblyProduct("DotNetRefEdit")]
13 | [assembly: AssemblyCopyright("")]
14 | [assembly: AssemblyTrademark("")]
15 | [assembly: AssemblyCulture("")]
16 |
17 | // Setting ComVisible to false makes the types in this assembly not visible
18 | // to COM components. If you need to access a type in this assembly from
19 | // COM, set the ComVisible attribute to true on that type.
20 | [assembly: ComVisible(false)]
21 |
22 | // The following GUID is for the ID of the typelib if this project is exposed to COM
23 | [assembly: Guid("fe65e058-3496-4f18-b459-4d22891accbf")]
24 |
25 | // Version information for an assembly consists of the following four values:
26 | //
27 | // Major Version
28 | // Minor Version
29 | // Build Number
30 | // Revision
31 | //
32 | // You can specify all the values or you can default the Build and Revision Numbers
33 | // by using the '*' as shown below:
34 | // [assembly: AssemblyVersion("1.0.*")]
35 | [assembly: AssemblyVersion("1.0.0.0")]
36 | [assembly: AssemblyFileVersion("1.0.0.0")]
37 |
--------------------------------------------------------------------------------
/Source/DotNetRefEdit/RefEditForm.Designer.cs:
--------------------------------------------------------------------------------
1 | namespace DotNetRefEdit
2 | {
3 | partial class RefEditForm
4 | {
5 | ///
6 | /// Required designer variable.
7 | ///
8 | private System.ComponentModel.IContainer components = null;
9 |
10 | ///
11 | /// Clean up any resources being used.
12 | ///
13 | /// true if managed resources should be disposed; otherwise, false.
14 | protected override void Dispose(bool disposing)
15 | {
16 | if (disposing && (components != null))
17 | {
18 | components.Dispose();
19 | }
20 | base.Dispose(disposing);
21 | }
22 |
23 | #region Windows Form Designer generated code
24 |
25 | ///
26 | /// Required method for Designer support - do not modify
27 | /// the contents of this method with the code editor.
28 | ///
29 | private void InitializeComponent()
30 | {
31 | this.InputBox1 = new System.Windows.Forms.RichTextBox();
32 | this.label1 = new System.Windows.Forms.Label();
33 | this.InputBox2 = new System.Windows.Forms.RichTextBox();
34 | this.label2 = new System.Windows.Forms.Label();
35 | this.label3 = new System.Windows.Forms.Label();
36 | this.DestinationBox = new System.Windows.Forms.RichTextBox();
37 | this.label4 = new System.Windows.Forms.Label();
38 | this.InsertButton = new System.Windows.Forms.Button();
39 | this.label5 = new System.Windows.Forms.Label();
40 | this.EvaluationBox = new System.Windows.Forms.RichTextBox();
41 | this.SuspendLayout();
42 | //
43 | // InputBox1
44 | //
45 | this.InputBox1.Location = new System.Drawing.Point(62, 44);
46 | this.InputBox1.Multiline = false;
47 | this.InputBox1.Name = "InputBox1";
48 | this.InputBox1.Size = new System.Drawing.Size(395, 28);
49 | this.InputBox1.TabIndex = 0;
50 | this.InputBox1.Text = "";
51 | //
52 | // label1
53 | //
54 | this.label1.AutoSize = true;
55 | this.label1.Font = new System.Drawing.Font("Microsoft Sans Serif", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
56 | this.label1.Location = new System.Drawing.Point(12, 9);
57 | this.label1.Name = "label1";
58 | this.label1.Size = new System.Drawing.Size(295, 20);
59 | this.label1.TabIndex = 1;
60 | this.label1.Text = "Select input ranges and sum the values !";
61 | //
62 | // InputBox2
63 | //
64 | this.InputBox2.Location = new System.Drawing.Point(62, 78);
65 | this.InputBox2.Multiline = false;
66 | this.InputBox2.Name = "InputBox2";
67 | this.InputBox2.Size = new System.Drawing.Size(395, 28);
68 | this.InputBox2.TabIndex = 2;
69 | this.InputBox2.Text = "";
70 | //
71 | // label2
72 | //
73 | this.label2.AutoSize = true;
74 | this.label2.Location = new System.Drawing.Point(12, 44);
75 | this.label2.Name = "label2";
76 | this.label2.Size = new System.Drawing.Size(44, 13);
77 | this.label2.TabIndex = 3;
78 | this.label2.Text = "Augend";
79 | //
80 | // label3
81 | //
82 | this.label3.AutoSize = true;
83 | this.label3.Location = new System.Drawing.Point(12, 78);
84 | this.label3.Name = "label3";
85 | this.label3.Size = new System.Drawing.Size(44, 13);
86 | this.label3.TabIndex = 4;
87 | this.label3.Text = "Addend";
88 | //
89 | // DestinationBox
90 | //
91 | this.DestinationBox.Location = new System.Drawing.Point(79, 148);
92 | this.DestinationBox.Multiline = false;
93 | this.DestinationBox.Name = "DestinationBox";
94 | this.DestinationBox.Size = new System.Drawing.Size(378, 28);
95 | this.DestinationBox.TabIndex = 5;
96 | this.DestinationBox.Text = "";
97 | //
98 | // label4
99 | //
100 | this.label4.AutoSize = true;
101 | this.label4.Location = new System.Drawing.Point(13, 151);
102 | this.label4.Name = "label4";
103 | this.label4.Size = new System.Drawing.Size(60, 13);
104 | this.label4.TabIndex = 6;
105 | this.label4.Text = "Destination";
106 | //
107 | // InsertButton
108 | //
109 | this.InsertButton.BackColor = System.Drawing.SystemColors.ScrollBar;
110 | this.InsertButton.Location = new System.Drawing.Point(79, 194);
111 | this.InsertButton.Name = "InsertButton";
112 | this.InsertButton.Size = new System.Drawing.Size(219, 30);
113 | this.InsertButton.TabIndex = 7;
114 | this.InsertButton.Text = "Insert";
115 | this.InsertButton.UseVisualStyleBackColor = false;
116 | this.InsertButton.Click += new System.EventHandler(this.InsertButton_Click);
117 | //
118 | // label5
119 | //
120 | this.label5.AutoSize = true;
121 | this.label5.Location = new System.Drawing.Point(13, 240);
122 | this.label5.Name = "label5";
123 | this.label5.Size = new System.Drawing.Size(57, 13);
124 | this.label5.TabIndex = 8;
125 | this.label5.Text = "Evaluation";
126 | //
127 | // EvaluationBox
128 | //
129 | this.EvaluationBox.Location = new System.Drawing.Point(79, 237);
130 | this.EvaluationBox.Name = "EvaluationBox";
131 | this.EvaluationBox.ReadOnly = true;
132 | this.EvaluationBox.Size = new System.Drawing.Size(378, 28);
133 | this.EvaluationBox.TabIndex = 9;
134 | this.EvaluationBox.Text = "";
135 | //
136 | // RefEditForm
137 | //
138 | this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
139 | this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
140 | this.ClientSize = new System.Drawing.Size(468, 278);
141 | this.Controls.Add(this.EvaluationBox);
142 | this.Controls.Add(this.label5);
143 | this.Controls.Add(this.InsertButton);
144 | this.Controls.Add(this.label4);
145 | this.Controls.Add(this.DestinationBox);
146 | this.Controls.Add(this.label3);
147 | this.Controls.Add(this.label2);
148 | this.Controls.Add(this.InputBox2);
149 | this.Controls.Add(this.label1);
150 | this.Controls.Add(this.InputBox1);
151 | this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedToolWindow;
152 | this.MaximizeBox = false;
153 | this.MaximumSize = new System.Drawing.Size(474, 302);
154 | this.MinimizeBox = false;
155 | this.MinimumSize = new System.Drawing.Size(474, 302);
156 | this.Name = "RefEditForm";
157 | this.Text = "RefEdit";
158 | this.ResumeLayout(false);
159 | this.PerformLayout();
160 |
161 | }
162 |
163 | #endregion
164 |
165 | private System.Windows.Forms.RichTextBox InputBox1;
166 | private System.Windows.Forms.Label label1;
167 | private System.Windows.Forms.RichTextBox InputBox2;
168 | private System.Windows.Forms.Label label2;
169 | private System.Windows.Forms.Label label3;
170 | private System.Windows.Forms.RichTextBox DestinationBox;
171 | private System.Windows.Forms.Label label4;
172 | private System.Windows.Forms.Button InsertButton;
173 | private System.Windows.Forms.Label label5;
174 | private System.Windows.Forms.RichTextBox EvaluationBox;
175 | }
176 | }
--------------------------------------------------------------------------------
/Source/DotNetRefEdit/RefEditForm.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Windows.Forms;
3 | using ExcelDna.Integration;
4 | using Application = Microsoft.Office.Interop.Excel.Application;
5 |
6 | namespace DotNetRefEdit
7 | {
8 | public partial class RefEditForm : Form
9 | {
10 | private readonly ExcelSelectionTracker _selectionTracker;
11 | private readonly Application _application;
12 | private RichTextBox _focusedBox;
13 |
14 | public RefEditForm(int excelThreadId)
15 | {
16 | InitializeComponent();
17 | _selectionTracker = new ExcelSelectionTracker(excelThreadId);
18 | _application = (Application)ExcelDnaUtil.Application;
19 |
20 | Closed += delegate
21 | {
22 | _selectionTracker.NewSelection -= ChangeText;
23 | _selectionTracker.Stop();
24 | };
25 |
26 | _selectionTracker.NewSelection += ChangeText;
27 |
28 | Deactivate += CheckFocus;
29 |
30 | InputBox1.TextChanged += OnNewInput;
31 | InputBox2.TextChanged += OnNewInput;
32 |
33 | InputBox1.KeyDown += CheckF4;
34 | InputBox2.KeyDown += CheckF4;
35 | DestinationBox.KeyDown += CheckF4;
36 | }
37 |
38 | private void CheckFocus(object sender, EventArgs eventArgs)
39 | {
40 | if (InputBox1.Focused)
41 | {
42 | _focusedBox = InputBox1;
43 | }
44 | else if (InputBox2.Focused)
45 | {
46 | _focusedBox = InputBox2;
47 | }
48 | else if (DestinationBox.Focused)
49 | {
50 | _focusedBox = DestinationBox;
51 | }
52 | else
53 | {
54 | _focusedBox = null;
55 | }
56 | }
57 |
58 | ///
59 | /// Build final formula: to be run in UI thread
60 | ///
61 | ///
62 | private string BuildFormula()
63 | {
64 | return string.Format("=sum({0},{1})", InputBox1.Text, InputBox2.Text);
65 | }
66 |
67 | ///
68 | /// Evaluate the formula: to be run in Excel thread
69 | ///
70 | private void UpdateEvaluation(string formula)
71 | {
72 | object formulaResult = ExcelHelper.EvaluateFormula(formula, _application);
73 | Invoke(new Action(() => EvaluationBox.Text = (formulaResult ?? "").ToString()));
74 | }
75 |
76 | private void OnNewInput(object sender, EventArgs e)
77 | {
78 | string formula = BuildFormula();
79 | ExcelAsyncUtil.QueueAsMacro(() => UpdateEvaluation(formula));
80 | }
81 |
82 | private void ChangeText(object sender, RangeAddressEventArgs args)
83 | {
84 | Invoke(new Action(() =>
85 | {
86 | if (_focusedBox != null)
87 | {
88 | _focusedBox.Text = args.Address;
89 | _focusedBox.Select(_focusedBox.Text.Length, 0);
90 | }
91 | }));
92 | }
93 |
94 | private void CheckF4(object sender, KeyEventArgs e)
95 | {
96 | RichTextBox textBox = sender as RichTextBox;
97 | if (e.KeyCode == Keys.F4 && textBox != null)
98 | {
99 | string text = textBox.Text;
100 |
101 | ExcelAsyncUtil.QueueAsMacro(() =>
102 | {
103 | string newAddress;
104 | if (ExcelHelper.TryF4(text, _application, out newAddress))
105 | {
106 | Invoke(new Action(() =>
107 | {
108 | textBox.Text = newAddress;
109 | textBox.Select(textBox.Text.Length, 0);
110 | }));
111 | }
112 | });
113 | }
114 | }
115 |
116 | private void InsertButton_Click(object sender, EventArgs e)
117 | {
118 | string formula = BuildFormula();
119 | string destination = DestinationBox.Text;
120 |
121 | if (formula != null && !string.IsNullOrEmpty(destination))
122 | {
123 | ExcelAsyncUtil.QueueAsMacro(() => ExcelHelper.InsertFormula(formula, _application, destination));
124 | }
125 | }
126 | }
127 | }
128 |
--------------------------------------------------------------------------------
/Source/DotNetRefEdit/RefEditForm.resx:
--------------------------------------------------------------------------------
1 |
2 |
3 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 | text/microsoft-resx
110 |
111 |
112 | 2.0
113 |
114 |
115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
116 |
117 |
118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
119 |
120 |
--------------------------------------------------------------------------------
/Source/DotNetRefEdit/RefEditWindow.xaml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
--------------------------------------------------------------------------------
/Source/DotNetRefEdit/RefEditWindow.xaml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Windows;
3 | using System.Windows.Input;
4 | using ExcelDna.Integration;
5 | using Application = Microsoft.Office.Interop.Excel.Application;
6 | using TextBox = System.Windows.Controls.TextBox;
7 |
8 | namespace DotNetRefEdit
9 | {
10 | public partial class RefEditWindow
11 | {
12 | private readonly ExcelSelectionTracker _selectionTracker;
13 | private readonly Application _application;
14 | private TextBox _focusedBox;
15 |
16 | public RefEditWindow(int excelThreadId)
17 | {
18 | InitializeComponent();
19 |
20 | _selectionTracker = new ExcelSelectionTracker(excelThreadId);
21 | _application = (Application)ExcelDnaUtil.Application;
22 |
23 | Closed += delegate
24 | {
25 | _selectionTracker.NewSelection -= ChangeText;
26 | _selectionTracker.Stop();
27 | };
28 |
29 | _selectionTracker.NewSelection += ChangeText;
30 |
31 | Deactivated += CheckFocus;
32 |
33 | InputBox1.TextChanged += OnNewInput;
34 | InputBox2.TextChanged += OnNewInput;
35 |
36 | InputBox1.KeyDown += CheckF4;
37 | InputBox2.KeyDown += CheckF4;
38 | DestinationBox.KeyDown += CheckF4;
39 | }
40 |
41 | private void CheckFocus(object sender, EventArgs eventArgs)
42 | {
43 | if (InputBox1.IsFocused)
44 | {
45 | _focusedBox = InputBox1;
46 | }
47 | else if (InputBox2.IsFocused)
48 | {
49 | _focusedBox = InputBox2;
50 | }
51 | else if (DestinationBox.IsFocused)
52 | {
53 | _focusedBox = DestinationBox;
54 | }
55 | else
56 | {
57 | _focusedBox = null;
58 | }
59 | }
60 |
61 | private void ChangeText(object sender, RangeAddressEventArgs args)
62 | {
63 | Dispatcher.Invoke(new Action(() =>
64 | {
65 | if (_focusedBox != null)
66 | {
67 | _focusedBox.Text = args.Address;
68 | _focusedBox.CaretIndex = _focusedBox.Text.Length;
69 | }
70 | }));
71 | }
72 |
73 | private void CheckF4(object sender, KeyEventArgs e)
74 | {
75 | TextBox textBox = sender as TextBox;
76 | if (e.Key == Key.F4 && textBox != null)
77 | {
78 | string text = textBox.Text;
79 |
80 | ExcelAsyncUtil.QueueAsMacro(() =>
81 | {
82 | string newAddress;
83 | if (ExcelHelper.TryF4(text, _application, out newAddress))
84 | {
85 | Dispatcher.Invoke(new Action(() =>
86 | {
87 | textBox.Text = newAddress;
88 | textBox.CaretIndex = textBox.Text.Length;
89 | }));
90 | }
91 | });
92 | }
93 | }
94 |
95 | ///
96 | /// Build final formula: to be run in UI thread
97 | ///
98 | ///
99 | private string BuildFormula()
100 | {
101 | return string.Format("=sum({0},{1})", InputBox1.Text, InputBox2.Text);
102 | }
103 |
104 | ///
105 | /// Evaluate the formula: to be run in Excel thread
106 | ///
107 | private void UpdateEvaluation(string formula)
108 | {
109 | object formulaResult = ExcelHelper.EvaluateFormula(formula, _application);
110 | Dispatcher.Invoke(new Action(() => EvaluationBox.Text = (formulaResult ?? "").ToString()));
111 | }
112 |
113 | private void OnNewInput(object sender, EventArgs e)
114 | {
115 | string formula = BuildFormula();
116 | ExcelAsyncUtil.QueueAsMacro(() => UpdateEvaluation(formula));
117 | }
118 |
119 | private void InsertFormula(object sender, RoutedEventArgs e)
120 | {
121 | string formula = BuildFormula();
122 | string destination = DestinationBox.Text;
123 |
124 | if (formula != null && !string.IsNullOrEmpty(destination))
125 | {
126 | ExcelAsyncUtil.QueueAsMacro(() => ExcelHelper.InsertFormula(formula, _application, destination));
127 | }
128 | }
129 | }
130 | }
131 |
--------------------------------------------------------------------------------
/Source/DotNetRefEdit/Ribbon.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Diagnostics;
3 | using System.Runtime.InteropServices;
4 | using System.Threading;
5 | using System.Windows.Forms;
6 | using ExcelDna.Integration;
7 | using ExcelDna.Integration.CustomUI;
8 | using Application = Microsoft.Office.Interop.Excel.Application;
9 |
10 | namespace DotNetRefEdit
11 | {
12 | [ComVisible(true)]
13 | public class MyRibbon : ExcelRibbon
14 | {
15 | private readonly int _excelThreadId;
16 |
17 | private RefEditForm _refEditForm1;
18 | private RefEditWindow _refEditWindow1;
19 | private RefEditForm _refEditForm2;
20 | private RefEditWindow _refEditWindow2;
21 |
22 | public MyRibbon()
23 | {
24 | _excelThreadId = WindowsInterop.GetCurrentThreadId();
25 | }
26 |
27 | private bool CheckWorkbook()
28 | {
29 | try
30 | {
31 | Application app = (Application) ExcelDnaUtil.Application;
32 | if (app.Workbooks.Count == 0)
33 | {
34 | MessageBox.Show("Please open a workbook before starting UI.", "Error");
35 | return false;
36 | }
37 |
38 | return true;
39 | }
40 | catch (Exception e)
41 | {
42 | Debug.Print("Couldn't check workbook: {0}", e);
43 | return false;
44 | }
45 | }
46 |
47 | public void OpenWinFormInExcelThread(IRibbonControl control)
48 | {
49 | if (!CheckWorkbook())
50 | {
51 | return;
52 | }
53 |
54 | if (_refEditForm1 == null)
55 | {
56 | try
57 | {
58 | _refEditForm1 = new RefEditForm(_excelThreadId);
59 | _refEditForm1.Closed += delegate { _refEditForm1 = null; };
60 | _refEditForm1.Show();
61 | }
62 | catch (Exception e)
63 | {
64 | Debug.Print("Error: {0}", e);
65 | }
66 | }
67 | else
68 | {
69 | _refEditForm1.Activate();
70 | }
71 | }
72 |
73 | public void OpenWinFormInSeparateThread(IRibbonControl control)
74 | {
75 | if (!CheckWorkbook())
76 | {
77 | return;
78 | }
79 |
80 | if (_refEditForm2 == null)
81 | {
82 | Thread thread = new Thread(() =>
83 | {
84 | try
85 | {
86 | _refEditForm2 = new RefEditForm(_excelThreadId);
87 | _refEditForm2.Closed += delegate { _refEditForm2 = null; };
88 | _refEditForm2.ShowDialog();
89 | }
90 | catch (Exception e)
91 | {
92 | Debug.Print("Error: {0}", e);
93 | }
94 | });
95 |
96 | thread.SetApartmentState(ApartmentState.STA);
97 | thread.Start();
98 | }
99 | else
100 | {
101 | _refEditForm2.Invoke(new Action(() => _refEditForm2.Activate()));
102 | }
103 | }
104 |
105 | public void OpenWPFInExcelThread(IRibbonControl control)
106 | {
107 | if (!CheckWorkbook())
108 | {
109 | return;
110 | }
111 |
112 | if (_refEditWindow1 == null)
113 | {
114 | try
115 | {
116 | _refEditWindow1 = new RefEditWindow(_excelThreadId);
117 | _refEditWindow1.Closed += delegate { _refEditWindow1 = null; };
118 | _refEditWindow1.Show();
119 | }
120 | catch (Exception e)
121 | {
122 | Debug.Print("Error: {0}", e);
123 | }
124 | }
125 | else
126 | {
127 | _refEditWindow1.Activate();
128 | }
129 | }
130 |
131 | public void OpenWPFInSeparateThread(IRibbonControl control)
132 | {
133 | if (!CheckWorkbook())
134 | {
135 | return;
136 | }
137 |
138 | if (_refEditWindow2 == null)
139 | {
140 | Thread thread = new Thread(() =>
141 | {
142 | try
143 | {
144 | _refEditWindow2 = new RefEditWindow(_excelThreadId);
145 | _refEditWindow2.Closed += delegate { _refEditWindow2 = null; };
146 | _refEditWindow2.ShowDialog();
147 | }
148 | catch (Exception e)
149 | {
150 | Debug.Print("Error: {0}", e);
151 | }
152 | });
153 |
154 | thread.SetApartmentState(ApartmentState.STA);
155 | thread.Start();
156 | }
157 | else
158 | {
159 | _refEditWindow2.Dispatcher.Invoke(new Action(() => _refEditWindow2.Activate()));
160 | }
161 | }
162 |
163 | public override string GetCustomUI(string uiName)
164 | {
165 | return @"
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 | ";
181 | }
182 | }
183 | }
184 |
--------------------------------------------------------------------------------
/Source/DotNetRefEdit/WindowsInterop.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Runtime.InteropServices;
3 | using System.Text;
4 |
5 | namespace DotNetRefEdit
6 | {
7 | public enum HookType
8 | {
9 | WH_JOURNALRECORD = 0,
10 | WH_JOURNALPLAYBACK = 1,
11 | WH_KEYBOARD = 2,
12 | WH_GETMESSAGE = 3,
13 | WH_CALLWNDPROC = 4,
14 | WH_CBT = 5,
15 | WH_SYSMSGFILTER = 6,
16 | WH_MOUSE = 7,
17 | WH_HARDWARE = 8,
18 | WH_DEBUG = 9,
19 | WH_SHELL = 10,
20 | WH_FOREGROUNDIDLE = 11,
21 | WH_CALLWNDPROCRET = 12,
22 | WH_KEYBOARD_LL = 13,
23 | WH_MOUSE_LL = 14
24 | }
25 |
26 | [StructLayout(LayoutKind.Sequential)]
27 | public struct CwpStruct
28 | {
29 | public IntPtr lparam;
30 | public IntPtr wparam;
31 | public int message;
32 | public IntPtr hwnd;
33 | }
34 |
35 | static class WindowsInterop
36 | {
37 | public delegate int HookProc(int nCode, IntPtr wParam, IntPtr lParam);
38 |
39 | public const int WM_MOUSEACTIVATE = 0x0021;
40 |
41 | [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
42 | public static extern int SetWindowsHookEx(HookType idHook, HookProc lpfn, IntPtr hInstance, int threadId);
43 |
44 | [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
45 | public static extern bool UnhookWindowsHookEx(int idHook);
46 |
47 | [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
48 | public static extern int CallNextHookEx(int idHook, int nCode, IntPtr wParam, IntPtr lParam);
49 |
50 | [DllImport("user32.dll")]
51 | public static extern IntPtr SetFocus(IntPtr hWnd);
52 |
53 | [DllImport("kernel32.dll")]
54 | public static extern int GetCurrentThreadId();
55 |
56 | [DllImport("user32.dll")]
57 | public static extern int GetClassNameW(IntPtr hwnd, [MarshalAs(UnmanagedType.LPWStr)] StringBuilder buf, int nMaxCount);
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/Source/ExcelDna.Integration.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ron-Ldn/DotNetRefEdit/1d8d58ec830cf93683467872e93540fdd5ee1f47/Source/ExcelDna.Integration.dll
--------------------------------------------------------------------------------