GuOrg/Gu.Wpf.DataGrid2D

How to make TwoWay binding with templates?

odinsacred opened this issue · 10 comments

Hello! Can anybody help me? I wanna bind 2D int array to DataGrid, but I don't understand how to create TwoWay binding. Now I'm use this code:

<DataGrid Grid.Column="1" Grid.Row="1" x:Name="AutoColumns"
    dataGrid2D:ItemsSource.Array2D="{Binding Path=Data2D, Mode=TwoWay}"
    dataGrid2D:ItemsSource.ColumnHeadersSource="{Binding ColumnHeaders}"
    dataGrid2D:ItemsSource.RowHeadersSource="{Binding RowHeaders}"
    ColumnWidth="50"
    SelectionUnit="Cell"
    IsReadOnly="False"
    IsEnabled="True">
<dataGrid2D:Cell.Template>
<DataTemplate>
    <TextBlock Text="{Binding Path=., StringFormat=X4}" IsEnabled="true"/>
</DataTemplate>
</dataGrid2D:Cell.Template>
    <dataGrid2D:Cell.EditingTemplate>
    <DataTemplate>
        <TextBox Text="{Binding Path=., StringFormat=X4}" />
    </DataTemplate>
</dataGrid2D:Cell.EditingTemplate>
</DataGrid>

Also I'm tried to add an attributes like "Mode=TwoWay, BindsDirectlyToSource=True, UpdateSourceTrigger=LostFocus, NotifyOnSourceUpdated=True" In data templates, but it's do nothing effect. Now it's allow me to entering some numbers in cells, but not save to my array. And one detail - everything work good without templates, but I need them.

Is the type you are binding to int[,]? If so you need to wrap the elements and bind to a property. This is the same limitation as if you do:

<DataGrid AutoGenerateColumns="False" ItemsSource="{Binding Values}">
    <DataGrid.Columns>
        <DataGridTemplateColumn Header="Value">
            <DataGridTemplateColumn.CellTemplate>
                <DataTemplate>
                    <TextBox Text="{Binding .}" />
                </DataTemplate>
            </DataGridTemplateColumn.CellTemplate>
        </DataGridTemplateColumn>
    </DataGrid.Columns>
</DataGrid>

with:

public class ViewModel
{
    public ObservableCollection<int> Values { get; } = new ObservableCollection<int> {1, 2, 3};
}

Thanks for answer! Yes, it's about int[,]. I'm novice in WPF, so I'm not completely understood this
public ObservableCollection<int> Values { get; } = new ObservableCollection<int> {1, 2, 3};
This is an 1D array, right? So for 2D array I'm need
public ObservableCollection<int> Values { get; } = new ObservableCollection<int> {{1, 2, 3}, {1,2,3}};
Isn't it?

What I want: view and edit values in HEX format, in addition I need to set names of rows and columns. Today I'm readed issue "Scientific Notation for Numeric Data", created by kaneorotar, he has a similar problem. Maybe we will try to write a complete example with templates and twoWay binding for other people, who will have this problem in the future?

Now I'm try do this: (but it's not working)
C# code:

        public ObservableCollection<int[,]> Values
        {
            get => this.values;

            set
            {
                if (Equals(value, this.values))
                {
                    return;
                }

                this.values = value;
                NotifyOfPropertyChange(() => Values);
            }
        }

// Constructor:
    ObservableCollection<int[,]> a = new ObservableCollection<int[,]>();
    a.Add(new int[,] { { 1, 2, 3 }, { 1, 2, 3 } });
    Values = a;

XAML code:

<DataGrid Grid.Column="1" Grid.Row="1" x:Name="AutoColumns"
    dataGrid2D:ItemsSource.Array2D="{Binding Path=Values}"
    dataGrid2D:ItemsSource.ColumnHeadersSource="{Binding ColumnHeaders}"
    dataGrid2D:ItemsSource.RowHeadersSource="{Binding RowHeaders}"
    ColumnWidth="50"
    SelectionUnit="Cell"
    IsReadOnly="False"
    IsEnabled="True">
    <dataGrid2D:Cell.Template>
        <DataTemplate>
            <TextBlock Text="{Binding Path=., StringFormat=X4}"/>
        </DataTemplate>
    </dataGrid2D:Cell.Template>
        <dataGrid2D:Cell.EditingTemplate>
            <DataTemplate>
                <TextBox Text="{Binding Path=., StringFormat=X4}" />
            </DataTemplate>
        </dataGrid2D:Cell.EditingTemplate>
</DataGrid>

The code was not a suggestion for a fix, it was an example of the limitation you hit.

And so, should I hope for comlete example, or I must create it by myself?

I'll ping you if I write one but not sure when I will find the time.

Ok, anyway, thanks for support))

@odinsacred for now you can use converter like this:

  public class EditableInt2DConverter : IValueConverter
    {
        class ItemWrapper
        {
            int[,] container;
            int i, j;

            public ItemWrapper(int[,] container, int i, int j)
            {
                this.container = container;
                this.i = i;
                this.j = j;
            }

            public int Value
            {
                get => container[i, j];
                set => container[i, j] = value;
            }
        }

        public object Convert(object value, Type targetType, object p, CultureInfo ci)
        {
            var array2d = (int[,])value;
            var n = array2d.GetLength(0);
            ItemWrapper[,] items = new ItemWrapper[n, n];
            for (int i = 0; i < n; i++)
                for (int j = 0; j < n; j++)
                    items[i,j] = new ItemWrapper(array2d, i, j);
            return items;
        }

        public object ConvertBack(object value, Type targetType, object p, CultureInfo ci) =>
            throw new NotSupportedException();
    }
dataGrid2D:ItemsSource.Array2D="{Binding Path=Data2D, Converter={StaticResource EditableInt2DConverter}}"

so, you will bind to Value in templates.

            <dataGrid2D:Cell.Template>
                <DataTemplate>
                    <TextBlock IsEnabled="true" Text="{Binding Path=Value, StringFormat=X4}" />
                </DataTemplate>
            </dataGrid2D:Cell.Template>
            <dataGrid2D:Cell.EditingTemplate>
                <DataTemplate>
                    <TextBox Text="{Binding Path=Value, StringFormat=X4}" />
                </DataTemplate>
            </dataGrid2D:Cell.EditingTemplate>

@FoggyFinder Thank you very much!