-
Notifications
You must be signed in to change notification settings - Fork 3
UsingPropertyList
The PropertyList control is a useful and powerful control, that allows developers and users to display, access, and modify the properties of any C# object. You can think of it being very similar to the PropertyGrid control in WinForms, or the Properties pane present in Visual Studio.
This guide will give you info on how to utilize it and best take advantage of its features to have it match what you need.
Note: in the 1.9.x releases of Solid Shine UI, the actual control is called ExperimentalPropertyList as the control was being actively developed and improved upon during this set of releases. While it's never been unstable in any version, it's highly recommended to use version 1.9.5 or later for the most bug-free and feature-complete experience. In this guide, I'll simply refer to the control as PropertyList, as this is the name it will have in version 2.0 and onward.
PropertyList is contained in its own namespace in Solid Shine UI: the SolidShineUi.PropertyList namespace. This is due to the large number of controls and classes present to make PropertyList work that would clutter up the main namespace if it were all present there.
To add in the PropertyList control into a WPF XAML file, you'll want to add a reference to its namespace as shown here:
xmlns:pl="clr-namespace:SolidShineUi.PropertyList;assembly=SolidShineUi"
To access the control in C#, you simply add the using statement as normal:
using SolidShineUi.PropertyList;
Nested within this namespace is also the PropertyEditors and Dialogs namespaces; these contain the controls and dialogs used by PropertyList to perform its functions; generally, there is no need to reference these namespaces.
Once the namespace is added in, you can start using the control.
The most basic function for PropertyList is loading in an object for the control to observe. This can be practically any C# object (any object that can be loaded and inspected via Reflection), including both WPF controls/visuals and non-visual objects.
To load in an object, use the LoadObject
method and pass in the object you want PropertyList to observe. PropertyList will find the public non-static properties of that object and list them along with their current values. For many properties, PropertyList will display a rich editor that will allow further viewing or editing of that property (which we'll get to in a bit).
TextBox tb = new TextBox();
var pl = new PropertyList();
pl.LoadObject(tb);
If the object's properties gets changed via another method (such as another class or your UI affecting the object), PropertyList is unable to automatically pick up on that, but you can use the Reload button or the ReloadObject()
function to have PropertyList fetch the latest values for the object's properties.
While PropertyList is observing an object, it will store a reference to the object in its memory. This means that by default, garbage collection will not collect that object, and disposing the object while PropertyList is observing it will have adverse effects.
To clear what's currently loaded in the PropertyList and reset it to a blank state (including releasing its reference on the loaded object), use the Clear()
method.
Out of the box, PropertyList contains editors for a variety of C# and WPF types, such as string, integer, GridLength, Brush, and Thickness. When a property is of a supported type, the editor for that type will be displayed, allowing that property's value to be changed.
When you set a new value for a property via an editor, this change occurs immediately; there is no Apply or Save button. (If the change fails, such as the change causing an exception, the PropertyList will reload that particular property and update the editor to put it back into a valid state.)
With these editors, this allows users to dynamically set the properties of an object, which can be used for fine-tuned customization or as a rudimentary settings editor.
As previously mentioned, PropertyList comes with editors for a variety of types. You can get a list of the types and the editors that it used by the RegisteredPropertyEditors
property.
You can add in editors of your own to support types that PropertyList doesn't support already, or to replace the in-box editors that PropertyList uses. You can accomplish this via the RegisterEditor method, providing both the type that your editor supports the editing of, and the type of your editor itself. Whenever PropertyList loads an object that has a property of that type, it will create a new instance of that editor to allow viewing and editing that property.
See the next section for information on how to create your own property editor.
To remove an editor from PropertyList, use the UnregisterEditor
method, passing in the type that you no longer want PropertyList to provide an editor for (not the type of the editor itself). For example, to have PropertyList no longer display the DoubleEditor for properties of type double, you would use UnregisterEditor(typeof(double))
.
For properties of a type of which PropertyList doesn't have an editor, PropertyList instead displays the ToString() output of that property.
All property editors in PropertyList inherit the interface IPropertyEditor
.
To get started with creating your own, you can follow this guide:
- Create a new WPF User Control in your project, giving it whatever name you feel is appropriate, such as MyTypeEditor. Once the control is created, open the C# backend file for that control, add
using SolidShineUi.PropertyList;
at the top, and add IPropertyEditor as an interface that your control inherits from (i.e.MyTypeEditor : IPropertyEditor
). - Modern IDEs, such as recent versions of Visual Studio, provide a refectoring method to automatically create stub methods for all the methods/properties required by the interface. If your IDE doesn't provide this, you will need to create these methods and properties yourself.
- Here's what to do for each of the properties and methods in IPropertyEditor:
-
ValidTypes
: provide a list of types that this editor supports loading -
EditorAllowsModifying
: get if this editor can actually edit/modify properties, or if it can only display the value of a given property -
IsPropertyWritable
: will be set tofalse
if the property loaded in is read-only; if it is read-only, set IsEnabled tofalse
for any controls that are used for editing -
GetFrameworkElement
: simply just doreturn this;
-
ColorScheme
: use the ColorScheme to set the visual appearance of your editor and its controls -
ParentPropertyList
: the PropertyList instance that this editor is hosted within (if you need this) -
ValueChanged
: raise the event whenever a control is used to change the value of the loaded property; once this event is raised, this will trigger the PropertyList control to update the actual property of the loaded object (viaGetValue
) -
GetValue
: get the value from the editor; return the stored or calculated value the editor has for this property (i.e. return the textbox's text in a StringEditor) -
LoadValue
: loads in a property's value into the editor
-
For a starting reference, you can look at the code for the BooleanEditor control.
Your editor class must have a parameter-less constructor, as this is what PropertyList will use to create an instance of your editor.
Custom editor classes should be wholly self contained, as PropertyList may end up creating multiple instances of your editor depending upon what object gets loaded in. It's also recommended that your editor is able to quickly get/display the value of a property (via LoadValue). Also, after LoadValue is called, there should not be leftover data or values from whatever value was in the editor prior to that call (as in, the editor should be reset prior to fully loading in the value); there are situations where LoadValue may be called even after the editor is first initialized.
Once your editor is ready, you can register it into the PropertyList you want to use via the RegisterEditor
method.
To allow developers and end users control over what gets shown, there's a lot of ways to hide or skip loading properties that meet certain criteria. In this section, I'll go into detail about each one.
Starting with Solid Shine UI version 1.9.5, you can use attributes to control which properties that PropertyList does or does not load in.
For example, if there's a property that you use that's declared as public, but you don't want it to be displayed when PropertyList loads that object, you can use the PropertyListHide attribute to hide that property from showing. PropertyList doesn't even load the property and simply skips over it, meaning its value is never accessed and it isn't hidden anywhere in PropertyList's UI.
Control over this feature of PropertyList is done via the DisplayOptions
property. DisplayOptions
is a flag enum, so you can actually set multiple types of items to be hidden. Here I'll list the options available for you to control what PropertyList shows or doesn't show:
-
HidePropertyListHide
: this is the default value. When set, a PropertyList doesn't load any property that has the PropertyListHide attribute. -
HideObsolete
: when set, a PropertyList doesn't load any property that has the Obsolete attribute -
HideBrowseableFalse
: when set, a PropertyList doesn't load any property that has the Browseable attribute with the value set to false, or the EditorBrowseable attribute with the value set to Never -
ShowAll
: if present, this overrides all other options. When set, a PropertyList always loads all public non-static properties regardless of the attributes they may have -
OnlyShowPropertyListShow
: if present, this overrides all other options except ShowAll. When set, a PropertyList only loads properties that have the PropertyListShow attribute.
internal class MyTestClass
{
public string VisibleText { get; set; } = "hello";
[PropertyListHide]
public string NotLoadedText { get; set; } = "can't see me!";
}
var pl = new PropertyList();
pl.LoadObject(new MyTestClass());
As detailed above, if you have a situation where you don't want PropertyList to load any properties from your object except the ones you specify, you can set the DisplayOptions
to OnlyShowPropertyListShow
and then add the PropertyListShow attribute to the specific properties you want visible.
internal class MyTestClass2
{
public string InternalProperty { get; set; } = "internal data";
public string InternalProperty2 { get; set; } = "don't want end users seeing this";
[PropertyListShow]
public string EditableProperty { get; set; } = "end users can edit this one!";
}
var pl = new PropertyList();
pl.DisplayOptions = PropertyListDisplayFlags.OnlyShowPropertyListShow;
pl.LoadObject(new MyTestClass2());
Using PropertyListShow and OnlyShowPropertyListShow
is akin to opting in which properties to display, where as PropertyListHide and HidePropertyListHide
is more like opting out which properties to not display. Thus, you can use whatever fits your needs best.
It's important to note that DisplayOptions
affect how PropertyList loads in objects, rather than affecting what's already been loaded. If you change DisplayOptions
, you won't see the change take effect until you reload the current object or load in a new object.
PropertyList also has a few other methods that can be used to control what properties get shown in the PropertyList. The ShowReadOnlyProperties
and ShowInheritedProperties
properties do pretty much what they say.
Unlike DisplayOptions, these take effect immediately and both get reset back to true when you load in a new object; this is because these properties actually apply a filter rather than controlling how PropertyList loads in an object. The properties that get filtered out are simply hidden in the PropertyList UI.
As you'd expect, setting ShowReadOnlyProperties
to false will hide any properties that only have a public get block and not a public set block (in other words, properties that can only be read from).
As you'd also expect, setting ShowInheritedProperties
to false will hide any properties that the current object only has through inheritance of another class. In other words, if the property's declaring type doesn't match the type of the object that's loaded, the property will be hidden. Properties that use the new modifier to hide an inherited property are not hidden.
TextBox tb = new TextBox();
var pl = new PropertyList();
pl.LoadObject(tb);
pl.ShowReadOnlyProperties = false;
// for a Textbox, this will hide properties like ActualWidth, LineCount, and CanUndo
pl.ShowInheritedProperties = false;
// for a TextBox, this will hide properties like Background, Margin, and Visibility
If the View menu is visible, end users are also free to toggle these two filters on and off via the "Show Read Only" and "Show Inherited" menu items.
PropertyList also supports a text filter, hiding properties that don't contain the filter text in either its name or its type's name.
Similar to the ShowReadOnlyProperties and ShowInheritedProperties properties above, this also takes effect immediately and is reset upon loading in a new object. The properties that get filtered out in this way are simply hidden in the UI.
End users are able to apply a filter via the filter text box, as long as ShowFilterBox
is set to true. Developers can also apply a filter via the FilterProperties(string)
method, passing in the filter text to use.
For example, entering in the text "str" will hide any properties that don't contain the exact string "str" somewhere in the name AND aren't of a type that contains the exact string "str" somewhere in the name. In other words, a property named Strong would not be filtered out, nor would the property Text (which is of type string). The filtering is not case sensitive.
To filter only by name and not both name and type, you can put an "@" symbol at the front of the filter text: in the previous example, "@str" would still not filter out the property Strong, but the property Text would be filtered out.
internal class MyTestClass3
{
public bool Strong { get; set; } = false;
public string Text { get; set; } = "sample text";
public FontFamily FontFamily { get; set; } = new FontFamily("Arial");
}
var pl = new PropertyList();
pl.LoadObject(new MyTestClass3());
pl.FilterProperties("str");
// now only Strong and Text will appear (Text is of type STRing)
pl.FitlerProperties("@str");
// now only Strong will appear
You're able to modify and fine-tune the appearance of the control to match what you need.
It's highly recommended to set the ColorScheme property to set the appearance of a PropertyList, as this will also propagate down to the property editors as well. Even with setting the ColorScheme, you are still able to use the brush properties to change the control.
Starting with Solid Shine UI version 1.9.5, you can use various Brush properties to change the brushes used for various parts of the control. For example, you can use ToolbarBackground to change the background of the toolbar in PropertyList, where the Reload and View menu buttons are. You can use TopPanelBackground and TopPanelForeground to change the appearance of the top section where the observed object's name and type is displayed. You can use HeaderBackground, HeaderForeground, and HeaderDividerBrush to change the colors and appearance of the column headers.
If you're using a ColorScheme in combination with using any of these brush properties, be sure to set the ColorScheme first before setting the brushes, as all brushes get reset when you set the ColorScheme.
Some text strings, but not all, can be changed via various Label properties, such as ViewMenuLabel, FilterBoxTooltip, and NameHeaderLabel. While technically any string can be put into these labels, they're primarily meant for the purposes of localization. I'm considering a better solution for localization in version 2.0, given that there isn't currently an easy way to access/change the text of the various property editor controls.
When you load in an object, PropertyList attempts to determine the object's name by using the Name property and sets the ObjectDisplayName
property to that object's name; this is then displayed at the top of the control. However, if there's scenarios where you want to provide your own custom name for the loaded object, you can set ObjectDisplayName
after the object is loaded.
internal class MyNamedClass
{
public string Name { get; set; } = "My Class";
public string Text { get; set; } = "sample text";
}
var pl = new PropertyList();
pl.LoadObject(new MyNamedClass());
// the name "My Class" will appear at the top of the PropertyList control,
// as this is the value of the Name property
pl.ObjectDisplayName = "New Custom Name";
// now the name "New Custom Name" will appear instead of "My Class"
// this can also be used with classes that don't have a Name property at all
You can hide elements of the UI that you don't want visible to end users, if desired.
To hide elements in the toolbar at the top of the control, you can use ShowFilterBox
, ShowReloadButton
, and ShowViewMenu
. If all three of these items are hidden, the entire toolbar is hidden.
You can use ShowNameDisplay
and ShowTypeDisplay
to hide the name and type fields displayed in the top panel above the toolbar.
If all toolbar items are hidden and both top panel items are hidden, then the only UI displayed in the PropertyList is the list of properties for whatever object is loaded.
PropertyList has the following known limitations:
- Loading in an object that is an enumerable or collection, such as a
List<string>
, will not display the items in the collection. Instead, PropertyList will only display the actual properties of that generic type object itself (i.e. for a List, it will display theCapacity
andCount
properties). If you need a way to view or edit the properties of items in a list, you'll want to build the experience to select items from the list yourself, and then have a PropertyList load in the particular selected item. - Set-only properties (i.e. properties that don't have a public get block/line) may not appear properly or cause exceptions to occur. If you have an object with set-only properties, I recommend applying the PropertyListHide attribute for those properties in question to skip PropertyList loading them. Properly supporting this type of property is not planned.
- PropertyList does not automatically update if an object's properties change via other means. There is no way to achieve this for most C# objects. The Reload button in the control's UI or the
ReloadObject()
function can be used to force PropertyList to reload the object and get the updated values of its properties. - PropertyList only displays the public non-static properties of an object. Properties that are marked as protected, private, internal, and/or static will not appear. I may add methods to display these in a future version, but it's highly recommended to stick to only what's declared as public.
- PropertyList only displays the properties of an object. Events, methods, constructors, and other items that can make up a class are not displayed.
- PropertyList is limited to only what can be achieved via Reflection. Situations that cannot be resolved via Reflection alone cannot be resolved by PropertyList. Despite being a WPF control, PropertyList is not aware of and does not consider WPF features, such as binding or animation.