How-To Create a Snap-in That Uses Property Pages
Applies To: Windows 10, Windows 7, Windows 8, Windows 8.1, Windows Server 2008, Windows Server 2008 R2, Windows Server 2012, Windows Server 2012 R2, Windows Server Technical Preview, Windows Vista
Property sheets are primarily used for editing the properties of a selected object, such as a scope node, a result node, or any other custom object. Snap-ins can associate multiple property pages with the property sheets for selected objects. Property sheets can be shown through the Properties standard verb or explicitly through a custom action.
MMC supports two ways of creating a property sheet. One of them is user-initiated and the other is snap-in initiated. A user can initiate the display of a property sheet by selecting the Properties context menu item. The context menu item is available when a properties verb is enabled for a scope node, a result node, or a multi-selection. When the Properties context menu is selected, MMC creates a property sheet and queries the snap-in for property pages to be displayed.
Snap-ins can also explicitly initiate a property sheet or a wizard. The ability of a snap-in to create multiple property sheets for the same node, a result node, or a multi-selection is limited. There can only be one property sheet at any given time for a node.
This sample creates a snap-in that uses property pages. It consists of four class definitions and the piece of code that implements the designer for the property sheet.
Special Note: MMC creates property sheets as modeless dialogs. The window owner of a property sheet is the desktop and the z-order of the windows on the screen is managed by Windows, not by MMC. Property sheets do not have a taskbar presence, and do not show up in the MMC open windows list. Unfortunately, property sheets are easily hidden behind other windows. Sometimes, selecting another results pane item in MMC can bring the MMC console window to the front of an already opened property sheet. With this behavior in mind, design a user interface that encourages a user to close a property sheet before clicking anywhere else.
Create PropertySheetSnapIn (PropertySheetSnapIn.cs file)
Create a PropertySheetSnapIn class and add the RunInstaller and SnapInSettings attributes to it. This step is similar to the step for creating a snap-in in the previous samples.
using System; using System.ComponentModel; using System.Security.Permissions; using Microsoft.ManagementConsole; [assembly: PermissionSetAttribute(SecurityAction.RequestMinimum, Unrestricted = true)] namespace Microsoft.ManagementConsole.Samples { /// <summary> /// Allows the .Net framework to install the assembly. /// </summary> [RunInstaller(true)] public class InstallUtilSupport : SnapInInstaller { } /// <summary> /// Provides the main entry point for the creation of a snap-in. /// </summary> [SnapInSettings("{2D3BD1F0-1404-4fcf-BBE1-0F45E908E923}", DisplayName = "- Property Sheet Sample", Description = "User List with Property Sheet SnapIn")] public class PropertySheetSnapIn : SnapIn { /// <summary> /// Constructor /// </summary> public PropertySheetSnapIn() { // Create the snap-in node. ScopeNode scopeNode = new SampleScopeNode(); scopeNode.DisplayName = "Property Sheet Sample"; this.RootNode = scopeNode; this.RootNode.EnabledStandardVerbs = StandardVerbs.Properties; ... } } ... } // namespace
Create an MMC list view description and give it a display name. We will subsequently define a user list view (UserListView) that demonstrates the use of property pages. Presently, we assign its type as the view type of the list view description. We will allow the selection of one object at a time by setting the Options property on the list view description to SingleSelect.
// Create result list view for the snap-in. MmcListViewDescription mmcListViewDescription = new MmcListViewDescription(); mmcListViewDescription.DisplayName = "User List with Properties"; mmcListViewDescription.ViewType = typeof(UserListView); mmcListViewDescription.Options = MmcListViewOptions.SingleSelect; scopeNode.ViewDescriptions.Add(mmcListViewDescription); scopeNode.ViewDescriptions.DefaultIndex = 0;
Here is the complete code for the PropertySheetSnapIn class.
using System; using System.ComponentModel; using System.Security.Permissions; using Microsoft.ManagementConsole; [assembly: PermissionSetAttribute(SecurityAction.RequestMinimum, Unrestricted = true)] namespace Microsoft.ManagementConsole.Samples { /// <summary> /// Allows the .Net framework to install the assembly. /// </summary> [RunInstaller(true)] public class InstallUtilSupport : SnapInInstaller { } /// <summary> /// Provides the main entry point for the creation of a snap-in. /// </summary> [SnapInSettings("{2D3BD1F0-1404-4fcf-BBE1-0F45E908E923}", DisplayName = "- Property Sheet Sample", Description = "User List with Property Sheet SnapIn")] public class PropertySheetSnapIn : SnapIn { /// <summary> /// Constructor /// </summary> public PropertySheetSnapIn() { // Create the snap-in node. ScopeNode scopeNode = new SampleScopeNode(); scopeNode.DisplayName = "Property Sheet Sample"; this.RootNode = scopeNode; this.RootNode.EnabledStandardVerbs = StandardVerbs.Properties; // Create result list view for the snap-in. MmcListViewDescription mmcListViewDescription = new MmcListViewDescription(); mmcListViewDescription.DisplayName = "User List with Properties"; mmcListViewDescription.ViewType = typeof(UserListView); mmcListViewDescription.Options = MmcListViewOptions.SingleSelect; scopeNode.ViewDescriptions.Add(mmcListViewDescription); scopeNode.ViewDescriptions.DefaultIndex = 0; } } /// <summary> /// /// </summary> public class SampleScopeNode : ScopeNode { /// <summary> /// Constructor /// </summary> public SampleScopeNode() { } /// <summary> /// OnAddPropertyPages is used to get the property pages to show. /// (triggered by Properties verbs) /// </summary> /// <param name="propertyPageCollection">property pages</param> protected override void OnAddPropertyPages(PropertyPageCollection propertyPageCollection) { propertyPageCollection.Add(new ScopePropertyPage(this)); } } } // namespace
Create UserListView class (UserListView.cs file)
Create an MMC list view class called UserListView that will be used to present a list in the results pane.
using System; using System.ComponentModel; using Microsoft.ManagementConsole; namespace Microsoft.ManagementConsole.Samples { /// <summary> /// Provides the base functionality required to present a list in the results pane. /// </summary> public class UserListView : MmcListView { /// <summary> /// Constructor. /// </summary> public UserListView() { } ... } // class } // namespace
Add the following methods to this class, starting with an OnInitialize method. This method creates Users and Birthday columns for the list view.
/// <summary> /// Initialize. /// </summary> /// <param name="status"></param> protected override void OnInitialize(AsyncStatus status) { // Call the parent class method. base.OnInitialize(status); // Use the default column that already exists. this.Columns[0].Title = "User"; this.Columns[0].SetWidth(300); // Create subsequent columns. this.Columns.Add(new MmcListViewColumn("Birthday", 200)); // Populate the list. Refresh(); //Create a Refresh action that resets the list view. this.ActionsPaneItems.Add(new Action("Refresh", "refresh", -1, "Refresh")); }
Define what happens when the Refresh action is triggered.
/// <summary> /// Handle the execution of the global action. /// </summary> /// <param name="action"></param> /// <param name="status"></param> protected override void OnAction(Action action, AsyncStatus status) { switch ((string)action.Tag) { case "Refresh": { Refresh(); break; } } }
Use the Refresh action to clear the list and load the list with fictitious data.
/// <summary> /// Load the list with data. /// </summary> protected void Refresh() { // Get some fictitious data to populate the lists with string[][] users = { new string[] {"Karen", "February 14th"}, new string[] {"Sue", "May 5th"}, new string[] {"Tina", "April 15th"}, new string[] {"Lisa", "March 27th"}, new string[] {"Tom", "December 25th"}, new string[] {"John", "January 1st"}, new string[] {"Harry", "October 31st"}, new string[] {"Bob", "July 4th"} }; // Remove any existing data. this.ResultNodes.Clear(); // Re-populate the list. foreach (string[] user in users) { ResultNode userNode = new ResultNode(); userNode.DisplayName = user[0]; userNode.SubItemDisplayNames.Add(user[1]); this.ResultNodes.Add(userNode); } }
Define what happens when a selection changes in the list view. Since we are using a single selection, the code is designed to act only on the selected row.
/// <summary> /// Handles changes in list view selection. Only acts on the first selected row. /// </summary> /// <param name="status"></param> protected override void OnSelectionChanged(SyncStatus status) { int count = SelectedNodes.Count; // Update selection context. if (count == 0) { this.SelectionData.Clear(); this.SelectionData.ActionsPaneItems.Clear(); } else { // Update the console with the selection information. this.SelectionData.Update((ResultNode)this.SelectedNodes[0], count > 1, null, null); this.SelectionData.ActionsPaneItems.Clear(); this.SelectionData.ActionsPaneItems.Add(new Action("Properties", "Properties", -1, "Properties")); } }
Define the method to handle actions the selected result node.
/// <summary> /// Handle the action for selected result node. /// </summary> /// <param name="action"></param> /// <param name="status"></param> protected override void OnSelectionAction(Action action, AsyncStatus status) { switch ((string)action.Tag) { case "Properties": { this.SelectionData.ShowPropertySheet("User Properties"); // triggers OnAddPropertyPages break; } } }
The ShowPropertySheet method triggers OnAddPropertyPages. Define this method as the method that shows property pages.
/// <summary> /// Get the property pages to show. /// </summary> /// <param name="propertyPageCollection"></param> protected override void OnAddPropertyPages(PropertyPageCollection propertyPageCollection) { if(this.SelectedNodes.Count == 0) { throw new Exception("there should be at least one selection"); } else { // add at least one property page relevant to the selection propertyPageCollection.Add(new UserPropertyPage()); } }
Here is the complete code for the user list view class.
using System; using System.ComponentModel; using Microsoft.ManagementConsole; namespace Microsoft.ManagementConsole.Samples { /// <summary> /// Provides the base functionality required to present a list in the results pane. /// </summary> public class UserListView : MmcListView { /// <summary> /// Constructor. /// </summary> public UserListView() { } /// <summary> /// Initialize. /// </summary> /// <param name="status"></param> protected override void OnInitialize(AsyncStatus status) { // Call the parent class method. base.OnInitialize(status); // Use the default column that already exists. this.Columns[0].Title = "User"; this.Columns[0].SetWidth(300); // Create subsequent columns. this.Columns.Add(new MmcListViewColumn("Birthday", 200)); // Populate the list. Refresh(); //Create a Refresh action that resets the list view. this.ActionsPaneItems.Add(new Action("Refresh", "refresh", -1, "Refresh")); } /// <summary> /// Handle the execution of the global action. /// </summary> /// <param name="action"></param> /// <param name="status"></param> protected override void OnAction(Action action, AsyncStatus status) { switch ((string)action.Tag) { case "Refresh": { Refresh(); break; } } } /// <summary> /// Load the list with data. /// </summary> protected void Refresh() { // Get some fictitious data to populate the lists with string[][] users = { new string[] {"Karen", "February 14th"}, new string[] {"Sue", "May 5th"}, new string[] {"Tina", "April 15th"}, new string[] {"Lisa", "March 27th"}, new string[] {"Tom", "December 25th"}, new string[] {"John", "January 1st"}, new string[] {"Harry", "October 31st"}, new string[] {"Bob", "July 4th"} }; // Remove any existing data. this.ResultNodes.Clear(); // Re-populate the list. foreach (string[] user in users) { ResultNode userNode = new ResultNode(); userNode.DisplayName = user[0]; userNode.SubItemDisplayNames.Add(user[1]); this.ResultNodes.Add(userNode); } } /// <summary> /// Handles changes in list view selection. Only acts on the first selected row. /// </summary> /// <param name="status"></param> protected override void OnSelectionChanged(SyncStatus status) { int count = SelectedNodes.Count; // Update selection context. if (count == 0) { this.SelectionData.Clear(); this.SelectionData.ActionsPaneItems.Clear(); } else { // Update the console with the selection information. this.SelectionData.Update((ResultNode)this.SelectedNodes[0], count > 1, null, null); this.SelectionData.ActionsPaneItems.Clear(); this.SelectionData.ActionsPaneItems.Add(new Action("Properties", "Properties", -1, "Properties")); } } /// <summary> /// Handle the action for selected result node. /// </summary> /// <param name="action"></param> /// <param name="status"></param> protected override void OnSelectionAction(Action action, AsyncStatus status) { switch ((string)action.Tag) { case "Properties": { this.SelectionData.ShowPropertySheet("User Properties"); // triggers OnAddPropertyPages break; } } } /// <summary> /// Get the property pages to show. /// </summary> /// <param name="propertyPageCollection"></param> protected override void OnAddPropertyPages(PropertyPageCollection propertyPageCollection) { if(this.SelectedNodes.Count == 0) { throw new Exception("there should be at least one selection"); } else { // add at least one property page relevant to the selection propertyPageCollection.Add(new UserPropertyPage()); } } } // class } // namespace
Create UserPropertiesControl class (UserPropertiesControl.cs file)
Create a user control class called UserPropertiesControl and declare a user property page private variable that keeps track of the data and the state for the property sheet.
using System; using System.Collections.Generic; using System.ComponentModel; using System.Drawing; using System.Data; using System.Text; using System.Windows.Forms; using Microsoft.ManagementConsole.Advanced; namespace Microsoft.ManagementConsole.Samples { /// <summary> /// Gets Name and Birthday /// </summary> public partial class UserPropertiesControl : UserControl { /// <summary> /// Parent property page to expose data and state of property sheet /// </summary> private UserPropertyPage userPropertyPage; /// <summary> /// Constructor /// </summary> /// <param name="parentPropertyPage">Container property page for the control</param> public UserPropertiesControl(UserPropertyPage parentPropertyPage) { // This call is required by the Windows.Forms Form Designer. InitializeComponent(); // keep reference to parent userPropertyPage = parentPropertyPage; } ... }//class }//namespace
Add a method that defines what happens when the property page is refreshed. Populate the control values from the SelectionObject that was set in UserListView.OnSelectionChanged.
/// <summary> /// Populate control values from the SelectionObject (set in UserListView.SelectionOnChanged) /// </summary> public void RefreshData(ResultNode userNode) { this.UserName.Text = userNode.DisplayName; this.Birthday.Text = userNode.SubItemDisplayNames[0]; // first subitem userPropertyPage.Dirty = false; }
Define the method that updates the node with the values from the control.
/// <summary> /// Update the node with the controls values /// </summary> /// <param name="userNode">Node being updated by property page</param> public void UpdateData(ResultNode userNode) { userNode.DisplayName = this.UserName.Text; userNode.SubItemDisplayNames[0] = this.Birthday.Text; // first subitem userPropertyPage.Dirty = false; }
This method is called during the execution of the OnApply method on UserPropertyPage to make sure that changes can be applied. It returns true if the changes are valid.
/// <summary> /// Check during UserProptertyPage.OnApply to ensure that changes can be Applied /// </summary> /// <returns>returns true if changes are valid</returns> public bool CanApplyChanges() { bool result = false; if (UserName.Text.Trim().Length == 0) { MessageBoxParameters messageBoxParameters = new MessageBoxParameters(); messageBoxParameters.Text = "Name cannot be blank"; userPropertyPage.ParentSheet.ShowDialog(messageBoxParameters); } else if (Birthday.Text.Trim().Length == 0) { MessageBoxParameters messageBoxParameters = new MessageBoxParameters(); messageBoxParameters.Text = "Birthday cannot be blank"; userPropertyPage.ParentSheet.ShowDialog(messageBoxParameters); } else { result = true; } return result; }
Notify the property page that the user name has changed and that the property sheet can change the buttons.
/// <summary> /// Notifies the PropertyPage that info has changed and that the PropertySheet can change the /// buttons /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void UserName_TextChanged(object sender, System.EventArgs e) { userPropertyPage.Dirty = true; }
Notify the property page that the birthday information has changed and that the property sheet can change the buttons.
/// <summary> /// Notifies the PropertyPage that info has changed and that the PropertySheet can change the /// buttons /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void Birthday_TextChanged(object sender, System.EventArgs e) { userPropertyPage.Dirty = true; }
Here is the complete code for creating the UserPropertiesControl class:
using System; using System.Collections.Generic; using System.ComponentModel; using System.Drawing; using System.Data; using System.Text; using System.Windows.Forms; using Microsoft.ManagementConsole.Advanced; namespace Microsoft.ManagementConsole.Samples { /// <summary> /// Gets Name and Birthday /// </summary> public partial class UserPropertiesControl : UserControl { /// <summary> /// Parent property page to expose data and state of property sheet /// </summary> private UserPropertyPage userPropertyPage; /// <summary> /// Constructor /// </summary> /// <param name="parentPropertyPage">Container property page for the control</param> public UserPropertiesControl(UserPropertyPage parentPropertyPage) { // This call is required by the Windows.Forms Form Designer. InitializeComponent(); // keep reference to parent userPropertyPage = parentPropertyPage; } /// <summary> /// Populate control values from the SelectionObject (set in UserListView.SelectionOnChanged) /// </summary> public void RefreshData(ResultNode userNode) { this.UserName.Text = userNode.DisplayName; this.Birthday.Text = userNode.SubItemDisplayNames[0]; // first subitem userPropertyPage.Dirty = false; } /// <summary> /// Update the node with the controls values /// </summary> /// <param name="userNode">Node being updated by property page</param> public void UpdateData(ResultNode userNode) { userNode.DisplayName = this.UserName.Text; userNode.SubItemDisplayNames[0] = this.Birthday.Text; // first subitem userPropertyPage.Dirty = false; } /// <summary> /// Check during UserProptertyPage.OnApply to ensure that changes can be Applied /// </summary> /// <returns>returns true if changes are valid</returns> public bool CanApplyChanges() { bool result = false; if (UserName.Text.Trim().Length == 0) { MessageBoxParameters messageBoxParameters = new MessageBoxParameters(); messageBoxParameters.Text = "Name cannot be blank"; userPropertyPage.ParentSheet.ShowDialog(messageBoxParameters); } else if (Birthday.Text.Trim().Length == 0) { MessageBoxParameters messageBoxParameters = new MessageBoxParameters(); messageBoxParameters.Text = "Birthday cannot be blank"; userPropertyPage.ParentSheet.ShowDialog(messageBoxParameters); } else { result = true; } return result; } /// <summary> /// Notifies the PropertyPage that info has changed and that the PropertySheet can change the /// buttons /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void UserName_TextChanged(object sender, System.EventArgs e) { userPropertyPage.Dirty = true; } /// <summary> /// Notifies the PropertyPage that info has changed and that the PropertySheet can change the /// buttons /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void Birthday_TextChanged(object sender, System.EventArgs e) { userPropertyPage.Dirty = true; } }//class }//namespace
UserPropertiesControl Designer (file UserPropertiesControl.Designer.cs)
For reference, here is part of the designer code for the property sheet.
namespace Microsoft.ManagementConsole.Samples { partial class UserPropertiesControl { /// <summary> /// Required designer variable. /// </summary> private System.ComponentModel.IContainer components = null; /// <summary> /// Clean up any resources being used. /// </summary> /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param> protected override void Dispose(bool disposing) { if (disposing && (components != null)) { components.Dispose(); } base.Dispose(disposing); } ... private System.Windows.Forms.TextBox Birthday; private System.Windows.Forms.Label BirthdayPrompt; private System.Windows.Forms.TextBox UserName; private System.Windows.Forms.Label UserNamePrompt; private System.Windows.Forms.GroupBox UserInfo; } }
All the code for the UserPropertiesControl designer used in this sample is available in the <MMC 3.0 Samples>\PropertySheetSample directory.
Create UserPropertyPage (UserPropertyPage.cs file)
Next, we proceed to create a user property page that will show the information for the selected user in the Users column.
using System; using Microsoft.ManagementConsole; namespace Microsoft.ManagementConsole.Samples { /// <summary> /// User property page. /// </summary> public class UserPropertyPage : PropertyPage { private UserPropertiesControl userPropertiesControl = null; /// <summary> /// Constructor. /// </summary> public UserPropertyPage() { // Set up the property page container. this.Title = "User Property Page"; // Set up the contained control. userPropertiesControl = new UserPropertiesControl(this); this.Control = userPropertiesControl; } ... } // class } // namespace
We will add the following methods to the property page. OnInitialize provides the initialize notification for the page. The default implementation is empty.
/// <summary> /// Initialize the notification for the page. /// </summary> protected override void OnInitialize() { base.OnInitialize(); // Populate the contained control. userPropertiesControl.RefreshData((ResultNode)this.ParentSheet.SelectionObject); }
OnApply defines what happens when a user clicks the Apply button. It causes the changes that are made by the user to take effect.
/// <summary> /// When the Apply button is clicked, this method makes the changes take effect. /// </summary> /// <returns></returns> protected override bool OnApply() { // If the control have valid values then save changes. if (this.Dirty) { if (userPropertiesControl.CanApplyChanges()) { userPropertiesControl.UpdateData((ResultNode)this.ParentSheet.SelectionObject); } else { return false; } } return true; }
The method defines what happens when the user clicks the "OK" button.
/// <summary> /// When the OK or Close button is clicked, this method makes the changes take effect. /// </summary> /// <returns></returns> protected override bool OnOK() { return this.OnApply(); }
QueryCancel is the method that allows a cancel operation.
/// <summary> /// Indicates that the property sheet needs to be canceled. /// </summary> /// <returns></returns> protected override bool QueryCancel() { return true; }
OnCancel defines what happens when the user cancels an operation and destroys the property sheet.
/// <summary> /// Action to be taken before the property sheet is destroyed. /// </summary> protected override void OnCancel() { userPropertiesControl.RefreshData((ResultNode)this.ParentSheet.SelectionObject); }
OnDestroy notifies the page that the property sheet is being destroyed. You may use this notification message as an opportunity to perform cleanup operations. Here, we simply create a stub.
/// <summary> /// Opportunity to perform cleanup operations. /// </summary> protected override void OnDestroy() { }
This completes the definition of the property page class.
using System; using Microsoft.ManagementConsole; namespace Microsoft.ManagementConsole.Samples { /// <summary> /// User property page. /// </summary> public class UserPropertyPage : PropertyPage { private UserPropertiesControl userPropertiesControl = null; /// <summary> /// Constructor. /// </summary> public UserPropertyPage() { // Set up the property page container. this.Title = "User Property Page"; // Set up the contained control. userPropertiesControl = new UserPropertiesControl(this); this.Control = userPropertiesControl; } /// <summary> /// Initialize the notification for the page. /// </summary> protected override void OnInitialize() { base.OnInitialize(); // Populate the contained control. userPropertiesControl.RefreshData((ResultNode)this.ParentSheet.SelectionObject); } /// <summary> /// When the Apply button is clicked, this method makes the changes take effect. /// </summary> /// <returns></returns> protected override bool OnApply() { // If the control have valid values then save changes. if (this.Dirty) { if (userPropertiesControl.CanApplyChanges()) { userPropertiesControl.UpdateData((ResultNode)this.ParentSheet.SelectionObject); } else { return false; } } return true; } /// <summary> /// When the OK or Close button is clicked, this method makes the changes take effect. /// </summary> /// <returns></returns> protected override bool OnOK() { return this.OnApply(); } /// <summary> /// Indicates that the property sheet needs to be canceled. /// </summary> /// <returns></returns> protected override bool QueryCancel() { return true; } /// <summary> /// Action to be taken before the property sheet is destroyed. /// </summary> protected override void OnCancel() { userPropertiesControl.RefreshData((ResultNode)this.ParentSheet.SelectionObject); } /// <summary> /// Opportunity to perform cleanup operations. /// </summary> protected override void OnDestroy() { } } // class } // namespace
Create ScopePropertyPage (ScopePropertyPage.cs file)
Next, we proceed to create a scope property page that shows the information for the selected scope node. This code is similar to the code for the UserPropertyPage class.
using System; using Microsoft.ManagementConsole; namespace Microsoft.ManagementConsole.Samples { /// <summary> /// Defines a scope property page. /// </summary> public class ScopePropertyPage : PropertyPage { private ScopePropertiesControl scopePropertiesControl = null; private SampleScopeNode scopeNode = null; /// <summary> /// Defines the constructor for the page. /// </summary> public ScopePropertyPage(SampleScopeNode parentScopeNode) { scopeNode = parentScopeNode; // Assign a title. this.Title = "Scope Node Property Page"; // Set up the contained control and assign it a reference to its parent. scopePropertiesControl = new ScopePropertiesControl(this); this.Control = scopePropertiesControl; } /// <summary> /// Initialize notification for the page. The default implementation is empty. /// </summary> protected override void OnInitialize() { base.OnInitialize(); // Populate the contained control. scopePropertiesControl.RefreshData(scopeNode); } /// <summary> /// Sent to every page in the property sheet to indicate that the user has clicked /// the Apply button and wants all changes to take effect. /// </summary> protected override bool OnApply() { if (this.Dirty) { if (scopePropertiesControl.CanApplyChanges()) { // Save the changes. scopePropertiesControl.UpdateData(scopeNode); } else { // Indicates that something invalid was entered. return false; } } return true; } /// <summary> /// Sent to every page in the property sheet to indicate that the user has clicked the OK /// or Close button and wants all changes to take effect. /// </summary> protected override bool OnOK() { return this.OnApply(); } /// <summary> /// Indicates that the user wants to cancel the property sheet. /// The default implementation allows a cancel operation. /// </summary> protected override bool QueryCancel() { return true; } /// <summary> /// Indicates that the user has canceled and the property sheet is about to be destroyed. /// All changes made since the last PSN_APPLY notification are canceled. /// </summary> protected override void OnCancel() { scopePropertiesControl.RefreshData(scopeNode); } /// <summary> /// Notifies a page that the property sheet is getting destoyed. /// Uses this notification message as an opportunity to perform cleanup operations. /// </summary> protected override void OnDestroy() { } } // class } // namespace
Create ScopePropertiesControl class (ScopePropertiesControl.cs file)
Create a user control class called ScopePropertiesControl and declare a scope property page private variable that keeps track of the data and the state for the property sheet.
using System; using System.Collections.Generic; using System.ComponentModel; using System.Drawing; using System.Data; using System.Text; using System.Windows.Forms; using Microsoft.ManagementConsole.Advanced; namespace Microsoft.ManagementConsole.Samples { /// <summary> /// Gets the name and birthday. /// </summary> public partial class ScopePropertiesControl : UserControl { /// <summary> /// Defines the parent property page to expose data and state of property sheet. /// </summary> private ScopePropertyPage scopePropertyPage; /// <summary> /// Constructor /// </summary> /// <param name="parentPropertyPage">Container property page for the control</param> public ScopePropertiesControl(ScopePropertyPage parentPropertyPage) { // This call is required by the Windows form designer. InitializeComponent(); // Assign a reference to the parent. scopePropertyPage = parentPropertyPage; } ... }//class }//namespace
Similar to UserPropertiesControl class, add a methods that define what happens when the text is changed, when the property page is refreshed, or when the page is updated.Here is the complete code for creating the ScopePropertiesControl class:
using System; using System.Collections.Generic; using System.ComponentModel; using System.Drawing; using System.Data; using System.Text; using System.Windows.Forms; using Microsoft.ManagementConsole.Advanced; namespace Microsoft.ManagementConsole.Samples { /// <summary> /// Gets the name and birthday. /// </summary> public partial class ScopePropertiesControl : UserControl { /// <summary> /// Defines the parent property page to expose data and state of property sheet. /// </summary> private ScopePropertyPage scopePropertyPage; /// <summary> /// Constructor /// </summary> /// <param name="parentPropertyPage">Container property page for the control</param> public ScopePropertiesControl(ScopePropertyPage parentPropertyPage) { // This call is required by the Windows form designer. InitializeComponent(); // Assign a reference to the parent. scopePropertyPage = parentPropertyPage; } /// <summary> /// Populate control values from the SelectionObject (that is set in UserListView.SelectionOnChanged). /// </summary> public void RefreshData(SampleScopeNode scopeNode) { this.DisplayName.Text = scopeNode.DisplayName; scopePropertyPage.Dirty = false; } /// <summary> /// Update the node with the control values. /// </summary> /// <param name="scopeNode">Node being updated by property page</param> public void UpdateData(SampleScopeNode scopeNode) { scopeNode.DisplayName = this.DisplayName.Text; scopePropertyPage.Dirty = false; } /// <summary> /// Checks during UserProptertyPage.OnApply to ensure that changes can be applied. /// </summary> /// <returns>returns true if changes are valid</returns> public bool CanApplyChanges() { bool result = false; if (DisplayName.Text.Trim().Length == 0) { MessageBoxParameters messageBoxParameters = new MessageBoxParameters(); messageBoxParameters.Text = "Display Name cannot be blank"; scopePropertyPage.ParentSheet.ShowDialog(messageBoxParameters); // MessageBox.Show("Display Name cannot be blank"); } else { result = true; } return result; } /// <summary> /// Notifies the PropertyPage that info has changed and that the PropertySheet can change the /// buttons /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void DisplayName_TextChanged(object sender, System.EventArgs e) { scopePropertyPage.Dirty = true; } }//class }//namespace
ScopePropertiesControl Designer (file ScopePropertiesControl.Designer.cs)
For reference, here is part of the designer code for the property sheet.
namespace Microsoft.ManagementConsole.Samples { partial class ScopePropertiesControl { /// <summary> /// Required designer variable. /// </summary> private System.ComponentModel.IContainer components = null; /// <summary> /// Clean up any resources being used. /// </summary> /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param> protected override void Dispose(bool disposing) { if (disposing && (components != null)) { components.Dispose(); } base.Dispose(disposing); } ... private System.Windows.Forms.TextBox DisplayName; private System.Windows.Forms.Label DisplayNamePrompt; private System.Windows.Forms.GroupBox ScopeInfo; } }
All the code for the ScopePropertiesControl designer used in this sample is available in the <MMC 3.0 Samples>\PropertySheetSample directory.
Steps to create, install, and run the snap-in
To install this snap-in, run the .NET Framework InstallUtil.exe program using the following command-line command: InstallUtil.exe PropertySheetSample.dll. Note that if the Microsoft.ManagementConsole dll is not in the GAC, both the Microsoft.ManagementConsole.dll and the PropertySheetSample.dll must be in the same directory. If you need to uninstall the snap-in later, run the previous InstallUtil.exe command with the /uninstall switch.
The InstallUtil.exe command attempts to install your snap-in using the SnapInSettingsAttribute. The utility creates a file called InstallUtil.InstallLog to show the success or failure of the install and all the actions that were taken.
InstallUtil.exe populates the registry entries for the given snap-in under the HKLM/Software/Microsoft/MMC/SnapIns key.
After the snap-in is installed, the snap-in is visible to MMC and can be added to the MMC Console using the Add/Remove Dialog. To test this snap-in, run MMC 3.0 (mmc.exe) and use the Add/Remove Snap-in menu. The Property Sheet Sample displays in the dialog and can be loaded in the MMC console.