From d09839855eb67af02152eed0889e9cf2ba50f159 Mon Sep 17 00:00:00 2001 From: ThunderFrame Date: Fri, 17 Feb 2017 17:24:30 +1100 Subject: [PATCH 1/8] Adds users32 methods for GetTopWindow and FindWindowEx --- Rubberduck.VBEEditor/NativeMethods.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Rubberduck.VBEEditor/NativeMethods.cs b/Rubberduck.VBEEditor/NativeMethods.cs index 1bf10c7635..0d536d9efb 100644 --- a/Rubberduck.VBEEditor/NativeMethods.cs +++ b/Rubberduck.VBEEditor/NativeMethods.cs @@ -55,6 +55,16 @@ public static class NativeMethods [DllImport("User32.dll")] internal static extern IntPtr GetParent(IntPtr hWnd); + /// Gets the child window at the top of the Z order. + /// + /// The window handle. + /// The child window IntPtr handle. + [DllImport("user32.dll")] + internal static extern IntPtr GetTopWindow(IntPtr hWnd); + + [DllImport("user32.dll", CharSet = CharSet.Unicode)] + internal static extern IntPtr FindWindowEx(IntPtr parentHandle, IntPtr childAfter, string lclassName, string windowTitle); + /// Gets window caption text by handle. /// /// Handle of the window to be activated. From bd68f8cb63bae0a359a8a8d9f3dbbba2f957c476 Mon Sep 17 00:00:00 2001 From: ThunderFrame Date: Fri, 17 Feb 2017 17:25:27 +1100 Subject: [PATCH 2/8] Adds VBE.MDIActiveChild method for getting the VBE's active MDI window even if it isn't the active VBE window. --- .../SafeComWrappers/Abstract/IVBE.cs | 1 + .../SafeComWrappers/VB6/VBE.cs | 5 ++++ .../SafeComWrappers/VBA/VBE.cs | 28 +++++++++++++++++++ 3 files changed, 34 insertions(+) diff --git a/Rubberduck.VBEEditor/SafeComWrappers/Abstract/IVBE.cs b/Rubberduck.VBEEditor/SafeComWrappers/Abstract/IVBE.cs index 0478864e60..5c1aa50b79 100644 --- a/Rubberduck.VBEEditor/SafeComWrappers/Abstract/IVBE.cs +++ b/Rubberduck.VBEEditor/SafeComWrappers/Abstract/IVBE.cs @@ -21,6 +21,7 @@ public interface IVBE : ISafeComWrapper, IEquatable IWindows Windows { get; } IHostApplication HostApplication(); + IWindow ActiveMDIChild(); bool IsInDesignMode { get; } } diff --git a/Rubberduck.VBEEditor/SafeComWrappers/VB6/VBE.cs b/Rubberduck.VBEEditor/SafeComWrappers/VB6/VBE.cs index 10515004fd..9277e8f2fe 100644 --- a/Rubberduck.VBEEditor/SafeComWrappers/VB6/VBE.cs +++ b/Rubberduck.VBEEditor/SafeComWrappers/VB6/VBE.cs @@ -109,6 +109,11 @@ public IHostApplication HostApplication() return null; } + public IWindow ActiveMDIChild() + { + throw new NotImplementedException(); + } + public bool IsInDesignMode { get { return VBProjects.All(project => project.Mode == EnvironmentMode.Design); } diff --git a/Rubberduck.VBEEditor/SafeComWrappers/VBA/VBE.cs b/Rubberduck.VBEEditor/SafeComWrappers/VBA/VBE.cs index 57fef9c3ea..4cff494cae 100644 --- a/Rubberduck.VBEEditor/SafeComWrappers/VBA/VBE.cs +++ b/Rubberduck.VBEEditor/SafeComWrappers/VBA/VBE.cs @@ -3,6 +3,7 @@ using System.IO; using System.Linq; using System.Runtime.InteropServices; +using System.Text; using Rubberduck.VBEditor.Application; using Rubberduck.VBEditor.SafeComWrappers.Abstract; using Rubberduck.VBEditor.SafeComWrappers.MSForms; @@ -279,6 +280,33 @@ public IHostApplication HostApplication() return null; } + /// Returns the topmost MDI child window. + public IWindow ActiveMDIChild() + { + const string mdiClientClass = "MDIClient"; + const int maxCaptionLength = 512; + + IntPtr mainWindow = (IntPtr)MainWindow.HWnd; + + IntPtr mdiClient = NativeMethods.FindWindowEx(mainWindow, IntPtr.Zero, mdiClientClass, string.Empty); + + IntPtr mdiChild = NativeMethods.GetTopWindow(mdiClient); + StringBuilder mdiChildCaption = new StringBuilder(); + int captionLength = NativeMethods.GetWindowText(mdiChild, mdiChildCaption, maxCaptionLength); + + if (captionLength > 0) + { + try + { + return Windows[mdiChildCaption]; + } + catch + { + } + } + return null; + } + /// Returns whether the host supports unit tests. public bool HostSupportsUnitTests() { From 070e1a5ffc9faa48e346a210c328858062ca0bf1 Mon Sep 17 00:00:00 2001 From: ThunderFrame Date: Fri, 17 Feb 2017 23:13:51 +1100 Subject: [PATCH 3/8] resolves merge to use new namespace NativeMethods moved, this fix just makes the namespace explicit --- Rubberduck.VBEEditor/SafeComWrappers/VBA/VBE.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Rubberduck.VBEEditor/SafeComWrappers/VBA/VBE.cs b/Rubberduck.VBEEditor/SafeComWrappers/VBA/VBE.cs index 4cff494cae..6a5ed7a289 100644 --- a/Rubberduck.VBEEditor/SafeComWrappers/VBA/VBE.cs +++ b/Rubberduck.VBEEditor/SafeComWrappers/VBA/VBE.cs @@ -9,6 +9,7 @@ using Rubberduck.VBEditor.SafeComWrappers.MSForms; using Rubberduck.VBEditor.SafeComWrappers.Office.Core; using Rubberduck.VBEditor.SafeComWrappers.Office.Core.Abstract; +using Rubberduck.VBEditor.WindowsApi; using VB = Microsoft.Vbe.Interop; namespace Rubberduck.VBEditor.SafeComWrappers.VBA From b11039b02989f6c7759eacf8da218d0819616b8f Mon Sep 17 00:00:00 2001 From: ThunderFrame Date: Fri, 17 Feb 2017 23:15:25 +1100 Subject: [PATCH 4/8] fixes button widths and title/subtitle spacing, to make English captions readable might need tweaking for other languages --- .../ExtractInterfaceDialog.Designer.cs | 79 +++++++++---------- 1 file changed, 36 insertions(+), 43 deletions(-) diff --git a/RetailCoder.VBE/UI/Refactorings/ExtractInterfaceDialog.Designer.cs b/RetailCoder.VBE/UI/Refactorings/ExtractInterfaceDialog.Designer.cs index b303ce54ad..7c294dd60f 100644 --- a/RetailCoder.VBE/UI/Refactorings/ExtractInterfaceDialog.Designer.cs +++ b/RetailCoder.VBE/UI/Refactorings/ExtractInterfaceDialog.Designer.cs @@ -56,21 +56,19 @@ private void InitializeComponent() this.flowLayoutPanel2.Controls.Add(this.OkButton); this.flowLayoutPanel2.Dock = System.Windows.Forms.DockStyle.Bottom; this.flowLayoutPanel2.FlowDirection = System.Windows.Forms.FlowDirection.RightToLeft; - this.flowLayoutPanel2.Location = new System.Drawing.Point(0, 364); - this.flowLayoutPanel2.Margin = new System.Windows.Forms.Padding(4); + this.flowLayoutPanel2.Location = new System.Drawing.Point(0, 296); this.flowLayoutPanel2.Name = "flowLayoutPanel2"; - this.flowLayoutPanel2.Padding = new System.Windows.Forms.Padding(11, 10, 0, 10); - this.flowLayoutPanel2.Size = new System.Drawing.Size(572, 53); + this.flowLayoutPanel2.Padding = new System.Windows.Forms.Padding(8, 8, 0, 8); + this.flowLayoutPanel2.Size = new System.Drawing.Size(459, 43); this.flowLayoutPanel2.TabIndex = 28; // // CancelDialogButton // this.CancelDialogButton.BackColor = System.Drawing.SystemColors.ButtonFace; this.CancelDialogButton.DialogResult = System.Windows.Forms.DialogResult.Cancel; - this.CancelDialogButton.Location = new System.Drawing.Point(457, 14); - this.CancelDialogButton.Margin = new System.Windows.Forms.Padding(4); + this.CancelDialogButton.Location = new System.Drawing.Point(373, 11); this.CancelDialogButton.Name = "CancelDialogButton"; - this.CancelDialogButton.Size = new System.Drawing.Size(100, 28); + this.CancelDialogButton.Size = new System.Drawing.Size(75, 23); this.CancelDialogButton.TabIndex = 5; this.CancelDialogButton.Text = "Cancel"; this.CancelDialogButton.UseVisualStyleBackColor = false; @@ -79,10 +77,9 @@ private void InitializeComponent() // this.OkButton.BackColor = System.Drawing.SystemColors.ButtonFace; this.OkButton.DialogResult = System.Windows.Forms.DialogResult.OK; - this.OkButton.Location = new System.Drawing.Point(349, 14); - this.OkButton.Margin = new System.Windows.Forms.Padding(4); + this.OkButton.Location = new System.Drawing.Point(292, 11); this.OkButton.Name = "OkButton"; - this.OkButton.Size = new System.Drawing.Size(100, 28); + this.OkButton.Size = new System.Drawing.Size(75, 23); this.OkButton.TabIndex = 4; this.OkButton.Text = "Ok"; this.OkButton.UseVisualStyleBackColor = false; @@ -90,8 +87,7 @@ private void InitializeComponent() // InvalidNameValidationIcon // this.InvalidNameValidationIcon.Image = global::Rubberduck.Properties.Resources.cross_circle; - this.InvalidNameValidationIcon.Location = new System.Drawing.Point(549, 102); - this.InvalidNameValidationIcon.Margin = new System.Windows.Forms.Padding(4); + this.InvalidNameValidationIcon.Location = new System.Drawing.Point(412, 83); this.InvalidNameValidationIcon.Name = "InvalidNameValidationIcon"; this.InvalidNameValidationIcon.Size = new System.Drawing.Size(16, 16); this.InvalidNameValidationIcon.SizeMode = System.Windows.Forms.PictureBoxSizeMode.AutoSize; @@ -100,19 +96,17 @@ private void InitializeComponent() // // InterfaceNameBox // - this.InterfaceNameBox.Location = new System.Drawing.Point(16, 112); - this.InterfaceNameBox.Margin = new System.Windows.Forms.Padding(4); + this.InterfaceNameBox.Location = new System.Drawing.Point(12, 91); this.InterfaceNameBox.Name = "InterfaceNameBox"; - this.InterfaceNameBox.Size = new System.Drawing.Size(541, 22); + this.InterfaceNameBox.Size = new System.Drawing.Size(437, 20); this.InterfaceNameBox.TabIndex = 0; // // NameLabel // this.NameLabel.AutoSize = true; - this.NameLabel.Location = new System.Drawing.Point(13, 91); - this.NameLabel.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); + this.NameLabel.Location = new System.Drawing.Point(10, 74); this.NameLabel.Name = "NameLabel"; - this.NameLabel.Size = new System.Drawing.Size(49, 17); + this.NameLabel.Size = new System.Drawing.Size(38, 13); this.NameLabel.TabIndex = 29; this.NameLabel.Text = "Name:"; // @@ -120,20 +114,20 @@ private void InitializeComponent() // this.TitleLabel.AutoSize = true; this.TitleLabel.Font = new System.Drawing.Font("Microsoft Sans Serif", 9F, System.Drawing.FontStyle.Bold); - this.TitleLabel.Location = new System.Drawing.Point(20, 11); - this.TitleLabel.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); + this.TitleLabel.Location = new System.Drawing.Point(15, 9); this.TitleLabel.Name = "TitleLabel"; - this.TitleLabel.Padding = new System.Windows.Forms.Padding(3, 2, 3, 2); - this.TitleLabel.Size = new System.Drawing.Size(137, 22); + this.TitleLabel.Padding = new System.Windows.Forms.Padding(2, 2, 2, 2); + this.TitleLabel.Size = new System.Drawing.Size(115, 19); this.TitleLabel.TabIndex = 2; this.TitleLabel.Text = "Extract Interface"; // // InstructionsLabel // - this.InstructionsLabel.Location = new System.Drawing.Point(20, 30); + this.InstructionsLabel.Location = new System.Drawing.Point(15, 24); + this.InstructionsLabel.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0); this.InstructionsLabel.Name = "InstructionsLabel"; - this.InstructionsLabel.Padding = new System.Windows.Forms.Padding(4); - this.InstructionsLabel.Size = new System.Drawing.Size(549, 34); + this.InstructionsLabel.Padding = new System.Windows.Forms.Padding(3, 3, 3, 3); + this.InstructionsLabel.Size = new System.Drawing.Size(412, 28); this.InstructionsLabel.TabIndex = 3; this.InstructionsLabel.Text = "Please specify interface name and members."; // @@ -144,9 +138,8 @@ private void InitializeComponent() this.DescriptionPanel.Controls.Add(this.InstructionsLabel); this.DescriptionPanel.Dock = System.Windows.Forms.DockStyle.Top; this.DescriptionPanel.Location = new System.Drawing.Point(0, 0); - this.DescriptionPanel.Margin = new System.Windows.Forms.Padding(4); this.DescriptionPanel.Name = "DescriptionPanel"; - this.DescriptionPanel.Size = new System.Drawing.Size(572, 84); + this.DescriptionPanel.Size = new System.Drawing.Size(459, 68); this.DescriptionPanel.TabIndex = 33; // // MembersGroupBox @@ -157,11 +150,11 @@ private void InitializeComponent() this.MembersGroupBox.Controls.Add(this.InterfaceMembersGridView); this.MembersGroupBox.Controls.Add(this.DeselectAllButton); this.MembersGroupBox.Controls.Add(this.SelectAllButton); - this.MembersGroupBox.Location = new System.Drawing.Point(16, 142); - this.MembersGroupBox.Margin = new System.Windows.Forms.Padding(3, 2, 3, 2); + this.MembersGroupBox.Location = new System.Drawing.Point(12, 115); + this.MembersGroupBox.Margin = new System.Windows.Forms.Padding(2, 2, 2, 2); this.MembersGroupBox.Name = "MembersGroupBox"; - this.MembersGroupBox.Padding = new System.Windows.Forms.Padding(3, 2, 3, 2); - this.MembersGroupBox.Size = new System.Drawing.Size(541, 214); + this.MembersGroupBox.Padding = new System.Windows.Forms.Padding(2, 2, 2, 2); + this.MembersGroupBox.Size = new System.Drawing.Size(436, 174); this.MembersGroupBox.TabIndex = 1; this.MembersGroupBox.TabStop = false; this.MembersGroupBox.Text = "Members"; @@ -175,8 +168,8 @@ private void InitializeComponent() this.InterfaceMembersGridView.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize; this.InterfaceMembersGridView.ColumnHeadersVisible = false; this.InterfaceMembersGridView.EditMode = System.Windows.Forms.DataGridViewEditMode.EditOnEnter; - this.InterfaceMembersGridView.Location = new System.Drawing.Point(7, 26); - this.InterfaceMembersGridView.Margin = new System.Windows.Forms.Padding(3, 2, 3, 2); + this.InterfaceMembersGridView.Location = new System.Drawing.Point(5, 21); + this.InterfaceMembersGridView.Margin = new System.Windows.Forms.Padding(2, 2, 2, 2); this.InterfaceMembersGridView.MultiSelect = false; this.InterfaceMembersGridView.Name = "InterfaceMembersGridView"; this.InterfaceMembersGridView.RowHeadersVisible = false; @@ -184,25 +177,25 @@ private void InitializeComponent() this.InterfaceMembersGridView.RowTemplate.Height = 24; this.InterfaceMembersGridView.SelectionMode = System.Windows.Forms.DataGridViewSelectionMode.FullRowSelect; this.InterfaceMembersGridView.ShowEditingIcon = false; - this.InterfaceMembersGridView.Size = new System.Drawing.Size(427, 174); + this.InterfaceMembersGridView.Size = new System.Drawing.Size(320, 141); this.InterfaceMembersGridView.TabIndex = 1; // // DeselectAllButton // - this.DeselectAllButton.Location = new System.Drawing.Point(441, 64); - this.DeselectAllButton.Margin = new System.Windows.Forms.Padding(3, 2, 3, 2); + this.DeselectAllButton.Location = new System.Drawing.Point(331, 52); + this.DeselectAllButton.Margin = new System.Windows.Forms.Padding(2, 2, 2, 2); this.DeselectAllButton.Name = "DeselectAllButton"; - this.DeselectAllButton.Size = new System.Drawing.Size(93, 32); + this.DeselectAllButton.Size = new System.Drawing.Size(100, 26); this.DeselectAllButton.TabIndex = 3; this.DeselectAllButton.Text = "Deselect All"; this.DeselectAllButton.UseVisualStyleBackColor = true; // // SelectAllButton // - this.SelectAllButton.Location = new System.Drawing.Point(441, 26); - this.SelectAllButton.Margin = new System.Windows.Forms.Padding(3, 2, 3, 2); + this.SelectAllButton.Location = new System.Drawing.Point(331, 21); + this.SelectAllButton.Margin = new System.Windows.Forms.Padding(2, 2, 2, 2); this.SelectAllButton.Name = "SelectAllButton"; - this.SelectAllButton.Size = new System.Drawing.Size(93, 32); + this.SelectAllButton.Size = new System.Drawing.Size(100, 26); this.SelectAllButton.TabIndex = 2; this.SelectAllButton.Text = "Select All"; this.SelectAllButton.UseVisualStyleBackColor = true; @@ -210,10 +203,10 @@ private void InitializeComponent() // ExtractInterfaceDialog // this.AcceptButton = this.OkButton; - this.AutoScaleDimensions = new System.Drawing.SizeF(8F, 16F); + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; this.CancelButton = this.CancelDialogButton; - this.ClientSize = new System.Drawing.Size(572, 417); + this.ClientSize = new System.Drawing.Size(459, 339); this.Controls.Add(this.MembersGroupBox); this.Controls.Add(this.DescriptionPanel); this.Controls.Add(this.InvalidNameValidationIcon); @@ -222,7 +215,7 @@ private void InitializeComponent() this.Controls.Add(this.flowLayoutPanel2); this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog; this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon"))); - this.Margin = new System.Windows.Forms.Padding(3, 2, 3, 2); + this.Margin = new System.Windows.Forms.Padding(2, 2, 2, 2); this.MaximizeBox = false; this.MinimizeBox = false; this.Name = "ExtractInterfaceDialog"; From 0eafa7f5b6f1699460419b8206d1e69f3e54ce1f Mon Sep 17 00:00:00 2001 From: ThunderFrame Date: Fri, 17 Feb 2017 23:41:49 +1100 Subject: [PATCH 5/8] Adds check to if a userform selected in Project Explorer has a designer that is topmost in MDI If the user selects various components in Project Explorer, and then selects a userform, that component might already have an open designer that is visible and top-most in the MDIClient. If the designer is topmost, then set the status bar to the designer's control selection, otherwise use the component. If the userforms's code module is topmost, it has already been hit before we reach this check --- RetailCoder.VBE/App.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/RetailCoder.VBE/App.cs b/RetailCoder.VBE/App.cs index 78f6f1f1d6..6c3da0a863 100644 --- a/RetailCoder.VBE/App.cs +++ b/RetailCoder.VBE/App.cs @@ -142,6 +142,7 @@ private void RefreshSelection(IWindow window) WindowKind windowKind = _vbe.ActiveWindow.Type; var pane = _vbe.ActiveCodePane; var component = _vbe.SelectedVBComponent; + var activeMDI = _vbe.ActiveMDIChild(); Declaration selectedDeclaration = null; @@ -170,7 +171,9 @@ private void RefreshSelection(IWindow window) } else { - caption = component.Type == ComponentType.UserForm && component.HasOpenDesigner + caption = component.Type == ComponentType.UserForm + && component.HasOpenDesigner + && component.DesignerWindow().Caption == activeMDI.Caption ? GetComponentControlsCaption(component) : String.Format("{0}.{1} ({2})", component.ParentProject.Name, component.Name, component.Type); } From b839675af48cd28703e4d55815e9d2100b04a0ed Mon Sep 17 00:00:00 2001 From: ThunderFrame Date: Sat, 18 Feb 2017 00:39:00 +1100 Subject: [PATCH 6/8] fixes window selection from collection could be the wrapper needs to expose an Item method? --- Rubberduck.VBEEditor/SafeComWrappers/VBA/VBE.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Rubberduck.VBEEditor/SafeComWrappers/VBA/VBE.cs b/Rubberduck.VBEEditor/SafeComWrappers/VBA/VBE.cs index 6a5ed7a289..11cf2309a0 100644 --- a/Rubberduck.VBEEditor/SafeComWrappers/VBA/VBE.cs +++ b/Rubberduck.VBEEditor/SafeComWrappers/VBA/VBE.cs @@ -299,7 +299,7 @@ public IWindow ActiveMDIChild() { try { - return Windows[mdiChildCaption]; + return Windows.FirstOrDefault(win => win.Caption == mdiChildCaption.ToString()); } catch { From dc08ffc56c69c6eccd7f8b60963caffd2328e472 Mon Sep 17 00:00:00 2001 From: ThunderFrame Date: Sat, 18 Feb 2017 00:39:56 +1100 Subject: [PATCH 7/8] comments out codepane handling, because ParentProject is returning null WTF? see the PR comments --- RetailCoder.VBE/App.cs | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/RetailCoder.VBE/App.cs b/RetailCoder.VBE/App.cs index 6c3da0a863..aa3adf2abe 100644 --- a/RetailCoder.VBE/App.cs +++ b/RetailCoder.VBE/App.cs @@ -146,16 +146,18 @@ private void RefreshSelection(IWindow window) Declaration selectedDeclaration = null; - //TODO - I doubt this is the best way to check if the SelectedVBComponent and the ActiveCodePane are the same component. - if (windowKind == WindowKind.CodeWindow || (!_vbe.SelectedVBComponent.IsWrappingNullReference - && component.ParentProject.ProjectId == pane.CodeModule.Parent.ParentProject.ProjectId - && component.Name == pane.CodeModule.Parent.Name)) - { - selectedDeclaration = _parser.State.FindSelectedDeclaration(pane); - refCount = selectedDeclaration == null ? 0 : selectedDeclaration.References.Count(); - caption = _stateBar.GetContextSelectionCaption(_vbe.ActiveCodePane, selectedDeclaration); - } - else if (windowKind == WindowKind.Designer) + //TODO - Reinstate these lines once the host stops crashing - need to theck ParentProject doesn't return null + ////TODO - I doubt this is the best way to check if the SelectedVBComponent and the ActiveCodePane are the same component. + //if (windowKind == WindowKind.CodeWindow || (!_vbe.SelectedVBComponent.IsWrappingNullReference + // && component.ParentProject.ProjectId == pane.CodeModule.Parent.ParentProject.ProjectId + // && component.Name == pane.CodeModule.Parent.Name)) + //{ + // selectedDeclaration = _parser.State.FindSelectedDeclaration(pane); + // refCount = selectedDeclaration == null ? 0 : selectedDeclaration.References.Count(); + // caption = _stateBar.GetContextSelectionCaption(_vbe.ActiveCodePane, selectedDeclaration); + //} + //else + if (windowKind == WindowKind.Designer) { caption = GetComponentControlsCaption(component); } From 08c4ba7f45eae7fc9d57c1407f0ca14bfb240bf9 Mon Sep 17 00:00:00 2001 From: comintern Date: Sat, 18 Feb 2017 00:00:14 -0600 Subject: [PATCH 8/8] Add event source to track selection changes originating in Project Window. Refactor handlers. --- RetailCoder.VBE/App.cs | 102 +++++++++--------- RetailCoder.VBE/Extension.cs | 4 +- .../{VBEEvents.cs => VBENativeServices.cs} | 30 +++++- .../Events/WindowChangedEventArgs.cs | 13 +-- .../Rubberduck.VBEditor.csproj | 2 +- .../WindowsApi/CodePaneSubclass.cs | 4 +- .../WindowsApi/DesignerWindowSubclass.cs | 16 +++ .../WindowsApi/FocusSource.cs | 8 +- .../WindowsApi/NativeMethods.cs | 9 -- Rubberduck.VBEEditor/WindowsApi/User32.cs | 7 ++ 10 files changed, 112 insertions(+), 83 deletions(-) rename Rubberduck.VBEEditor/Events/{VBEEvents.cs => VBENativeServices.cs} (83%) diff --git a/RetailCoder.VBE/App.cs b/RetailCoder.VBE/App.cs index 78f6f1f1d6..4ab03cd999 100644 --- a/RetailCoder.VBE/App.cs +++ b/RetailCoder.VBE/App.cs @@ -63,8 +63,8 @@ public App(IVBE vbe, _version = version; _checkVersionCommand = checkVersionCommand; - VBEEvents.SelectionChanged += _vbe_SelectionChanged; - VBEEvents.WindowFocusChange += _vbe_FocusChanged; + VBENativeServices.SelectionChanged += _vbe_SelectionChanged; + VBENativeServices.WindowFocusChange += _vbe_FocusChanged; _configService.SettingsChanged += _configService_SettingsChanged; _parser.State.StateChanged += Parser_StateChanged; @@ -73,6 +73,9 @@ public App(IVBE vbe, UiDispatcher.Initialize(); } + //TODO - This should be able to move to the appropriate handling classes now. + #region Statusbar + private void State_StatusMessageUpdate(object sender, RubberduckStatusMessageEventArgs e) { var message = e.Message; @@ -92,90 +95,79 @@ private void _vbe_SelectionChanged(object sender, SelectionChangedEventArgs e) private void _vbe_FocusChanged(object sender, WindowChangedEventArgs e) { - if (e.EventType == WindowChangedEventArgs.FocusType.GotFocus) + if (e.EventType == FocusType.GotFocus) { switch (e.Window.Type) { case WindowKind.Designer: + //Designer or control on designer form selected. RefreshSelection(e.Window); break; case WindowKind.CodeWindow: + //Caret changed in a code pane. RefreshSelection(e.CodePane); break; } - } + } + else if (e.EventType == FocusType.ChildFocus) + { + //Treeview selection changed in project window. + RefreshSelection(); + } } private ParserState _lastStatus; private Declaration _lastSelectedDeclaration; private void RefreshSelection(ICodePane pane) { - Declaration selectedDeclaration = null; - if (!pane.IsWrappingNullReference) + if (pane == null || pane.IsWrappingNullReference) { - selectedDeclaration = _parser.State.FindSelectedDeclaration(pane); - var refCount = selectedDeclaration == null ? 0 : selectedDeclaration.References.Count(); - var caption = _stateBar.GetContextSelectionCaption(_vbe.ActiveCodePane, selectedDeclaration); - _stateBar.SetContextSelectionCaption(caption, refCount); - } - - var currentStatus = _parser.State.Status; - if (ShouldEvaluateCanExecute(selectedDeclaration, currentStatus)) - { - _appMenus.EvaluateCanExecute(_parser.State); - _stateBar.EvaluateCanExecute(_parser.State); + return; } - _lastStatus = currentStatus; - _lastSelectedDeclaration = selectedDeclaration; + var selectedDeclaration = _parser.State.FindSelectedDeclaration(pane); + var caption = _stateBar.GetContextSelectionCaption(_vbe.ActiveCodePane, selectedDeclaration); + UpdateStatusbarAndCommandState(caption, selectedDeclaration); } private void RefreshSelection(IWindow window) { - if (window.IsWrappingNullReference || window.Type != WindowKind.Designer) + if (window == null || window.IsWrappingNullReference || window.Type != WindowKind.Designer) { return; } - var caption = String.Empty; - var refCount = 0; - WindowKind windowKind = _vbe.ActiveWindow.Type; - var pane = _vbe.ActiveCodePane; var component = _vbe.SelectedVBComponent; + var caption = GetComponentControlsCaption(component); + //TODO: Need to find the selected declaration for the Form\Control. + UpdateStatusbarAndCommandState(caption, null); + } - Declaration selectedDeclaration = null; - - //TODO - I doubt this is the best way to check if the SelectedVBComponent and the ActiveCodePane are the same component. - if (windowKind == WindowKind.CodeWindow || (!_vbe.SelectedVBComponent.IsWrappingNullReference - && component.ParentProject.ProjectId == pane.CodeModule.Parent.ParentProject.ProjectId - && component.Name == pane.CodeModule.Parent.Name)) - { - selectedDeclaration = _parser.State.FindSelectedDeclaration(pane); - refCount = selectedDeclaration == null ? 0 : selectedDeclaration.References.Count(); - caption = _stateBar.GetContextSelectionCaption(_vbe.ActiveCodePane, selectedDeclaration); - } - else if (windowKind == WindowKind.Designer) + private void RefreshSelection() + { + var caption = string.Empty; + var component = _vbe.SelectedVBComponent; + if (component == null || component.IsWrappingNullReference) { - caption = GetComponentControlsCaption(component); + //The user might have selected the project node in Project Explorer + //If they've chosen a folder, we'll return the project anyway + caption = !_vbe.ActiveVBProject.IsWrappingNullReference + ? _vbe.ActiveVBProject.Name + : null; } else { - if (_vbe.SelectedVBComponent.IsWrappingNullReference) - { - //The user might have selected the project node in Project Explorer - //If they've chosen a folder, we'll return the project anyway - caption = !_vbe.ActiveVBProject.IsWrappingNullReference - ? _vbe.ActiveVBProject.Name - : null; - } - else - { - caption = component.Type == ComponentType.UserForm && component.HasOpenDesigner - ? GetComponentControlsCaption(component) - : String.Format("{0}.{1} ({2})", component.ParentProject.Name, component.Name, component.Type); - } + caption = component.Type == ComponentType.UserForm && component.HasOpenDesigner + ? GetComponentControlsCaption(component) + : string.Format("{0}.{1} ({2})", component.ParentProject.Name, component.Name, component.Type); } + //TODO: Need to find the selected declaration for the selected treeview item. + UpdateStatusbarAndCommandState(caption, null); + } + private void UpdateStatusbarAndCommandState(string caption, Declaration selectedDeclaration) + { + var refCount = selectedDeclaration == null ? 0 : selectedDeclaration.References.Count(); _stateBar.SetContextSelectionCaption(caption, refCount); var currentStatus = _parser.State.Status; @@ -186,7 +178,7 @@ private void RefreshSelection(IWindow window) } _lastStatus = currentStatus; - _lastSelectedDeclaration = selectedDeclaration; + _lastSelectedDeclaration = selectedDeclaration; } private string GetComponentControlsCaption(IVBComponent component) @@ -215,6 +207,8 @@ private bool ShouldEvaluateCanExecute(Declaration selectedDeclaration, ParserSta (selectedDeclaration == null && _lastSelectedDeclaration != null); } + #endregion + private void _configService_SettingsChanged(object sender, ConfigurationChangedEventArgs e) { _config = _configService.LoadConfiguration(); @@ -366,8 +360,8 @@ public void Dispose() _parser.State.StatusMessageUpdate -= State_StatusMessageUpdate; } - VBEEvents.SelectionChanged += _vbe_SelectionChanged; - VBEEvents.WindowFocusChange += _vbe_FocusChanged; + VBENativeServices.SelectionChanged += _vbe_SelectionChanged; + VBENativeServices.WindowFocusChange += _vbe_FocusChanged; if (_configService != null) { diff --git a/RetailCoder.VBE/Extension.cs b/RetailCoder.VBE/Extension.cs index b53012ff34..4da14b26e5 100644 --- a/RetailCoder.VBE/Extension.cs +++ b/RetailCoder.VBE/Extension.cs @@ -56,7 +56,7 @@ public void OnConnection(object Application, ext_ConnectMode ConnectMode, object { var vbe = (Microsoft.Vbe.Interop.VBE) Application; _ide = new VBEditor.SafeComWrappers.VBA.VBE(vbe); - VBEEvents.HookEvents(_ide); + VBENativeServices.HookEvents(_ide); var addin = (Microsoft.Vbe.Interop.AddIn)AddInInst; _addin = new VBEditor.SafeComWrappers.VBA.AddIn(addin) { Object = this }; @@ -221,7 +221,7 @@ private void Startup() private void ShutdownAddIn() { - VBEEvents.UnhookEvents(); + VBENativeServices.UnhookEvents(); var currentDomain = AppDomain.CurrentDomain; currentDomain.AssemblyResolve -= LoadFromSameFolder; diff --git a/Rubberduck.VBEEditor/Events/VBEEvents.cs b/Rubberduck.VBEEditor/Events/VBENativeServices.cs similarity index 83% rename from Rubberduck.VBEEditor/Events/VBEEvents.cs rename to Rubberduck.VBEEditor/Events/VBENativeServices.cs index 661d1bd067..2391e5c652 100644 --- a/Rubberduck.VBEEditor/Events/VBEEvents.cs +++ b/Rubberduck.VBEEditor/Events/VBENativeServices.cs @@ -9,7 +9,7 @@ namespace Rubberduck.VBEditor.Events { - public static class VBEEvents + public static class VBENativeServices { private static User32.WinEventProc _eventProc; private static IntPtr _eventHandle; @@ -66,7 +66,10 @@ public static void UnhookEvents() public static void VbeEventCallback(IntPtr hWinEventHook, uint eventType, IntPtr hwnd, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime) { - if (hwnd != IntPtr.Zero && idObject == (int)ObjId.Caret && eventType == (uint)WinEvent.ObjectLocationChange && hwnd.ToWindowType() == WindowType.VbaWindow) + if (hwnd != IntPtr.Zero && + idObject == (int)ObjId.Caret && + (eventType == (uint)WinEvent.ObjectLocationChange || eventType == (uint)WinEvent.ObjectCreate) && + hwnd.ToWindowType() == WindowType.VbaWindow) { OnSelectionChanged(hwnd); } @@ -83,6 +86,17 @@ public static void VbeEventCallback(IntPtr hWinEventHook, uint eventType, IntPtr DetachWindow(hwnd); } } + else if (eventType == (uint)WinEvent.ObjectFocus && idObject == (int)ObjId.Client) + { + //Test to see if it was a selection change in the project window. + var parent = User32.GetParent(hwnd); + if (parent != IntPtr.Zero && parent.ToWindowType() == WindowType.Project) + { + FocusDispatcher(_vbe, new WindowChangedEventArgs(parent, null, null, FocusType.ChildFocus)); + } + } + //This is an output window firehose, leave this here, but comment it out when done. + //if (idObject != (int)ObjId.Cursor) Debug.WriteLine("Hwnd: {0:X4} - EventType {1:X4}, idObject {2}, idChild {3}", (int)hwnd, eventType, idObject, idChild); } private static void AttachWindow(IntPtr hwnd) @@ -176,15 +190,21 @@ public enum WindowType { Indeterminate, VbaWindow, - DesignerWindow + DesignerWindow, + Project } public static WindowType ToWindowType(this IntPtr hwnd) + { + WindowType id; + return Enum.TryParse(hwnd.ToClassName(), true, out id) ? id : WindowType.Indeterminate; + } + + public static string ToClassName(this IntPtr hwnd) { var name = new StringBuilder(128); User32.GetClassName(hwnd, name, name.Capacity); - WindowType id; - return Enum.TryParse(name.ToString(), out id) ? id : WindowType.Indeterminate; + return name.ToString(); } } } diff --git a/Rubberduck.VBEEditor/Events/WindowChangedEventArgs.cs b/Rubberduck.VBEEditor/Events/WindowChangedEventArgs.cs index 0d0bf99611..6357f23824 100644 --- a/Rubberduck.VBEEditor/Events/WindowChangedEventArgs.cs +++ b/Rubberduck.VBEEditor/Events/WindowChangedEventArgs.cs @@ -3,14 +3,15 @@ namespace Rubberduck.VBEditor.Events { - public class WindowChangedEventArgs : EventArgs + public enum FocusType { - public enum FocusType - { - GotFocus, - LostFocus - } + GotFocus, + LostFocus, + ChildFocus + } + public class WindowChangedEventArgs : EventArgs + { public IntPtr Hwnd { get; private set; } public IWindow Window { get; private set; } public ICodePane CodePane { get; private set; } diff --git a/Rubberduck.VBEEditor/Rubberduck.VBEditor.csproj b/Rubberduck.VBEEditor/Rubberduck.VBEditor.csproj index bb6b162fe0..672bc3ae6a 100644 --- a/Rubberduck.VBEEditor/Rubberduck.VBEditor.csproj +++ b/Rubberduck.VBEEditor/Rubberduck.VBEditor.csproj @@ -125,7 +125,7 @@ - + diff --git a/Rubberduck.VBEEditor/WindowsApi/CodePaneSubclass.cs b/Rubberduck.VBEEditor/WindowsApi/CodePaneSubclass.cs index 5eee5941bf..46bb11e003 100644 --- a/Rubberduck.VBEEditor/WindowsApi/CodePaneSubclass.cs +++ b/Rubberduck.VBEEditor/WindowsApi/CodePaneSubclass.cs @@ -16,9 +16,9 @@ internal CodePaneSubclass(IntPtr hwnd, ICodePane pane) : base(hwnd) _pane = pane; } - protected override void DispatchFocusEvent(WindowChangedEventArgs.FocusType type) + protected override void DispatchFocusEvent(FocusType type) { - var window = VBEEvents.GetWindowInfoFromHwnd(Hwnd); + var window = VBENativeServices.GetWindowInfoFromHwnd(Hwnd); if (window == null) { return; diff --git a/Rubberduck.VBEEditor/WindowsApi/DesignerWindowSubclass.cs b/Rubberduck.VBEEditor/WindowsApi/DesignerWindowSubclass.cs index 9edf56ec2b..799d184b0b 100644 --- a/Rubberduck.VBEEditor/WindowsApi/DesignerWindowSubclass.cs +++ b/Rubberduck.VBEEditor/WindowsApi/DesignerWindowSubclass.cs @@ -1,4 +1,7 @@ using System; +using System.Diagnostics; +using Rubberduck.Common.WinAPI; +using Rubberduck.VBEditor.Events; namespace Rubberduck.VBEditor.WindowsApi { @@ -6,5 +9,18 @@ internal class DesignerWindowSubclass : FocusSource { //Stub for designer window replacement. :-) internal DesignerWindowSubclass(IntPtr hwnd) : base(hwnd) { } + + public override int SubClassProc(IntPtr hWnd, IntPtr msg, IntPtr wParam, IntPtr lParam, IntPtr uIdSubclass, IntPtr dwRefData) + { + //Any time the selected control changes in the hosted userform, the F3 Overlay has to be redrawn. This is a good proxy + //for child control selections, so raise a focus event. + if ((int) msg == (int)WM.ERASEBKGND) + { + DispatchFocusEvent(FocusType.GotFocus); + } + //This is an output window firehose, leave this here, but comment it out when done. + //Debug.WriteLine("WM: {0:X4}, wParam {1}, lParam {2}", msg, wParam, lParam); + return base.SubClassProc(hWnd, msg, wParam, lParam, uIdSubclass, dwRefData); + } } } diff --git a/Rubberduck.VBEEditor/WindowsApi/FocusSource.cs b/Rubberduck.VBEEditor/WindowsApi/FocusSource.cs index 9c6539a543..9b4e1f35f4 100644 --- a/Rubberduck.VBEEditor/WindowsApi/FocusSource.cs +++ b/Rubberduck.VBEEditor/WindowsApi/FocusSource.cs @@ -17,9 +17,9 @@ protected void OnFocusChange(WindowChangedEventArgs eventArgs) } } - protected virtual void DispatchFocusEvent(WindowChangedEventArgs.FocusType type) + protected virtual void DispatchFocusEvent(FocusType type) { - var window = VBEEvents.GetWindowInfoFromHwnd(Hwnd); + var window = VBENativeServices.GetWindowInfoFromHwnd(Hwnd); if (window == null) { return; @@ -33,10 +33,10 @@ public override int SubClassProc(IntPtr hWnd, IntPtr msg, IntPtr wParam, IntPtr { case (uint)WM.SETFOCUS: - DispatchFocusEvent(WindowChangedEventArgs.FocusType.GotFocus); + DispatchFocusEvent(FocusType.GotFocus); break; case (uint)WM.KILLFOCUS: - DispatchFocusEvent(WindowChangedEventArgs.FocusType.LostFocus); + DispatchFocusEvent(FocusType.LostFocus); break; } return base.SubClassProc(hWnd, msg, wParam, lParam, uIdSubclass, dwRefData); diff --git a/Rubberduck.VBEEditor/WindowsApi/NativeMethods.cs b/Rubberduck.VBEEditor/WindowsApi/NativeMethods.cs index 43f7dd7846..2219cf51bf 100644 --- a/Rubberduck.VBEEditor/WindowsApi/NativeMethods.cs +++ b/Rubberduck.VBEEditor/WindowsApi/NativeMethods.cs @@ -69,15 +69,6 @@ public static string GetWindowText(this IntPtr windowHandle) return result; } - /// Gets the parent window of this item. - /// - /// The window handle. - /// The parent window IntPtr handle. - [DllImport("User32.dll")] - internal static extern IntPtr GetParent(IntPtr hWnd); - - - /// Activates the window by simulating a click. /// /// Handle of the window to be activated. diff --git a/Rubberduck.VBEEditor/WindowsApi/User32.cs b/Rubberduck.VBEEditor/WindowsApi/User32.cs index dc6a2f1170..9b182a2d70 100644 --- a/Rubberduck.VBEEditor/WindowsApi/User32.cs +++ b/Rubberduck.VBEEditor/WindowsApi/User32.cs @@ -66,5 +66,12 @@ public static class User32 /// The length of the returned class name (without the null terminator), zero on error. [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)] public static extern int GetClassName(IntPtr hWnd, StringBuilder lpClassName, int nMaxCount); + + /// Gets the parent window of this item. + /// + /// The window handle. + /// The parent window IntPtr handle. + [DllImport("User32.dll")] + internal static extern IntPtr GetParent(IntPtr hWnd); } }