1

There is list with columns to display. I am using ListView with data templates for cells. And my problem is to access both: row and column from template.

Below is demo, xaml:

<ListView x:Name="listView" ItemsSource="{Binding Items}">
    <ListView.Resources>
        <GridViewColumn x:Key="Column" x:Shared="False">
            <GridViewColumn.CellTemplate>
                <DataTemplate>
                    <TextBlock>
                        <Run Text="{Binding WhatHere}" /> <!-- problem here -->
                        <Run Text="{Binding Mode=OneWay}" />
                    </TextBlock>
                </DataTemplate>
            </GridViewColumn.CellTemplate>
        </GridViewColumn>
    </ListView.Resources>
    <ListView.View>
        <GridView />
    </ListView.View>
</ListView>

and the code:

public partial class MainWindow : Window
{
    public List<string> Items { get; } = new List<string> { "1", "2", "3" };

    public MainWindow()
    {
        InitializeComponent();
        DataContext = this;

        var a = (GridViewColumn)listView.FindResource("Column");
        a.Header = "a";
        ((GridView)listView.View).Columns.Add(a);

        var b = (GridViewColumn)listView.FindResource("Column");
        b.Header = "b";
        ((GridView)listView.View).Columns.Add(b);
    }
}

will produce

My aim is to have:

a     b
a1    b1
a2    b2
a3    b3

Possible? How do I pass column to DataTemplate ? In fact I want to simply know to which column current cell belongs, but please consider following:

  1. This is simplified case.
  2. In reality data templates are more complicated: many elements with bindings, triggers, etc.
  3. In reality column related data are more complicated than just header.
  4. Columns will be generated at runtime with different headers, etc.
  5. Adding columns and setting Header in code-behind is not a problem, DataTemplate is.

I was thinking to use attached property on GridViewColumn, however, it's not parent of cells in the visual tree:

Sinatr
  • 20,892
  • 15
  • 90
  • 319
  • 2
    Can't you just bind to an instance of a type that has a property for both the row- and the column index? – mm8 Jul 19 '17 at 12:24
  • @mm8, can you explain what you mean? – Sinatr Jul 19 '17 at 12:30
  • How about `Converter`? and use of `{RelativeSource Self}` as a parameter? Then you would have access to the UIElement and in the converter `Grid.GetRow(UIElement)` or `Grid.GetColumn()` respectively? – XAMlMAX Jul 19 '17 at 15:28
  • I even want to say possible duplicate of https://stackoverflow.com/q/363100/2029607 – XAMlMAX Jul 19 '17 at 15:32
  • The main issue here is that you're going backwards. You are creating a view, and then trying to set your data based on that. WPF isn't designed to work that way, and it's always going to be painful to try to force it to. You need to start with the data. Create a data structure, your view model, that describes the relationships you want, including what column each element is in. Then, bind that data to the view, using the properties in the data to ensure each data element is displayed in the right place. Then the data elements _start_ with all the information you want. – Peter Duniho Jul 19 '17 at 17:17
  • @XAMlMAX, `Grid` != `GridView` and the problem is that `GridViewColumn` simply contain cell data template, it's not cell container in the view, `RelativeSource` (my first thought as well) won't help. – Sinatr Jul 20 '17 at 06:56
  • My bad for not reading it properly. I had a look at the `GridView` on MSDN and I have found that [`GridViewRowPresenter`](https://learn.microsoft.com/en-us/dotnet/api/system.windows.controls.gridviewrowpresenter?view=netframework-4.7) has a property called columns. Would that be of use to you? – XAMlMAX Jul 20 '17 at 07:18
  • @XAMlMAX, in what way it can be useful? See edit, I've attached screenshot of visual tree from some `ListView`. – Sinatr Jul 20 '17 at 07:29
  • @PeterDuniho, I simply want to use same data template for different columns. If `a1` and `b1` (first row) cells have same `DataContext`, how could they know to which column they belong? This is why I need to pass some kind of *parameter* to data template. – Sinatr Jul 20 '17 at 07:34
  • I can't see that screen shot as I am behind a proxy. And the `GridViewRowPresenter` has `Columns` property which you can use to get the column name or column header from. – XAMlMAX Jul 20 '17 at 07:43
  • 1
    In my experience, almost always, when I find myself wanting to pass information _from_ the view back _to_ the model/data, I've chosen poorly. And I admit, I've chosen poorly more than once. I've regretted it every time. I don't feel there's enough context in your question to really understand it. You've vaguely stated some constraints, but even so the fact that you are building UI in code-behind is very suspect. All that said, I did post [an answer to a similar question](https://stackoverflow.com/a/44581811) recently, and you might find some useful suggestions there. – Peter Duniho Jul 20 '17 at 08:09
  • I have found [this alteration Index answer](https://stackoverflow.com/a/5719147/2029607) that would give you the row index. The answer is for `ListBox` though. – XAMlMAX Jul 20 '17 at 09:00

1 Answers1

0

If you want to pass parameter to data template, then you need more MVVM (c) unknown wpf programmer

@mm8, was right, my design lack one more abstraction to hold column information (column name). I've to create column view model (simplified again):

public class ViewModelColumn
{
    public string Column { get; }

    public ViewModelColumn(string column)
    {
        Column = column;
    }
}

and the code to add column will become something like

var a = new FrameworkElementFactory(typeof(ContentControl));
a.SetValue(ContentControl.ContentProperty, new ViewModelColumn("a"));
((GridView)listView.View).Columns.Add(new GridViewColumn
{
    Header = "a",
    CellTemplate = new DataTemplate { VisualTree = a }
});

Cell data template is created in code behind. The idea is to supply ContentControl for all cells with column instance bound to Content, then the view needs another data template (this time fully defined in xaml), to know how to visualize it:

<DataTemplate DataType="{x:Type local:ViewModelColumn}">
    <TextBlock>
        <Run Text="{Binding Column, Mode=OneTime}" />
        <Run Text="{Binding DataContext,RelativeSource={RelativeSource AncestorType={x:Type ListViewItem}}, Mode=OneWay}" />
    </TextBlock>
</DataTemplate>

Such cell DataContext contains column information. To access row (access item from Items), we have to use ListViewItem.DataContext.

Now the view will looks like this:

I am pretty happy about this solution, mostly about combination of things what makes it working, but I guess it could be improved. Hopefully after posting the answer the question become clearer.

Sinatr
  • 20,892
  • 15
  • 90
  • 319