I’m looking for a way to create grid rows that expand into a user-definable panel of controls. I’ve found a few possibilities that come close but don’t quite do what we want:
Do you have any suggestions?
I think I follow what you're saying in terms of making sure that INotifyPropertyChanged on the control property object is implemented and is invoked, which I have already done when the editing control's object is modified upon changes to the UI. What I was expecting was that I would then see the set accessor invoked for the rendering control's property object (which is the same object assigned to the editing control). But that wasn't happening. So now I've added a handler for the property changed event to the rendering control, and I can now update the rendering control upon changes from the editing control.
Is that explicit handler necessary? I was under the impression that the rendering control's set accessor would be called automatically by some mechanism after changes in the editing control, given the two controls belong to the same UltraControlContainerEditor. In other words, is the purpose of implementing INofityPropertyChanged to trigger an automatic notification between editing and rendering mode, or is it to provide a way that I can manually process the change?
Hi Jeff,
You kinda lost me a bit here.
Jeff said:What I was expecting was that I would then see the set accessor invoked for the rendering control's property object (which is the same object assigned to the editing control).
When a cell that is NOT in edit mode (not the activecell) is painted on the screen, the grid will set the value of the RenderingControl. So you are correct, you would see the setter for the property on the RenderingControl get hit. And at that point you would update your control's UI to reflect the value(s).
The RenderingControl CANNOT be the same control as the EditingControl though. You cannot use the same instance for both. You can use the same type, but they have to be two different instances. Maybe that's what you mean, but when you say "which is the same object assigned to the editing control" it makes it sounds like they are the same instance and that cannot be the case.
Jeff said:But that wasn't happening.
If this is not happening, then something is wrong. This should happen any time a cell with the UltraControlContainerEditor paints itself. Even mousing over such a cell (that is not in edit mode) will trigger a repaint for that cell.
But, I'm confused because you seemed to indicate that this was working, except in the case where you edit a cell. That says to me that the rendering is working fine and it's just the editing that isn't working right.
Jeff said:So now I've added a handler for the property changed event to the rendering control, and I can now update the rendering control upon changes from the editing control.
I'm not sure what you mean by this. The PropertyChanged notification is fired by your code, typically in the setter of the property. So whether you handle updating your UI in the property setter or in the PropertChanged makes no difference. There's no way the notification could fire unless the setter was getting hit first.
Jeff said:Is that explicit handler necessary? I was under the impression that the rendering control's set accessor would be called automatically by some mechanism after changes in the editing control, given the two controls belong to the same UltraControlContainerEditor. In other words, is the purpose of implementing INofityPropertyChanged to trigger an automatic notification between editing and rendering mode, or is it to provide a way that I can manually process the change?
The RenderingControl is only used for cells that are NOT in edit mode. While a cell is in edit mode, the EditingControl is used for that cell. So until you leave that cell or otherwise commit the changes, it's the EditingControl you are dealing with, not the RenderingControl - unless the user mouses over some other cell or does something else that causes some other cell (other than the one in edit mode) to paint.
Anyway, if you can whip up a small sample project of what you have so far that I run and debug, I'd be happy to take a look at it and see if I can figure out what's wrong. My guess, as I said, it that your EditingControl is not sending the PropertyChanged notification when some property on your cell value changes. The grid, therefore, does not know that the cell's value has changed and when you leave edit mode on that cell, it doesn't bother updating the underlying value of the cell in the data source.
I apologize, Mike: I determined that I actually am seeing calls to the rendering control's set accessor. I had forgotten that I conditionalized the set accessor implementation because of performance concerns, and my breakpoint was past the "if" statement.
I am using different instances of the same class for the editing and rendering controls, as you originally suggested. Part of my confusion has occurred because code written for that class of course applies to both controls. The property set accessor must be used to trigger the refresh for the rendering control, yes--but I'm wondering if it's necessary to update the editing control UI after the first rendering (because the changes are in that control as soon as the user makes them). And I'm using the property get accessor to determine which UI fields have changed in order to notify/propagate only the property data that actually changed, and of course that code only applies for the editing control. My approach sounds different from your mentioning above that the property setter typically fires the changed notification; maybe I'm going a non-standard way, but I'm doing so in an attempt to improve performance: I want the rendering control to refresh only when data has changed, not every time the set accessor is called, so I'm now conditionalizing the set accessor implementation on a flag that indicates whether a property changed notification has occurred since the last refresh.
I'm conditionalizing the refreshes because the controls in the child band are complex (they include another grid with a child band), and the initial expansion of a row in the main grid is quite slow (takes a few seconds), even if there are just a few rows in the secondary grid. Subsequent refreshes are quicker but still impose a noticeable delay. Is this simply the nature of the nested-grid behavior, or would there be something I could do to improve performance? One thing I wonder about (which may be totally off-base) is that objects shown in the child band of the secondary grid have many properties declared with the Browsable attribute set to false in order to prevent their appearance in the grid. Would those unused fields be causing some performance overhead that could be avoided if I created an object that contained only the relevant fields for editing in the grid? I'm just brainstorming for ideas that might speed things up. Another alternative would be to display a wait cursor when the row is first initializing, but I don't see a way to do that because the delay occurs during events that fire after the last command called from my code. Any suggestions you have regarding performance would be welcome.
I'm not 100% sure I follow you, but I think I get the gist and I can see how there might be some confusion about the setter.
The grid is going to be accessing the setter on the RenderingControl a LOT. Every time a cell paints anywhere in the grid, the setter is going to get hit. So it's certainly a good idea to make this efficient. But unless I am missing something, tracking PropertyChanged as a means of determining whether something changed doesn't make sense - at least for the RenderingControl, since the RenderingControl's setter is constantly going to get set to new objects and it's PropertyChanged notification will almost NEVER fire for any of the sub-objects. To put it another way, the user will never interact with the RenderingControl's UI. It's just used to paint the cell. So the only thing that will ever happen to it is that it's value gets set (which needs to update it's UI) and then the grid calls DrawtoBitmap on that control.
The only control the user will ever interact with is the EditingControl. And any time something changes in the UI, you have to make sure that the UI updates the value, and the property notifications are fired via INotifyPropertyChanged.
In theory, the RenderingControl doesn't even need to fire IPropertyChanged notifications, because the only time it's value will ever change is when the grid calls into it's setter.
In an extreme case, it might be worthwhile for you to have two different controls, or at least to have a property on your control class that gives it context so it "knows" whether it's the EditingControl or the RenderingControl.
The EditingControl needs to do everything. It needs to able to update it's UI based on setting the value, and it also needs to send the notifications back when the value is changed via the UI. But the RenderingControl only really needs to go one way - it only needs to update the UI via the setter. The user will never interact with it's UI, so it doesn't technically needs to send notification.
Frankly, though, I don't see how that would help much performance-wise - since no one will be listening to the PropertyChanged notifications on the RenderingControl, anyway.
I don't see any way that you could use the PropertyChange notifications to determine if something changed, though. You really need to validate the UI every time the value of the control is set. Because it will almost always be setting the value from the value of some other cell.
You are right to be concerned about performance, and the grid and the ControlContainerEditor assume that your control is relatively efficient. It has to be, since as I said it will be constantly having it's value set and it's UI updated. So there are going to be limits to how complex your RenderingControl can be.
Regarding the Browsable attribute, setting Browsable to false on your data source hides that property from the BindingManager. So neither the grid nor the ControlContainerEditor are even aware of the existence of those properties. So I suppose that could improve performance in some cases, if you have a lot of fields in your data and you only need to use a few.