Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C# DataTable DataGrid special character "/" (slash) in column names

I would like to display a table of strings in C# using a DataGrid. The column names are dynamically generated (i.e. not known at compile time).

Here is an example:

Here, the numbers have been converted to strings.

I am using a DataTable as the source for the DataGrid which contains the whole table (rows and column headers). However, I have the problem that the values in column "climate w/change" are not shown in the DataGrid. Instead I get the following error message on the Console

"BindingExpression path error: 'climate  w' property not found on 'object' 
''DataRowView' (HashCode=22429696)'. BindingExpression:Path=climate  
w/change; DataItem='DataRowView' (HashCode=22429696); target element is 
'TextBlock' (Name=''); target property is 'Text' (type 'String')"

I understand that this is due to the slash ("/") in the column name which is interpreted as a binding expression.

My questions are

  1. Is there a way to switch off that behavior, i.e. that column names are interpreted as binding expressions? Since I provide a DataTable with all values and headers for the rows and columns, none of the data values need to be computed based on the column name. Also the column "human toxicity" has no problems although there does not exists a property called HumanToxicity.
  2. If I cannot use DataTable as the source for the DataGrid to achieve the above, which is the correct data structure to be used?

Here is the code to generate the DataTable.

    public DataTable PaValues { get; set; }

    private void CreateDataSet()
    {
        var dt = new DataTable("Perturbation Analysis");
        List<String> ics = _perturbationAnalysis.ImpactCatagories();
        dt.Columns.Add("Parameters");

        foreach (var ic in ics)
        {
            dt.Columns.Add(Sanatize(ic));
        }
        foreach (var parameter in _perturbationAnalysis.ParameterNames())
        {
            var dr = dt.NewRow();
            dr[0] = parameter;
            for (int i = 0; i < ics.Count; i++)
            {
                dr[i+1] = _perturbationAnalysis[parameter, ics[i]].ToString();
            }
            dt.Rows.Add(dr);
        }
        PaValues = dt;
    }

    private string Sanatize(string ic)
    {
        //return ic.Replace("/", "[/]").Replace("[", "").Replace("]", "").Replace(".", " ");
        //return "[" + ic + "]";
        return ic;

    }

Here is the excerpt from the XAML file

            <DataGrid
                x:Name="PAGrid"
                CanUserAddRows="False" 
                CanUserDeleteRows="False"
                ClipboardCopyMode="IncludeHeader"
                FrozenColumnCount="1"
                ItemsSource="{Binding Path=PaValues,UpdateSourceTrigger=PropertyChanged}"
                Style="{StaticResource DataGridStyle}"
                IsReadOnly="True">
            </DataGrid>

As proposed in the comments, I have added an AutoGeneratingColumn handler, which changes the binding to use square brackets:

    private void PaViewAutoGeneratingColumnHandler(object sender, DataGridAutoGeneratingColumnEventArgs e)
    {
        e.Column = new DataGridTextColumn
        {
            Binding = new Binding("[" + e.Column.Header + "]"), Header=e.Column.Header

        };
    }

It now works fine for "climate w/change", but it does not work for a column named "EDIP2003 w/o LT, acidification w/o LT, acidification w/o LT", which is a real life example used by the customer. The error message is the same as before

BindingExpression path error: '[]' property not found on 'object' ''DataRowView' (HashCode=56876317)'. 
BindingExpression:Path=[EDIP2003 w/o LT, acidification w/o LT, acidification w/o LT] 
like image 740
Hubert Avatar asked May 14 '18 10:05

Hubert


1 Answers

Looking at the documentation on PropertyPath syntax, we can see why the strings fail to map.

The first one, which you already pointed out and could fix, is the slash /:

Source Traversal (Binding to Hierarchies of Collections)

<object Path="propertyName/propertyNameX" .../>

The / in this syntax is used to navigate within a hierarchical data source object, and multiple steps into the hierarchy with successive / characters are supported.

This is fixed by using an indexer ([]).

However, the indexer format supports multiple indexers, separated by comma:

Multiple Indexers

<object Path="[index1,index2...]" .../>

or

<object Path="propertyName[index,index2...]" .../>

If a given object supports multiple indexers, those indexers can be specified in order, similar to an array referencing syntax. The object in question can be either the current context or the value of a property that contains a multiple index object.

If we look further, we can see that we can escape the comma using the following syntax:

Escapes for Property Path Strings

For certain business objects, you might encounter a case where the property path string requires an escape sequence in order to parse correctly. The need to escape should be rare, because many of these characters have similar naming-interaction issues in languages that would typically be used to define the business object.

  • Inside indexers ([ ]), the caret character (^) escapes the next character.

Using all of this, I came up with the following solution to escape everything while still using auto-generated columns. It might be overkill, but it guarantees that the string is interpreted correctly.

private void PaViewAutoGeneratingColumnHandler(object sender, DataGridAutoGeneratingColumnEventArgs e)
{
    var columnName = (string)e.Column.Header;

    // We'll build a string with escaped characters.
    // The capacity is the length times 2 (for the carets),
    // plus 2 for the square brackets.
    // This is not optimized for multi-character glyphs, like emojis

    var bindingBuilder = new StringBuilder(columnName.Length * 2 + 2);

    bindingBuilder.Append('[');
    foreach (var c in columnName)
    {
        bindingBuilder.Append('^');
        bindingBuilder.Append(c);
    }
    bindingBuilder.Append(']');

    e.Column = new DataGridTextColumn
    {
        Binding = new Binding(bindingBuilder.ToString()),
        Header = e.Column.Header,
    };
}
like image 52
Arturo Torres Sánchez Avatar answered Sep 18 '22 06:09

Arturo Torres Sánchez