The .NET designer architecture provides a RAD (Rapid Application Development) style environment for applications development. One of the keys to the RAD environment is the capability to modify the properties of components visually at design-time. The Windows Forms Designer provides a PropertyGrid that allows for modifying control properties and instantly viewing the result of those modifications. The .NET designer base classes provide extensive support for editing various types of properties, from intrinsic types such as strings and integers to complex objects such as fonts, collections, and images. Because it is impossible to provide a property editor for types yet unknown, the EditorAttribute is used to define the UITypeEditor that the PropertyGrid will use to support modification of the property. Figure 5.6 shows the UITypeEditor for the Nodes collection of the TreeView control. Figure 5.6. Nodes property UITypeEditor.Although the Nodes property represents a collection, the TreeView control provides an alternative UITypeEditor rather than the standard CollectionEditor generally associated with collection properties. UITypeEditorProviding a custom UITypeEditor for a control or component is fairly simple, thanks in part to the existing designer architecture. The UITypeEditor base class provides four virtual methods that can be overridden to provide a custom UITypeEditor for a component. Table 5.15 lists these methods along with descriptions of each method's purpose.
All that is required in order to associate the UITypeEditor with a particular property is the use of the EditorAttribute. The EditorAttribute specifies the type of editor to be used when editing the property at design-time. Figure 5.7 shows the UITypeEditor that will be built for the IconButton's Icon property. Figure 5.7. IconEditor for the IconButton.The typical coding convention for Modal style editors is to have the Form class contained within the editor class as a nested class. The drawback to this is that the designer will not allow for visual development of a nested class. When developing the UITypeEditor, you may want to create the Form class as you would any other Form and then, when finished testing, move the Form class into the editor class. This, however, creates a small issue with the resource file, the corresponding file with the resx extension. To preserve the resource information, it is necessary to include the resource file into the project. I suggest the following steps for creating nested Form classes:
If it seems like a complex set of steps, it is. Unfortunately, the resource file support for VS .NET is not quite up to par with the way it should be in terms of ease of use. In an attempt to simplify the development process, certain tasks such as resource file management have been made overly complex. Listing 5.2 contains the source for the IconEditor, along with the nested IconEditorDialog class to act as a custom UITypeEditor. Listing 5.2 IconEditor UITypeEditor Source1: using System; 2: using System.Drawing; 3: using System.Drawing.Design; 4: using System.Collections; 5: using System.ComponentModel; 6: using System.Windows.Forms; 7: using System.Windows.Forms.Design; 8: 9: 10: namespace SAMS.ToolKit.Design 11: { 12: 13: public class IconEditor : System.Drawing.Design.UITypeEditor 14: { 15: 16: public override object EditValue(ITypeDescriptorContext context, 17: IServiceProvider provider, 18: object value) { 19: IconEditorDialog dlg = new IconEditorDialog( ); 20: IWindowsFormsEditorService winFormEditorService = (IWindowsFormsEditorService)provider.GetService( typeof( IWindowsFormsEditorService ) ); 21: if( DialogResult.OK == winFormEditorService.ShowDialog(dlg)) 22: value = dlg.SelectedIcon; 23: 24: return value; 25: } 26: 27: public override UITypeEditorEditStyle GetEditStyle( ITypeDescriptorContext context ) { 28: return UITypeEditorEditStyle.Modal; 29: } 30: 31: public override bool GetPaintValueSupported( ITypeDescriptorContext context) { 32: return true; 33: } 34: 35: public override void PaintValue( System.Drawing.Design.PaintValueEventArgs e) { 36: if( !(e.Value is Icon) ) 37: return; 38: Image img = ((Icon)e.Value).ToBitmap( ); 39: Rectangle rcBounds = e.Bounds; 40: rcBounds.Inflate(-1,-1); 41: Pen p = System.Drawing.SystemPens.WindowFrame; 42: e.Graphics.DrawRectangle(p, rcBounds ); 43: if( img != null ) 44: e.Graphics.DrawImage( img, e.Bounds ); 45: } 46: 47: 48: protected class IconEditorDialog : System.Windows.Forms.Form 49: { 50: private System.Windows.Forms.PictureBox iconPreviewPictureBox; 51: private System.Windows.Forms.Button btnOpen; 52: private System.Windows.Forms.Button btnOK; 53: private System.Windows.Forms.Button btnCancel; 54: private System.Windows.Forms.PictureBox samsImagePictureBox; 55: private System.Windows.Forms.OpenFileDialog openFileDialog; 56: 57: private System.ComponentModel.Container components = null; 58: 59: private System.Drawing.Icon selectedIcon; 60: 61: public Icon SelectedIcon 62: { 63: get { return selectedIcon; } 64: set 65: { 66: selectedIcon = value; 67: iconPreviewPictureBox.Image = ( selectedIcon != null ? selectedIcon.ToBitmap( ) : null ); 68: } 69: } 70: 71: 72: public IconEditorDialog() 73: { 74: InitializeComponent(); 75: } 76: 77: /// <summary> 78: /// Clean up any resources being used. 79: /// </summary> 80: protected override void Dispose( bool disposing ) 81: { 82: if( disposing ) 83: { 84: if (components != null) 85: { 86: components.Dispose(); 87: } 88: } 89: base.Dispose( disposing ); 90: } 91: 92: private void InitializeComponent() 93: { 94: System.Resources.ResourceManager resources = new System.Resources.ResourceManager(typeof(IconEditor.IconEditorDialog)); 95: this.openFileDialog = new System.Windows.Forms.OpenFileDialog(); 96: this.btnCancel = new System.Windows.Forms.Button(); 97: this.iconPreviewPictureBox = new System.Windows.Forms.PictureBox(); 98: this.btnOpen = new System.Windows.Forms.Button(); 99: this.samsImagePictureBox = new System.Windows.Forms.PictureBox(); 100: this.btnOK = new System.Windows.Forms.Button(); 101: this.SuspendLayout(); 102: // 103: // openFileDialog 104: // 105: this.openFileDialog.Filter = "Icon Files | *.ico"; 106: // 107: // btnCancel 108: // 109: this.btnCancel.Location = new System.Drawing.Point(120, 88); 110: this.btnCancel.Name = "btnCancel"; 111: this.btnCancel.Size = new System.Drawing.Size(88, 24); 112: this.btnCancel.TabIndex = 2; 113: this.btnCancel.Text = "Cancel"; 114: this.btnCancel.Click += new System.EventHandler(this.btnCancel_Click); 115: // 116: // iconPreviewPictureBox 117: // 118: this.iconPreviewPictureBox.BorderStyle = System.Windows.Forms.BorderStyle.Fixed3D; 119: this.iconPreviewPictureBox.Location = new System.Drawing.Point(8, 8); 120: this.iconPreviewPictureBox.Name = "iconPreviewPictureBox"; 121: this.iconPreviewPictureBox.Size = new System.Drawing.Size(100, 68); 122: this.iconPreviewPictureBox.SizeMode = System.Windows.Forms.PictureBoxSizeMode.CenterImage; 123: this.iconPreviewPictureBox.TabIndex = 1; 124: this.iconPreviewPictureBox.TabStop = false; 125: // 126: // btnOpen 127: // 128: this.btnOpen.Location = new System.Drawing.Point(120, 8); 129: this.btnOpen.Name = "btnOpen"; 130: this.btnOpen.Size = new System.Drawing.Size(88, 24); 131: this.btnOpen.TabIndex = 2; 132: this.btnOpen.Text = "Open..."; 133: this.btnOpen.Click += new System.EventHandler(this.btnOpen_Click); 134: // 135: // samsImagePictureBox 136: // 137: this.samsImagePictureBox.Image = ((System.Drawing.Bitmap)(resources.GetObject( "samsImagePictureBox.Image"))); 138: this.samsImagePictureBox.Location = new System.Drawing.Point(8, 88); 139: this.samsImagePictureBox.Name = "samsImagePictureBox"; 140: this.samsImagePictureBox.Size = new System.Drawing.Size(96, 32); 141: this.samsImagePictureBox.SizeMode = System.Windows.Forms.PictureBoxSizeMode.CenterImage; 142: this.samsImagePictureBox.TabIndex = 3; 143: this.samsImagePictureBox.TabStop = false; 144: // 145: // btnOK 146: // 147: this.btnOK.Location = new System.Drawing.Point(120, 48); 148: this.btnOK.Name = "btnOK"; 149: this.btnOK.Size = new System.Drawing.Size(88, 24); 150: this.btnOK.TabIndex = 2; 151: this.btnOK.Text = "OK"; 152: this.btnOK.Click += new System.EventHandler(this.btnOK_Click); 153: // 154: // IconEditorDialog 155: // 156: this.AutoScaleBaseSize = new System.Drawing.Size(5, 13); 157: this.ClientSize = new System.Drawing.Size(228, 129); 158: this.Controls.AddRange(new System.Windows.Forms.Control[]{ 159: this.samsImagePictureBox, 160: this.btnCancel, 161: this.btnOK, 162: this.btnOpen, 163: this.iconPreviewPictureBox} ); 164: this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog; 165: this.Name = "IconEditorDialog"; 166: this.Text = "IconButton UITypeEditor"; 167: this.ResumeLayout(false); 168: 169: } 170: 171: private void btnOpen_Click(object sender, System.EventArgs e) 172: { 173: if( DialogResult.OK == openFileDialog.ShowDialog( ) ) { 174: try { 175: selectedIcon = new Icon( openFileDialog.FileName ); 176: } catch( Exception exception ) { 177: MessageBox.Show( this, exception.Message ); 178: } finally { 179: iconPreviewPictureBox.Image = ( selectedIcon != null ? selectedIcon.ToBitmap( ) : null ); 180: } 181: } 182: } 183: 184: private void btnOK_Click(object sender, System.EventArgs e) 185: { 186: DialogResult = DialogResult.OK; 187: Close( ); 188: } 189: 190: private void btnCancel_Click(object sender, System.EventArgs e) 191: { 192: DialogResult = DialogResult.Cancel; 193: Close( ); 194: } 195: } 196: } 197: } The code in Listing 5.2 consists of two major components: the dialog for selecting and previewing the icon image, and the code necessary to paint the icon image within the PropertyGrid. The nested IconEditorDialog is a basic Form-derived class that is used to display an OpenFileDialog for selecting an icon. In addition, the IconEditDialog creates an instance of an IconButton to allow for a simple preview mechanism. After an icon is selected, the IconEditDialog is closed and the IconEditor class is responsible for providing the necessary code to update the PropertyGrid. The IconEditor class provides the implementation necessary to interact with the PropertyGrid. The first task is to return the type of editor that will be provided. For the IconEditor, the type of Modal is returned, as a nested Form will be used. Other types are DropDown and None. When the EditValue method is invoked, the IconEditor creates and displays the nested IconEditorDialog to allow for the selection of an icon image. When the value of the icon is modified, the PropertyGrid will invoke the GetPaintValueSupported method to determine whether the editor will handle displaying some representation of the property within the PropertyGrid. Next, the PaintValue method is invoked if the GetPaintValueSupported method returns true. The PaintValue method is responsible for painting within the PropertyGrid for the associated property. The IconEditor renders a miniature image of the icon within0 the PropertyGrid. To associate the IconEditor with the Icon property, modify the IconButton source and add the EditorAttribute to the Icon property as shown in Listing 5.3. Listing 5.3 Applying the EditorAttribute1: [ 2: System.ComponentModel.Description("The Icon to be displayed in the button"), 3: System.ComponentModel.Category( "Appearance" ), 4: System.ComponentModel.DefaultValue( null ), 5: System.ComponentModel.Editor( typeof( SAMS.ToolKit.Design.IconEditor ), typeof( System.Drawing.Design.UITypeEditor ) ) 6: ] 7: public Icon Icon { /* omitted */ } |