Hi there
Another issue, this time with master-detail binding. First, let me explain the layout as seen in the screenshot below:
- If a node in the tree is being selected, the upper grid's data source is being changed programmatically (no binding).- the lower grid is part of a user control. This user control's data context is bound to the upper grid's active record. Thus, if I select another record in the upper grid, the lower grid displays related data.- If data was changed, the cells display an icon (marked with red circels, you might have to view the image in a new browser window if the forum crops them). Whether or not this icon is visible depends on a style that uses a converter.
The first image display a proper rendering. I changed the data source of the upper grid by selecting a tree item. This causes the lower grid to be updated and the styles are evaluated (converter is being invoked for each cell):
However, if the main grid's data source remains and i just select another record, the lower grid's DATA is being updated (marked by blue circle), but not the CellValuePresenter styles! As you can see, the indcators just stay there. Breakpoints in the debugger confirm that the converter is not being invoked again, although the grid's data source changed due to the binding:
I'm getting a little nervous here: I have a grid that renders wrong data all over the place and my customers expect a working version by next week, so any idea is greatly appreciated
Thanks for your advice
Philipp
I would try setting the lower grid's RecordContainerGenerationMode to "Virtualize" and its CellContainerGenerationMode="Preload". This should prevent record and cell presenters from being re-used in the bottom grid.
If this still doesn't work you can try a brute force approach by calling GetRecordsInView on the bottom grid. Then use the CellValuePresenter's static FromRecordAndField method to get specific CellValuePresenters for each record. From here you can get to any element in the tree of the cell.
Hi Joe
That fixed it - thanks! However, can I consider the current behaviour a bug (and your solution a workaround) that will be fixed with the 7.2 RTM release?
Edit:With that workaround, I ran in to the scrolling bug described in http://forums.infragistics.com/forums/t/2002.aspx. Besides that, the grid has become extremely sluggish and loading times have shot through the roof. Unfortunately, this is currently not a viable solution.
Cheers,
btw: This is another classic candidate for a built-in refresh method on a row/grid: the icons stay the same until the grid's data source is being replaced. This is something I really missing in quite a few scenarios (the CellValuePresenter.CoerceValue workaround that was posted by Joe Modica a while ago doesn't work here neither).
Actually, I not sure what caused your original issue, the suggestion to go with 'Virtualize' was just an attempt to eliminate element recycling from the equation so I'm not sure if any related issue has been fixed in the current build.
What was the binding statement used in xaml for the CellValuePresenters that didn't refresh correctly in the bottom grid?
Joe,
I'm using a binding to dynamically show/hide an icon using a converte:
Visibility="{Binding RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource Grid_SyncFieldInfoConverter}}"
The whole style looks like this:
<!-- displays a change status image if the underlying entity is marked as dirty --> <Style x:Key="Grid_SyncedFieldPresenter" TargetType="{x:Type igDP:CellValuePresenter}"> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type igDP:CellValuePresenter}"> <Grid VerticalAlignment="Center"> <Grid.ColumnDefinitions> <ColumnDefinition Width="*" /> <ColumnDefinition Width="Auto" /> </Grid.ColumnDefinitions> <ContentPresenter x:Name="PART_EditorSite" Grid.Column="0"/> <Button x:Name="ShowInfo" Style="{x:Null}" Grid.Column="1" Cursor="Hand" Command="cmd:CtmsCommands.ShowSyncedFieldInformationCommand" CommandParameter="{Binding RelativeSource={RelativeSource TemplatedParent}}" Visibility="{Binding RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource Grid_SyncFieldInfoConverter}}" ToolTip="Show difference" Margin="2,0,4,0"> <Button.Template> <ControlTemplate> <Image Source="/Shared/Images/SyncStatus/ChangedField.png" Width="16" Height="16" /> </ControlTemplate> </Button.Template> </Button> </Grid> </ControlTemplate> </Setter.Value> </Setter> </Style>
That xaml should work. One thing to note is that you shouldn't need to set RelativeSource at all since DataContext inherits, e.g. the following should work as well, you might want to try that instead:
Visibility="{Binding Converter={StaticResource Grid_SyncFieldInfoConverter}}"
In this case DataContext is set to the DataRecord. That must have changed to the new DataRecord since the other CellValuePresenters were correct. Therefore, when it did change the framework binding infrastructure should have called your converter's Convert method again with the new DataRecord.
If you set a breakpoint do you get called again after the refresh?
If not, then the only thing I can think of, other than a bug in the framework, is if the DataRecord was reused somehow. Note: we will use the same DataRecord after a refresh if its associated DataItem from the underlying data source is the same. This is by design. If that is the case then you may need to bind to something off the dataitem itself, e.g.
Visibility="{Binding Path=(DataItem).SomeProperty, Converter={StaticResource Grid_SyncFieldInfoConverter}}"
In this scenario 'SomeProperty' would either have to be a DependencyProperty or the object would have to implement INotifyPropertyChanged.
Hi Philipp,
This is not the result of our design. This is how binding with a converter works. The only time the Convert method gets called is once on the initial set and anytime after that when the value of the binding changes.
Maybe I wasn't clear about using RelativeSource. You can use RelativeSource=TemplatedParent but when you use it without a Path statement then the value of the binding is the templated parent, in this case the CellValuePresenter itself. This will never change, i.e. your convert method will only ever get called once. Btw, there is no way that I know of for us to force it without clearing the Template property and then re-setting it, which of course would recreate the entire tree of elements thereby killing the advantage of element recycling.
The reason that it is not an issue with e.g. a Listbox is that the listbox does not support element recycling, i.e. it blows away the element trees for each item as they get scrolled out of view and creates brand new ones for the new items.
The reason you see the issue with the XamDataGrid is because element recycling is turned on by default. Therefore, there are only 2 things you can do.
Setting the Path to the Value property of the cvp. When the Value changes you converter will get called
Visibility="{Binding Path=Value, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource Grid_SyncFieldInfoConverter}}"
Setting the Path to 'SomeProeprty of the underlying data. When SomeProperty changes you converter will get called assuming you implement INotifyPropertyChange
Visibility="{Binding Path=(DataContext).SomeProperty, Converter={StaticResource Grid_SyncFieldInfoConverter}}"
If these don't work in your scenario you can use a MultiBinding instead, e.g.
<Border BorderThickness="2">
<Border.BorderBrush>
<MultiBinding Converter="{StaticResource conv}">
<MultiBinding.Bindings>
<Binding/>
<Binding Path="Field" RelativeSource="{RelativeSource TemplatedParent}"/>
</MultiBinding.Bindings>
</MultiBinding>
</Border.BorderBrush>
<ContentPresenter ContentSource="{Binding RelativeSource={RelativeSource TemplatedParent}}"
</Border>
In this case your converter must implement IMultiValueConverter, e.g.
{
return Brushes.Black;
Field field = values[1] as Field;
int fieldValue = (int)cell.Value;
//return red if the value is above 10
}
I hope this helps. Sorry for not being clearer in my last posting but something must be bound to a value that will actually change before the Convert method will get called again.
Hi Joe,
In that case, I'd say that design is severly flawed - I'm just doing something that is completely natural in WPF - I provide a style for a CellValuePresenter, and try to access the templated CellValuePresenter. In WPF, this is done all over the place and basically data binding 101. A a user of the control, I shouldn't have to care whether the control is reused or not - to me, it's a new record with a new cell that contains new data, and I naturally expect my style to be fully applied no matter what records were displayed before - the grid definitely doesn't follow the rules here. And I haven't tried it yet, but I guess commands might become a liability as well if fired from within a cell (I will want to know from which cell the command was fired).
Don't take this as a rant (although I am disappointed), but: I'd take it for guaranteed that *many* developers will do exactly what I did, and they'll probably loose a lot of time as well. This was my first application with WPF / XamDataGrid, and without doing too much fancy stuff, I already wanted to access the CellValuePresenter several times. Even worse: It doesn't break, it just doesn't work properly (and maybe even fine during initial development when there's just a few test records).
Regarding your workaround - I was afraid you would be saying that. This might work (I haven't tested it yet), but it means that I will have to declare styles for every cell I want to format, because it no longer is a generic solution. I don't have an EmployeeId, I have various tables with various cells, that all rely on the converter. This won't be fun - and it *does* feel like a hack . I'd be glad if you re-evaluated this behaviour. Recycling looks like the right concept, but I expect it to be transparent to me.
Phillip,
I see the problem and it is not a bug in the control. When you set the RelativeSource to the TemplatedParent then the value that gets passed in to the Convert method is the CellValuePresenter itself. Since, in a recycling case, this doesn't change, i.e. we re-use the same CellValuePresenter, the converter's Convert method never gets called again. This is correct behavior.
If you remove the RelativeSource then the value that gets passed in to the Convert method is the DataContext, which in this case is the DataRecord. Since you also need to know the field to access the correct cell you can either use a separate converter specific to a field or pass the field name in as a ConverterParameter. e.g.
BorderBrush="{Binding Converter={StaticResource conv}, ConverterParameter=EmployeeID}"
In this case you would modify the Convert method to something like this:
string fieldName = parameter as string;
int fieldValue = (int) cell.Value;