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
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.
Cheers,
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;
Ok, I created a sample that illustrates the issue. You can download the sample project from here: http://www.mediafire.com/?1ydk9jbkbzj
Basis is an IG project that renders some employee data. I've added a style which renders a different border brush based on the employee ID using a converter:
<Grid.Resources> <local:ColorConverter x:Key="conv" /> <!-- a presenter style which renders an icon for an employee if the ID is bigger than a given value --> <Style x:Key="MyPresenter" TargetType="{x:Type igDP:CellValuePresenter}"> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type igDP:CellValuePresenter}"> <Border BorderThickness="2" BorderBrush="{Binding RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource conv}}"> <ContentPresenter ContentSource="{Binding RelativeSource={RelativeSource TemplatedParent}}" x:Name="PART_EditorSite" /> </Border> </ControlTemplate> </Setter.Value> </Setter> </Style></Grid.Resources>
The converter is trivial: It gets the presenter and returns a brush based on the employee ID:
public object Convert(object value, Type targetType, object parameter, CultureInfo culture){ //get the processed presenter CellValuePresenter presenter = (CellValuePresenter)value; //get the active cell in order to determine the field in question string fieldName = presenter.Field.Name; Cell cell = presenter.Record.Cells[fieldName]; int fieldValue = (int) cell.Value; //return red if the value is above xxx return fieldValue > 3 ? Brushes.Red : Brushes.Black;}
This trivial style already breaks the sample. Lets assume the converter would return a red brush if the employee ID was bigger than 3. This would look like this:
Looks fine! However, note that our grid initially displays only about 8 records. This is, how it looks if I scroll down a little - there are our first 4 black records again:
And this is, how it looks with the limit of 10 instead of 3: As the initially created records were all black and they are all reused, all cells render invalid colors: