-1

Data model:

public class Metric
{
    public string                         Name      { get; set; }
    public bool                           IsEnabled { get; set; }
}

public class FileItem
{
    public string                         FN        { get; set; }
    public ObservableCollection<string>   Values    { get; set; }
}

public class MainViewModel
{
    public ObservableCollection<Metric>   Metrics   { get; set; }
    public ObservableCollection<FileItem> FileItems { get; set; }
}

XAML:

<Window.Resources>
    <local:ValueConverter x:Key="ValueConverter" />
    <DataTemplate x:Key="MetricHeaderTemplate">
        <TextBlock Text="{Binding Name}" />
    </DataTemplate>
</Window.Resources>
...
<ListView ItemsSource="{Binding FileItems}">
    <ListView.View>
        <GridViewColumn Header="Path to file" Width="400">
            <GridViewColumn.CellTemplate>
                <DataTemplate>
                    <TextBlock Text="{Binding FN}" />
                </DataTemplate>
            </GridViewColumn.CellTemplate>
        </GridViewColumn>
        <GridViewColumn HeaderTemplate="{StaticResource MetricHeaderTemplate}" Header="{Binding Metrics[0]}">
            <GridViewColumn.CellTemplate>
                <DataTemplate>
                    <TextBox Text="{Binding Path=Values[0], Converter={StaticResource ValueConverter}}"/>
                </DataTemplate>
            </GridViewColumn.CellTemplate>
        </GridViewColumn>
        <GridViewColumn HeaderTemplate="{StaticResource MetricHeaderTemplate}" Header="{Binding Metrics[1]}">
            <GridViewColumn.CellTemplate>
                <DataTemplate>
                    <TextBox Text="{Binding Path=Values[1], Converter={StaticResource ValueConverter}}"/>
                </DataTemplate>
            </GridViewColumn.CellTemplate>
        </GridViewColumn>
    </ListView.View>
</ListView>

All works as expected. I'd like to move the CellTemplate to StaticResources (to re-use and not duplicate the logic) and trying this:

<Window.Resources>
    <local:ValueConverter x:Key="ValueConverter" />
    <DataTemplate x:Key="MetricHeaderTemplate">
        <TextBlock Text="{Binding Name}" />
    </DataTemplate>
    <DataTemplate x:Key="MetricCellTemplate">
        <TextBox Text="{Binding Path=., Converter={StaticResource ValueConverter}}"/>
    </DataTemplate>
</Window.Resources>
...
<ListView ItemsSource="{Binding FileItems}">
    <ListView.View>
        <GridViewColumn Header="Path to file" Width="400">
            <GridViewColumn.CellTemplate>
                <DataTemplate>
                    <TextBlock Text="{Binding FN}" />
                </DataTemplate>
            </GridViewColumn.CellTemplate>
        </GridViewColumn>
        <GridViewColumn
            HeaderTemplate="{StaticResource MetricHeaderTemplate}"
            Header="{Binding Metrics[0]}"
            CellTemplate="{StaticResource MetricCellTemplate}"
            DisplayMemberBinding="{Binding Values[0]}"
        />
        <GridViewColumn
            HeaderTemplate="{StaticResource MetricHeaderTemplate}"
            Header="{Binding Metrics[1]}"
            CellTemplate="{StaticResource MetricCellTemplate}"
            DisplayMemberBinding="{Binding Values[1]}"
        />
    </ListView.View>
</ListView>

Unfortunately, no luck: the cell's value not displayed. What am I missing?

Thanks.

P.S. This is simplified version, in fact both header template and cell template are more complicated and have different elements and logic.

fifonik
  • 1,556
  • 1
  • 10
  • 18

2 Answers2

1

Incorect binding path (Path=.) on MyCellTemplate

<DataTemplate x:Key="MyCellTemplate">
    <TextBox Text="{Binding Path=., Converter={StaticResource MyConverter}}"/>
</DataTemplate>
homa
  • 66
  • 5
  • What do you mean under "incorrect binding path"? **Path=.** is valid based on [this MS article](https://learn.microsoft.com/en-us/dotnet/framework/wpf/data/binding-declarations-overview) and it worked fine in other places when I checked. – fifonik Jul 11 '20 at 10:36
  • 1
    I wrote incorect not invaild , the binding property path is set to the DataContext itself instead of a property . [https://learn.microsoft.com/en-us/dotnet/framework/wpf/data/data-templating-overview?redirectedfrom=MSDN] – homa Jul 11 '20 at 16:07
1

According to the code you said it is working you are using the wrong property path.
Just because the template is defined as a resource, the Binding.Path doesn't change. This is because the binding you set is relative to the DataContext of the DataTemplate. Once the externally defined template is being applied, the DataContext is the same.
So the Binding.Path remains the same:

<Window.Resources>
  <local:MyConverter x:Key="MyConverter" />
  <DataTemplate x:Key="MyCellTemplate">
    <TextBox Text="{Binding Path=Values[0], Converter={StaticResource MyConverter}}"/>
  </DataTemplate>
</Window.Resources>
BionicCode
  • 1
  • 4
  • 28
  • 44
  • I'm confirming that if I change Path=. to Path=Values[0] it works. I thought the DisplayMemberBinding I was using is exactly for changing context. The whole point of moving this bit to static template is for reusing it so I wanted to use on one column DisplayMemberBinding="{Binding Values[0]}", on another column DisplayMemberBinding="{Binding Values[1]}] etc. Is it possible to change the data context while using the cell static template? (context for Header template correctly changed while passing in Header and data context for header should be different that data context for cell) – fifonik Jul 11 '20 at 10:48
  • `DisplayMemberPath` only selects a property of the binding source. It doesn't allow nested properties. To have the `DataContext` changed based on your item you may need to redesign your data item. You didn't show it, so I can't be very precise. But generally the `DataContext` of a column is the row's data object i.e. the current item of the `ListView`. Now, you usually bind each column to a property on this data item. This means the column data shouldn't be "hidden" in a collection where each index maps to a column. This can' never be dynamic as you must explicitly map index to column. – BionicCode Jul 11 '20 at 14:01
  • Each item must expose the values for each column. This means a property for each column. By having individual values assigned to column specific properties, you allow dynamic data for each row (item) and its columns (properties). If you need help to redesign your data objects you would have to post their implementation and a short explaination what you want to achieve or how want the data to be presented. So, to reuse a `DataTemplate` for other columns is possible, but requires the proper class design. Your issue _can_ be a hint that you generally design classes wrong. – BionicCode Jul 11 '20 at 14:04
  • I've added some information about data model my question. In grid some columns are mapped directly (FN in this example) and some dynamically. All Metrics should become separate columns and Values should be shown as content of these columns. – fifonik Jul 12 '20 at 00:24
  • It looks like you are binding to `FileItem` items. Each each item has an individual `FN` value but every item shares the same collection of `Values`. To fix your problem each item must hold its individual item of the `Values` collection. So `public ObservableCollection Values { get; set; }` must become `public string Value { get; set; }`. Now you no longer have to select the value of the collection as it is directly accessible: `Text="{Binding Path=Value}"`. – BionicCode Jul 12 '20 at 07:22
  • Yes, I'm binding FileItem items. Each FileItem item hold its own observable collection Values. Each file have a few metrics that should be displayed in a different columns. File "a" have "a1", "a2", "a3" in this collection, file "b" -- "b1", "b2", "b3". a1 & b1 is displayed in first column after filename, a2 & b2 -- in the second column etc. – fifonik Jul 12 '20 at 11:51
  • You could then add a property that holds "a1&b1" and "a2&b2" - one property for each column. If you want to stay with the collection binding then makee sure the relevant values are always stored at the same index. – BionicCode Jul 12 '20 at 12:22
  • Not sure if I follow your suggestion about storing "a1&b1". Do you mean store all metric's values as single string per each FileItem? Duplicating? How can I split them and pass into the CellTemplate? – fifonik Jul 12 '20 at 21:24
  • I was just refering to your "explanation" of _"a1 & b1 is displayed in first column after filename, a2 & b2 -- in the second column etc."_. Don't know anything about a1, b2 or their relationship. Since you don't want to define a template for each column, but share one across columns, you have to get rid of the indexer. You have to create a property for each column and assign the corresponding values to it. If you want to display a1&b1 in the first column then assign this values to a property on `FileItem` and bind to it. – BionicCode Jul 13 '20 at 12:13
  • If you you still don't understand, please consider to post a real example, which we can refactor and not some meaningless a1&b1 abstraction. – BionicCode Jul 13 '20 at 12:13