├── LICENSE ├── README.md ├── demo ├── App.xaml ├── App.xaml.cs ├── ClassDiagram1.cd ├── CustomEditor.cs ├── DemoClasses.cs ├── MainWindow.xaml ├── MainWindow.xaml.cs ├── Properties │ ├── AssemblyInfo.cs │ ├── Resources.Designer.cs │ ├── Resources.resx │ ├── Settings.Designer.cs │ └── Settings.settings ├── WpfPropertyGrid.cs ├── WpfPropertyGrid_Demo.csproj └── WpfPropertyGrid_Demo.sln ├── images ├── ClassDiagram.png ├── Person.png ├── PersonProps.png ├── Place.png ├── PlaceProps1.png ├── PlaceProps2.png ├── Vehicle.png ├── VehicleProps1.png ├── VehicleProps2.png ├── WorkflowFoundationDesigner.jpg ├── screenshot1.png └── screenshot2.PNG └── src └── WpfPropertyGrid.cs /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2011 - 2024 - Jaime Olivares 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # wpf-propertygrid 2 | Repackaging of Workflow Foundation's property grid for general use in WPF applications 3 | 4 | ![](images/screenshot2.PNG) ![](images/screenshot1.png) 5 | 6 | ## Disclaimer 7 | The following implementation was originally developed in 2010 and published in CodeProject. After so many years, it seems that it still attracts interest and a few stars, so I decided to move the entire Readme to GitHub. 8 | 9 | I no longer maintain the source code or advise on how to implement it. However, I encourage active users to help others in the Issues section. Pull Requests may be accepted if they have broad utility. 10 | 11 | ## Introduction 12 | 13 | While working with Windows Workflow Foundation 4.0, I realized that the `PropertyInspectorView` control is a fully-fledged WPF PropertyGrid control, including support for the customization of attributes and custom editors. A PropertyInspectorView is associated with a parent `WorkflowDesigner` object and a sibling workflow `View` object, which is the real drag-and-drop canvas, as shown in this MSDN's screenshot: 14 | 15 | ![](images/WorkflowFoundationDesigner.jpg) 16 | _Workflow Foundation example. Left: Activities Toolbox, Middle: Designer View, Right: Property Inspector._ 17 | 18 | ## Internal Architecture 19 | The overall approach to making the `PropertyInspector` available for general-purpose use is the following: 20 | 21 | - Derive a new control from a `Grid` class. The grid will contain the real UI element. 22 | - Incorporate a Workflow Foundation's `WorkflowDesigner` object as a private class member. 23 | - For the designer object, add the corresponding `PropertyInspectorView` as a grid's child. Although it is exposed as a Grid, its real type is `PropertyInspector`. 24 | - Capture some methods of the `PropertyInspector` by using reflection, for further use. 25 | - Implement `SelectedObject` and `SelectedObjects` properties, as in regular PropertyGrid, and handle the change of selection at the `PropertyInspector`. 26 | - Add a `GridSplitter` and a `TextBlock` to mimic the HelpText functionality of the original PropertyGrid. 27 | 28 | The following diagram depicts the internal architecture of the WpfPropertyGrid class, as explained in previous lines: 29 | 30 | ![](images/ClassDiagram.png) 31 | 32 | It is needed at least to invoke the public constructor and set the `SelectedObject` or `SelectedObjects`. The `RefreshPropertyList` method will be helpful in refreshing the control when the selected object has been changed externally. 33 | 34 | Set the `HelpVisible` property to show the property descriptions at the bottom, while the `ToolbarVisible` will show or hide the upper toolbar. Those properties have the same name as Winform´s PropertyGrid, in order to keep some degree of compatibility. 35 | 36 | The same is true for the `PropertySort` property, which accepts a PropertySort enumeration type. It allows to group the properties by categories or show them in a flat fashion. 37 | 38 | The `FontAndColorData` property can be used to restyle the control, since it internally sets the `WorkflowDesigner.PropertyInspectorFontAndColorData` property, but little information is available on the Internet. 39 | 40 | ## Basic Usage - Person Class 41 | The supplied demo project will allow you to test all the `WpfropertyGrid` capabilities. Three classes are defined in DemoClasses.cs, with different features, like custom attributes and editors. Here is the declaration of the first and simplest one: 42 | 43 | ````csharp 44 | public class Person 45 | { 46 | public enum Gender { Male, Female } 47 | 48 | #region private fields 49 | private string[] _Names = new string[3]; 50 | #endregion 51 | 52 | // The following properties are wrapping an array of strings 53 | #region Public Properties 54 | [Category("Name")] 55 | [DisplayName("First Name")] 56 | public string FirstName 57 | { 58 | set { _Names[0] = value; } 59 | get { return _Names[0]; } 60 | } 61 | 62 | [Category("Name")] 63 | [DisplayName("Mid Name")] 64 | public string MidName 65 | { 66 | set { _Names[1] = value; } 67 | get { return _Names[1]; } 68 | } 69 | 70 | [Category("Name")] 71 | [DisplayName("Last Name")] 72 | public string LastName 73 | { 74 | set { _Names[2] = value; } 75 | get { return _Names[2]; } 76 | } 77 | 78 | // The following are auto-implemented properties (C# 3.0 and up) 79 | [Category("Characteristics")] 80 | [DisplayName("Gender")] 81 | public Gender PersonGender { get; set; } 82 | 83 | [Category("Characteristics")] 84 | [DisplayName("Birth Date")] 85 | public DateTime BirthDate { get; set; } 86 | 87 | [Category("Characteristics")] 88 | public int Income { get; set; } 89 | 90 | // Other cases of hidden read-only property and formatted property 91 | [DisplayName("GUID"), ReadOnly(true), Browsable(true)] 92 | public string GuidStr 93 | { 94 | get { return Guid.ToString(); } 95 | } 96 | 97 | [Browsable(false)] // this property will not be displayed 98 | public System.Guid Guid 99 | { 100 | get; 101 | private set; 102 | } 103 | #endregion 104 | 105 | public Person() 106 | { 107 | // default values 108 | for (int i = 0; i < 3; i++) 109 | _Names[i] = ""; 110 | this.PersonGender = Gender.Male; 111 | this.Guid = System.Guid.NewGuid(); 112 | } 113 | 114 | public override string ToString() 115 | { 116 | return string.Format("{0} {1} {2}", FirstName, 117 | MidName, LastName).Trim().Replace(" ", " "); 118 | } 119 | } 120 | ```` 121 | 122 | Here is the class diagram and how it appears in the demo application: 123 | 124 | ![](images/Person.png) ![](images/PersonProps.png) 125 | 126 | Notice that the control will show just the properties, not the fields. As we are using C# 3.0 or 4.0, we can avoid declaring the underlying fields by using auto-implemented properties, whenever is convenient. 127 | 128 | To show the properties of a `Person` object is quite simple; just assign it to the control's `SelectedObject` property, as shown: 129 | 130 | ````csharp 131 | PropertyGrid1.SelectedObject = thePerson; // typeof(thePerson) == Person 132 | ```` 133 | 134 | ## Basic Attributes 135 | In the `Person` class implementation, you will notice some properties that have attributes (those with square brackets); they won't affect your class behavior, but will do with the property grid. These attributes are similar to those implemented in Winforms's `PropertyGrid`. Let's see them in detail. 136 | 137 | - `Category`: Lets you specify a category group for the affected property. A category appears by default at the property grid with a gray background, as you can see in the first screenshot. If the property doesn't have a Category attribute, it will belong to a blank category group, as with the GUID property in the previous screenshot. It is recommended to always specify a category for each property. 138 | - `DisplayName`: Will be useful when you want to display a property name that is different from the real one. It is usually used to increase readability with white spaces, or to abbreviate the name. 139 | - `ReadOnly`: When set to `true`, will prevent the property from being edited; it will be just shown in the property grid. To prevent the read-only properties from being hidden, it will necessary to mark them as `Browsable=true`, as with the `GUIDStr` property. 140 | - `Browsable`: When set to `false`, the property will not be shown. It is useful when you have a property you don't want to show, like the GUID property in the first example. 141 | 142 | All these attributes are declared in the `System.ComponentModel` namespace and automatically recognized by the property inspector. 143 | 144 | ## Custom Properties - Vehicle Class 145 | 146 | While the simplest implementation of `WpfPropertyGrid` exposes all the properties of a class (except of those with the `Browsable` attribute set to false), the `ICustomProperties` interface will allow some properties to be conditionally exposed. There are some customizations needed to accomplish this, as in the following example: 147 | 148 | ````csharp 149 | public class Vehicle : 150 | ICustomTypeDescriptor, INotifyPropertyChanged 151 | { 152 | public enum CarType { Sedan, StationWagon, Coupe, 153 | Roadster, Van, Pickup, Truck } 154 | public enum CarBrand { Acura, Audi, BMW, Citroen, 155 | Ford, GMC, Honda, Lexus, Mercedes, Mitsubishi, 156 | Nissan, Porshe, Suzuki, Toyota, VW, Volvo } 157 | 158 | #region Private fields 159 | private CarType _TypeOfCar; 160 | #endregion 161 | 162 | #region Public Properties 163 | [Category("Classification")] 164 | public CarBrand Brand { get; set; } 165 | 166 | [Category("Classification")] 167 | [DisplayName("Type")] 168 | public CarType TypeOfCar 169 | { 170 | get { return this._TypeOfCar; } 171 | set { 172 | this._TypeOfCar = value; 173 | NotifyPropertyChanged("TypeOfCar"); 174 | } 175 | } 176 | 177 | [Category("Classification")] 178 | public string Model { get; set; } 179 | 180 | [Category("Identification")] 181 | [DisplayName("Manuf.Year")] 182 | public int Year { get; set; } 183 | 184 | [Category("Identification")] 185 | [DisplayName("License Plate")] 186 | public string Plate { get; set; } 187 | 188 | // Will shown only for Pickup and Truck 189 | [Category("Capacity")] 190 | [DisplayName("Volume (ft³)")] 191 | public int Volume { get; set; } 192 | 193 | [Category("Capacity")] 194 | [DisplayName("Payload (kg)")] 195 | public int Payload { get; set; } 196 | 197 | [Category("Capacity")] 198 | [DisplayName("Crew cab?")] 199 | public bool CrewCab { get; set; } 200 | #endregion 201 | 202 | #region ICustomTypeDescriptor Members 203 | public AttributeCollection GetAttributes() ... 204 | public string GetClassName() ... 205 | public string GetComponentName() ... 206 | public TypeConverter GetConverter() ... 207 | public EventDescriptor GetDefaultEvent() ... 208 | public PropertyDescriptor GetDefaultProperty() ... 209 | public object GetEditor(Type editorBaseType) 210 | public EventDescriptorCollection 211 | GetEvents(Attribute[] attributes) ... 212 | public EventDescriptorCollection GetEvents() ... 213 | public object 214 | GetPropertyOwner(PropertyDescriptor pd) ... 215 | public PropertyDescriptorCollection 216 | GetProperties(Attribute[] attributes) ... 217 | 218 | // Method implemented to expose Capacity properties 219 | // conditionally, depending on TypeOfCar 220 | public PropertyDescriptorCollection GetProperties() 221 | { 222 | var props = new PropertyDescriptorCollection(null); 223 | 224 | foreach (PropertyDescriptor prop in 225 | TypeDescriptor.GetProperties(this, true)) 226 | { 227 | if (prop.Category=="Capacity" && 228 | (this.TypeOfCar != CarType.Pickup && 229 | this.TypeOfCar != CarType.Truck)) 230 | continue; 231 | props.Add(prop); 232 | } 233 | 234 | return props; 235 | } 236 | #endregion 237 | 238 | #region INotifyPropertyChanged Members 239 | public event PropertyChangedEventHandler PropertyChanged; 240 | 241 | private void NotifyPropertyChanged(String info) 242 | { 243 | if (PropertyChanged != null) 244 | PropertyChanged(this, new PropertyChangedEventArgs(info)); 245 | } 246 | #endregion 247 | } 248 | ```` 249 | 250 | ![](images/Vehicle.png) ![](images/VehicleProps1.png) ![](images/VehicleProps2.png) 251 | 252 | Notice that the most important method needed to implement the `PropertyGrid.ICustomProperties` interface is `GetProperties()`. This method shall return all the property names you want to expose as an array, depending on some conditions. In this example, if the car type is a _PickUp_ or _Truck_, the `Volume`, `Payload`, and `CrewCab` properties will be exposed. 253 | 254 | # Custom Editors - Place Class 255 | 256 | > Disclaimer: Although the intent of this article is not to go deep with Editor customization, I will show a couple of examples of the two kinds of editors: extended and dialog-based. The [Workflow Foundation documentation](https://learn.microsoft.com/en-us/dotnet/framework/windows-workflow-foundation/customizing-the-workflow-design-experience) provides more information. 257 | 258 | Custom editors are the most powerful feature of this control. There are several tricks you can do with it. By default, the control will provide an editor for all the fundamental classes: `int`, `float`, `double`, etc., and also for strings and enumerations, the latter as a `ComboBox`. If you have a custom class object as a property, it will show the string representation but just as read-only because the grid control doesn't know how to edit it. 259 | 260 | The `Place` class implementation shows both. Despite the kind of editor, it must be derived from the `PropertyValueEditor` class, as we will see in detail later. 261 | 262 | To specify a custom editor for a property, an `EditorAttribute` attribute must be added to the property declaration, as with `CountryInfo` and `Picture` properties. 263 | 264 | ````csharp 265 | public class Place 266 | { 267 | public struct CountryInfo 268 | { 269 | public static readonly CountryInfo[] Countries = { 270 | // African countries 271 | new CountryInfo(Continent.Africa , "AO", "ANGOLA" ), 272 | new CountryInfo(Continent.Africa, "CM", "CAMEROON" ), 273 | // American countries 274 | new CountryInfo(Continent.America, "MX", "MEXICO" ), 275 | new CountryInfo(Continent.America, "PE", "PERU" ), 276 | // Asian countries 277 | new CountryInfo(Continent.Asia, "JP", "JAPAN" ), 278 | new CountryInfo(Continent.Asia, "MN", "MONGOLIA" ), 279 | // European countries 280 | new CountryInfo(Continent.Europe, "DE", "GERMANY" ), 281 | new CountryInfo(Continent.Europe, "NL", "NETHERLANDS" ), 282 | // Oceanian countries 283 | new CountryInfo(Continent.Oceania, "AU", "AUSTRALIA" ), 284 | new CountryInfo(Continent.Oceania, "NZ", "NEW ZEALAND" ) 285 | }; 286 | 287 | public Continent Contin { get; set; } 288 | public string Abrev { get; set; } 289 | public string Name { get; set; } 290 | 291 | public override string ToString() 292 | { 293 | return string.Format("{0} ({1})", Name, Abrev); 294 | } 295 | public CountryInfo(Continent _continent, 296 | string _abrev, string _name) : this() 297 | { 298 | this.Contin = _continent; 299 | this.Abrev = _abrev; 300 | this.Name = _name; 301 | } 302 | } 303 | 304 | #region Private fields 305 | private string[] _Address = new string[4]; 306 | #endregion 307 | 308 | #region Public properties 309 | [Category("Address")] 310 | public string Street 311 | { 312 | get { return _Address[0]; } 313 | set { _Address[0] = value; } 314 | } 315 | 316 | [Category("Address")] 317 | public string City 318 | { 319 | get { return _Address[1]; } 320 | set { _Address[1] = value; } 321 | } 322 | 323 | [Category("Address")] 324 | public string Province 325 | { 326 | get { return _Address[2]; } 327 | set { _Address[2] = value; } 328 | } 329 | 330 | [Category("Address")] 331 | public string Postal 332 | { 333 | get { return _Address[3]; } 334 | set { _Address[3] = value; } 335 | } 336 | 337 | // Custom editor for the following 2 properties 338 | [Category("Address")] 339 | [Editor(typeof(CountryEditor), typeof(PropertyValueEditor))] 340 | public CountryInfo Country { get; set; } 341 | 342 | [Category("Characteristics")] 343 | [Editor(typeof(PictureEditor), typeof(PropertyValueEditor))] 344 | public BitmapImage Picture { get; set; } 345 | 346 | [Category("Characteristics")] 347 | public int Floors { get; set; } 348 | 349 | [Category("Characteristics")] 350 | public int CurrentValue { get; set; } 351 | #endregion 352 | 353 | public Place() 354 | { 355 | for (int i = 0; i < _Address.Length; i++) 356 | _Address[i] = string.Empty; 357 | this.Country = CountryInfo.Countries[0]; 358 | } 359 | } 360 | ```` 361 | 362 | ![](images/Place.png) ![](images/PlaceProps1.png) ![](images/PlaceProps2.png) 363 | 364 | As mentioned, there are two examples of custom editor implementations in the `Place` class; the first one, `CountryEditor`, is an extended editor. It asks for a country with two `ComboBox`es: one for `Continent` and one for `Country`, as shown in the screenshot. To simplify the demonstration, the required XAML `DataTemplate` is placed in the source code file, which is not so usual: 365 | 366 | ````csharp 367 | class CountryEditor : ExtendedPropertyValueEditor 368 | { 369 | public CountryEditor() 370 | { 371 | // Template for normal view 372 | string template1 = @"...xaml template here..."; 373 | 374 | // Template for extended view. Shown when dropdown button is pressed. 375 | string template2 = @"...xaml template here..."; 376 | 377 | // Load templates 378 | using (var sr = new MemoryStream(Encoding.UTF8.GetBytes(template1))) 379 | { 380 | this.InlineEditorTemplate = XamlReader.Load(sr) as DataTemplate; 381 | } 382 | using (var sr = new MemoryStream(Encoding.UTF8.GetBytes(template2))) 383 | { 384 | this.ExtendedEditorTemplate = XamlReader.Load(sr) as DataTemplate; 385 | } 386 | } 387 | } 388 | ```` 389 | 390 | For an extended editor, it has to be derived from `ExtendedPropertyValueEditor` class. That will allow the property grid to drop down a customized control to input the property data. 391 | 392 | The constructor should load both templates, the normal one and the extended one, from some XAML `DataTemplate` declarations. Usually those templates are placed in a XAML Resource file. 393 | 394 | The second example of a custom editor is `PictureEditor`; it is different from an extended editor because it shows a new dialog when the dropdown button is pressed, so it will require implementing that window separately. Also, it is derived from a different base class: `DialogPropertyValueEditor`. The sample class is shown partially for abbreviation purposes: 395 | 396 | ````csharp 397 | class PictureEditor : DialogPropertyValueEditor 398 | { 399 | // Window to show the current image and optionally pick a different one 400 | public class ImagePickerWindow : Window 401 | { 402 | // regular window implementation here 403 | } 404 | 405 | public PictureEditor() 406 | { 407 | string template = @"...xmal template here..."; 408 | 409 | using (var sr = new MemoryStream(Encoding.UTF8.GetBytes(template))) 410 | { 411 | this.InlineEditorTemplate = XamlReader.Load(sr) as DataTemplate; 412 | } 413 | } 414 | 415 | // Open the dialog to pick image, when the dropdown button is pressed 416 | public override void ShowDialog(PropertyValue propertyValue, IInputElement commandSource) 417 | { 418 | ImagePickerWindow window = new ImagePickerWindow(propertyValue.Value as BitmapImage); 419 | if (window.ShowDialog().Equals(true)) 420 | { 421 | var ownerActivityConverter = new ModelPropertyEntryToOwnerActivityConverter(); 422 | ModelItem activityItem = ownerActivityConverter.Convert(propertyValue.ParentProperty, 423 | typeof(ModelItem), false, null) as ModelItem; 424 | using (ModelEditingScope editingScope = activityItem.BeginEdit()) 425 | { 426 | propertyValue.Value = window.TheImage; 427 | editingScope.Complete(); // commit the changes 428 | } 429 | } 430 | } 431 | } 432 | ```` 433 | 434 | ## Multiple selection 435 | 436 | While single selection can be done by setting the `SelectedObject` property to any value, multiple selection is achieved by setting the `SelectedObjects` property. 437 | 438 | When multiple objects are selected, the Type Label at the top of the control will show the word "<multiple>" to the right of the type. If all the selected objects are the same type, the type name is shown (see screenshot below). Otherwise, the type _"Object"_ is displayed. 439 | 440 | All the properties that have the same type and name for all the selected objects are shown, even if the selected objects are not of the same type. Try using Person and Place in the Demo application, which shares the FirstName and LastName properties. 441 | 442 | ![](images/screenshot2.PNG) 443 | 444 | ## Help text 445 | The textbox at the bottom of the control is called the HelpText. It will shown a property description set with the `DescriptionAttribute` attribute (see screenshot above). 446 | 447 | When multiple selected objects are present, the description will be shown only if all the selected objects are of the same type. 448 | 449 | The HelpText box can be shown or hidden by setting the `HelpVisible` property in the `PropertyGrid`. 450 | 451 | ## How to use it 452 | 453 | The WpfPropertyGrid can be embedded directly into your application. It doesn't need to be compiled in a separate DLL. To include it in some XAML declaration, you have to specify the correct namespace (a local `System.Windows.Control`) and add the corresponding tag to your WPF window or dialog: 454 | 455 | ````xml 456 | 461 | 462 | 463 | 464 | 465 | 466 | 467 | ````` 468 | 469 | ### Dependency Properties 470 | 471 | As the control properties are _Dependency Properties_, they can be bound to other elements in the container dialog or window like in the demo application (simplified): 472 | 473 | ````xml 474 | 481 | 482 | 487 | ```` 488 | 489 | The demo application has been built with Visual Studio 2010. As the WPF property inspector is a new feature in .net 4.0, this implementation won't be available for applications written for .net 3.0 or 3.5, even when they implement Workflow Foundation. 490 | 491 | To use this control, you just need to add the WpfPropertyGrid.cs file to your project. Some references will be required in your solution: 492 | 493 | - System.Activities 494 | - System.Activities.Core.Presentation 495 | - System.Activities.Presentation 496 | 497 | -------------------------------------------------------------------------------- /demo/App.xaml: -------------------------------------------------------------------------------- 1 |  5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /demo/App.xaml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Configuration; 4 | using System.Data; 5 | using System.Linq; 6 | using System.Windows; 7 | 8 | namespace WpfPropertyGrid_Demo 9 | { 10 | /// 11 | /// Interaction logic for App.xaml 12 | /// 13 | public partial class App : Application 14 | { 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /demo/ClassDiagram1.cd: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /demo/CustomEditor.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Activities.Presentation.Converters; 3 | using System.Activities.Presentation.Model; 4 | using System.Activities.Presentation.PropertyEditing; 5 | using System.IO; 6 | using System.Text; 7 | using System.Windows; 8 | using System.Windows.Controls; 9 | using System.Windows.Markup; 10 | using System.Windows.Media; 11 | using System.Windows.Media.Imaging; 12 | using Microsoft.Win32; 13 | 14 | // Sample XAML Templates have been incorporated here for simplification of source code 15 | // Usually they are placed into a Resource file 16 | 17 | namespace WpfPropertyGrid_Demo 18 | { 19 | /// 20 | /// Custom editor to select continent/country 21 | /// 22 | class CountryEditor : ExtendedPropertyValueEditor 23 | { 24 | public CountryEditor() 25 | { 26 | // Template for normal view 27 | string template1 = @" 28 | 32 | 33 | 35 | 36 | 37 | "; 38 | 39 | // Template for extended view. Shown when dropdown button is pressed. 40 | string template2 = @" 41 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 59 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | "; 69 | 70 | // Load templates 71 | using (var sr = new MemoryStream(Encoding.UTF8.GetBytes(template1))) 72 | { 73 | this.InlineEditorTemplate = XamlReader.Load(sr) as DataTemplate; 74 | } 75 | using (var sr = new MemoryStream(Encoding.UTF8.GetBytes(template2))) 76 | { 77 | this.ExtendedEditorTemplate = XamlReader.Load(sr) as DataTemplate; 78 | } 79 | } 80 | } 81 | 82 | /// 83 | /// Custom editor to select a picture 84 | /// 85 | class PictureEditor : DialogPropertyValueEditor 86 | { 87 | // Window to show the current image and optionally pick a different one 88 | public class ImagePickerWindow : Window 89 | { 90 | public BitmapImage TheImage = null; 91 | public Image ImageControl = null; 92 | 93 | public ImagePickerWindow(BitmapImage bitmap) 94 | { 95 | this.TheImage = bitmap; 96 | 97 | this.Title = "Select Picture:"; 98 | this.Width = 195; 99 | this.Height = 235; 100 | this.WindowStartupLocation = System.Windows.WindowStartupLocation.CenterScreen; 101 | this.WindowStyle = System.Windows.WindowStyle.ToolWindow; 102 | this.ResizeMode = System.Windows.ResizeMode.NoResize; 103 | this.ShowInTaskbar = false; 104 | 105 | var image = new Image(); 106 | image.Width = 180; 107 | image.Height = 180; 108 | image.HorizontalAlignment = System.Windows.HorizontalAlignment.Stretch; 109 | image.VerticalAlignment = System.Windows.VerticalAlignment.Stretch; 110 | 111 | var border = new Border(); 112 | border.Width = 180; 113 | border.Height = 180; 114 | border.BorderThickness = new Thickness(1); 115 | border.BorderBrush = Brushes.Black; 116 | border.HorizontalAlignment = System.Windows.HorizontalAlignment.Left; 117 | border.VerticalAlignment = System.Windows.VerticalAlignment.Top; 118 | border.Margin = new Thickness(5, 5, 0, 0); 119 | border.Child = image; 120 | 121 | var button1 = new Button(); 122 | button1.Content = "Pick..."; 123 | button1.HorizontalAlignment = System.Windows.HorizontalAlignment.Left; 124 | button1.VerticalAlignment = System.Windows.VerticalAlignment.Top; 125 | button1.Width = 70; 126 | button1.Height = 20; 127 | button1.Margin = new Thickness(5, 190, 0, 0); 128 | button1.Click += new RoutedEventHandler(PickButton_Click); 129 | 130 | var button2 = new Button(); 131 | button2.Content = "OK"; 132 | button2.HorizontalAlignment = System.Windows.HorizontalAlignment.Left; 133 | button2.VerticalAlignment = System.Windows.VerticalAlignment.Top; 134 | button2.Width = 70; 135 | button2.Height = 20; 136 | button2.Margin = new Thickness(85, 190, 0, 0); 137 | button2.Click += new RoutedEventHandler(OKButton_Click); 138 | 139 | var grid = new Grid(); 140 | grid.Children.Add(border); 141 | grid.Children.Add(button1); 142 | grid.Children.Add(button2); 143 | this.AddChild(grid); 144 | 145 | this.ImageControl = image; 146 | if (bitmap != null) 147 | ImageControl.Source = bitmap; 148 | } 149 | 150 | void OKButton_Click(object sender, RoutedEventArgs e) 151 | { 152 | this.DialogResult = true; 153 | this.Close(); 154 | } 155 | void PickButton_Click(object sender, RoutedEventArgs e) 156 | { 157 | OpenFileDialog dialog = new OpenFileDialog(); 158 | 159 | dialog.CheckFileExists = true; 160 | dialog.DefaultExt = ".jpg"; 161 | dialog.Filter = "Picture Files (*.jpg, *.bmp, *.png)|*.jpg;*.bmp;*.png"; 162 | dialog.Multiselect = false; 163 | dialog.Title = "Select Picture"; 164 | 165 | if (dialog.ShowDialog().Equals(true)) 166 | { 167 | try 168 | { 169 | BitmapImage image = new BitmapImage(); 170 | image.BeginInit(); 171 | image.UriSource = new Uri(dialog.FileName, UriKind.RelativeOrAbsolute); 172 | image.EndInit(); 173 | 174 | this.TheImage = image; 175 | this.ImageControl.Source = image; 176 | } 177 | catch { } 178 | } 179 | } 180 | } 181 | 182 | public PictureEditor() 183 | { 184 | string template = @" 185 | 189 | 190 | ... 192 | 193 | 194 | "; 195 | 196 | using (var sr = new MemoryStream(Encoding.UTF8.GetBytes(template))) 197 | { 198 | this.InlineEditorTemplate = XamlReader.Load(sr) as DataTemplate; 199 | } 200 | } 201 | 202 | // Open the dialog to pick image, when the dropdown button is pressed 203 | public override void ShowDialog(PropertyValue propertyValue, IInputElement commandSource) 204 | { 205 | ImagePickerWindow window = new ImagePickerWindow(propertyValue.Value as BitmapImage); 206 | if (window.ShowDialog().Equals(true)) 207 | { 208 | var ownerActivityConverter = new ModelPropertyEntryToOwnerActivityConverter(); 209 | ModelItem activityItem = ownerActivityConverter.Convert(propertyValue.ParentProperty, typeof(ModelItem), false, null) as ModelItem; 210 | using (ModelEditingScope editingScope = activityItem.BeginEdit()) 211 | { 212 | propertyValue.Value = window.TheImage; 213 | editingScope.Complete(); // commit the changes 214 | 215 | var control = commandSource as Control; 216 | var oldData = control.DataContext; 217 | control.DataContext = null; 218 | control.DataContext = oldData; 219 | } 220 | } 221 | } 222 | } 223 | } 224 | -------------------------------------------------------------------------------- /demo/DemoClasses.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Activities.Presentation.PropertyEditing; 3 | using System.Collections.ObjectModel; 4 | using System.ComponentModel; 5 | using System.Windows.Data; 6 | using System.Windows.Media.Imaging; 7 | 8 | // Sample classes to be shown in WpfPropertyGrid 9 | // They have regular and customized properties, and custom editors as well 10 | 11 | namespace WpfPropertyGrid_Demo 12 | { 13 | // Simple sample class with properties of several types 14 | public class Person 15 | { 16 | public enum Gender { Male, Female } 17 | 18 | #region private fields 19 | private string[] _Names = new string[3]; 20 | #endregion 21 | 22 | // The following properties are wrapping an array of strings 23 | #region Public Properties 24 | [Category("Name")] 25 | [DisplayName("First Name")] 26 | public string FirstName 27 | { 28 | set { _Names[0] = value; } 29 | get { return _Names[0]; } 30 | } 31 | 32 | [Category("Name")] 33 | [DisplayName("Mid Name")] 34 | public string MidName 35 | { 36 | set { _Names[1] = value; } 37 | get { return _Names[1]; } 38 | } 39 | 40 | [Category("Name")] 41 | [DisplayName("Last Name")] 42 | public string LastName 43 | { 44 | set { _Names[2] = value; } 45 | get { return _Names[2]; } 46 | } 47 | 48 | // The following are autoimplemented properties (C# 3.0 and up) 49 | [Category("Characteristics")] 50 | [DisplayName("Gender")] 51 | public Gender PersonGender { get; set; } 52 | 53 | [Category("Characteristics")] 54 | [DisplayName("Birth Date")] 55 | public DateTime BirthDate { get; set; } 56 | 57 | [Category("Characteristics")] 58 | public int Income { get; set; } 59 | 60 | // Other cases of hidden read-only property and formatted property 61 | [DisplayName("GUID"), ReadOnly(true), Browsable(true)] // many attributes defined in the same row 62 | public string GuidStr 63 | { 64 | get { return Guid.ToString(); } 65 | } 66 | 67 | [Browsable(false)] // this property will not be displayed 68 | public System.Guid Guid 69 | { 70 | get; 71 | private set; 72 | } 73 | #endregion 74 | 75 | public Person() 76 | { 77 | // default values 78 | for (int i = 0; i < 3; i++) 79 | _Names[i] = ""; 80 | this.PersonGender = Gender.Male; 81 | this.Guid = System.Guid.NewGuid(); 82 | } 83 | 84 | public override string ToString() 85 | { 86 | return string.Format("{0} {1} {2}", FirstName, MidName, LastName).Trim().Replace(" ", " "); 87 | } 88 | } 89 | 90 | // Sample class with custom properties. Implements ICustomTypeDescriptor interface. 91 | public class Vehicle : ICustomTypeDescriptor, INotifyPropertyChanged 92 | { 93 | public enum CarType { Sedan, StationWagon, Coupe, Roadster, Van, Pickup, Truck } 94 | public enum CarBrand { Acura, Audi, BMW, Citroen, Ford, GMC, Honda, Lexus, Mercedes, Mitsubishi, Nissan, Porshe, Suzuki, Toyota, VW, Volvo } 95 | 96 | #region Private fields 97 | private CarType _TypeOfCar; 98 | #endregion 99 | 100 | #region Public Properties 101 | [Category("Classification")] 102 | public CarBrand Brand { get; set; } 103 | 104 | [Category("Classification")] 105 | [DisplayName("Type")] 106 | [Description("Extra fields can be shown depending on type of car.")] 107 | public CarType TypeOfCar 108 | { 109 | get 110 | { 111 | return this._TypeOfCar; 112 | } 113 | set 114 | { 115 | this._TypeOfCar = value; 116 | NotifyPropertyChanged("TypeOfCar"); 117 | } 118 | } 119 | 120 | [Category("Classification")] 121 | public string Model { get; set; } 122 | 123 | [Category("Identification")] 124 | [DisplayName("Manuf.Year")] 125 | public int Year { get; set; } 126 | 127 | [Category("Identification")] 128 | [DisplayName("License Plate")] 129 | public string Plate { get; set; } 130 | 131 | // Will shown only for Pickup and Truck 132 | [Category("Capacity")] 133 | [DisplayName("Volume (ft³)")] 134 | public int Volume { get; set; } 135 | 136 | [Category("Capacity")] 137 | [DisplayName("Payload (kg)")] 138 | public int Payload { get; set; } 139 | 140 | [Category("Capacity")] 141 | [DisplayName("Crew cab?")] 142 | public bool CrewCab { get; set; } 143 | #endregion 144 | 145 | #region ICustomTypeDescriptor Members 146 | public AttributeCollection GetAttributes() 147 | { 148 | return TypeDescriptor.GetAttributes(this, true); 149 | } 150 | public string GetClassName() 151 | { 152 | return TypeDescriptor.GetClassName(this, true); 153 | } 154 | public string GetComponentName() 155 | { 156 | return null; 157 | } 158 | public TypeConverter GetConverter() 159 | { 160 | return TypeDescriptor.GetConverter(this, true); 161 | } 162 | public EventDescriptor GetDefaultEvent() 163 | { 164 | return TypeDescriptor.GetDefaultEvent(this, true); 165 | } 166 | public PropertyDescriptor GetDefaultProperty() 167 | { 168 | return TypeDescriptor.GetDefaultProperty(this, true); 169 | } 170 | public object GetEditor(Type editorBaseType) 171 | { 172 | return null; 173 | } 174 | public EventDescriptorCollection GetEvents(Attribute[] attributes) 175 | { 176 | return TypeDescriptor.GetEvents(this, attributes, true); 177 | } 178 | public EventDescriptorCollection GetEvents() 179 | { 180 | return TypeDescriptor.GetEvents(this, true); 181 | } 182 | public object GetPropertyOwner(PropertyDescriptor pd) 183 | { 184 | return this; 185 | } 186 | public PropertyDescriptorCollection GetProperties(Attribute[] attributes) 187 | { 188 | return this.GetProperties(); 189 | } 190 | 191 | // Method implemented to expose Volume and PayLoad properties conditionally, depending on TypeOfCar 192 | public PropertyDescriptorCollection GetProperties() 193 | { 194 | var props = new PropertyDescriptorCollection(null); 195 | 196 | foreach (PropertyDescriptor prop in TypeDescriptor.GetProperties(this, true)) 197 | { 198 | if (prop.Category=="Capacity" && (this.TypeOfCar != CarType.Pickup && this.TypeOfCar != CarType.Truck)) 199 | continue; 200 | props.Add(prop); 201 | } 202 | 203 | return props; 204 | } 205 | #endregion 206 | 207 | #region INotifyPropertyChanged Members 208 | public event PropertyChangedEventHandler PropertyChanged; 209 | 210 | private void NotifyPropertyChanged(String info) 211 | { 212 | if (PropertyChanged != null) 213 | { 214 | PropertyChanged(this, new PropertyChangedEventArgs(info)); 215 | } 216 | } 217 | #endregion 218 | } 219 | 220 | public enum Continent { Africa = 1, America = 2, Asia = 3, Europe = 4, Oceania = 5 } 221 | 222 | public class FilteredCountriesConverter : IMultiValueConverter 223 | { 224 | public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture) 225 | { 226 | ObservableCollection items = new ObservableCollection(Place.CountryInfo.Countries); 227 | CollectionView cv = CollectionViewSource.GetDefaultView(items) as CollectionView; 228 | 229 | if (values != null && values[0] != null) 230 | { 231 | Continent continent = (Continent)values[0]; 232 | if (continent > 0) 233 | cv.Filter = new Predicate(c => ((Place.CountryInfo)c).Contin == continent); 234 | } 235 | return items; 236 | } 237 | 238 | public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture) 239 | { 240 | throw new NotImplementedException(); 241 | } 242 | } 243 | 244 | // Sample class with properties with custom editors 245 | public class Place 246 | { 247 | public struct CountryInfo 248 | { 249 | public static readonly CountryInfo[] Countries = { 250 | // African countries 251 | new CountryInfo(Continent.Africa , "AO", "ANGOLA" ), 252 | new CountryInfo(Continent.Africa, "CM", "CAMEROON" ), 253 | // American countries 254 | new CountryInfo(Continent.America, "MX", "MEXICO" ), 255 | new CountryInfo(Continent.America, "PE", "PERU" ), 256 | // Asian countries 257 | new CountryInfo(Continent.Asia, "JP", "JAPAN" ), 258 | new CountryInfo(Continent.Asia, "MN", "MONGOLIA" ), 259 | // European countries 260 | new CountryInfo(Continent.Europe, "DE", "GERMANY" ), 261 | new CountryInfo(Continent.Europe, "NL", "NETHERLANDS" ), 262 | // Oceanian countries 263 | new CountryInfo(Continent.Oceania, "AU", "AUSTRALIA" ), 264 | new CountryInfo(Continent.Oceania, "NZ", "NEW ZEALAND" ) 265 | }; 266 | 267 | public Continent Contin { get; set; } 268 | public string Abrev { get; set; } 269 | public string Name { get; set; } 270 | 271 | public override string ToString() 272 | { 273 | return string.Format("{0} ({1})", Name, Abrev); 274 | } 275 | public CountryInfo(Continent _continent, string _abrev, string _name) 276 | : this() 277 | { 278 | this.Contin = _continent; 279 | this.Abrev = _abrev; 280 | this.Name = _name; 281 | } 282 | } 283 | 284 | #region Private fields 285 | private string[] _Address = new string[4]; 286 | #endregion 287 | 288 | #region Public properties 289 | [Category("Address")] 290 | public string Street 291 | { 292 | get { return _Address[0]; } 293 | set { _Address[0] = value; } 294 | } 295 | 296 | [Category("Address")] 297 | public string City 298 | { 299 | get { return _Address[1]; } 300 | set { _Address[1] = value; } 301 | } 302 | 303 | [Category("Address")] 304 | [Description("Province, state or department, depending on selected country")] 305 | public string Province 306 | { 307 | get { return _Address[2]; } 308 | set { _Address[2] = value; } 309 | } 310 | 311 | [Category("Address")] 312 | [Description("ZIP or other postal code according to the selected country")] 313 | public string Postal 314 | { 315 | get { return _Address[3]; } 316 | set { _Address[3] = value; } 317 | } 318 | 319 | [Category("Address")] 320 | [Editor(typeof(CountryEditor), typeof(PropertyValueEditor))] 321 | public CountryInfo Country { get; set; } 322 | 323 | [Category("Characteristics")] 324 | [Description("Photo or drawing of the place")] 325 | [Editor(typeof(PictureEditor), typeof(PropertyValueEditor))] 326 | public BitmapImage Picture { get; set; } 327 | 328 | [Category("Characteristics")] 329 | public int Floors { get; set; } 330 | 331 | [Category("Characteristics")] 332 | public int CurrentValue { get; set; } 333 | 334 | [Category("Proprietary")] 335 | public string FirstName { get; set; } 336 | 337 | [Category("Proprietary")] 338 | public string LastName { get; set; } 339 | #endregion 340 | 341 | public Place() 342 | { 343 | for (int i = 0; i < _Address.Length; i++) 344 | _Address[i] = string.Empty; 345 | this.Country = CountryInfo.Countries[0]; 346 | } 347 | } 348 | } 349 | -------------------------------------------------------------------------------- /demo/MainWindow.xaml: -------------------------------------------------------------------------------- 1 |  10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 31 | 32 | Selected Object: 33 | 34 | Selection Type: 35 | 36 | 37 | 38 | Features: 39 | 40 | 41 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /demo/MainWindow.xaml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Windows; 3 | using System.Collections; 4 | using System.Windows.Controls.Primitives; 5 | using System.Windows.Controls; 6 | 7 | namespace WpfPropertyGrid_Demo 8 | { 9 | /// 10 | /// Interaction logic for MainWindow.xaml 11 | /// 12 | public partial class MainWindow : Window 13 | { 14 | private Person Person = new Person(); 15 | private Vehicle Vehicle1 = new Vehicle(); 16 | private Vehicle Vehicle2 = new Vehicle(); 17 | private Place Place = new Place(); 18 | 19 | // names must match the data members 20 | private object[] ItemArray = { "Person", "Vehicle1", "Vehicle2", "Place" }; 21 | 22 | public MainWindow() 23 | { 24 | InitializeComponent(); 25 | 26 | this.Vehicle1.PropertyChanged += new System.ComponentModel.PropertyChangedEventHandler(Vehicle_PropertyChanged); 27 | this.Vehicle2.PropertyChanged += new System.ComponentModel.PropertyChangedEventHandler(Vehicle_PropertyChanged); 28 | 29 | this.Radio3.IsChecked = true; 30 | this.NoSelection_Click(this, null); 31 | } 32 | 33 | // Special handling for vehicle type change 34 | void Vehicle_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e) 35 | { 36 | this.PropertyGrid1.RefreshPropertyList(); 37 | } 38 | 39 | private void SingleSelect_Click(object sender, RoutedEventArgs e) 40 | { 41 | this.ItemList.ItemTemplate = this.Resources["RadioButtons"] as DataTemplate; 42 | this.ItemList.ItemsSource = this.ItemArray; 43 | this.PropertyGrid1.SelectedObject = null; 44 | } 45 | private void MultiSelect_Click(object sender, RoutedEventArgs e) 46 | { 47 | this.ItemList.ItemTemplate = this.Resources["CheckBoxes"] as DataTemplate; 48 | this.ItemList.ItemsSource = this.ItemArray; 49 | this.PropertyGrid1.SelectedObject = null; 50 | } 51 | private void NoSelection_Click(object sender, RoutedEventArgs e) 52 | { 53 | this.ItemList.ItemTemplate = null; 54 | this.ItemList.ItemsSource = new string[] { "(none)" }; 55 | this.PropertyGrid1.SelectedObject = null; 56 | } 57 | 58 | private void Item_Checked(object sender, RoutedEventArgs e) 59 | { 60 | if (e.Source is RadioButton) 61 | { 62 | object selected = this.GetType().GetField((e.Source as RadioButton).Content.ToString(), 63 | System.Reflection.BindingFlags.GetField | System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic).GetValue(this); 64 | this.PropertyGrid1.SelectedObject = selected; 65 | } 66 | else if (e.Source is CheckBox && this.Radio2.IsChecked.GetValueOrDefault()) 67 | { 68 | ArrayList selected = new ArrayList(); 69 | 70 | for (int i = 0; i < ItemList.Items.Count; i++) 71 | { 72 | ContentPresenter container = ItemList.ItemContainerGenerator.ContainerFromIndex(i) as ContentPresenter; 73 | DataTemplate dataTemplate = container.ContentTemplate; 74 | CheckBox chk = (CheckBox)dataTemplate.FindName("chk", container); 75 | if (chk.IsChecked.GetValueOrDefault()) 76 | { 77 | object item = this.GetType().GetField(chk.Content.ToString(), 78 | System.Reflection.BindingFlags.GetField | System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic).GetValue(this); 79 | selected.Add(item); 80 | } 81 | } 82 | this.PropertyGrid1.SelectedObjects = selected.ToArray(); 83 | } 84 | } 85 | 86 | private void ShowDescrip_Click(object sender, RoutedEventArgs e) 87 | { 88 | this.PropertyGrid1.HelpVisible = !this.PropertyGrid1.HelpVisible; 89 | } 90 | 91 | private void ShowToolbar_Click(object sender, RoutedEventArgs e) 92 | { 93 | this.PropertyGrid1.ToolbarVisible = !this.PropertyGrid1.ToolbarVisible; 94 | } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /demo/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Resources; 3 | using System.Runtime.CompilerServices; 4 | using System.Runtime.InteropServices; 5 | using System.Windows; 6 | 7 | // General Information about an assembly is controlled through the following 8 | // set of attributes. Change these attribute values to modify the information 9 | // associated with an assembly. 10 | [assembly: AssemblyTitle("WpfPropertyGrid_Demo")] 11 | [assembly: AssemblyDescription("")] 12 | [assembly: AssemblyConfiguration("")] 13 | [assembly: AssemblyCompany("Philips")] 14 | [assembly: AssemblyProduct("WpfPropertyGrid_Demo")] 15 | [assembly: AssemblyCopyright("Copyright © Philips 2010")] 16 | [assembly: AssemblyTrademark("")] 17 | [assembly: AssemblyCulture("")] 18 | 19 | // Setting ComVisible to false makes the types in this assembly not visible 20 | // to COM components. If you need to access a type in this assembly from 21 | // COM, set the ComVisible attribute to true on that type. 22 | [assembly: ComVisible(false)] 23 | 24 | //In order to begin building localizable applications, set 25 | //CultureYouAreCodingWith in your .csproj file 26 | //inside a . For example, if you are using US english 27 | //in your source files, set the to en-US. Then uncomment 28 | //the NeutralResourceLanguage attribute below. Update the "en-US" in 29 | //the line below to match the UICulture setting in the project file. 30 | 31 | //[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)] 32 | 33 | 34 | [assembly: ThemeInfo( 35 | ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located 36 | //(used if a resource is not found in the page, 37 | // or application resource dictionaries) 38 | ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located 39 | //(used if a resource is not found in the page, 40 | // app, or any theme specific resource dictionaries) 41 | )] 42 | 43 | 44 | // Version information for an assembly consists of the following four values: 45 | // 46 | // Major Version 47 | // Minor Version 48 | // Build Number 49 | // Revision 50 | // 51 | // You can specify all the values or you can default the Build and Revision Numbers 52 | // by using the '*' as shown below: 53 | // [assembly: AssemblyVersion("1.0.*")] 54 | [assembly: AssemblyVersion("1.0.0.0")] 55 | [assembly: AssemblyFileVersion("1.0.0.0")] 56 | -------------------------------------------------------------------------------- /demo/Properties/Resources.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30128.1 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace WpfPropertyGrid_Demo.Properties 12 | { 13 | 14 | 15 | /// 16 | /// A strongly-typed resource class, for looking up localized strings, etc. 17 | /// 18 | // This class was auto-generated by the StronglyTypedResourceBuilder 19 | // class via a tool like ResGen or Visual Studio. 20 | // To add or remove a member, edit your .ResX file then rerun ResGen 21 | // with the /str option, or rebuild your VS project. 22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] 23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 25 | internal class Resources 26 | { 27 | 28 | private static global::System.Resources.ResourceManager resourceMan; 29 | 30 | private static global::System.Globalization.CultureInfo resourceCulture; 31 | 32 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] 33 | internal Resources() 34 | { 35 | } 36 | 37 | /// 38 | /// Returns the cached ResourceManager instance used by this class. 39 | /// 40 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 41 | internal static global::System.Resources.ResourceManager ResourceManager 42 | { 43 | get 44 | { 45 | if ((resourceMan == null)) 46 | { 47 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("WpfPropertyGrid_Demo.Properties.Resources", typeof(Resources).Assembly); 48 | resourceMan = temp; 49 | } 50 | return resourceMan; 51 | } 52 | } 53 | 54 | /// 55 | /// Overrides the current thread's CurrentUICulture property for all 56 | /// resource lookups using this strongly typed resource class. 57 | /// 58 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 59 | internal static global::System.Globalization.CultureInfo Culture 60 | { 61 | get 62 | { 63 | return resourceCulture; 64 | } 65 | set 66 | { 67 | resourceCulture = value; 68 | } 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /demo/Properties/Resources.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | text/microsoft-resx 107 | 108 | 109 | 2.0 110 | 111 | 112 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 113 | 114 | 115 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | -------------------------------------------------------------------------------- /demo/Properties/Settings.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30128.1 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace WpfPropertyGrid_Demo.Properties 12 | { 13 | 14 | 15 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 16 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "10.0.0.0")] 17 | internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase 18 | { 19 | 20 | private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); 21 | 22 | public static Settings Default 23 | { 24 | get 25 | { 26 | return defaultInstance; 27 | } 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /demo/Properties/Settings.settings: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /demo/WpfPropertyGrid.cs: -------------------------------------------------------------------------------- 1 | // ********************************************************************* 2 | // PLEASE DO NOT REMOVE THIS DISCLAIMER 3 | // 4 | // WpfPropertyGrid - By Jaime Olivares 5 | // July 11, 2011 6 | // Article site: http://www.codeproject.com/KB/grid/WpfPropertyGrid.aspx 7 | // Author site: www.jaimeolivares.com 8 | // License: Code Project Open License (CPOL) 9 | // 10 | // ********************************************************************* 11 | 12 | using System.Activities.Presentation; 13 | using System.Activities.Presentation.Model; 14 | using System.Activities.Presentation.View; 15 | using System.ComponentModel; 16 | using System.Reflection; 17 | using System.Windows.Data; 18 | 19 | namespace System.Windows.Controls 20 | { 21 | public enum PropertySort 22 | { 23 | NoSort = 0, 24 | Alphabetical = 1, 25 | Categorized = 2, 26 | CategorizedAlphabetical = 3 27 | }; 28 | 29 | /// WPF Native PropertyGrid class, uses Workflow Foundation's PropertyInspector 30 | public class WpfPropertyGrid : Grid 31 | { 32 | #region Private fields 33 | private WorkflowDesigner Designer; 34 | private MethodInfo RefreshMethod; 35 | private MethodInfo OnSelectionChangedMethod; 36 | private MethodInfo IsInAlphaViewMethod; 37 | private TextBlock SelectionTypeLabel; 38 | private Control PropertyToolBar; 39 | private Border HelpText; 40 | private GridSplitter Splitter; 41 | private double HelpTextHeight = 60; 42 | #endregion 43 | 44 | #region Public properties 45 | /// Get or sets the selected object. Can be null. 46 | public object SelectedObject 47 | { 48 | get { return GetValue(SelectedObjectProperty); } 49 | set { SetValue(SelectedObjectProperty, value); } 50 | } 51 | /// Get or sets the selected object collection. Returns empty array by default. 52 | public object[] SelectedObjects 53 | { 54 | get { return GetValue(SelectedObjectsProperty) as object[]; } 55 | set { SetValue(SelectedObjectsProperty, value); } 56 | } 57 | /// XAML information with PropertyGrid's font and color information 58 | /// Documentation for WorkflowDesigner.PropertyInspectorFontAndColorData 59 | public string FontAndColorData 60 | { 61 | set 62 | { 63 | Designer.PropertyInspectorFontAndColorData = value; 64 | } 65 | } 66 | /// Shows the description area on the top of the control 67 | public bool HelpVisible 68 | { 69 | get { return (bool)GetValue(HelpVisibleProperty); } 70 | set { SetValue(HelpVisibleProperty, value); } 71 | } 72 | /// Shows the tolbar on the top of the control 73 | public bool ToolbarVisible 74 | { 75 | get { return (bool)GetValue(ToolbarVisibleProperty); } 76 | set { SetValue(ToolbarVisibleProperty, value); } 77 | } 78 | public PropertySort PropertySort 79 | { 80 | get { return (PropertySort)GetValue(PropertySortProperty); } 81 | set { SetValue(PropertySortProperty, value); } 82 | } 83 | #endregion 84 | 85 | #region Dependency properties registration 86 | public static readonly DependencyProperty SelectedObjectProperty = 87 | DependencyProperty.Register("SelectedObject", typeof(object), typeof(WpfPropertyGrid), 88 | new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, SelectedObjectPropertyChanged)); 89 | 90 | public static readonly DependencyProperty SelectedObjectsProperty = 91 | DependencyProperty.Register("SelectedObjects", typeof(object[]), typeof(WpfPropertyGrid), 92 | new FrameworkPropertyMetadata(new object[0], FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, SelectedObjectsPropertyChanged, CoerceSelectedObjects)); 93 | 94 | public static readonly DependencyProperty HelpVisibleProperty = 95 | DependencyProperty.Register("HelpVisible", typeof(bool), typeof(WpfPropertyGrid), 96 | new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, HelpVisiblePropertyChanged)); 97 | public static readonly DependencyProperty ToolbarVisibleProperty = 98 | DependencyProperty.Register("ToolbarVisible", typeof(bool), typeof(WpfPropertyGrid), 99 | new FrameworkPropertyMetadata(true, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, ToolbarVisiblePropertyChanged)); 100 | public static readonly DependencyProperty PropertySortProperty = 101 | DependencyProperty.Register("PropertySort", typeof(PropertySort), typeof(WpfPropertyGrid), 102 | new FrameworkPropertyMetadata(PropertySort.CategorizedAlphabetical, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, PropertySortPropertyChanged)); 103 | #endregion 104 | 105 | #region Dependency properties events 106 | private static object CoerceSelectedObject(DependencyObject d, object value) 107 | { 108 | WpfPropertyGrid pg = d as WpfPropertyGrid; 109 | 110 | object[] collection = pg.GetValue(SelectedObjectsProperty) as object[]; 111 | 112 | return collection.Length == 0 ? null : value; 113 | } 114 | private static object CoerceSelectedObjects(DependencyObject d, object value) 115 | { 116 | WpfPropertyGrid pg = d as WpfPropertyGrid; 117 | 118 | object single = pg.GetValue(SelectedObjectsProperty); 119 | 120 | return single == null ? new object[0] : value; 121 | } 122 | 123 | private static void SelectedObjectPropertyChanged(DependencyObject source, DependencyPropertyChangedEventArgs e) 124 | { 125 | WpfPropertyGrid pg = source as WpfPropertyGrid; 126 | pg.CoerceValue(SelectedObjectsProperty); 127 | 128 | if (e.NewValue == null) 129 | { 130 | pg.OnSelectionChangedMethod.Invoke(pg.Designer.PropertyInspectorView, new object[] { null }); 131 | pg.SelectionTypeLabel.Text = string.Empty; 132 | } 133 | else 134 | { 135 | var context = new EditingContext(); 136 | var mtm = new ModelTreeManager(context); 137 | mtm.Load(e.NewValue); 138 | Selection selection = Selection.Select(context, mtm.Root); 139 | 140 | pg.OnSelectionChangedMethod.Invoke(pg.Designer.PropertyInspectorView, new object[] { selection }); 141 | pg.SelectionTypeLabel.Text = e.NewValue.GetType().Name; 142 | } 143 | 144 | pg.ChangeHelpText(string.Empty, string.Empty); 145 | } 146 | private static void SelectedObjectsPropertyChanged(DependencyObject source, DependencyPropertyChangedEventArgs e) 147 | { 148 | WpfPropertyGrid pg = source as WpfPropertyGrid; 149 | pg.CoerceValue(SelectedObjectsProperty); 150 | 151 | object[] collection = e.NewValue as object[]; 152 | 153 | if (collection.Length == 0) 154 | { 155 | pg.OnSelectionChangedMethod.Invoke(pg.Designer.PropertyInspectorView, new object[] { null }); 156 | pg.SelectionTypeLabel.Text = string.Empty; 157 | } 158 | else 159 | { 160 | bool same = true; 161 | Type first = null; 162 | 163 | var context = new EditingContext(); 164 | var mtm = new ModelTreeManager(context); 165 | Selection selection = null; 166 | 167 | // Accumulates the selection and determines the type to be shown in the top of the PG 168 | for (int i = 0; i < collection.Length; i++) 169 | { 170 | mtm.Load(collection[i]); 171 | if (i == 0) 172 | { 173 | selection = Selection.Select(context, mtm.Root); 174 | first = collection[0].GetType(); 175 | } 176 | else 177 | { 178 | selection = Selection.Union(context, mtm.Root); 179 | if (!collection[i].GetType().Equals(first)) 180 | same = false; 181 | } 182 | } 183 | 184 | pg.OnSelectionChangedMethod.Invoke(pg.Designer.PropertyInspectorView, new object[] { selection }); 185 | pg.SelectionTypeLabel.Text = same ? first.Name + " " : "Object "; 186 | } 187 | 188 | pg.ChangeHelpText(string.Empty, string.Empty); 189 | } 190 | private static void HelpVisiblePropertyChanged(DependencyObject source, DependencyPropertyChangedEventArgs e) 191 | { 192 | WpfPropertyGrid pg = source as WpfPropertyGrid; 193 | 194 | if (e.NewValue != e.OldValue) 195 | { 196 | if (e.NewValue.Equals(true)) 197 | { 198 | pg.RowDefinitions[1].Height = new GridLength(5); 199 | pg.RowDefinitions[2].Height = new GridLength(pg.HelpTextHeight); 200 | } 201 | else 202 | { 203 | pg.HelpTextHeight = pg.RowDefinitions[2].Height.Value; 204 | pg.RowDefinitions[1].Height = new GridLength(0); 205 | pg.RowDefinitions[2].Height = new GridLength(0); 206 | } 207 | } 208 | } 209 | private static void ToolbarVisiblePropertyChanged(DependencyObject source, DependencyPropertyChangedEventArgs e) 210 | { 211 | WpfPropertyGrid pg = source as WpfPropertyGrid; 212 | pg.PropertyToolBar.Visibility = e.NewValue.Equals(true) ? Visibility.Visible : Visibility.Collapsed; 213 | } 214 | private static void PropertySortPropertyChanged(DependencyObject source, DependencyPropertyChangedEventArgs e) 215 | { 216 | WpfPropertyGrid pg = source as WpfPropertyGrid; 217 | PropertySort sort = (PropertySort)e.NewValue; 218 | 219 | bool isAlpha = (sort == PropertySort.Alphabetical || sort == PropertySort.NoSort); 220 | pg.IsInAlphaViewMethod.Invoke(pg.Designer.PropertyInspectorView, new object[] { isAlpha }); 221 | } 222 | #endregion 223 | 224 | /// Default constructor, creates the UIElements including a PropertyInspector 225 | public WpfPropertyGrid() 226 | { 227 | this.ColumnDefinitions.Add(new ColumnDefinition()); 228 | this.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(1, GridUnitType.Star) }); 229 | this.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(0) }); 230 | this.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(0) }); 231 | 232 | this.Designer = new WorkflowDesigner(); 233 | TextBlock title = new TextBlock() 234 | { 235 | Visibility = Windows.Visibility.Visible, 236 | TextWrapping = TextWrapping.NoWrap, 237 | TextTrimming = TextTrimming.CharacterEllipsis, 238 | FontWeight = FontWeights.Bold 239 | }; 240 | TextBlock descrip = new TextBlock() 241 | { 242 | Visibility = Windows.Visibility.Visible, 243 | TextWrapping = TextWrapping.Wrap, 244 | TextTrimming = TextTrimming.CharacterEllipsis 245 | }; 246 | DockPanel dock = new DockPanel() 247 | { 248 | Visibility = Windows.Visibility.Visible, 249 | LastChildFill = true, 250 | Margin = new Thickness(3,0,3,0) 251 | }; 252 | 253 | title.SetValue(DockPanel.DockProperty, Dock.Top); 254 | dock.Children.Add(title); 255 | dock.Children.Add(descrip); 256 | this.HelpText = new Border() 257 | { 258 | Visibility = Windows.Visibility.Visible, 259 | BorderBrush = SystemColors.ActiveBorderBrush, 260 | Background = SystemColors.ControlBrush, 261 | BorderThickness = new Thickness(1), 262 | Child = dock 263 | }; 264 | this.Splitter = new GridSplitter() 265 | { 266 | Visibility = Windows.Visibility.Visible, 267 | ResizeDirection = GridResizeDirection.Rows, 268 | Height=5, 269 | HorizontalAlignment= Windows.HorizontalAlignment.Stretch 270 | }; 271 | 272 | var inspector = Designer.PropertyInspectorView; 273 | inspector.Visibility = Visibility.Visible; 274 | inspector.SetValue(FrameworkElement.VerticalAlignmentProperty, VerticalAlignment.Stretch); 275 | 276 | this.Splitter.SetValue(Grid.RowProperty, 1); 277 | this.Splitter.SetValue(Grid.ColumnProperty, 0); 278 | 279 | this.HelpText.SetValue(Grid.RowProperty, 2); 280 | this.HelpText.SetValue(Grid.ColumnProperty, 0); 281 | 282 | Binding binding = new Binding("Parent.Background"); 283 | title.SetBinding(BackgroundProperty, binding); 284 | descrip.SetBinding(BackgroundProperty, binding); 285 | 286 | this.Children.Add(inspector); 287 | this.Children.Add(this.Splitter); 288 | this.Children.Add(this.HelpText); 289 | 290 | Type inspectorType = inspector.GetType(); 291 | var props = inspectorType.GetProperties(Reflection.BindingFlags.Public | Reflection.BindingFlags.NonPublic | Reflection.BindingFlags.Instance | 292 | Reflection.BindingFlags.DeclaredOnly); 293 | 294 | var methods = inspectorType.GetMethods(Reflection.BindingFlags.Public | Reflection.BindingFlags.NonPublic | Reflection.BindingFlags.Instance | 295 | Reflection.BindingFlags.DeclaredOnly); 296 | 297 | this.RefreshMethod = inspectorType.GetMethod("RefreshPropertyList", 298 | Reflection.BindingFlags.NonPublic | Reflection.BindingFlags.Instance | Reflection.BindingFlags.DeclaredOnly); 299 | this.IsInAlphaViewMethod = inspectorType.GetMethod("set_IsInAlphaView", 300 | Reflection.BindingFlags.Public | Reflection.BindingFlags.Instance | Reflection.BindingFlags.DeclaredOnly); 301 | this.OnSelectionChangedMethod = inspectorType.GetMethod("OnSelectionChanged", 302 | Reflection.BindingFlags.Public | Reflection.BindingFlags.Instance | Reflection.BindingFlags.DeclaredOnly); 303 | this.SelectionTypeLabel = inspectorType.GetMethod("get_SelectionTypeLabel", 304 | Reflection.BindingFlags.Public | Reflection.BindingFlags.NonPublic | Reflection.BindingFlags.Instance | 305 | Reflection.BindingFlags.DeclaredOnly).Invoke(inspector, new object[0]) as TextBlock; 306 | this.PropertyToolBar = inspectorType.GetMethod("get_PropertyToolBar", 307 | Reflection.BindingFlags.Public | Reflection.BindingFlags.NonPublic | Reflection.BindingFlags.Instance | 308 | Reflection.BindingFlags.DeclaredOnly).Invoke(inspector, new object[0]) as Control; 309 | inspectorType.GetEvent("GotFocus").AddEventHandler(this, 310 | Delegate.CreateDelegate(typeof(RoutedEventHandler), this, "GotFocusHandler", false)); 311 | 312 | this.SelectionTypeLabel.Text = string.Empty; 313 | } 314 | /// Updates the PropertyGrid's properties 315 | public void RefreshPropertyList() 316 | { 317 | RefreshMethod.Invoke(Designer.PropertyInspectorView, new object[] { false }); 318 | } 319 | 320 | /// Traps the change of focused property and updates the help text 321 | /// Not used 322 | /// Points to the source control containing the selected property 323 | private void GotFocusHandler(object sender, RoutedEventArgs args) 324 | { 325 | //if (args.OriginalSource is TextBlock) 326 | //{ 327 | string title = string.Empty; 328 | string descrip = string.Empty; 329 | var theSelectedObjects = this.GetValue(SelectedObjectsProperty) as object[]; 330 | 331 | if (theSelectedObjects != null && theSelectedObjects.Length > 0) 332 | { 333 | Type first = theSelectedObjects[0].GetType(); 334 | for (int i = 1; i < theSelectedObjects.Length; i++) 335 | { 336 | if (!theSelectedObjects[i].GetType().Equals(first)) 337 | { 338 | ChangeHelpText(title, descrip); 339 | return; 340 | } 341 | } 342 | 343 | object data = (args.OriginalSource as FrameworkElement).DataContext; 344 | PropertyInfo propEntry = data.GetType().GetProperty("PropertyEntry"); 345 | if (propEntry == null) 346 | { 347 | propEntry = data.GetType().GetProperty("ParentProperty"); 348 | } 349 | 350 | if (propEntry != null) 351 | { 352 | object propEntryValue = propEntry.GetValue(data, null); 353 | string propName = propEntryValue.GetType().GetProperty("PropertyName").GetValue(propEntryValue, null) as string; 354 | title = propEntryValue.GetType().GetProperty("DisplayName").GetValue(propEntryValue, null) as string; 355 | PropertyInfo property = theSelectedObjects[0].GetType().GetProperty(propName); 356 | object[] attrs = property.GetCustomAttributes(typeof(DescriptionAttribute), true); 357 | 358 | if (attrs != null && attrs.Length > 0) 359 | descrip = (attrs[0] as DescriptionAttribute).Description; 360 | } 361 | ChangeHelpText(title, descrip); 362 | } 363 | //} 364 | } 365 | /// Changes the text help area contents 366 | /// Title in bold 367 | /// Description with ellipsis 368 | private void ChangeHelpText(string title, string descrip) 369 | { 370 | DockPanel dock = this.HelpText.Child as DockPanel; 371 | (dock.Children[0] as TextBlock).Text = title; 372 | (dock.Children[1] as TextBlock).Text = descrip; 373 | } 374 | } 375 | } 376 | -------------------------------------------------------------------------------- /demo/WpfPropertyGrid_Demo.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Debug 5 | x86 6 | 8.0.30703 7 | 2.0 8 | {F391A081-0658-44F6-943F-99CF8D23ED5A} 9 | WinExe 10 | Properties 11 | WpfPropertyGrid_Demo 12 | WpfPropertyGrid_Demo 13 | v4.0 14 | Client 15 | 512 16 | {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 17 | 4 18 | 19 | 20 | x86 21 | true 22 | full 23 | false 24 | bin\Debug\ 25 | DEBUG;TRACE 26 | prompt 27 | 4 28 | 29 | 30 | x86 31 | pdbonly 32 | true 33 | bin\Release\ 34 | TRACE 35 | prompt 36 | 4 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 4.0 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | MSBuild:Compile 59 | Designer 60 | 61 | 62 | MSBuild:Compile 63 | Designer 64 | 65 | 66 | App.xaml 67 | Code 68 | 69 | 70 | 71 | 72 | 73 | MainWindow.xaml 74 | Code 75 | 76 | 77 | 78 | 79 | Code 80 | 81 | 82 | True 83 | True 84 | Resources.resx 85 | 86 | 87 | True 88 | Settings.settings 89 | True 90 | 91 | 92 | ResXFileCodeGenerator 93 | Resources.Designer.cs 94 | 95 | 96 | 97 | SettingsSingleFileGenerator 98 | Settings.Designer.cs 99 | 100 | 101 | 102 | 103 | 110 | -------------------------------------------------------------------------------- /demo/WpfPropertyGrid_Demo.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 11.00 3 | # Visual Studio 2010 4 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WpfPropertyGrid_Demo", "WpfPropertyGrid_Demo.csproj", "{F391A081-0658-44F6-943F-99CF8D23ED5A}" 5 | EndProject 6 | Global 7 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 8 | Debug|x86 = Debug|x86 9 | Release|x86 = Release|x86 10 | EndGlobalSection 11 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 12 | {F391A081-0658-44F6-943F-99CF8D23ED5A}.Debug|x86.ActiveCfg = Debug|x86 13 | {F391A081-0658-44F6-943F-99CF8D23ED5A}.Debug|x86.Build.0 = Debug|x86 14 | {F391A081-0658-44F6-943F-99CF8D23ED5A}.Release|x86.ActiveCfg = Release|x86 15 | {F391A081-0658-44F6-943F-99CF8D23ED5A}.Release|x86.Build.0 = Release|x86 16 | EndGlobalSection 17 | GlobalSection(SolutionProperties) = preSolution 18 | HideSolutionNode = FALSE 19 | EndGlobalSection 20 | EndGlobal 21 | -------------------------------------------------------------------------------- /images/ClassDiagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaime-olivares/wpf-propertygrid/56b2ca15853f1ab0585a5b57197b620a675a601e/images/ClassDiagram.png -------------------------------------------------------------------------------- /images/Person.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaime-olivares/wpf-propertygrid/56b2ca15853f1ab0585a5b57197b620a675a601e/images/Person.png -------------------------------------------------------------------------------- /images/PersonProps.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaime-olivares/wpf-propertygrid/56b2ca15853f1ab0585a5b57197b620a675a601e/images/PersonProps.png -------------------------------------------------------------------------------- /images/Place.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaime-olivares/wpf-propertygrid/56b2ca15853f1ab0585a5b57197b620a675a601e/images/Place.png -------------------------------------------------------------------------------- /images/PlaceProps1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaime-olivares/wpf-propertygrid/56b2ca15853f1ab0585a5b57197b620a675a601e/images/PlaceProps1.png -------------------------------------------------------------------------------- /images/PlaceProps2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaime-olivares/wpf-propertygrid/56b2ca15853f1ab0585a5b57197b620a675a601e/images/PlaceProps2.png -------------------------------------------------------------------------------- /images/Vehicle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaime-olivares/wpf-propertygrid/56b2ca15853f1ab0585a5b57197b620a675a601e/images/Vehicle.png -------------------------------------------------------------------------------- /images/VehicleProps1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaime-olivares/wpf-propertygrid/56b2ca15853f1ab0585a5b57197b620a675a601e/images/VehicleProps1.png -------------------------------------------------------------------------------- /images/VehicleProps2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaime-olivares/wpf-propertygrid/56b2ca15853f1ab0585a5b57197b620a675a601e/images/VehicleProps2.png -------------------------------------------------------------------------------- /images/WorkflowFoundationDesigner.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaime-olivares/wpf-propertygrid/56b2ca15853f1ab0585a5b57197b620a675a601e/images/WorkflowFoundationDesigner.jpg -------------------------------------------------------------------------------- /images/screenshot1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaime-olivares/wpf-propertygrid/56b2ca15853f1ab0585a5b57197b620a675a601e/images/screenshot1.png -------------------------------------------------------------------------------- /images/screenshot2.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaime-olivares/wpf-propertygrid/56b2ca15853f1ab0585a5b57197b620a675a601e/images/screenshot2.PNG -------------------------------------------------------------------------------- /src/WpfPropertyGrid.cs: -------------------------------------------------------------------------------- 1 | // ********************************************************************* 2 | // PLEASE DO NOT REMOVE THIS DISCLAIMER 3 | // 4 | // WpfPropertyGrid - By Jaime Olivares 5 | // Copyright (c) 2011 - 2024 6 | // Code repository: https://github.com/jaime-olivares/wpf-propertygrid 7 | // 8 | // ********************************************************************* 9 | 10 | using System.Activities.Presentation; 11 | using System.Activities.Presentation.Model; 12 | using System.Activities.Presentation.View; 13 | using System.ComponentModel; 14 | using System.Reflection; 15 | using System.Windows.Data; 16 | 17 | namespace System.Windows.Controls 18 | { 19 | public enum PropertySort 20 | { 21 | NoSort = 0, 22 | Alphabetical = 1, 23 | Categorized = 2, 24 | CategorizedAlphabetical = 3 25 | }; 26 | 27 | /// WPF Native PropertyGrid class, uses Workflow Foundation's PropertyInspector 28 | public class WpfPropertyGrid : Grid 29 | { 30 | #region Private fields 31 | private WorkflowDesigner Designer; 32 | private MethodInfo RefreshMethod; 33 | private MethodInfo OnSelectionChangedMethod; 34 | private MethodInfo IsInAlphaViewMethod; 35 | private TextBlock SelectionTypeLabel; 36 | private Control PropertyToolBar; 37 | private Border HelpText; 38 | private GridSplitter Splitter; 39 | private double HelpTextHeight = 60; 40 | #endregion 41 | 42 | #region Public properties 43 | /// Get or sets the selected object. Can be null. 44 | public object SelectedObject 45 | { 46 | get { return GetValue(SelectedObjectProperty); } 47 | set { SetValue(SelectedObjectProperty, value); } 48 | } 49 | /// Get or sets the selected object collection. Returns empty array by default. 50 | public object[] SelectedObjects 51 | { 52 | get { return GetValue(SelectedObjectsProperty) as object[]; } 53 | set { SetValue(SelectedObjectsProperty, value); } 54 | } 55 | /// XAML information with PropertyGrid's font and color information 56 | /// Documentation for WorkflowDesigner.PropertyInspectorFontAndColorData 57 | public string FontAndColorData 58 | { 59 | set 60 | { 61 | Designer.PropertyInspectorFontAndColorData = value; 62 | } 63 | } 64 | /// Shows the description area on the top of the control 65 | public bool HelpVisible 66 | { 67 | get { return (bool)GetValue(HelpVisibleProperty); } 68 | set { SetValue(HelpVisibleProperty, value); } 69 | } 70 | /// Shows the tolbar on the top of the control 71 | public bool ToolbarVisible 72 | { 73 | get { return (bool)GetValue(ToolbarVisibleProperty); } 74 | set { SetValue(ToolbarVisibleProperty, value); } 75 | } 76 | public PropertySort PropertySort 77 | { 78 | get { return (PropertySort)GetValue(PropertySortProperty); } 79 | set { SetValue(PropertySortProperty, value); } 80 | } 81 | #endregion 82 | 83 | #region Dependency properties registration 84 | public static readonly DependencyProperty SelectedObjectProperty = 85 | DependencyProperty.Register("SelectedObject", typeof(object), typeof(WpfPropertyGrid), 86 | new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, SelectedObjectPropertyChanged)); 87 | 88 | public static readonly DependencyProperty SelectedObjectsProperty = 89 | DependencyProperty.Register("SelectedObjects", typeof(object[]), typeof(WpfPropertyGrid), 90 | new FrameworkPropertyMetadata(new object[0], FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, SelectedObjectsPropertyChanged, CoerceSelectedObjects)); 91 | 92 | public static readonly DependencyProperty HelpVisibleProperty = 93 | DependencyProperty.Register("HelpVisible", typeof(bool), typeof(WpfPropertyGrid), 94 | new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, HelpVisiblePropertyChanged)); 95 | public static readonly DependencyProperty ToolbarVisibleProperty = 96 | DependencyProperty.Register("ToolbarVisible", typeof(bool), typeof(WpfPropertyGrid), 97 | new FrameworkPropertyMetadata(true, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, ToolbarVisiblePropertyChanged)); 98 | public static readonly DependencyProperty PropertySortProperty = 99 | DependencyProperty.Register("PropertySort", typeof(PropertySort), typeof(WpfPropertyGrid), 100 | new FrameworkPropertyMetadata(PropertySort.CategorizedAlphabetical, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, PropertySortPropertyChanged)); 101 | #endregion 102 | 103 | #region Dependency properties events 104 | private static object CoerceSelectedObject(DependencyObject d, object value) 105 | { 106 | WpfPropertyGrid pg = d as WpfPropertyGrid; 107 | 108 | object[] collection = pg.GetValue(SelectedObjectsProperty) as object[]; 109 | 110 | return collection.Length == 0 ? null : value; 111 | } 112 | private static object CoerceSelectedObjects(DependencyObject d, object value) 113 | { 114 | WpfPropertyGrid pg = d as WpfPropertyGrid; 115 | 116 | object single = pg.GetValue(SelectedObjectsProperty); 117 | 118 | return single == null ? new object[0] : value; 119 | } 120 | 121 | private static void SelectedObjectPropertyChanged(DependencyObject source, DependencyPropertyChangedEventArgs e) 122 | { 123 | WpfPropertyGrid pg = source as WpfPropertyGrid; 124 | pg.CoerceValue(SelectedObjectsProperty); 125 | 126 | if (e.NewValue == null) 127 | { 128 | pg.OnSelectionChangedMethod.Invoke(pg.Designer.PropertyInspectorView, new object[] { null }); 129 | pg.SelectionTypeLabel.Text = string.Empty; 130 | } 131 | else 132 | { 133 | var context = new EditingContext(); 134 | var mtm = new ModelTreeManager(context); 135 | mtm.Load(e.NewValue); 136 | Selection selection = Selection.Select(context, mtm.Root); 137 | 138 | pg.OnSelectionChangedMethod.Invoke(pg.Designer.PropertyInspectorView, new object[] { selection }); 139 | pg.SelectionTypeLabel.Text = e.NewValue.GetType().Name; 140 | } 141 | 142 | pg.ChangeHelpText(string.Empty, string.Empty); 143 | } 144 | private static void SelectedObjectsPropertyChanged(DependencyObject source, DependencyPropertyChangedEventArgs e) 145 | { 146 | WpfPropertyGrid pg = source as WpfPropertyGrid; 147 | pg.CoerceValue(SelectedObjectsProperty); 148 | 149 | object[] collection = e.NewValue as object[]; 150 | 151 | if (collection.Length == 0) 152 | { 153 | pg.OnSelectionChangedMethod.Invoke(pg.Designer.PropertyInspectorView, new object[] { null }); 154 | pg.SelectionTypeLabel.Text = string.Empty; 155 | } 156 | else 157 | { 158 | bool same = true; 159 | Type first = null; 160 | 161 | var context = new EditingContext(); 162 | var mtm = new ModelTreeManager(context); 163 | Selection selection = null; 164 | 165 | // Accumulates the selection and determines the type to be shown in the top of the PG 166 | for (int i = 0; i < collection.Length; i++) 167 | { 168 | mtm.Load(collection[i]); 169 | if (i == 0) 170 | { 171 | selection = Selection.Select(context, mtm.Root); 172 | first = collection[0].GetType(); 173 | } 174 | else 175 | { 176 | selection = Selection.Union(context, mtm.Root); 177 | if (!collection[i].GetType().Equals(first)) 178 | same = false; 179 | } 180 | } 181 | 182 | pg.OnSelectionChangedMethod.Invoke(pg.Designer.PropertyInspectorView, new object[] { selection }); 183 | pg.SelectionTypeLabel.Text = same ? first.Name + " " : "Object "; 184 | } 185 | 186 | pg.ChangeHelpText(string.Empty, string.Empty); 187 | } 188 | private static void HelpVisiblePropertyChanged(DependencyObject source, DependencyPropertyChangedEventArgs e) 189 | { 190 | WpfPropertyGrid pg = source as WpfPropertyGrid; 191 | 192 | if (e.NewValue != e.OldValue) 193 | { 194 | if (e.NewValue.Equals(true)) 195 | { 196 | pg.RowDefinitions[1].Height = new GridLength(5); 197 | pg.RowDefinitions[2].Height = new GridLength(pg.HelpTextHeight); 198 | } 199 | else 200 | { 201 | pg.HelpTextHeight = pg.RowDefinitions[2].Height.Value; 202 | pg.RowDefinitions[1].Height = new GridLength(0); 203 | pg.RowDefinitions[2].Height = new GridLength(0); 204 | } 205 | } 206 | } 207 | private static void ToolbarVisiblePropertyChanged(DependencyObject source, DependencyPropertyChangedEventArgs e) 208 | { 209 | WpfPropertyGrid pg = source as WpfPropertyGrid; 210 | pg.PropertyToolBar.Visibility = e.NewValue.Equals(true) ? Visibility.Visible : Visibility.Collapsed; 211 | } 212 | private static void PropertySortPropertyChanged(DependencyObject source, DependencyPropertyChangedEventArgs e) 213 | { 214 | WpfPropertyGrid pg = source as WpfPropertyGrid; 215 | PropertySort sort = (PropertySort)e.NewValue; 216 | 217 | bool isAlpha = (sort == PropertySort.Alphabetical || sort == PropertySort.NoSort); 218 | pg.IsInAlphaViewMethod.Invoke(pg.Designer.PropertyInspectorView, new object[] { isAlpha }); 219 | } 220 | #endregion 221 | 222 | /// Default constructor, creates the UIElements including a PropertyInspector 223 | public WpfPropertyGrid() 224 | { 225 | this.ColumnDefinitions.Add(new ColumnDefinition()); 226 | this.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(1, GridUnitType.Star) }); 227 | this.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(0) }); 228 | this.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(0) }); 229 | 230 | this.Designer = new WorkflowDesigner(); 231 | TextBlock title = new TextBlock() 232 | { 233 | Visibility = Windows.Visibility.Visible, 234 | TextWrapping = TextWrapping.NoWrap, 235 | TextTrimming = TextTrimming.CharacterEllipsis, 236 | FontWeight = FontWeights.Bold 237 | }; 238 | TextBlock descrip = new TextBlock() 239 | { 240 | Visibility = Windows.Visibility.Visible, 241 | TextWrapping = TextWrapping.Wrap, 242 | TextTrimming = TextTrimming.CharacterEllipsis 243 | }; 244 | DockPanel dock = new DockPanel() 245 | { 246 | Visibility = Windows.Visibility.Visible, 247 | LastChildFill = true, 248 | Margin = new Thickness(3,0,3,0) 249 | }; 250 | 251 | title.SetValue(DockPanel.DockProperty, Dock.Top); 252 | dock.Children.Add(title); 253 | dock.Children.Add(descrip); 254 | this.HelpText = new Border() 255 | { 256 | Visibility = Windows.Visibility.Visible, 257 | BorderBrush = SystemColors.ActiveBorderBrush, 258 | Background = SystemColors.ControlBrush, 259 | BorderThickness = new Thickness(1), 260 | Child = dock 261 | }; 262 | this.Splitter = new GridSplitter() 263 | { 264 | Visibility = Windows.Visibility.Visible, 265 | ResizeDirection = GridResizeDirection.Rows, 266 | Height=5, 267 | HorizontalAlignment= Windows.HorizontalAlignment.Stretch 268 | }; 269 | 270 | var inspector = Designer.PropertyInspectorView; 271 | inspector.Visibility = Visibility.Visible; 272 | inspector.SetValue(FrameworkElement.VerticalAlignmentProperty, VerticalAlignment.Stretch); 273 | 274 | this.Splitter.SetValue(Grid.RowProperty, 1); 275 | this.Splitter.SetValue(Grid.ColumnProperty, 0); 276 | 277 | this.HelpText.SetValue(Grid.RowProperty, 2); 278 | this.HelpText.SetValue(Grid.ColumnProperty, 0); 279 | 280 | Binding binding = new Binding("Parent.Background"); 281 | title.SetBinding(BackgroundProperty, binding); 282 | descrip.SetBinding(BackgroundProperty, binding); 283 | 284 | this.Children.Add(inspector); 285 | this.Children.Add(this.Splitter); 286 | this.Children.Add(this.HelpText); 287 | 288 | Type inspectorType = inspector.GetType(); 289 | var props = inspectorType.GetProperties(Reflection.BindingFlags.Public | Reflection.BindingFlags.NonPublic | Reflection.BindingFlags.Instance | 290 | Reflection.BindingFlags.DeclaredOnly); 291 | 292 | var methods = inspectorType.GetMethods(Reflection.BindingFlags.Public | Reflection.BindingFlags.NonPublic | Reflection.BindingFlags.Instance | 293 | Reflection.BindingFlags.DeclaredOnly); 294 | 295 | this.RefreshMethod = inspectorType.GetMethod("RefreshPropertyList", 296 | Reflection.BindingFlags.NonPublic | Reflection.BindingFlags.Instance | Reflection.BindingFlags.DeclaredOnly); 297 | this.IsInAlphaViewMethod = inspectorType.GetMethod("set_IsInAlphaView", 298 | Reflection.BindingFlags.Public | Reflection.BindingFlags.Instance | Reflection.BindingFlags.DeclaredOnly); 299 | this.OnSelectionChangedMethod = inspectorType.GetMethod("OnSelectionChanged", 300 | Reflection.BindingFlags.Public | Reflection.BindingFlags.Instance | Reflection.BindingFlags.DeclaredOnly); 301 | this.SelectionTypeLabel = inspectorType.GetMethod("get_SelectionTypeLabel", 302 | Reflection.BindingFlags.Public | Reflection.BindingFlags.NonPublic | Reflection.BindingFlags.Instance | 303 | Reflection.BindingFlags.DeclaredOnly).Invoke(inspector, new object[0]) as TextBlock; 304 | this.PropertyToolBar = inspectorType.GetMethod("get_PropertyToolBar", 305 | Reflection.BindingFlags.Public | Reflection.BindingFlags.NonPublic | Reflection.BindingFlags.Instance | 306 | Reflection.BindingFlags.DeclaredOnly).Invoke(inspector, new object[0]) as Control; 307 | inspectorType.GetEvent("GotFocus").AddEventHandler(this, 308 | Delegate.CreateDelegate(typeof(RoutedEventHandler), this, "GotFocusHandler", false)); 309 | 310 | this.SelectionTypeLabel.Text = string.Empty; 311 | } 312 | /// Updates the PropertyGrid's properties 313 | public void RefreshPropertyList() 314 | { 315 | RefreshMethod.Invoke(Designer.PropertyInspectorView, new object[] { false }); 316 | } 317 | 318 | /// Traps the change of focused property and updates the help text 319 | /// Not used 320 | /// Points to the source control containing the selected property 321 | private void GotFocusHandler(object sender, RoutedEventArgs args) 322 | { 323 | //if (args.OriginalSource is TextBlock) 324 | //{ 325 | string title = string.Empty; 326 | string descrip = string.Empty; 327 | var theSelectedObjects = this.GetValue(SelectedObjectsProperty) as object[]; 328 | 329 | if (theSelectedObjects != null && theSelectedObjects.Length > 0) 330 | { 331 | Type first = theSelectedObjects[0].GetType(); 332 | for (int i = 1; i < theSelectedObjects.Length; i++) 333 | { 334 | if (!theSelectedObjects[i].GetType().Equals(first)) 335 | { 336 | ChangeHelpText(title, descrip); 337 | return; 338 | } 339 | } 340 | 341 | object data = (args.OriginalSource as FrameworkElement).DataContext; 342 | PropertyInfo propEntry = data.GetType().GetProperty("PropertyEntry"); 343 | if (propEntry == null) 344 | { 345 | propEntry = data.GetType().GetProperty("ParentProperty"); 346 | } 347 | 348 | if (propEntry != null) 349 | { 350 | object propEntryValue = propEntry.GetValue(data, null); 351 | string propName = propEntryValue.GetType().GetProperty("PropertyName").GetValue(propEntryValue, null) as string; 352 | title = propEntryValue.GetType().GetProperty("DisplayName").GetValue(propEntryValue, null) as string; 353 | PropertyInfo property = theSelectedObjects[0].GetType().GetProperty(propName); 354 | object[] attrs = property.GetCustomAttributes(typeof(DescriptionAttribute), true); 355 | 356 | if (attrs != null && attrs.Length > 0) 357 | descrip = (attrs[0] as DescriptionAttribute).Description; 358 | } 359 | ChangeHelpText(title, descrip); 360 | } 361 | //} 362 | } 363 | /// Changes the text help area contents 364 | /// Title in bold 365 | /// Description with ellipsis 366 | private void ChangeHelpText(string title, string descrip) 367 | { 368 | DockPanel dock = this.HelpText.Child as DockPanel; 369 | (dock.Children[0] as TextBlock).Text = title; 370 | (dock.Children[1] as TextBlock).Text = descrip; 371 | } 372 | } 373 | } 374 | --------------------------------------------------------------------------------