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?
Hi Jeff,
It's a bit tricky to answer this question without knowing more about your requirements.
What do you mean by "a user-definable panel of controls?" How user-definable are you talking about here? Is this a single control or UserControl? Or are you suggesting that users can create and customize their own controls?
- The expandable group box gives us flexibility for the expanded controls, but the header content must be strings
An ExpandableGroupBox has a header that shows text. So I'm a bit confused about what you mean here or what the problem is.
- Using multiple bands allows grid columns in the top-level control, but the expanded info must be grid data
Yes, that's true. You would need to structure your data source in such a way that you have parent rows and then child rows. Presumably, this is okay and all you really want to do is change the display of the child data so it's not displayed as typically grid cells.
- I’ve looked at the row layout grid option, but I’m not sure if it could be used for a child band and/or if the column headers could be eliminated.
RowLayouts are pretty flexible, but at the end of the day, it's just grid cells in a more complex layout. You can hide the column headers in the child rows, though. So that might work for you if you can arrange your child rows in some kind of layout of grid cells.
Another option would be to use an UltraControlContainerEditor. This allows you to embed a control into a grid cell. So you could have a child band in your data with a single column and then the data for that column would bind to some Control or UserControl that you created.
If it's just a single control then that would make things simpler. But then we come back to "user-definable" and I'm not sure what that means. Does that mean the user picks one of a number of possible options? Or is it more complex than that?
How complicated it the control you would want to display? A control that has a single Value property is very easy to implement, but if you have a complex control with a dozen different TextBoxes or other controls within it then your child data will have to contain an object that has properties for each one and you'd have to handle a lot of the interaction between the grid data, the editor, and the display.
Thanks for the reply, and sorry for the vague "user-definable" term. What I meant is that different rows in the grid would need to expand to different known sets of controls depending on the type of the parent. The controls would include multiple text boxes / combo boxes / property grids. (So one parent type might require three text boxes and a property grid in the child band, and a different parent type might require a couple of combo boxes.) From your reply, sounds like the single-column UltraControlContainerEditor might be a possibility, but we would need a separate object for each control and we would have to manually map data between the objects and the controls, is that right? Could we use different sets of objects and controls for different types, or if not could we define "masters" that are the union of all needed objects/controls and ignore/hide those that don't apply to a particular type? Hope that question makes sense.
The need for flexibility in the set of controls by type is what initially drove us to the ExpandableGroupBox, but the fact that headers must be strings prevents us from showing extended info in a columnar format when the group boxes are collapsed, which is a problem. I haven't tried using tabs in the header strings to see if we could approximate the grid look. I'm open to some alternative like that, but utilizing parent-child bands with the ability to edit using controls that we specify sounds like the more elegant approach if we can get it to work. Would love to see an UltraControlContainerEditor example with multiple child controls if you have one.
Okay, so let's walk through this a little bit.
So you would have a grid with some hierarchical data. You could handle the data source in a couple of different ways. Either way, you are going to have a root band which will display in the grid as normal grid rows.
Under that, you seem to be saying that you have a fixed number of options depending on the parent row. Let's call the options A, B, and C for the sake of discussion. I assume that A, B, and C will each contain some completely different set of data and will display using some control/UserControl that you create.
You would have a couple of options for the data source. The child band only needs a single field, but it needs to be able to contain three different types of objects, so you would have to make the DataType of the field object or some base class of the three types. Alternately, you could create 3 child bands in your data source and simply add a row to only one of them for each parent row. This second approach is probably better, IMO, since this way the child band field can be strongly-typed.
Then you have to create controls that display (and presumably edit) the data of each of the three objects (A, B, and C). Your control would need to interact with the data via a SINGLE property. So Control A would have a property called Data which is of type DataA, Control B would have a property called Data which is of type DataB, etc. The name of the property is not significant, but the type is. The child band field in the grid has to be of the same type as the property on the control (DataA, DataB, DataC).
The DataA, DataB, and DataC types have to implement INotifyPropertyChanged and the Data property on the controls have to be get/set.
Your controls will probably also want to override GetPreferredSize so that the grid knows how big to make the cells. Hopefully, they are all the same size - if your controls are going to vary in size from row to row, then that might be a problem - especially if you intend them to vary in height.
That's a sort've very basic outline of how I would approach it. It's going to be pretty tricky. The UltraControlContainerEditor works with a single property on the control and handles the interaction between the value in the grid cell and that property on the control. But in this case you need multiple properties, so your single property has to be an object with its own properties, which is why you need to implement INotifyPropertyChanged.
There are probably some little quirks and details here I am missing because I can't think of everything off the top of my head, so if you go down this road, you should be prepared for some pitfalls. You may want to try to create a simple prototype with a single child band just to see how it works. I will, of course, be happy to help if you get stuck. :)
Hey Mike, thanks for the detailed reply. I appreciate both the warnings and the encouragement.
Your description makes sense conceptually, including how we need separate data types (possibly derived from the same base) for each parent type, and how Controls A B & C from your description would each include a single property of the relevant datatype. Not entirely sure how the UltraControlContainerEditor works, but from the online examples it looks like the editing and rendering controls can be set to the same control, which would be Control A B or C, is that right? And each row’s child band (which might be the only band or one of multiple possibilities, as you mentioned) would have a unique UltraControlContainerEditor assigned as its EditorComponent? And the A/B/C controls could be derived from a container control like a panel and/or contain additional controls?
A couple things give me pause, such as the requirement that any control used as the rendering control must support the DrawToBitmap method; not sure how much of a constraint that is for certain types of our “child” controls like property grids. And we will have different heights of the control sets for different parent types, which would ideally be presented as such; but if we needed to converge on a standard height and provide scrolling for anything bigger (if that approach would work), we could probably live with that.
Jeff said:from the online examples it looks like the editing and rendering controls can be set to the same control, which would be Control A B or C, is that right?
The UltraControlContainerEditor needs two controls: the EditingControl and the RenderingControl. These can be two totally different controls, or they could be two instances of the same control. The latter is more common and provides a consistent experience between cells that are in edit mode and those that are not. If you are going to have buttons and other interactive controls inside the cell, then you probably want the cell to look the same both in and out of edit mode, so using two instances of the same control would be the way to go.
Jeff said:each row’s child band (which might be the only band or one of multiple possibilities, as you mentioned) would have a unique UltraControlContainerEditor assigned as its EditorComponent?
Not exactly. You don't need unique UltraControlContainerEditor with two unique controls for each cell. You only need one for each type. So in this case, you would need an UltraControlContainerEditorA, UltraControlContainerEditorB, and UltraControlContainerEditorC. UltraControlContainerEditorA would need two instances of ControlA (one for editing and one for rendering). UltraControlContainerEditorB would need two instances of ControlB (one for editing and one for rendering). And so on for UltraControlContainerEditorC.
Jeff said:And the A/B/C controls could be derived from a container control like a panel and/or contain additional controls?
Yes, you would probably just create a class that derives from Control or UserControl and place other controls on it. Whenever the Data property on the control is set, or some property on the data is changed, you would populate the UI for that control. When the user updates the UI, you would propagate that changed into the Data for that control instance.
Jeff said:A couple things give me pause, such as the requirement that any control used as the rendering control must support the DrawToBitmap method; not sure how much of a constraint that is for certain types of our “child” controls like property grids.
This should not be a problem. If you derive a class from Control or UserControl and put other DotNet controls on it, all of those controls already support DrawToBitmap. That includes pretty much all of the Inbox control and the Infragistics controls and generally every DotNet control with a few exceptions. The exceptions are some rarely-used controls that simply wrap Windows controls like the RichTextBox control.
Jeff said:nd we will have different heights of the control sets for different parent types, which would ideally be presented as such; but if we needed to converge on a standard height and provide scrolling for anything bigger (if that approach would work), we could probably live with that.
Frankly, I'm not entirely sure if the control height will matter all that much, now that I think about it. The grid supports variable Row heights, anyway. It's certainly not an issue if ControlA's height differs from ControlB or ControlC. And if you override GetPreferredSize on the controls, the grid will AutoSize the rows and columns.
Mike Saltzman said:You don't need unique UltraControlContainerEditor with two unique controls for each cell. You only need one for each type. So in this case, you would need an UltraControlContainerEditorA, UltraControlContainerEditorB, and UltraControlContainerEditorC. UltraControlContainerEditorA would need two instances of ControlA (one for editing and one for rendering). UltraControlContainerEditorB would need two instances of ControlB (one for editing and one for rendering). And so on for UltraControlContainerEditorC.
Okay, I can create multiple UltraControlContainerEditors as you suggest, but I'm not sure what to do with more than one of them. Following the online example for UltraControlContainerEditor, my code has a statement like:
this.ultraGrid1.DisplayLayout.Bands[1].Columns[0].EditorComponent = ce;
But of course that sets the container editor for all rows in the band. Is there a way to set the EditorComponent for specific row(s) within the band?
Actually, I realized your suggestion to have multiple UltraControlContainerEditors must go with the scenario of having multiple child bands, and a different editor would be assigned to each band, is that right? If we went with a single child band, that would mean only one UltraControlContainerEditor and one control for rendering (or for editing); would the control code have to check for something in the data type value and dynamically show/hide/create sub-controls to present different UIs for the different types?
Yes and no. You could do it with multiple child bands. That would simplify things a bit and you would only need to assign the UltraControlContainerEditor to each child band column once.
Another option would be to have a single child band and use the InitializeRow event of the grid In that event, you would examine the row to determine that it's a row in the child band. If so, you would get the parent row. And then you would use some value in the parent row to determine which UltraControlContainerEditor to assign to the Cell in the child row.