This thread looks to be a little on the old side and therefore may no longer be relevant. Please see if there is a newer thread on the subject and ensure you're using the most recent build of any software if your question regards a particular product.
This thread has been locked and is no longer accepting new posts, if you have a question regarding this topic please email us at support@mindscape.co.nz
|
Hi,
I am using MultipleObjectWrapper to support the PropertyChanging and PropertyChanged events, and a DataTemplate with DataTriggers for modifying the look of custom textbox editor in case there are some errors found. (using IDataErrorInfo) During the initial load, if a property of the object has invalid value, the red border would be shown as expected for the respective editor. However, when I tried to commit a valid value to the property, the red border remain. Only after I refresh the datagrid then the red border will be gone. Is this behavior due to the fact that the object is being wrapped in a enumerable collection, but not ObservableCollection? I notice that MultipleObjectWrapper.Objects is an IEnumerable. FYI, the wrapped object implement both IDataErrorInfo and INotifyPropertyChanged interface, and for each property setter, I fire the notify property change in case the value changes.
Thank you! |
|
|
Hmm, I'm not sure what you mean by "using MultipleObjectWrapper to support the PropertyChanging and PropertyChanged events." MOW is a way of packaging up multiple objects so that they can all be edited at the same time. If you are displaying only one object then there is no need to use MOW. Cutting MOW out of the picture should also sort out your validation problem as MOW does not implement IDataErrorInfo -- by removing MOW, you will enable the property grid to talk directly to your object which does implement IDataErrorInfo. |
|
|
Hi Ivan,
I'm sorry for not explaining the situation clearly. The PropertyChanging and PropertyChanged events are mentioned at http://www.mindscapehq.com/forums/Thread.aspx?ThreadID=2011. It was the suggestion that you have given to the other user for supporting the value changes. And also, there is a possibility that multiple objects are being shown in the property grid, hence I decided to use MOW to wrap them. In this case, all my objects are implementing IDataErrorInfo. Since MOW does not support IDataErrorInfo, I use datatrigger to show the validation result to the user. Using this approach, the validation are shown correctly for the initial value. However, for the subsequent value changes, I found out that I need to refresh the property grid in order to show the validation result. So in my case, the requirements are:
Could you advice me how to meet these requirements with the property grid?
Thank you. |
|
|
Thanks for the additional info -- I see where you're coming from now. The DataTrigger approach sounds good, and sounds like it should work because we should be raising the change events to get WPF to re-evaluate it. It would be very useful to see how your binding is set up so we can determine if this is a trigger issue or a bug or limitation of the property grid. Is it possible for you to post a small (but complete and buildable) sample project that demonstrates the problem? (You can attach a zip file via the Options tab.) Thanks! |
|
|
Hi Ivan,
Thanks for your prompt reply. I will create a small project that reproduce this behavior and attach it here as soon as possible.
Thank you!
|
|
|
Hi Ivan,
I am still creating a test project that mimic my project now. Meanwhile, is there any way to remember which category that has been expanded or collapsed, and to set its state? So that when the property grid is refreshed, I may restore the previous state of the category.
Best Regards. |
|
|
Hi Shaun, This will handle saving and restoring node expansion state. http://www.mindscapehq.com/forums/Thread.aspx?PostID=3586 Saving and restoring category (group) expansion state is much trickier because the expansion happens purely at the visual level (it is not part of the WPF collection view grouping model). I think you would need to search the visual tree for GroupItem objects, then for each one look down its visual trees to find an Expander, and work with its IsExpanded property. Another way you could try is handling the Expander.CollapsedEvent and Expander.ExpandedEvent routed events. Because these are routed you should be able to set up a handler at the grid level (using UIElement.AddHandler) rather than having to hack into the template and assign the CLR events. You would of course need to check that any given Expanded/Collapsed event came from a group rather than from an internal control of an editor (though I don't think this would be an issue with the default editors). |
|
|
Hi Ivan,
Thanks for the solution. I am trying to use the AddHandler method. However, I notice that initially the header property of the expander is null after refresh. Is this expected?
Thanks! |
|
|
Sort of. The elements within the grid are created by WPF data binding, so they will not appear until after the WPF data binding infrastructure has run, and this is expected. So you would need to defer the re-expand code until after data binding is complete: pg.Dispatcher.BeginInvoke( However, I would expect that the expanders would be instantiated and their headers set as part of the same data binding pass, so I'm a bit surprised (not a lot, but a bit) that you see the expanders existing but not having a header. If you're not already doing a deferred invoke as shown, try adding it and see if it works. If you are already doing a deferred invoke, you may need to have RestoreExpansionState do further deferrals as it crawls each level of the visual tree -- I'm afraid some trial and error may be required in this case. |
|
|
Hi Ivan,
I am not familiar with dispatcher, but I will try to follow the steps. So after I add the handler on the View itself for Expander.ExpandedEvent and Expander.CollapsedEvent, how am I suppose to relate this with the dispatcher? Thanks for your great help. |
|
|
In your Expanded and Collapsed handlers, you would keep an up-to-date record of the status of each Expander. You shouldn't need to use deferred invocation here. In the code which restores the expanded/collapsed states after a refresh, that is where you would use the deferred invoke via the dispatcher. (If you meant that you're seeing null headers in your Expanded and Collapsed handlers, then instead of logging the status against the header, log it against the Expander object instance. Then in your Refresh method, BEFORE doing the actual refresh, go through the list of Expander objects getting their headers and build a temporary record of the status against the header, which should definitely be available at that point. You still shouldn't need to use deferred invocation on the 'before' side of the refresh. The point of the deferred invocation is just to give WPF time to build the new Expanders after the refresh sweeps away the old ones.) |
|
|
Hi Ivan,
I get the picture now. Thanks a lot. Let me try this one.
Cheers! |
|
|
Hi Ivan,
I have created a sample project that illustrates my problem. Please refer to the attachment. The object model may be described as the following:
I applied MVVM pattern in the project, the mechanism is as the following for clarity:
And since MultipleObjectWrapper does not implement IDataErrorInfo, I use a DataTrigger in the DataTemplate that contains the TextBox editor in order to show the red border when validation fails. This is located in the xaml of ObjectDetailView. The DataTrigger uses a converter, HasErrorConverter that will disect the MultipleObjectWrapper and get the actual object. If the error is found in the actual object, the converter will return true and border should be shown correspondingly.
During the initial load of my project, if the object has some invalid properties, the red border will be shown as expected. However, if I put some valid value, the border will remain, and I would need to refresh the PropertyGrid to update the layout correctly. Could you help me to investigate on this matter?
Thanks for your support.
Shaun
|
|
|
Thanks for the sample. The behaviour you are seeing is due to multiple causes, one of which relates to the interaction of the DataTrigger with the property grid, but several of which are in the view model's IDataErrorInfo implementation. To deal with the property grid related issue first, your data trigger is binding to the ObjectWrapper instance. But the ObjectWrapper instance never changes -- its properties change, but the instance -- the object identity -- does not. So the trigger never re-evaluates and the converter never runs after the initial load. To force the data trigger to re-evaluate when the edited value changes, you must bind to Value. But your HasErrorConverter needs the ObjectWrapper to get at the property name and UnderlyingObject. The solution is to use trickery: replace your DataTrigger.Binding with a MultiBinding that binds to both Value and the ObjectWrapper instance. Your converter (which will need to be converted to an IMultiValueConverter) will only ever look at the ObjectWrapper instance, but including Value in the MultiBinding tricks WPF into re-evaluating the trigger whenever the Value changes! It will look something like this: <DataTrigger Value="True"> Now if you try this and drop some logging code in, you will see that the HasErrorConverter is now being called, but that employee.Error is always returning null. The reason for this is that your view model only updates the Error string when the this[columnName] property is called, which your value converter never does. You could try to work around this by calling employee[propertyName] in your converter, but there is another bug in this[columnName] which results in errors always being appended to the error string and never removed again. So objects could then become invalid but would never appear to become valid again. Rather, the best solution is to implement the Employee.Error property getter to evaluate errors on demand, instead of relying on other properties being called. Something like: public string Error { You would also amend this[columnName] to return the error instead of appending it to the global error: if (!isValid) { With these changes in place I am getting correct behaviour on the Name text box. (Actually, with these changes in place, an even better solution is for the converter to call employee[propertyName] and work with that instead of the getting global error string and parsing it to see if it contains the property name at hand. This avoids the risk of missing errors that don't include the property name such as "Employee is too young". But again, for this to work, this[columnName] must RETURN the error on that property instead of adding it to a global list and returning String.Empty as it does at the moment.) Incidentally, I noticed that you seem to be using WPF Elements 3.0. I'd suggest you upgrade to version 4 if possible so that you and I are on the same version if we need to do any further investigation. |
|
|
Hi Ivan, Thanks a lot for your help! After I use the MultiBinding approach, the validation works as expected. This helps a lot, since I don't have to refresh the PropertyGrid and remember which category has been collapsed or expanded to restore its previous state. Regading the Error property of IDataErrorInfo, I have utilized it as what you described above in my real project. And I could not use .NET version 4 per customer request. I also notice one intersting behavior of the MultipleObjectWrapper. If I modify a property value of an object wrapped by the MultipleObjectWrapper through PropertyGrid, the PropertyChanging and PropertyChanged event will be triggered as expected. However, if I modify the property of a wrapped object outside the PropertyGrid, let's say a DataGrid, when both showing the same object instance, the PropertyChanged is still triggered, but the PropertyChanging is not. In other words, let's say there is a scenario where:
If I modify A's property from DataGrid, the PropertyChanging event is not fired, but the PropertyChanged event will be fired. Is this behavior expected? And I sincerely thank you for your great help. I will recommend this software to others also. Thank you! |
|
|
WPF Elements 4 does not require .NET 4. Obviously, if your customer is prescribing a particular version of WPF Elements, then so be it, but if the customer is merely prescribing .NET Framework 3.5, then WPF Elements 4 will work fine. Regarding the PropertyChanging event, yes, this is expected behaviour. When you wrap an object in a MultipleObjectWrapper, the property grid operates on the MOW, not on the object directly. Therefore all property sets pass through the MOW, which allows it to raise PropertyChanging and PropertyChanged events even if the wrapped object does not raise them. Whereas when you modify the object from somewhere else, the property set does NOT pass through the MOW, and it cannot raise the events -- it can only react to the events the object itself raises. |
|