(version 19.1.20191.82)
A colleague wrote a custom `FilterCondition` a while ago, and when applied to a column, it automatically gets stored as part of the DisplayLayout. That is, if I later on call `grid.DisplayLayout.LoadFromXml()`, I get the same filter settings back. It appears they accomplished this by implementing `ISerializable` as well as having a constructor with `SerializationInfo` and `StreamingContext`.
I now wanted to do the same for a custom `GroupByComparer` and `GroupByEvaluator`. However, the same approach doesn't appear to work — when saving the layout, `ISerializable.GetObjectData` is never called.
Is there a way to store the GroupByComparer and/or GroupByEvaluator in the layout? Failing that, is there a more generalized way to add additional information to the layout (ideally, associated with the respective column)?
Hello Soren,
Thank you for your post. If we can save and load the custom 'filterCondition' then i was expecting GroupByComparer should also support that. In order to understand the scenario i set up a sample of UltraGrid with a GroupByComparer column, i save and load the layout using SaveAsXml and LoadFromXml and it working fine for me .I attached my sample here.
I am using version 19.2.
If this sample project is not an accurate demonstration of what you're trying to do, please feel free to modify it and send it back, or send a small sample project of your own if you have one.
Please let me know if I can provide any further assistance.
Sincerely,
Divya Jain
InfragisticsSample.zip
Hello Divya,
Thanks for the quick response. I think your test case demonstrates what I'm trying to solve:
1) run the code unmodified. Notice that the groups are sorted Col2Row3, Col2Row4, Col2Row2, Col2Row1, as the custom comparer does. Click Save.
2) exit the app.
3) comment out lines 92 and 93 (the last two lines in the constructor) such that Col2 is no longer a GroupBy column, and the comparer is no longer assigned:
//this.ultraGrid1.DisplayLayout.Bands[0].SortedColumns.Add("Col2", true, true);
//this.ultraGrid1.DisplayLayout.Bands[0].Columns["Col2"].GroupByComparer = new GroupByComparer("Col1", "Sum");
4) run the app again. There's no grouping now, as expected.
5) Click Load, and click the column in the GroupBy area to make the sorting descending again. The groups are now sorted Col2Row4, Col2Row3, Col2Row2, ColRow1.
That is, clicking Load did restore that I wanted the list grouped by Col2, but it did not restore that I wanted to use a custom GroupByComparer. Looking at the DisplayLayout XML file suggests that this information isn't serialized at all.
The goal here is to store as much information as possible about the user's layout settings, including in this case details like what kind of GroupByComparer they used. (This is useful because the user can then switch between different layouts, which each come with their palette of settings.)
I hope that clarifies my question. The equivalent issue occurs with a GroupByEvaluator.
Thanks again,
Sören Kuklau
Hi Soren,
I'm following up on this issue. I've been looking into it, and the problem is that FilterCondition is a concrete class, whereas GroupByComparer and GroupByEvaluator are interfaces. Of course, in your application, there will be some kind of concrete implementation of the GroupByComparer or GroupByEvaluator that could, in theory, be serialized. But since these are interfaces, it's possible for you to implement them on some other class that you really don't want to be serialized - like your Form. It's not uncommon for application to implement an interface like this on a Form or other object and it would not be good if you saved your grid layout and it serialized the entire Form along with it.
So the best we could possibly do here is expose some kind of option which would allow you to opt-in to serializing these interfaces. But it's a pretty dangerous thing to do and someone could easily end up serializing their form or some other object without realizing they are doing it, so I am very wary of going down that road.
I'm curious as to why you would want to serialize these classes, anyway, though. The user can create and change FilterConditions and it's possible for you to add custom FilterConditions into the dropdown list for a column, and so it makes perfect sense to want to serialize these as user-customized options.
But the user can't change the GroupByComparer or GroupByEvaluator on a column - at least not through any UI the grid exposes. So are you exposing a UI for your user to choose different GroupByComparer or GroupByEvaluator options? What exactly is the use case here? If you are saving the grid's layout to Xml, then in theory, you could create a stream, save that layout into that stream, and then save addition information into the stream. You could add any Xml into the stream that you wanted. The only tricky part is that when you read that stream back in, you load the grid layout from the stream first and that will position the stream to the end of the layout so you can then read in your custom data. If you wanted to serialize your GroupByComparer or GroupByEvaluator, you could do that, although frankly, I think it might be simpler for you to just store some text that indicates which column to apply to and then the minuimum basic information your application needs in order to re-apply the correct object.
Hi Mike,
Thanks for getting back to me.
Mike Saltzman said: It's not uncommon for application to implement an interface like this on a Form or other object and it would not be good if you saved your grid layout and it serialized the entire Form along with it.
Right. I understand the concern, but I'm making model classes expressly for these settings.
Mike Saltzman said:But the user can't change the GroupByComparer or GroupByEvaluator on a column - at least not through any UI the grid exposes. So are you exposing a UI for your user to choose different GroupByComparer or GroupByEvaluator options? What exactly is the use case here?
Yes — I'm making a UI to further customize GroupByRows.
The use case arose from two customer requests:
The above scenarios basically already work; I still need to fiddle a bit with how I'm adding a button to show my UI (using a IUIElementCreationFilter), and I'd like the whole thing to be serialized as part of a layout (there's a UI to let users choose between multiple different layouts, so it'd be beneficial if this got restored as part of them).
Mike Saltzman said:If you are saving the grid's layout to Xml, then in theory, you could create a stream, save that layout into that stream, and then save addition information into the stream. You could add any Xml into the stream that you wanted. The only tricky part is that when you read that stream back in, you load the grid layout from the stream first and that will position the stream to the end of the layout so you can then read in your custom data.
That would work (although it might not be possible to do in a backwards-compatible manner), but I was hoping for something that is just part of the existing layout. For example, the layout already has a notion of column context, ("which column do these settings apply to?"), and I'd have to replicate all that.
Mike Saltzman said:If you wanted to serialize your GroupByComparer or GroupByEvaluator, you could do that, although frankly, I think it might be simpler for you to just store some text that indicates which column to apply to and then the minuimum basic information your application needs in order to re-apply the correct object.
Right. I was hoping to reuse the existing structures here.
Well, I still don't see a great deal of value in storing a class that you created in code and that the user cannot really customize as part of the layout. Except insomuch as that it would be convenient to just loading it up. But really, you could just load the layout and then assigned it, just as you did the first time. If you really want to store it as part of the layout, then the simplest way to handle this would be to use the Tag property on the column. The Tag gets serialized with the layout. So all you would have to do is store the object you want in the column's Tag right before you save the layout and then restore it again after loading the layout.
Of course, since you have two objects to store there (The GroupByComparer and the GroupByEvaluator), you would need to create an object that is serializable and stores those two object on itself, but that shouldn't be very difficult. Or, if you don't want to do that, you could use the Tag of the column to store the Comparer and some other object associated with the column (such as the Header) to store the other one.
Does that make sense?
Mike Saltzman said:Well, I still don't see a great deal of value in storing a class that you created in code and that the user cannot really customize as part of the layout. Except insomuch as that it would be convenient to just loading it up. But really, you could just load the layout and then assigned it, just as you did the first time.
Right. I guess I don't quite see how that argument doesn't also apply to filter conditions, though: we wrote a custom class, needed to create a custom GUI for the user to customize it, and it got serialized as part of the layout. So when loading the layout, it gets applied.
Thus, I had intuitively assumed the same approach could be used by something like GroupByComparer.
Mike Saltzman said:
If you really want to store it as part of the layout, then the simplest way to handle this would be to use the Tag property on the column. The Tag gets serialized with the layout. So all you would have to do is store the object you want in the column's Tag right before you save the layout and then restore it again after loading the layout.
Of course, since you have two objects to store there (The GroupByComparer and the GroupByEvaluator), you would need to create an object that is serializable and stores those two object on itself, but that shouldn't be very difficult.
Yeah, I'll look into doing it that way.
Thanks!
Yeah, the distinction is pretty subtle at first glance. But the main reason is that it's an interface vs. a derived class. Another difference is that it's possible to select a custom FilterCondition through the grid's UI. You could handle the BeforeRowFilterDropDown event and add items to the list such that the DataValue is actually an instance of a FilterConditon-derived class so the user can choose it that way. Whereas the GroupByComparer is typically something that the user can't choose - at least not via the grid's UI.