I'm using the WinForms PropertyGrid to edit object properties in a WPF app. However, the PropertyGrid isn't working as expected with certain property types. Specifically, objects exposing properties of type Color or Brush are displayed as text, #FF000000, for example, and the drop-down color selector list doesn't appear when a cell of that type is clicked in the PropertyGrid. It is my understanding that built-in editors exists for these types and I shouldn't have to do anything extra to get this to work (please correct me if I'm wrong).
For sake of brevity, I won't include a gazillion lines of code, but suffice to say that everything is pretty boilerplate. My implementation is culled straight from MSDN, SO or the myriad of WPF PropertyGrid examples floating around on the web. The PropertyGrid is wrapped in a WindowsFormsHost which goes into a Window that pops up whenever a user wants to edit an object. Something akin to this:
`<Window>
<Grid>
<WindowsFormsHost>
<PropertyGrid>
...
</PropertyGrid>
</WindowsFormsHost>
</Grid>
</Window>
// BTW everything's implemented in code.
The actual objects are of course not passed to the PropertyGrid directly, but wrapped in custom PropertyEditorObjects which expose only those properties of interest in a predefined way, and at times these PropertyEditorObjects may implement an ICustomTypeDescriptor, especially when the objects being edited have child objects whose properties also need to be exposed to the PropertyGrid as nested properties. Generally:
public class PropertyEditorObject
{
...
property bool SomeProperty { get; set; }
property Size AnotherProperty { get; set; }
property Brush YetAnotherProperty { get; set; }
[TypeConverter(typeof(ExpandableObjectConverter))]
property ContentProperties MoreProperties { get; }
};
public class ContentProperties : ICustomTypeDescriptor
{
// Implements all the usual ICustomTypeDescriptor stuff.
}
// The PropertyGrid.SelectedObject is set to an instance of the PropertyEditorObject.
Everything works with the exception of what I mentioned at the start of this post: that certain property types (Colors, Fonts, etc.) are displayed as text only and the PropertyGrid doesn't use the proper Editor for that type.
The reason I'm asking fro help with this is because I'd like to use the built-in WinForms editors, such as the ColorDialog, FontDialog, etc. to edit these types without having to reinvent the wheel as my app is only in the demo stages. Any direction on this matter would be greatly appreciated. Thanx.
This is to be expected. The WinForms property grid knows nothing of WPF types like Color, Brush, or FontFamily, so it has no custom editors for them. Have you tried one of the third-party WPF property grids? I believe the Extended WPF Toolkit has one.
My temporary solution: Install Extended WPF Toolkit Plus, use the PropertyGrid Plus (which has a SelectedObject*s* property) as the editor, wait for the 'fix' in v2.3 that will allegedly support more 'types' (Shapes, spinning squigglies with flames shooting out the sides, the letter 'a', etc.) and, in the meantime if a user attempts to edit an unsupported 'type', throw an exception stating sorry, but you should really send us some money first. That's all for now. More sarcasm available as soon as I invent it. Thanx to the commenters for their suggestions. Toodles.
Update on solutions:
1) Use the Winforms PropertyGrid, as stated in the original post, add EditorAttributes to properties that need them, e.g. Colors, Fonts, etc., and open the Winforms ColorDialog, FontDialog, etc., to edit them. Requires some type conversion but only a few lines of code. Not the best solution, but WORKS.
2) Use the Extended WPF Toolkit PropertyGrid. Works, sorta, but with limitations. Doesn't support all types nor multiple-object selection, i.e. no SelectedObjects property. The first issue is to be fixed in v2.3 of the Toolkit. The second is sort of a showstopper without writing wrappers to facilitate the functionality.
3) Use the Extended WPF Toolkit Plus, PropertyGrid Plus. Allows for multiple-selection, but still suffers from limited type selection. Also, costs big dinero.
4) Use the WWF PropertyInspector as a WPF PropertyGrid. Looked good on paper, but wound up functioning no better than the WinForms PropertyGrid. Requires editors/converters.
Related
Advance apologies for the event-style explanation; there's a lot of factors that I feel all play a role of their own. WPF is not my native framework of choice, and it probably shows. :)
Old situation: I had a window with several controls. Depending on their selections, I used multibindings and a converter to determine whether certain controls needed to be shown that inform the user about the implications of their changes before they'd eventually confirm them by OK (or simply dismissed by using Cancel). This worked perfectly.
Problem: Too many controls as time went by, too much clutter.
Solution: Put stuff in different Pages so it becomes easier to browse for the user. In order to have changes-to-be persist as a user arbitrarily browses between the pages, I create these dynamically and put them in a cache (Dictionary<string, BasePage>, see below), from which they will be pulled as the user chooses them.
Consequence: I need to decouple the bindings to the notification controls as the different options are now on different pages.
Solution? I put a BasePage class in that exposes certain abstract read-only properties that define the various aspects that the window needs to know about in order to do its notifications. For example, a bool requiresReboot property defines whether the current state of things on that page requires a reboot to take (full) effect. A specific page implements the property based on its controls.
Problem: I do not know how to keep create a proper binding that properly gets updated as the pages are changed. I tried giving my notification controls a binding to the Dictionary<string, BasePage> with a converter that checks all pages and the relevant property.
Questions:
1) How do I create a proper property for this purpose? I presume I need a DependancyProperty as I did a fair bit of reading on MSDN, but I can't figure out how this fits together.
2) How do I make a link between my custom property so that it allows (multiple) control(s) on a page to change that property? Do I use INotifyPropertyChanged somehow? My old example bound against several CheckBox.IsChecked properties in XAML. I am trying to avoid putting tons of events (OnChange, etc) on the controls as the original code did not need it and I have been told it makes for a messy solution for as far WPF is concerned.
3) Finally, I suspect I may need to change my Dictionary<string, BasePage> class to a custom implementation that implements some sort of INotifyPropertyChanged but for Collections? Observable Collection is the term I am looking for, I believe.
I hope someone is able to bridge the gap in my understanding of WPF (property) internals; I would very much appreciate it. A basic sample would be even better, but if it is too complicated, just a nudge in the right direction will do. Thank you. :)
It's been a while since I solved this, and while I cannot remember the exact cause of the problems, there were a few different issues that made up the bulk of the trouble I ran into.
I ended up making the Property in question a non-abstract DependencyProperty in the base class; it was the only way in which I could properly delegate the change notifications to the interface. Derived classes simply ended up binding it to their controls (with a proper Converter in the case extra logic was necessitated).
As Dictionary<string, BasePage> does not support any sort of change notification, I made an extra collection of ObservableCollection<BasePage> which I used for binding purposes.
However, such a collection does not propagate a change event when items inside of it has a property changed. Since this situation required that, and I was binding to the collection itself in the context of a property that does not have a Master<->Detail relationship like a DataGrid (which basically add their own OnPropertyChanged handlers to the binded object), I ended up subclassing a VeryObservableCollecton<>. This one listens to its own items, and throws a proper change event (I think it was an OnPropertyChanged from the INotifyPropertyChanged interface) so that the binding (or in this case a multi-binding) would refresh properly and allow my interface to update.
It is hardly the prettiest code, and it feels over-engineered, but at least it allows me to properly bind the UI to the data in this manner.
Coming from a WinForms background, I'm a little surprised to see that most (if not all) of the field-related properties for things like ItemsControls in WPF lack any sort of dropdown list for selecting fields.
For example, when using a ComboBox, if I bind the ItemsSource to a strongly-typed collection on my ViewModel in the designer, the DisplayMemberPath and ValueMemberPath properties require that I type in the names of the appropriate fields manually. My previous WinForms experience is that when binding to a strongly-typed list (in particular, a source that implements ITypedList), I would be given a dropdown of available fields so that there's no chance of fat-fingering the field name.
Am I doing something wrong here, or is this just not something that's been baked into WPF yet?
Edit
I know that this functionality was provided by the ITypedList interface in WinForms, but my understanding was that the System.ComponentModel approach to binding (PropertyDescriptors, ITypedList, IBindingList, and IListSource) were not used in WPF. Things like data grids seem to have no problems obtaining a list of fields to create columns, so I'm just curious if (and/or why) these properties that are intended to represent property names do not provide the same level of functionality.
In WPF properties like DisplayMemberPath and ValueMemberPath aren't just properties - an example might be:
<ComboBox
DisplayMemberPath="Addresses[0].Line1"
ValueMemberPath="Address[0].Id"
SelectedValue="{Binding Path=FavoriteAddressId}"
...
/>
If the designer properties only let you select from a list of properties, you'd be missing out on some pretty useful features. But you are right that providing a list in addition to being able to type it in would be useful.
There's always been a power struggle between WPF's binding system, which is quite dynamic, and the team's vision for tooling which requires a certain amount of rigidity. This is one of those cases that probably fell in the gap.
Edit: PropertyDescriptors, IBindingList and some other components of Windows Forms binding are used in WPF too - for example, my MicroModels library relies on PropertyDescriptors to work, and is built for WPF. Silverlight however doesn't support many of these.
I think this is a bug in the WPF toolkit. Check Stack Overflow question ValueMemberPath Binding in AutoCompleteBox WPF only returns top result in last name search?.
I think it has the answer to the problem.
When setting the DataContext in code it is extremely difficult for the designer to determine the type that is being bound to.
When assigning the DataContext in XAML it is much easier and although there is no drop-down in the XAML editor, when you click the little square behind the property name in the properties box you can select the properties.
In addition you can even use a design-time datacontext to add design-time types and data (beta2 related post but still valid)
My guess it is a missing feature of the XAML-editor.
I'm having trouble deciding what to think about this piece of code:
public SolidColorBrush Brush
{
get { return IsValid ? _validItemBrush : _invalidItemBrush; }
}
It is part of a view model in my current project and as you can imagine, the Brush will be bound to some text elements in the UI, to indicate (in-)validity of other pieces of data, in an otherwise fairly simple and straightforward dialog.
The proponents of this piece of code say that since we're using WPF, we might as well allow for some simple WPF specific constructs in the view model.
The opponents say that this violates Separation of Concerns, as it clearly dictates style which should be taken care of solely by the view.
Please share your arguments, and if you're not happy with the code above, please share your ideas around alternative solutions. (I'm particularly interested in what you have to say about using DataTemplates).
Is it possible that there is one solution that could be considered best practice?
Personally, I would have the two brushes be defined in XAML, and have the controls that use them switch brushes (in xaml) based on the IsValid property. This could be done very easily with DataTriggers, or even a single IValueConverter - the converter could take 2 brushes and a boolean and swap between them fairly easily.
This keeps the business logic presentation-neutral - a "Brush" is very specific to a specific form of presentation, and a pure View choice. Hard-coding this into the ViewModel violates the single responsibility principle as well as is not a clean separation of concerns.
I would very much keep this in the View, and switch based on the IsValid (bound) property that is ViewModel specific.
While there are circumstances where I might use WPF constructs in the view model, this isn't one of them. Here's why:
It's harder to change. If you define brushes as resources and use them in styles, changing your application's color scheme can simply be a matter of loading a different resource dictionary. If you hard-code color values in your view models, you have a lot of different things to change if it turns out your end users need different colors.
It's harder to test. If you want to write a unit test that checks to see if a property is returning the right brush, you have to create a brush in your unit test and compare the values of the two, since it's a reference type.
In many, maybe even most cases, it doesn't make the code simpler or easier to maintain. You're pretty likely to already be using a style (assuming that you are conversant with styles), since they make just about everything in WPF easier. Binding IsValid to brush colors is just a matter of adding a DataTrigger to a style. Which is where anyone maintaining this code would expect to find it.
There are certainly times when I do use WPF constructs in the view model - for instance, long ago stopped wondering if it was problem if a view model exposed a property of type Visibility. Note that none of the above concerns apply to that case.
In cases like yours where it's purely aesthetic I use Triggers or the Visual State Manager to change colors.
Sometimes I do use colors in my ViewModels, but only if its part of my software spec (e.g., the color of the chart displaying a patient's CO2 depends on localization). In that case, I use a Color struct bound property, allowing the View to use the Color for a SolidColorBrush, a GradientStop, or whatever it wants. I initially used a string in #AARRGGBB format to completely remove the WPF dependency but my more seasoned co-workers didn't like that.
I'm doing a small research on localisation methods in WPF. I heard about the idea with markup extension:
<Label Content="{local:Translate {-- label ID here --}}" />
I like this solution very much: it's extremely easy to implement and seems to be nicely flexible. I've got one concern, however. Let's suppose, that user changes the locale in the runtime. How to ensure, that localized properties will be reevaluated to match new language?
You would need to call DependencyObject.InvalidateProperty. Keep in mind that if you were binding to an object implementing INotifyPropertyChanged they would reevaluate by way of the underlying data changing.
DependencyObject.InvalidateProperty can be called on a given DependencyProperty such as Label.Content.
Label label = new Label();
label.InvalidateProperty(ContentProperty);
This would have to be done for the varying properties that need re-evaluation. There is an in depth article on MSDN around localization within WPF for varying alternatives as well that should be investigated.
Seriously, it seems like every time I want to make my UI elements talk to each other, I end up coding a new, custom, IValueConverter :(. Someone tell me that I'm doing it wrong, please!
Examples:
I wanted a button to be enabled only if my textbox contained a valid URI. Great, time to code up a UriIsValidConverter!
Oh oops, I also wanted to disable it while I'm processing something. I guess now I need to code up a UriIsValidAndBoolIsFalseMultiConverter!
I want to display a list of files in a certain directory (specified by a textbox) inside a listbox. I guess I need a DirectoryPathToFileList converter!
Oh hey, I want icons for each of those files in the listview. Time for a FileInfoToBitmap converter!
I want my status to be red if my status-string contains "Error", and green otherwise. Yay, I get to code up a StatusStringToSolidColorBrushConverter!
I'm really thinking this isn't that much better than the old Windows Forms method of just wiring up everything manually using TextChanged events (or whatever). Which I guess is still an option. Is that what people actually do, perhaps, and I'm trying too hard to make everything fit into the databinding paradigm?
So yeah, please tell me if this is really how WPF coding is---or if I'm doing it wrong, and if so, what I should be doing.
Your approach is perfectly valid (though I would use a multibinding for the second example, rather than a such a specialised converter), though by placing all your logic into the XAML you are producing very high coupling between the way the application looks and the way that it behaves, because of this you may want to look into the MVVM pattern to separate those things out.
Under the MVVM pattern your XAML (the view) just contains very simple data bindings into a ViewModel which handles all the logic and updates the view through the INotifyPropertyChanged interface. The code for your third example may look something like:
public class DirectoryManagerViewModel : INotifyPropertyChanged
{
private string _directory;
public string Directory
{
get { reutrn _directory; }
set
{
if (_directory != value)
{
_directory = value;
OnPropertyChanged("Directory");
if (IsValidDirectory(value))
{
PopulateFiles();
}
}
}
}
public ObservableCollection<FileViewModel> Files { get; private set; }
private bool IsValidDirectory(string directory)
{
//return if the directory exists etc.
}
private bool PopulateFiles()
{
//Populate Files with files in directory
}
}
Where FileViewModel is another view model which contains the name and the icon for a file.
The advantage of this approach is that the ViewModels can be reused with other views and other technologies such as ASP.NET or Winforms so you are not locked into the WPF stack. (alos if you work in an environment where there are designers responsible for the look and developers responsible for the behaviour, this helps define those boundaries)
At the end of the day though this logic does need to go somewhere and while there are better and worse ways to architect your application you are still going to be writing code that takes a string and converts it into a series of filenames and icons somewhere.
First, you might want to start by reading about the Model-View-ViewModel pattern (MVVM). Josh Smith had a fantastic article in MSDN Magazine recently. MVVM and WPF go perfectly together. Done right, you won't need IValueConverters so much. The way that you are going about it now is causing a very tight coupling between your visualization and your application actions. MVVM is designed to decouple these elements.
In this context, your view model will track state for you. Your button will be enabled if the CanExecute method on a certain ICommand in your view model returns true. This same concept can handle disabling the button when processing something.
You want to display a list of files in a certain directory that is specified inside a listbox? Have a DirectoryViewModel view model that will handle providing the list of files to the view by binding to the view model. The display of the files can be specified with a DataTemplate specified in XAML with no code behind. This same concept can handle providing the icons to the view whose display can be specified in the template.
You want your status to be red if a status message contains "Error" and green otherwise? Let a view model handle determining the state and let the view bind to that state and now you only need an IStateConverter to convert the state to red or green appropriately (this is one of many ways to handle this problem in the MVVM context).
Get in the habit of keep data and state separate from your view and your applications will be loosely coupled, easier to design and maintain, and easier to test.
Don't know if you are wrong, just making it a lot harder than it has to be!
I use MVVM, so where you are writing customer converters, I provide a bindable property on the view model that tells the view what to do. For example:
To display a list of files, I provide a collection that contains that list.
If I want icons the object in that collection has a icon property
If I want a status to be red or green I provide a StatusColorbrush property.
By moving this logic into the view model, I get:
much simpler Xaml.
can test my view logic without the view.
This approach uses one of the strong points of WPF, it's binding capabilities.