I am using an IEditorDataFilter to control how my data is displayed. The display of the data changes depending on whether the cell is active (i.e. in edit mode). This causes issues with the default behavior of UltraGrids sharing editors with other columns of the same style/among cells in the same column, as when the editor is in edit mode, it uses a cached result instead of getting the value from the IEditorDataFilter, even though the cached value is for a different cell.
For example, suppose I want the decimal 0 to be displayed as "0" when not in edit mode and as "0.0" when in edit mode, and both cell a and cell b have a value of 0. If cell a is in edit mode and I mouseover to cell b, then cell b will also display as "0.0" even though it is not in edit mode.
Currently, the only way to avoid this seems to be to set the editor of each cell to a separate object, which seems like a waste of memory. Is there a better way of accomplishing what I am trying to do?
Hi,
I think the problem must be in the way in which you are checking for Edit mode. The editor serves the entire column, so if you are simply checking conversionArgs.Editor.IsInEditMode then that will only tell if is a cell is in edit mode somewhere, not necessarily the cell the cell for which the DataFilter is currently firing.
I think what you need to do is cast conversionArgs.Context into an UltraGridCell and then check the cell's IsInEditMode property.
UltraGridCell cell = conversionArgs.Context as UltraGridCell; bool isInEditMode = cell.IsInEditMode;
I'm not entirely sure that the Context will always return a cell, though, so you should probably check for null and put in an assert to detect if it ever gets in there with some other object.
Here is a minimal test case. I set up my ultragrid with an ultradatasource with two decimal columns and two rows with 0s. I suggest having one cell not have a 0, otherwise there is no way to clear the cache. (I am using version 12.1.20121.2008)
public partial class Form1 : Form { public Form2() { InitializeComponent(); DF df = new DF(); foreach(UltraGridColumn uc in ultraGrid1.DisplayLayout.Bands[0].Columns) { uc.Editor.DataFilter = df; } } } class DF : IEditorDataFilter { public object Convert(EditorDataFilterConvertArgs conversionArgs) { switch(conversionArgs.Direction) { case ConversionDirection.EditorToDisplay: UltraGridCell cell = (UltraGridCell)conversionArgs.Context; if(cell.IsActiveCell && conversionArgs.Value is decimal) { conversionArgs.Handled = true; return ((decimal)conversionArgs.Value).ToString("0.0"); } break; } return conversionArgs.Value; } }
Thank you (belatedly ^^;) for taking the time to respond.
Hi Jon,
Okay, I see the issue, now. The DataFilter caches the conversions for efficiency. So once you convert "0" to "0.0", it assumes you will be doing the same conversion for any cell with a value of 0, and it doesn't know you are doing this based on some other state information.
There's currently no way to turn this caching off.
However, as long as the cells you are dealing with here are not using any masking, there might be an alternative solution.
private void ultraGrid1_AfterEnterEditMode(object sender, EventArgs e) { UltraGridCell cell = this.ultraGrid1.ActiveCell; if (cell == null) return; if (cell.Value is decimal) { EditorWithText editorWithText = cell.EditorResolved as EditorWithText; if (editorWithText == null) return; decimal d = (decimal)cell.Value; editorWithText.TextBox.Text = d.ToString("0.0"); } }
One good thing about this solution is that is only applies to cells that are actually in edit mode. The code you have here is dealing with the ActiveCell, but that's not really what you want. It's possible for a cell to be active and not be in edit mode.
The down side of this approach is that it marks the row dirty.
I thought of another potential solution, in case you don't like that the row is marked dirty. The caching is done on the editor, so you could get around the issue by applying a new editor to every cell, as you mentioned in your original post. That, of course, would be horribly inefficient.
But, in fact, you don't need an editor for every cell. What you need is one single editor that is applied to the active cell and only the active cell. So you could create an editor with the DataFilter attached and then assign that editor to the cell before it activates, and then clear it out when it deactivates.
public partial class Form1 : Form { EditorWithText editorWithTextWithDataFilter; public Form1() { InitializeComponent(); this.editorWithTextWithDataFilter = new EditorWithText(); DF df = new DF(); this.editorWithTextWithDataFilter.DataFilter = df; } private void ultraGrid1_BeforeCellActivate(object sender, CancelableCellEventArgs e) { UltraGrid grid = (UltraGrid)sender; UltraGridCell cell = e.Cell; if (cell == null) return; if (cell.Value is decimal) cell.Editor = this.editorWithTextWithDataFilter; } private void ultraGrid1_BeforeCellDeactivate(object sender, CancelEventArgs e) { UltraGrid grid = (UltraGrid)sender; UltraGridCell cell = grid.ActiveCell; if (cell == null) return; if (cell.Editor == this.editorWithTextWithDataFilter) cell.Editor = null; } } class DF : IEditorDataFilter { public object Convert(EditorDataFilterConvertArgs conversionArgs) { switch (conversionArgs.Direction) { case ConversionDirection.EditorToDisplay: if (conversionArgs.Value is decimal) { conversionArgs.Handled = true; return ((decimal)conversionArgs.Value).ToString("0.0"); } break; } return conversionArgs.Value; } }
Mike, thanks for the alternative solutions.
I'm liking the second option more for some of my applications, but I'm working with improving a framework that makes heavy use of events already and allows for overriding of default behavior, so I was hoping to be able to do something at initialization where I wouldn't have to worry so much about interaction with existing overrides or the timing of the override.
I'll look into applying it, but the takehome message seems to be that I shouldn't use the Context in my DataFilter to affect how a value is displayed.
After digging a little more, I did find a function in the EmbeddableEditorBase marked for internal use that will clear the cache for a particular ConversionDirection. If I override GetElementText to clear the cache for ConversionDirection.EditorToDisplay before going to the base GetElementText, then this appears to give the desired result and doesn't appear to aversly affect performance in my limited testing.
However, besaides the fact that I'm making use of an undocumented feature, that may not be the only location where I would need to clear the cache, so I agree that using the enter/exit edit mode is probably the best solution.
That's a very interesting point.
The DataFilter is making the assumption that a value will always translate the same. This allows the editor to cache the data, which is a big boost to performance.
In your case, you are correct - the Context is of no real value. In fact, the only time it would be useful is the first time the Convert method is called for any particular value.
One potential solution would be for us to add a property to the editor so that you could turn off the caching. I'm not sure how this would affect performance, though. I think it would probably be pretty bad, because right now, the cache is probably being used a lot and preventing a large number of calls to the Convert method that you never see.
Another option would be do us to add a method to the editor to clear the cache. But I don't think that would work in this case, because when would you call it? The obvious answer is before or after a cell is activated or deactivated - but that won't work because while the cell is in edit mode you will still have a cache and mousing over other cells will cause the same problem you have now.
So I don't think there is any way for us to solve this problem on our end. The workaround is probably the best way to go.