CustomColumn and Binding

54 Views Asked by At

I have DataGrid with ObservableCollection of My class type as DataSource. I'm using a few TemplateColumns with the same DataTemplate so i got an idea that I create class for that column. The problem is that I don't know how to Bind data in that case.

My class obviously extends DataGridTemplateColumn

public class WindowedColumn : DataGridTemplateColumn

Also i have properties

public static readonly DependencyProperty TextProperty =
        DependencyProperty.Register("Text", typeof(Binding), typeof(WindowedColumn), new       PropertyMetadata(null));

public static readonly DependencyProperty MaxLengthProperty =
        DependencyProperty.Register("MaxLength", typeof(Binding), typeof(WindowedColumn), new PropertyMetadata(null));

public Binding Text
{
    get { return (Binding)GetValue(TextProperty); }
    set { SetValue(TextProperty, value); }
}

public Binding MaxLength
{
    get { return (Binding)GetValue(MaxLengthProperty); }
    set { SetValue(MaxLengthProperty, value); }
}

My constructor code:

BindingOperations.SetBinding(this, FrameworkElement.DataContextProperty, new Binding
{
    RelativeSource = new RelativeSource(RelativeSourceMode.FindAncestor, typeof(DataGrid), 1)
});
var standardTemplate = new DataTemplate();
var standardTextBlock = new FrameworkElementFactory(typeof(TextBlock));
standardTextBlock.SetBinding(TextBlock.TextProperty, Text);
standardTemplate.VisualTree = standardTextBlock;

DataTemplate editingTemplate = new DataTemplate();
var editingStackPanel = new FrameworkElementFactory(typeof(StackPanel));
editingStackPanel.SetValue(StackPanel.OrientationProperty, Orientation.Horizontal);
var editingTextBox = new FrameworkElementFactory(typeof(TextBox));
editingTextBox.SetBinding(TextBox.TextProperty, Text);
    

//editingTextBox.SetBinding(TextBox.MaxLengthProperty, MaxLength);
editingTextBox.SetValue(TextBox.MinWidthProperty, 50.0);
editingTextBox.SetValue(TextBox.MaxWidthProperty, 200.0);
var editingButton = new FrameworkElementFactory(typeof(Button));
editingButton.SetValue(ContentControl.ContentProperty, ":");
editingStackPanel.AppendChild(editingTextBox);
editingStackPanel.AppendChild(editingButton);
editingTemplate.VisualTree = editingStackPanel;

CellTemplate = standardTemplate;
CellEditingTemplate = editingTemplate;

in my xaml file i use it like that

<Columns:WindowedColumn Header="My Column" Text="{Binding OrderNumber}" MaxLength="{Binding MyMaxLengthProperty}"/>

Visual shows me in xaml that no dataContext found for OrderNumber and of course when I run the program I don't see any value which i see in other TextColumn with the same binding for test

2

There are 2 best solutions below

0
hubert kwiecień On

I found the solution. If anyone ever want to create custom column and use ItemsSource for DataGrid should know that data are store in rows not in DataGrid so you can't relate to DataContext at DataGrid. You should relate to DataContext in DataGridRow. Here is example how it can works:

Text="{Binding DataContext.OrderNumber, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DataGridRow}}}"

You should also create columns after property change event like that

 protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e)
    {
        base.OnPropertyChanged(e);
        if (e.Property == TextProperty || e.Property == MaxLengthProperty)
        {
            CellTemplate.VisualTree = CreateContentTemplate();
            CellEditingTemplate.VisualTree = CreateEditingTemplate();
        }
    }

Constructor can be simplified to

 public WindowedColumn()
    {
        CellTemplate = new DataTemplate();

        CellEditingTemplate = new DataTemplate();
        
    }
0
EldHasp On

Your mistake is in the type of properties and in expectation of RelativeSource binding. The DataGridColumn is not a UI element and is not a Freezable. Because of this, it "does not see" the current Data Context and is not included in the visual tree. Therefore, setting a binding of type RelativeSource FindAncestor in it will not work.
Your Text and MaxLength properties should be regular CLR properties. It's better to name them TextBinding and MaxLengthBinding.
Implementation example:

using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;

namespace Core2023.SO.hubert_kwiecień
{
    public class WindowedColumn : DataGridTemplateColumn
    {
        private BindingBase? _maxLength;
        private BindingBase? _text;

        public BindingBase? TextBinding { get => _text; set => Set(ref _text, value); }

        public BindingBase? MaxLengthBinding { get => _maxLength; set => Set(ref _maxLength, value); }

        private void Set(ref BindingBase? _field, BindingBase? @new)
        {
            if (BindingBase.Equals(_field, @new))
                return;
            _field = @new;
            BindingChanged();
        }
        private void BindingChanged()
        {
            var standardTemplate = new DataTemplate();
            var standardTextBlock = new FrameworkElementFactory(typeof(TextBlock));
            if (TextBinding is not null)
                standardTextBlock.SetBinding(TextBlock.TextProperty, TextBinding);
            standardTemplate.VisualTree = standardTextBlock;

            DataTemplate editingTemplate = new DataTemplate();
            var editingStackPanel = new FrameworkElementFactory(typeof(StackPanel));
            editingStackPanel.SetValue(StackPanel.OrientationProperty, Orientation.Horizontal);
            var editingTextBox = new FrameworkElementFactory(typeof(TextBox));
            if (TextBinding is not null)
                editingTextBox.SetBinding(TextBox.TextProperty, TextBinding);

            if (MaxLengthBinding is not null)
                editingTextBox.SetBinding(TextBox.MaxLengthProperty, MaxLengthBinding);
            editingTextBox.SetValue(TextBox.MinWidthProperty, 50.0);
            editingTextBox.SetValue(TextBox.MaxWidthProperty, 200.0);
            var editingButton = new FrameworkElementFactory(typeof(Button));
            editingButton.SetValue(ContentControl.ContentProperty, ":");
            editingStackPanel.AppendChild(editingTextBox);
            editingStackPanel.AppendChild(editingButton);
            editingTemplate.VisualTree = editingStackPanel;

            CellTemplate = standardTemplate;
            CellEditingTemplate = editingTemplate;
        }
    }
}
using System;
using System.Collections.Generic;
using System.Linq;

namespace Core2023.SO.hubert_kwiecień
{
    public class SomeExampleOrderNumber
    {
        public int OrderNumber { get; set; }

        private static readonly Random random = new Random();
        public static IEnumerable<SomeExampleOrderNumber> Items { get; }
            = Enumerable
            .Range(1, 10)
            .Select(_ => new SomeExampleOrderNumber() { OrderNumber = random.Next()})
            .ToArray();
    }
}
<Window x:Class="Core2023.SO.hubert_kwiecień.WindowedColumnWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:Core2023.SO.hubert_kwiecień"
        mc:Ignorable="d"
        Title="WindowedColumnWindow" Height="450" Width="800">
    <Grid>
        <DataGrid ItemsSource="{x:Static local:SomeExampleOrderNumber.Items}">
            <DataGrid.Columns>
                <local:WindowedColumn Header="My Column"
                                      TextBinding="{Binding OrderNumber}"
                                      MaxLengthBinding="{Binding Source=20}"/>
            </DataGrid.Columns>
        </DataGrid>
    </Grid>
</Window>