Skip to content

Commit 73eb2a2

Browse files
committed
initial commit; barebones add-in and app implementation.
1 parent 85417fd commit 73eb2a2

10 files changed

+1690
-31
lines changed

App.cs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Text;
5+
using System.Threading.Tasks;
6+
using Microsoft.Vbe.Interop;
7+
8+
namespace SlimDucky
9+
{
10+
public class App
11+
{
12+
private readonly VBE _vbe;
13+
14+
public App(VBE vbe)
15+
{
16+
_vbe = vbe;
17+
}
18+
19+
public void Startup()
20+
{
21+
22+
}
23+
24+
public void Shutdown()
25+
{
26+
27+
}
28+
}
29+
}

Class1.cs

Lines changed: 0 additions & 12 deletions
This file was deleted.

Connect.cs

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
using System;
2+
using System.ComponentModel;
3+
using System.Runtime.InteropServices;
4+
using Extensibility;
5+
using Microsoft.Vbe.Interop;
6+
7+
namespace SlimDucky
8+
{
9+
[ComVisible(true)]
10+
[Guid(Guid)]
11+
[ProgId(ProgId)]
12+
[EditorBrowsable(EditorBrowsableState.Never)]
13+
public class Connect : IDTExtensibility2
14+
{
15+
public const string Guid = "3971959D-003F-4D83-8747-3CE434E7553D";
16+
public const string ProgId = "SlimDucky.Connect";
17+
18+
private VBE _vbe;
19+
private AddIn _addIn;
20+
private App _app;
21+
22+
private bool _isInitialized;
23+
private bool _isBeginShutdownInvoked;
24+
25+
public void OnConnection(object Application, ext_ConnectMode ConnectMode, object AddInInst, ref Array custom)
26+
{
27+
_vbe = Application as VBE;
28+
_addIn = AddInInst as AddIn;
29+
30+
_isBeginShutdownInvoked = false;
31+
32+
switch(ConnectMode)
33+
{
34+
case ext_ConnectMode.ext_cm_Startup:
35+
// normal execution path - don't initialize just yet, wait for OnStartupComplete to be called by the host.
36+
break;
37+
case ext_ConnectMode.ext_cm_AfterStartup:
38+
InitializeAddIn();
39+
break;
40+
}
41+
}
42+
43+
public void OnDisconnection(ext_DisconnectMode RemoveMode, ref Array custom)
44+
{
45+
switch(RemoveMode)
46+
{
47+
case ext_DisconnectMode.ext_dm_UserClosed:
48+
ShutdownAddIn();
49+
break;
50+
51+
case ext_DisconnectMode.ext_dm_HostShutdown:
52+
if(_isBeginShutdownInvoked)
53+
{
54+
// this is the normal case: nothing to do here, we already ran ShutdownAddIn.
55+
}
56+
else
57+
{
58+
// some hosts do not call OnBeginShutdown: this mitigates it.
59+
ShutdownAddIn();
60+
}
61+
break;
62+
}
63+
}
64+
65+
public void OnAddInsUpdate(ref Array custom) { }
66+
67+
public void OnStartupComplete(ref Array custom)
68+
{
69+
InitializeAddIn();
70+
}
71+
72+
public void OnBeginShutdown(ref Array custom)
73+
{
74+
_isBeginShutdownInvoked = true;
75+
ShutdownAddIn();
76+
}
77+
78+
private void InitializeAddIn()
79+
{
80+
if(_isInitialized)
81+
{
82+
// The add-in is already initialized. See:
83+
// The strange case of the add-in initialized twice
84+
// http://msmvps.com/blogs/carlosq/archive/2013/02/14/the-strange-case-of-the-add-in-initialized-twice.aspx
85+
return;
86+
}
87+
88+
_app = new App(_vbe);
89+
_app.Startup();
90+
91+
_isInitialized = true;
92+
}
93+
94+
private void ShutdownAddIn()
95+
{
96+
_app.Shutdown();
97+
_app = null;
98+
_isInitialized = false;
99+
}
100+
}
101+
}

DockableWindowHost.cs

Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
using System;
2+
using System.ComponentModel;
3+
using System.Diagnostics;
4+
using System.Drawing;
5+
using System.Runtime.InteropServices;
6+
using System.Windows.Forms;
7+
8+
namespace SlimDucky
9+
{
10+
[ComVisible(true)]
11+
[Guid(Guid)]
12+
[ProgId(ProgId)]
13+
[EditorBrowsable(EditorBrowsableState.Never)]
14+
//Nothing breaks because we declare a ProgId
15+
// ReSharper disable once InconsistentNaming
16+
//Underscores make classes invisible to VB6 object explorer
17+
public partial class _DockableWindowHost : UserControl
18+
{
19+
public const string ProgId = "SlimDucky.DockableWindowHost";
20+
public const string Guid = "F6F1B4EC-9A1E-4311-91C2-D79A6A6F047E";
21+
22+
// ReSharper disable UnusedAutoPropertyAccessor.Local
23+
[StructLayout(LayoutKind.Sequential)]
24+
private struct Rect
25+
{
26+
public int Left { get; set; }
27+
public int Top { get; set; }
28+
public int Right { get; set; }
29+
public int Bottom { get; set; }
30+
}
31+
// ReSharper restore UnusedAutoPropertyAccessor.Local
32+
33+
[StructLayout(LayoutKind.Explicit)]
34+
private struct LParam
35+
{
36+
[FieldOffset(0)]
37+
public uint Value;
38+
[FieldOffset(0)]
39+
public readonly ushort LowWord;
40+
[FieldOffset(2)]
41+
public readonly ushort HighWord;
42+
}
43+
44+
[DllImport("User32.dll")]
45+
static extern IntPtr GetParent(IntPtr hWnd);
46+
47+
[DllImport("User32.dll", EntryPoint = "GetClientRect")]
48+
static extern int GetClientRect(IntPtr hWnd, ref Rect lpRect);
49+
50+
private IntPtr _parentHandle;
51+
private ParentWindow _subClassingWindow;
52+
private GCHandle _thisHandle;
53+
54+
internal void AddUserControl(UserControl control, IntPtr vbeHwnd)
55+
{
56+
_parentHandle = GetParent(Handle);
57+
_subClassingWindow = new ParentWindow(vbeHwnd, new IntPtr(GetHashCode()), _parentHandle);
58+
_subClassingWindow.CallBackEvent += OnCallBackEvent;
59+
60+
//DO NOT REMOVE THIS CALL. Dockable windows are instantiated by the VBE, not directly by RD. On top of that,
61+
//since we have to inherit from UserControl we don't have to keep handling window messages until the VBE gets
62+
//around to destroying the control's host or it results in an access violation when the base class is disposed.
63+
//We need to manually call base.Dispose() ONLY in response to a WM_DESTROY message.
64+
_thisHandle = GCHandle.Alloc(this, GCHandleType.Normal);
65+
66+
if(control != null)
67+
{
68+
control.Dock = DockStyle.Fill;
69+
Controls.Add(control);
70+
}
71+
AdjustSize();
72+
}
73+
74+
private void OnCallBackEvent(object sender, SubClassingWindowEventArgs e)
75+
{
76+
if(!e.Closing)
77+
{
78+
var param = new LParam { Value = (uint)e.LParam };
79+
Size = new Size(param.LowWord, param.HighWord);
80+
}
81+
else
82+
{
83+
Debug.WriteLine("DockableWindowHost removed event handler.");
84+
_subClassingWindow.CallBackEvent -= OnCallBackEvent;
85+
}
86+
}
87+
88+
private void AdjustSize()
89+
{
90+
var rect = new Rect();
91+
if(GetClientRect(_parentHandle, ref rect) != 0)
92+
{
93+
Size = new Size(rect.Right - rect.Left, rect.Bottom - rect.Top);
94+
}
95+
}
96+
97+
protected override bool ProcessKeyPreview(ref Message m)
98+
{
99+
const int wmKeydown = 0x100;
100+
var result = false;
101+
102+
var hostedUserControl = (UserControl)Controls[0];
103+
104+
if(m.Msg == wmKeydown)
105+
{
106+
var pressedKey = (Keys)m.WParam;
107+
switch(pressedKey)
108+
{
109+
case Keys.Tab:
110+
switch(ModifierKeys)
111+
{
112+
case Keys.None:
113+
SelectNextControl(hostedUserControl.ActiveControl, true, true, true, true);
114+
result = true;
115+
break;
116+
case Keys.Shift:
117+
SelectNextControl(hostedUserControl.ActiveControl, false, true, true, true);
118+
result = true;
119+
break;
120+
}
121+
break;
122+
case Keys.Return:
123+
if(hostedUserControl.ActiveControl.GetType() == typeof(Button))
124+
{
125+
var activeButton = (Button)hostedUserControl.ActiveControl;
126+
activeButton.PerformClick();
127+
}
128+
break;
129+
}
130+
}
131+
132+
if(!result)
133+
{
134+
result = base.ProcessKeyPreview(ref m);
135+
}
136+
return result;
137+
}
138+
139+
protected override void DefWndProc(ref Message m)
140+
{
141+
//See the comment in the ctor for why we have to listen for this.
142+
if(m.Msg == (int)WM.DESTROY)
143+
{
144+
Debug.WriteLine("DockableWindowHost received WM.DESTROY.");
145+
_thisHandle.Free();
146+
}
147+
base.DefWndProc(ref m);
148+
}
149+
150+
//override
151+
152+
public void Release()
153+
{
154+
Debug.WriteLine("DockableWindowHost release called.");
155+
_subClassingWindow.Dispose();
156+
}
157+
158+
protected override void DestroyHandle()
159+
{
160+
Debug.WriteLine("DockableWindowHost DestroyHandle called.");
161+
base.DestroyHandle();
162+
}
163+
164+
[ComVisible(false)]
165+
public class ParentWindow : SubclassingWindow
166+
{
167+
public event SubClassingWindowEventHandler CallBackEvent;
168+
public delegate void SubClassingWindowEventHandler(object sender, SubClassingWindowEventArgs e);
169+
170+
private readonly IntPtr _vbeHwnd;
171+
172+
private void OnCallBackEvent(SubClassingWindowEventArgs e)
173+
{
174+
CallBackEvent?.Invoke(this, e);
175+
}
176+
177+
public ParentWindow(IntPtr vbeHwnd, IntPtr id, IntPtr handle) : base(id, handle)
178+
{
179+
_vbeHwnd = vbeHwnd;
180+
}
181+
182+
private bool _closing;
183+
public override int SubClassProc(IntPtr hWnd, IntPtr msg, IntPtr wParam, IntPtr lParam, IntPtr uIdSubclass, IntPtr dwRefData)
184+
{
185+
switch((uint)msg)
186+
{
187+
case (uint)WM.SIZE:
188+
var args = new SubClassingWindowEventArgs(lParam);
189+
if(!_closing)
190+
OnCallBackEvent(args);
191+
break;
192+
case (uint)WM.SETFOCUS:
193+
if(!_closing)
194+
User32.SendMessage(_vbeHwnd, WM.RUBBERDUCK_CHILD_FOCUS, Hwnd, Hwnd);
195+
break;
196+
case (uint)WM.KILLFOCUS:
197+
if(!_closing)
198+
User32.SendMessage(_vbeHwnd, WM.RUBBERDUCK_CHILD_FOCUS, Hwnd, IntPtr.Zero);
199+
break;
200+
case (uint)WM.RUBBERDUCK_SINKING:
201+
OnCallBackEvent(new SubClassingWindowEventArgs(lParam) { Closing = true });
202+
_closing = true;
203+
break;
204+
}
205+
return base.SubClassProc(hWnd, msg, wParam, lParam, uIdSubclass, dwRefData);
206+
}
207+
}
208+
}
209+
}

0 commit comments

Comments
 (0)