diff --git a/WDAC-Policy-Wizard/app/src/BuildPage.cs b/WDAC-Policy-Wizard/app/src/BuildPage.cs index 0b41d04..d2e9369 100644 --- a/WDAC-Policy-Wizard/app/src/BuildPage.cs +++ b/WDAC-Policy-Wizard/app/src/BuildPage.cs @@ -11,7 +11,7 @@ namespace WDAC_Wizard { - public partial class BuildPage : UserControl + public partial class BuildPage : UserControl, IWizardPage { public string XmlFilePath { get; set; } // File path for the WDAC policy XML file public string BinFilePath { get; set; } // File path for the WDAC policy binary file diff --git a/WDAC-Policy-Wizard/app/src/ConfigTemplate_Control.cs b/WDAC-Policy-Wizard/app/src/ConfigTemplate_Control.cs index 1ed52f4..e2b8f67 100644 --- a/WDAC-Policy-Wizard/app/src/ConfigTemplate_Control.cs +++ b/WDAC-Policy-Wizard/app/src/ConfigTemplate_Control.cs @@ -14,7 +14,7 @@ namespace WDAC_Wizard { - public partial class ConfigTemplate_Control : UserControl + public partial class ConfigTemplate_Control : UserControl, IWizardPage { public MainWindow _MainWindow; private WDAC_Policy Policy; diff --git a/WDAC-Policy-Wizard/app/src/EditWorkflow.Designer.cs b/WDAC-Policy-Wizard/app/src/EditWorkflow.Designer.cs index 17e2a9f..14757d5 100644 --- a/WDAC-Policy-Wizard/app/src/EditWorkflow.Designer.cs +++ b/WDAC-Policy-Wizard/app/src/EditWorkflow.Designer.cs @@ -237,7 +237,7 @@ private void InitializeComponent() // buttonParseEventLog.Location = new System.Drawing.Point(343, 72); buttonParseEventLog.Name = "buttonParseEventLog"; - buttonParseEventLog.Size = new System.Drawing.Size(133, 27); + buttonParseEventLog.Size = new System.Drawing.Size(165, 32); buttonParseEventLog.TabIndex = 112; buttonParseEventLog.Text = "Parse Event Logs"; buttonParseEventLog.UseVisualStyleBackColor = true; @@ -248,7 +248,7 @@ private void InitializeComponent() buttonParseLogFile.Font = new System.Drawing.Font("Microsoft Sans Serif", 8F); buttonParseLogFile.Location = new System.Drawing.Point(343, 174); buttonParseLogFile.Name = "buttonParseLogFile"; - buttonParseLogFile.Size = new System.Drawing.Size(133, 27); + buttonParseLogFile.Size = new System.Drawing.Size(165, 32); buttonParseLogFile.TabIndex = 113; buttonParseLogFile.Text = "Parse Log File(s)"; buttonParseLogFile.UseVisualStyleBackColor = true; @@ -384,7 +384,7 @@ private void InitializeComponent() buttonParseMDELog.Font = new System.Drawing.Font("Microsoft Sans Serif", 8F); buttonParseMDELog.Location = new System.Drawing.Point(342, 273); buttonParseMDELog.Name = "buttonParseMDELog"; - buttonParseMDELog.Size = new System.Drawing.Size(133, 27); + buttonParseMDELog.Size = new System.Drawing.Size(165, 32); buttonParseMDELog.TabIndex = 124; buttonParseMDELog.Text = "Parse Log File(s)"; buttonParseMDELog.UseVisualStyleBackColor = true; diff --git a/WDAC-Policy-Wizard/app/src/EditWorkflow.cs b/WDAC-Policy-Wizard/app/src/EditWorkflow.cs index 38c6e9c..d9fe606 100644 --- a/WDAC-Policy-Wizard/app/src/EditWorkflow.cs +++ b/WDAC-Policy-Wizard/app/src/EditWorkflow.cs @@ -12,7 +12,7 @@ namespace WDAC_Wizard { - public partial class EditWorkflow : UserControl + public partial class EditWorkflow : UserControl, IWizardPage { public string EditPath { get; set; } private int NumberRules; diff --git a/WDAC-Policy-Wizard/app/src/EventLogRuleConfiguration.Designer.cs b/WDAC-Policy-Wizard/app/src/EventLogRuleConfiguration.Designer.cs index f8a0c3b..093181f 100644 --- a/WDAC-Policy-Wizard/app/src/EventLogRuleConfiguration.Designer.cs +++ b/WDAC-Policy-Wizard/app/src/EventLogRuleConfiguration.Designer.cs @@ -86,6 +86,7 @@ private void InitializeComponent() addButton.Location = new System.Drawing.Point(442, 412); addButton.Name = "addButton"; addButton.Size = new System.Drawing.Size(130, 30); + addButton.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left; addButton.TabIndex = 0; addButton.Text = "+ Add Allow Rule"; addButton.UseVisualStyleBackColor = true; @@ -106,6 +107,7 @@ private void InitializeComponent() publisherRulePanel.Location = new System.Drawing.Point(167, 447); publisherRulePanel.Name = "publisherRulePanel"; publisherRulePanel.Size = new System.Drawing.Size(716, 190); + publisherRulePanel.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left; publisherRulePanel.TabIndex = 9; // // productTextBox @@ -227,6 +229,7 @@ private void InitializeComponent() label3.Location = new System.Drawing.Point(163, 415); label3.Name = "label3"; label3.Size = new System.Drawing.Size(90, 21); + label3.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left; label3.TabIndex = 8; label3.Text = "Rule Type:"; // @@ -238,6 +241,7 @@ private void InitializeComponent() ruleTypeComboBox.Location = new System.Drawing.Point(259, 412); ruleTypeComboBox.Name = "ruleTypeComboBox"; ruleTypeComboBox.Size = new System.Drawing.Size(163, 29); + ruleTypeComboBox.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left; ruleTypeComboBox.TabIndex = 3; ruleTypeComboBox.SelectedIndexChanged += RuleTypeChanged; // @@ -256,6 +260,7 @@ private void InitializeComponent() fileAttributeRulePanel.Location = new System.Drawing.Point(906, 447); fileAttributeRulePanel.Name = "fileAttributeRulePanel"; fileAttributeRulePanel.Size = new System.Drawing.Size(551, 190); + fileAttributeRulePanel.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left; fileAttributeRulePanel.TabIndex = 21; fileAttributeRulePanel.Visible = false; // @@ -373,7 +378,7 @@ private void InitializeComponent() // eventsDataGridView.AllowUserToDeleteRows = false; eventsDataGridView.AllowUserToResizeRows = false; - eventsDataGridView.AutoSizeColumnsMode = System.Windows.Forms.DataGridViewAutoSizeColumnsMode.AllCells; + eventsDataGridView.AutoSizeColumnsMode = System.Windows.Forms.DataGridViewAutoSizeColumnsMode.None; eventsDataGridView.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize; eventsDataGridView.Columns.AddRange(new System.Windows.Forms.DataGridViewColumn[] { addedColumn, eventIdColumn, filenameColumn, productColumn, policyColumn, publisherColumn, issuerTbsHashColumn }); eventsDataGridView.EnableHeadersVisualStyles = false; @@ -387,6 +392,7 @@ private void InitializeComponent() eventsDataGridView.RowTemplate.Height = 24; eventsDataGridView.SelectionMode = System.Windows.Forms.DataGridViewSelectionMode.FullRowSelect; eventsDataGridView.Size = new System.Drawing.Size(896, 287); + eventsDataGridView.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; eventsDataGridView.TabIndex = 4; eventsDataGridView.VirtualMode = true; eventsDataGridView.CellClick += EventRowClick; @@ -395,7 +401,7 @@ private void InitializeComponent() // // addedColumn // - addedColumn.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.AllCells; + addedColumn.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.None; addedColumn.HeaderText = "Added To Policy"; addedColumn.MinimumWidth = 100; addedColumn.Name = "addedColumn"; @@ -405,7 +411,7 @@ private void InitializeComponent() // // eventIdColumn // - eventIdColumn.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.AllCells; + eventIdColumn.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.None; eventIdColumn.HeaderText = "Event Id"; eventIdColumn.MinimumWidth = 6; eventIdColumn.Name = "eventIdColumn"; @@ -415,7 +421,7 @@ private void InitializeComponent() // // filenameColumn // - filenameColumn.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.AllCells; + filenameColumn.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.None; filenameColumn.HeaderText = "Filename"; filenameColumn.MinimumWidth = 6; filenameColumn.Name = "filenameColumn"; @@ -425,7 +431,7 @@ private void InitializeComponent() // // productColumn // - productColumn.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.AllCells; + productColumn.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.None; productColumn.HeaderText = "Product"; productColumn.MinimumWidth = 6; productColumn.Name = "productColumn"; @@ -435,7 +441,7 @@ private void InitializeComponent() // // policyColumn // - policyColumn.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.AllCells; + policyColumn.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.None; policyColumn.HeaderText = "Policy Name"; policyColumn.MinimumWidth = 6; policyColumn.Name = "policyColumn"; @@ -445,7 +451,7 @@ private void InitializeComponent() // // publisherColumn // - publisherColumn.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.AllCells; + publisherColumn.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.None; publisherColumn.HeaderText = "Publisher"; publisherColumn.MinimumWidth = 6; publisherColumn.Name = "publisherColumn"; @@ -482,6 +488,7 @@ private void InitializeComponent() hashRulePanel.Location = new System.Drawing.Point(765, 664); hashRulePanel.Name = "hashRulePanel"; hashRulePanel.Size = new System.Drawing.Size(603, 93); + hashRulePanel.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left; hashRulePanel.TabIndex = 22; hashRulePanel.Visible = false; // @@ -534,6 +541,7 @@ private void InitializeComponent() filePathRulePanel.Location = new System.Drawing.Point(16, 664); filePathRulePanel.Name = "filePathRulePanel"; filePathRulePanel.Size = new System.Drawing.Size(743, 93); + filePathRulePanel.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left; filePathRulePanel.TabIndex = 22; filePathRulePanel.Visible = false; // @@ -595,6 +603,7 @@ private void InitializeComponent() // AutoScaleDimensions = new System.Drawing.SizeF(120F, 120F); AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi; + AutoScroll = true; BackColor = System.Drawing.Color.White; Controls.Add(publisherRulePanel); Controls.Add(label3); diff --git a/WDAC-Policy-Wizard/app/src/EventLogRuleConfiguration.cs b/WDAC-Policy-Wizard/app/src/EventLogRuleConfiguration.cs index 50d2995..c3e9831 100644 --- a/WDAC-Policy-Wizard/app/src/EventLogRuleConfiguration.cs +++ b/WDAC-Policy-Wizard/app/src/EventLogRuleConfiguration.cs @@ -12,7 +12,7 @@ namespace WDAC_Wizard { - public partial class EventLogRuleConfiguration : UserControl + public partial class EventLogRuleConfiguration : UserControl, IWizardPage { private List CiEvents; // Declare an ArrayList to serve as the data store. @@ -94,6 +94,10 @@ private void DisplayEvents() this.DisplayObjects.Add(dpObject); this.eventsDataGridView.RowCount += 1; } + + // Size each column to fit its text. Columns remain user-resizable afterwards + // since AutoSizeColumnsMode stays None. + GridLayoutHelper.AutoFitColumns(this.eventsDataGridView); } /// @@ -188,15 +192,14 @@ private void RowSelectionChanged(object sender, EventArgs e) return; } - // Set the UI - ResetCustomRulesPanel(); - SetPublisherPanel(this.CiEvents[selectedRow].SignerInfo.IssuerName, - this.CiEvents[selectedRow].SignerInfo.PublisherName, - this.CiEvents[selectedRow].OriginalFilename, - this.CiEvents[selectedRow].FileVersion, - this.CiEvents[selectedRow].ProductName); - + // Update the selected row before refreshing the UI so that the + // rule type panel reflects the newly selected event. this.SelectedRow = selectedRow; + + // Set the UI. Keep the user's current rule type selection and + // repopulate the matching panel for the newly selected row. + ResetCustomRulesPanel(); + RefreshRulePanelForSelectedRow(); } /// @@ -206,14 +209,7 @@ private void RowSelectionChanged(object sender, EventArgs e) /// private void EventRowClick(object sender, DataGridViewCellEventArgs e) { - // Set the UI - ResetCustomRulesPanel(); - int selectedRow = e.RowIndex; - if(selectedRow >= this.CiEvents.Count) - { - return; - } // Header selected, sort table if(selectedRow == -1) @@ -221,14 +217,19 @@ private void EventRowClick(object sender, DataGridViewCellEventArgs e) SortDataGrid(sender, e); return; } - - SetPublisherPanel(this.CiEvents[selectedRow].SignerInfo.IssuerName, - this.CiEvents[selectedRow].SignerInfo.PublisherName, - this.CiEvents[selectedRow].OriginalFilename, - this.CiEvents[selectedRow].FileVersion, - this.CiEvents[selectedRow].ProductName); + if(selectedRow >= this.CiEvents.Count) + { + return; + } + + // Update the selected row before refreshing the UI this.SelectedRow = selectedRow; + + // Set the UI. Keep the user's current rule type selection and + // repopulate the matching panel for the newly selected row. + ResetCustomRulesPanel(); + RefreshRulePanelForSelectedRow(); } /// @@ -410,8 +411,8 @@ private void ResetCustomRulesPanel() this.versionTextBox.Clear(); this.productTextBox.Clear(); - // Dropdown - this.ruleTypeComboBox.SelectedIndex = 0; + // NOTE: The rule type dropdown is intentionally left unchanged so the + // user's selection persists as they navigate between rows. } @@ -725,6 +726,20 @@ private void RuleTypeChanged(object sender, EventArgs e) // Path // File Attributes // File Hash + RefreshRulePanelForSelectedRow(); + } + + /// + /// Populates the rule details panel for the currently selected row based + /// on the rule type currently chosen in the dropdown. The dropdown + /// selection is intentionally preserved as the user navigates rows. + /// + private void RefreshRulePanelForSelectedRow() + { + if (this.SelectedRow < 0 || this.SelectedRow >= this.CiEvents.Count) + { + return; + } HideAllPanels(); diff --git a/WDAC-Policy-Wizard/app/src/Exceptions_Control.Designer.cs b/WDAC-Policy-Wizard/app/src/Exceptions_Control.Designer.cs index be10767..92bf979 100644 --- a/WDAC-Policy-Wizard/app/src/Exceptions_Control.Designer.cs +++ b/WDAC-Policy-Wizard/app/src/Exceptions_Control.Designer.cs @@ -290,7 +290,7 @@ private void InitializeComponent() // dataGridView_Exceptions // this.dataGridView_Exceptions.AllowUserToDeleteRows = false; - this.dataGridView_Exceptions.AutoSizeColumnsMode = System.Windows.Forms.DataGridViewAutoSizeColumnsMode.AllCells; + this.dataGridView_Exceptions.AutoSizeColumnsMode = System.Windows.Forms.DataGridViewAutoSizeColumnsMode.None; this.dataGridView_Exceptions.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize; this.dataGridView_Exceptions.Columns.AddRange(new System.Windows.Forms.DataGridViewColumn[] { this.column_Action, @@ -420,6 +420,7 @@ private void InitializeComponent() // this.AutoScaleDimensions = new System.Drawing.SizeF(120F, 120F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi; + this.AutoScroll = true; this.BackColor = System.Drawing.Color.White; this.Controls.Add(this.panel_ExceptionRule); this.Name = "Exceptions_Control"; diff --git a/WDAC-Policy-Wizard/app/src/Exceptions_Control.cs b/WDAC-Policy-Wizard/app/src/Exceptions_Control.cs index 8287204..108b763 100644 --- a/WDAC-Policy-Wizard/app/src/Exceptions_Control.cs +++ b/WDAC-Policy-Wizard/app/src/Exceptions_Control.cs @@ -469,6 +469,10 @@ public void AddException() this.displayObjects.Add(displayObject); this.dataGridView_Exceptions.RowCount += 1; + // Size each column to fit its text. Columns remain user-resizable afterwards + // since AutoSizeColumnsMode stays None. + GridLayoutHelper.AutoFitColumns(this.dataGridView_Exceptions); + // Scroll to bottom to see new rule added to list this.dataGridView_Exceptions.FirstDisplayedScrollingRowIndex = this.dataGridView_Exceptions.RowCount - 1; this.ExceptionRule = new PolicyCustomRules(); diff --git a/WDAC-Policy-Wizard/app/src/GridLayoutHelper.cs b/WDAC-Policy-Wizard/app/src/GridLayoutHelper.cs new file mode 100644 index 0000000..266d1b9 --- /dev/null +++ b/WDAC-Policy-Wizard/app/src/GridLayoutHelper.cs @@ -0,0 +1,55 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using System.Windows.Forms; + +namespace WDAC_Wizard +{ + /// + /// Helper utilities for DataGridView layout behavior shared across the wizard pages. + /// + internal static class GridLayoutHelper + { + /// + /// Sizes each column to fit its content. The columns remain user-resizable afterwards + /// because the grid's AutoSizeColumnsMode is left as None. + /// The resize is deferred until the grid has a created handle so that text measurement + /// works correctly (calling it too early, e.g. during Load, is a silent no-op). + /// + /// The DataGridView to resize. + public static void AutoFitColumns(DataGridView grid) + { + if (grid == null) + { + return; + } + + void Resize() + { + if (grid.IsDisposed || grid.ColumnCount == 0) + { + return; + } + + grid.AutoResizeColumns(DataGridViewAutoSizeColumnsMode.AllCells); + } + + if (grid.IsHandleCreated) + { + // Defer to the message loop so it runs after the current layout pass completes. + grid.BeginInvoke((MethodInvoker)Resize); + } + else + { + // Handle not created yet (e.g. populated during Load). Resize once it is. + void Handler(object sender, System.EventArgs e) + { + grid.HandleCreated -= Handler; + grid.BeginInvoke((MethodInvoker)Resize); + } + + grid.HandleCreated += Handler; + } + } + } +} diff --git a/WDAC-Policy-Wizard/app/src/IWizardPage.cs b/WDAC-Policy-Wizard/app/src/IWizardPage.cs new file mode 100644 index 0000000..062e0e9 --- /dev/null +++ b/WDAC-Policy-Wizard/app/src/IWizardPage.cs @@ -0,0 +1,12 @@ +namespace WDAC_Wizard +{ + /// + /// Marker interface implemented by full-page wizard content (UserControls) that the MainWindow + /// hosts and docks to fill its client area. The host uses this marker to dock only intended + /// wizard pages, rather than every UserControl that may be added to the form. Helper or + /// non-page UserControls should NOT implement this interface so they keep their own layout. + /// + public interface IWizardPage + { + } +} diff --git a/WDAC-Policy-Wizard/app/src/MainForm.Designer.cs b/WDAC-Policy-Wizard/app/src/MainForm.Designer.cs index 9e654d4..c70f674 100644 --- a/WDAC-Policy-Wizard/app/src/MainForm.Designer.cs +++ b/WDAC-Policy-Wizard/app/src/MainForm.Designer.cs @@ -101,6 +101,7 @@ private void InitializeComponent() label_Info.Tag = "IgnoreDarkMode"; label_Info.Text = "Info Text"; label_Info.Visible = false; + label_Info.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left; // // backgroundWorker1 // @@ -164,6 +165,7 @@ private void InitializeComponent() control_Panel.Margin = new System.Windows.Forms.Padding(3, 2, 3, 2); control_Panel.Name = "control_Panel"; control_Panel.Size = new System.Drawing.Size(150, 700); + control_Panel.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left; control_Panel.TabIndex = 30; // // workflow_Label @@ -334,6 +336,7 @@ private void InitializeComponent() settings_Button.Name = "settings_Button"; settings_Button.Size = new System.Drawing.Size(129, 45); settings_Button.TabIndex = 31; + settings_Button.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left; settings_Button.Text = " Settings"; settings_Button.TextAlign = System.Drawing.ContentAlignment.MiddleLeft; settings_Button.UseVisualStyleBackColor = false; @@ -346,6 +349,7 @@ private void InitializeComponent() button_Next.Name = "button_Next"; button_Next.Size = new System.Drawing.Size(93, 33); button_Next.TabIndex = 31; + button_Next.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right; button_Next.Text = "Next"; button_Next.UseVisualStyleBackColor = true; button_Next.Visible = false; @@ -416,10 +420,12 @@ private void InitializeComponent() Controls.Add(button_Edit); Controls.Add(label_Info); Controls.Add(button_New); - FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog; + FormBorderStyle = System.Windows.Forms.FormBorderStyle.Sizable; HelpButton = true; Icon = (System.Drawing.Icon)resources.GetObject("$this.Icon"); Margin = new System.Windows.Forms.Padding(3, 2, 3, 2); + MaximizeBox = true; + MinimumSize = new System.Drawing.Size(900, 600); Name = "MainWindow"; StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen; Text = "App Control Policy Wizard"; diff --git a/WDAC-Policy-Wizard/app/src/MainForm.cs b/WDAC-Policy-Wizard/app/src/MainForm.cs index 6fe5073..bc1f97a 100644 --- a/WDAC-Policy-Wizard/app/src/MainForm.cs +++ b/WDAC-Policy-Wizard/app/src/MainForm.cs @@ -83,9 +83,38 @@ public MainWindow() Helper.LicenseCheck(); } - // ############### - // HEADER CONTROLS - // ############### + // ##################### + // DYNAMIC LAYOUT SUPPORT + // ##################### + + /// + /// Hosts a wizard page (UserControl) so it fills the MainWindow client area and tracks the + /// window as it is resized or maximized. Each page now manages its own responsive layout + /// internally (via WinForms layout containers and Dock/Anchor), so the host only needs to + /// dock the page to fill the available space. + /// + private void RegisterPage(Control page) + { + if (page == null) + { + return; + } + + page.Dock = DockStyle.Fill; + } + + /// + /// Automatically docks wizard pages added to the MainWindow so their content expands and + /// contracts with the host window. Only controls marked with are + /// docked, so helper/non-page UserControls keep their own layout and are not forcibly docked. + /// + private void MainWindow_ControlAdded(object sender, ControlEventArgs e) + { + if (e.Control is IWizardPage) + { + RegisterPage(e.Control); + } + } /// /// New policy button selected: User can select either base or suppl policy, @@ -2239,7 +2268,10 @@ private void MainWindow_Load(object sender, EventArgs e) SetControlPanelUI(); // Set UI for the 'Next' Button - SetNextButtonUI(); + SetNextButtonUI(); + + // Dock wizard pages so their content fills and tracks the window as it is resized + this.ControlAdded += MainWindow_ControlAdded; } /// diff --git a/WDAC-Policy-Wizard/app/src/PolicyMerge_Control.Designer.cs b/WDAC-Policy-Wizard/app/src/PolicyMerge_Control.Designer.cs index 3006639..e008ae4 100644 --- a/WDAC-Policy-Wizard/app/src/PolicyMerge_Control.Designer.cs +++ b/WDAC-Policy-Wizard/app/src/PolicyMerge_Control.Designer.cs @@ -170,6 +170,7 @@ private void InitializeComponent() // AutoScaleDimensions = new System.Drawing.SizeF(120F, 120F); AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi; + AutoScroll = true; Controls.Add(label_Error); Controls.Add(button_RemovePolicy); Controls.Add(button_AddPolicy); diff --git a/WDAC-Policy-Wizard/app/src/PolicyMerge_Control.cs b/WDAC-Policy-Wizard/app/src/PolicyMerge_Control.cs index 8965172..7e7c216 100644 --- a/WDAC-Policy-Wizard/app/src/PolicyMerge_Control.cs +++ b/WDAC-Policy-Wizard/app/src/PolicyMerge_Control.cs @@ -12,7 +12,7 @@ namespace WDAC_Wizard.src { - public partial class PolicyMerge_Control : UserControl + public partial class PolicyMerge_Control : UserControl, IWizardPage { private int nPolicies; private string mergePolicyPath; @@ -110,6 +110,10 @@ private void Button_AddPolicy_Click(object sender, EventArgs e) this.displayObjects.Add(new DisplayObject(this.nPolicies.ToString(), policyPath)); this.policiesDataGrid.RowCount += 1; + // Size each column to fit its text. Columns remain user-resizable + // afterwards since AutoSizeColumnsMode stays None. + GridLayoutHelper.AutoFitColumns(this.policiesDataGrid); + this._MainWindow.Policy.PoliciesToMerge = this.policiesToMerge; if (this.nPolicies >= 2 && !String.IsNullOrEmpty(this.mergePolicyPath)) diff --git a/WDAC-Policy-Wizard/app/src/PolicyType.cs b/WDAC-Policy-Wizard/app/src/PolicyType.cs index 9f8b4ca..3e6f2ad 100644 --- a/WDAC-Policy-Wizard/app/src/PolicyType.cs +++ b/WDAC-Policy-Wizard/app/src/PolicyType.cs @@ -11,7 +11,7 @@ namespace WDAC_Wizard { - public partial class PolicyType : UserControl + public partial class PolicyType : UserControl, IWizardPage { public string BaseToSupplementPath { get; set; } // Path to the supplemental policy on disk diff --git a/WDAC-Policy-Wizard/app/src/SettingsPage.cs b/WDAC-Policy-Wizard/app/src/SettingsPage.cs index d867855..3a3cdc4 100644 --- a/WDAC-Policy-Wizard/app/src/SettingsPage.cs +++ b/WDAC-Policy-Wizard/app/src/SettingsPage.cs @@ -17,7 +17,7 @@ namespace WDAC_Wizard { - public partial class SettingsPage : UserControl + public partial class SettingsPage : UserControl, IWizardPage { private Dictionary SettingsDict; private MainWindow _MainWindow; diff --git a/WDAC-Policy-Wizard/app/src/SigningRules_Control.Designer.cs b/WDAC-Policy-Wizard/app/src/SigningRules_Control.Designer.cs index 1653d53..bd98524 100644 --- a/WDAC-Policy-Wizard/app/src/SigningRules_Control.Designer.cs +++ b/WDAC-Policy-Wizard/app/src/SigningRules_Control.Designer.cs @@ -82,7 +82,7 @@ private void InitializeComponent() // rulesDataGrid // this.rulesDataGrid.AllowUserToDeleteRows = false; - this.rulesDataGrid.AutoSizeColumnsMode = System.Windows.Forms.DataGridViewAutoSizeColumnsMode.AllCells; + this.rulesDataGrid.AutoSizeColumnsMode = System.Windows.Forms.DataGridViewAutoSizeColumnsMode.None; dataGridViewCellStyle1.Alignment = System.Windows.Forms.DataGridViewContentAlignment.MiddleLeft; dataGridViewCellStyle1.Font = new System.Drawing.Font("Microsoft Sans Serif", 7.8F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); dataGridViewCellStyle1.WrapMode = System.Windows.Forms.DataGridViewTriState.True; @@ -106,62 +106,70 @@ private void InitializeComponent() this.rulesDataGrid.RowTemplate.Height = 24; this.rulesDataGrid.SelectionMode = System.Windows.Forms.DataGridViewSelectionMode.FullRowSelect; this.rulesDataGrid.Size = new System.Drawing.Size(879, 440); + this.rulesDataGrid.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) | System.Windows.Forms.AnchorStyles.Left) | System.Windows.Forms.AnchorStyles.Right))); this.rulesDataGrid.TabIndex = 92; this.rulesDataGrid.VirtualMode = true; this.rulesDataGrid.CellValueNeeded += new System.Windows.Forms.DataGridViewCellValueEventHandler(this.RulesDataGrid_CellValueNeeded); + this.rulesDataGrid.ColumnHeaderMouseClick += new System.Windows.Forms.DataGridViewCellMouseEventHandler(this.RulesDataGrid_ColumnHeaderMouseClick); // // column_Action // - this.column_Action.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.AllCells; + this.column_Action.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.None; this.column_Action.HeaderText = "Action"; this.column_Action.MinimumWidth = 6; this.column_Action.Name = "column_Action"; this.column_Action.ReadOnly = true; + this.column_Action.SortMode = System.Windows.Forms.DataGridViewColumnSortMode.Programmatic; this.column_Action.Width = 76; // // column_Level // - this.column_Level.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.AllCells; + this.column_Level.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.None; this.column_Level.HeaderText = "Level"; this.column_Level.MinimumWidth = 6; this.column_Level.Name = "column_Level"; this.column_Level.ReadOnly = true; + this.column_Level.SortMode = System.Windows.Forms.DataGridViewColumnSortMode.Programmatic; this.column_Level.Width = 71; // // Column_Name // - this.Column_Name.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.AllCells; + this.Column_Name.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.None; this.Column_Name.HeaderText = "Name"; this.Column_Name.MinimumWidth = 6; this.Column_Name.Name = "Column_Name"; this.Column_Name.ReadOnly = true; + this.Column_Name.SortMode = System.Windows.Forms.DataGridViewColumnSortMode.Programmatic; this.Column_Name.Width = 74; // // Column_Files // - this.Column_Files.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.AllCells; + this.Column_Files.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.None; this.Column_Files.HeaderText = "Associated Files"; this.Column_Files.MinimumWidth = 6; this.Column_Files.Name = "Column_Files"; this.Column_Files.ReadOnly = true; + this.Column_Files.SortMode = System.Windows.Forms.DataGridViewColumnSortMode.Programmatic; this.Column_Files.Width = 127; // // Column_Exceptions // - this.Column_Exceptions.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.AllCells; + this.Column_Exceptions.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.None; this.Column_Exceptions.HeaderText = "Exceptions"; this.Column_Exceptions.MinimumWidth = 6; this.Column_Exceptions.Name = "Column_Exceptions"; this.Column_Exceptions.ReadOnly = true; + this.Column_Exceptions.SortMode = System.Windows.Forms.DataGridViewColumnSortMode.Programmatic; this.Column_Exceptions.Width = 105; // // column_ID // - this.column_ID.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.AllCells; + this.column_ID.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.None; this.column_ID.HeaderText = "Rule ID"; this.column_ID.MinimumWidth = 8; this.column_ID.Name = "column_ID"; this.column_ID.ReadOnly = true; + this.column_ID.SortMode = System.Windows.Forms.DataGridViewColumnSortMode.Programmatic; this.column_ID.Width = 77; // // label8 @@ -195,6 +203,7 @@ private void InitializeComponent() this.deleteButton.Margin = new System.Windows.Forms.Padding(2, 3, 2, 3); this.deleteButton.Name = "deleteButton"; this.deleteButton.Size = new System.Drawing.Size(114, 26); + this.deleteButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); this.deleteButton.TabIndex = 93; this.deleteButton.Text = "- Remove Rule"; this.deleteButton.TextAlign = System.Drawing.ContentAlignment.MiddleRight; @@ -211,6 +220,7 @@ private void InitializeComponent() this.label_Error.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0); this.label_Error.Name = "label_Error"; this.label_Error.Size = new System.Drawing.Size(648, 18); + this.label_Error.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); this.label_Error.TabIndex = 96; this.label_Error.Tag = "IgnoreDarkMode"; this.label_Error.Text = "Label_Error: Lorem Ipsum text text text text. Lorum Ipsum text text text text tex" + @@ -224,6 +234,7 @@ private void InitializeComponent() this.addButton.Margin = new System.Windows.Forms.Padding(2, 3, 2, 3); this.addButton.Name = "addButton"; this.addButton.Size = new System.Drawing.Size(146, 26); + this.addButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); this.addButton.TabIndex = 97; this.addButton.Text = "+ Add Custom Rule"; this.addButton.TextAlign = System.Drawing.ContentAlignment.MiddleRight; @@ -236,6 +247,7 @@ private void InitializeComponent() this.checkBox_KernelList.Location = new System.Drawing.Point(163, 634); this.checkBox_KernelList.Name = "checkBox_KernelList"; this.checkBox_KernelList.Size = new System.Drawing.Size(320, 21); + this.checkBox_KernelList.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); this.checkBox_KernelList.TabIndex = 98; this.checkBox_KernelList.Text = "Merge with Recommended Kernel Block Rules"; this.checkBox_KernelList.UseVisualStyleBackColor = true; @@ -248,6 +260,7 @@ private void InitializeComponent() this.checkBox_UserModeList.Location = new System.Drawing.Point(163, 607); this.checkBox_UserModeList.Name = "checkBox_UserModeList"; this.checkBox_UserModeList.Size = new System.Drawing.Size(348, 21); + this.checkBox_UserModeList.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); this.checkBox_UserModeList.TabIndex = 99; this.checkBox_UserModeList.Text = "Merge with Recommended User Mode Block Rules"; this.checkBox_UserModeList.UseVisualStyleBackColor = false; @@ -295,6 +308,7 @@ private void InitializeComponent() // this.AutoScaleDimensions = new System.Drawing.SizeF(120F, 120F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi; + this.AutoScroll = true; this.BackColor = System.Drawing.Color.White; this.BackgroundImageLayout = System.Windows.Forms.ImageLayout.Center; this.Controls.Add(this.panel_Progress); diff --git a/WDAC-Policy-Wizard/app/src/SigningRules_Control.cs b/WDAC-Policy-Wizard/app/src/SigningRules_Control.cs index 91a0078..955f987 100644 --- a/WDAC-Policy-Wizard/app/src/SigningRules_Control.cs +++ b/WDAC-Policy-Wizard/app/src/SigningRules_Control.cs @@ -16,7 +16,7 @@ namespace WDAC_Wizard { - public partial class SigningRules_Control : UserControl + public partial class SigningRules_Control : UserControl, IWizardPage { // CI Policy objects public WDAC_Policy Policy; @@ -504,6 +504,10 @@ private void DisplayRules() } } } + + // Size each column to fit its text once all rules are loaded. Columns remain + // user-resizable afterwards since AutoSizeColumnsMode stays None. + GridLayoutHelper.AutoFitColumns(this.rulesDataGrid); } @@ -1137,6 +1141,139 @@ private void RemoveRuleIdFromFileAttribs(string ruleId) } } + /// + /// Sorts the rules grid by the clicked column header. Mirrors the event grid behavior: + /// the first click on a column sorts ascending and subsequent clicks toggle the direction. + /// The underlying displayObjects data store and the custom rule RowNumber references are + /// kept in sync so deletion and editing continue to map to the correct rows. + /// + /// + /// + private void RulesDataGrid_ColumnHeaderMouseClick(object sender, DataGridViewCellMouseEventArgs e) + { + if (e.ColumnIndex < 0 || this.displayObjects.Count == 0) + { + return; + } + + string columnName = this.rulesDataGrid.Columns[e.ColumnIndex].Name; + + // Determine sort direction. If the column is already the sorted column, toggle the + // direction. Otherwise sort ascending and clear the sorted tag on the other columns. + bool ascending = true; + if (this.rulesDataGrid.Columns[e.ColumnIndex].Tag as string == "SortedAsc") + { + ascending = false; + } + + // Project the display objects onto a sortable list paired with their original row index + // so the custom rule RowNumber references can be remapped after sorting. + var indexedObjects = new List>(); + for (int i = 0; i < this.displayObjects.Count; i++) + { + indexedObjects.Add(new KeyValuePair(i, (DisplayObject)this.displayObjects[i])); + } + + Comparison comparison = GetDisplayObjectComparison(columnName); + indexedObjects.Sort((x, y) => + { + int result = comparison(x.Value, y.Value); + return ascending ? result : -result; + }); + + // Map each original row index to its new sorted position. + var oldToNewIndex = new Dictionary(); + for (int newIndex = 0; newIndex < indexedObjects.Count; newIndex++) + { + oldToNewIndex[indexedObjects[newIndex].Key] = newIndex; + } + + // Rebuild the display objects data store in the sorted order. + this.displayObjects.Clear(); + foreach (var pair in indexedObjects) + { + this.displayObjects.Add(pair.Value); + } + + // Remap the in-session custom rule row references so deletion still targets the right row. + if (this.Policy != null && this.Policy.CustomRules != null) + { + foreach (var customRule in this.Policy.CustomRules) + { + if (oldToNewIndex.TryGetValue(customRule.RowNumber, out int newRowNumber)) + { + customRule.RowNumber = newRowNumber; + } + } + } + + // Update the sorted tags so the next click toggles the direction. + foreach (DataGridViewColumn column in this.rulesDataGrid.Columns) + { + column.Tag = null; + column.HeaderCell.SortGlyphDirection = SortOrder.None; + } + + this.rulesDataGrid.Columns[e.ColumnIndex].Tag = ascending ? "SortedAsc" : "SortedDesc"; + this.rulesDataGrid.Columns[e.ColumnIndex].HeaderCell.SortGlyphDirection = + ascending ? SortOrder.Ascending : SortOrder.Descending; + + // Repaint the virtual-mode grid with the newly ordered data store. + this.rulesDataGrid.Refresh(); + } + + /// + /// Returns the comparison delegate used to sort the display objects for the given column. + /// Empty/null values are sorted to the bottom for ascending sorts. + /// + /// + /// + private Comparison GetDisplayObjectComparison(string columnName) + { + switch (columnName) + { + case "column_Action": + return (x, y) => CompareDisplayStrings(x.Action, y.Action); + + case "column_Level": + return (x, y) => CompareDisplayStrings(x.Level, y.Level); + + case "Column_Name": + return (x, y) => CompareDisplayStrings(x.Name, y.Name); + + case "Column_Files": + return (x, y) => CompareDisplayStrings(x.Files, y.Files); + + case "Column_Exceptions": + return (x, y) => CompareDisplayStrings(x.Exceptions, y.Exceptions); + + case "column_ID": + return (x, y) => CompareDisplayStrings(x.Id, y.Id); + + default: + return (x, y) => 0; + } + } + + /// + /// Case-insensitive string comparison that sorts empty/null values to the bottom. + /// + /// + /// + /// + private static int CompareDisplayStrings(string x, string y) + { + bool xEmpty = string.IsNullOrWhiteSpace(x); + bool yEmpty = string.IsNullOrWhiteSpace(y); + + // Sort empty/null strings to the bottom + if (xEmpty && yEmpty) return 0; + if (xEmpty) return 1; + if (yEmpty) return -1; + + return string.Compare(x, y, StringComparison.CurrentCultureIgnoreCase); + } + /// /// Sets the display object when the DataGridView needed to paint data /// @@ -1213,6 +1350,10 @@ public void AddRuleToTable(string [] displayObjectArray, PolicyCustomRules custo this.displayObjects.Add(new DisplayObject(action, level, name, files, exceptions)); this.rulesDataGrid.RowCount += 1; + // Size each column to fit its text. Columns remain user-resizable afterwards + // since AutoSizeColumnsMode stays None. + GridLayoutHelper.AutoFitColumns(this.rulesDataGrid); + // Add custom list to RulesList this.Policy.CustomRules.Add(customRule); diff --git a/WDAC-Policy-Wizard/app/src/TemplatePage.cs b/WDAC-Policy-Wizard/app/src/TemplatePage.cs index 698d0e9..9ee71f9 100644 --- a/WDAC-Policy-Wizard/app/src/TemplatePage.cs +++ b/WDAC-Policy-Wizard/app/src/TemplatePage.cs @@ -11,7 +11,7 @@ namespace WDAC_Wizard { - public partial class TemplatePage : UserControl + public partial class TemplatePage : UserControl, IWizardPage { // Properties to maintain the policy mode selected private MainWindow _MainWindow;