Martijn
Martijn

Reputation: 392

How to have a Custom Datagrid control with fixed columns whose width is adjustable?

What i am trying to do is make a CustomDatagrid which has already 2 fixed columns. Then i can re-use this CustomDatagrid and add extra columns to fit my best purpose. But when i do add extra columns i would like to be able to resize the 2 fixed columns. I tried working it out with dependency properties as the example underneath but to no avail. And i do not get any binding errors whatsoever which leaves me without any idea what is wrong.

=> this small example will clarify what i am trying to do

CustomDataGrid.Xaml

<DataGrid x:Class="DataGridWidthTestControl.CustomDataGrid"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
         xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
         xmlns:local="clr-namespace:DataGridWidthTestControl"
         mc:Ignorable="d" 
         d:DesignHeight="300" d:DesignWidth="300">
<DataGrid.Columns>
    <DataGridTextColumn Width="{Binding Column1Width}" Header="Column1"/>
    <DataGridTextColumn Width="{Binding Column2Width}" Header="Column2"/>
</DataGrid.Columns>

CustomDataGrid.Xaml.CS - Codebehind

namespace DataGridWidthTestControl
{
    /// <summary>
    /// Interaction logic for CustomDataGrid.xaml
    /// </summary>
    public partial class CustomDataGrid : DataGrid
    {
        public CustomDataGrid()
        {
            InitializeComponent();
            DataContext = this;
        }

        public static readonly DependencyProperty Column1WidthProperty = DependencyProperty.Register( "Column1Width", typeof(DataGridLength), typeof(CustomDataGrid), new FrameworkPropertyMetadata(new DataGridLength(25, DataGridLengthUnitType.Star)));

        public DataGridLength Column1Width
        {
            get { return (DataGridLength)GetValue(Column1WidthProperty); }
            set { SetValue(Column1WidthProperty, value); }
        }

        public static readonly DependencyProperty Column2WidthProperty = DependencyProperty.Register("Column2Width", typeof(DataGridLength), typeof(CustomDataGrid), new FrameworkPropertyMetadata(new DataGridLength(15, DataGridLengthUnitType.Star)));

        public DataGridLength Column2Width
        {
            get { return (DataGridLength)GetValue(Column2WidthProperty); }
            set { SetValue(Column2WidthProperty, value); }
        }

    }
}

Mainwindow.xaml (codebehind is empty except for default initialize call)

<Window x:Class="DataGridWidthTestControl.MainWindow"
        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:DataGridWidthTestControl"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <local:CustomDataGrid Column1Width="1*" Column2Width="1*" >
            <DataGrid.Columns>
                <DataGridTextColumn Width="10*" Header="column3"/>
            </DataGrid.Columns>
        </local:CustomDataGrid>
    </Grid>
</Window>

Upvotes: 1

Views: 85

Answers (2)

Grx70
Grx70

Reputation: 10349

First of all you need to know that bindings without any source specified (like in your case), bindings using Binding.RelativeSource and bindings using Binding.ElementName will not work with DataGridColumn, because it derives directly from DependencyObject, which is not sufficient. Basically, in order for those to work, the target object needs to be of type deriving from FrameworkElement or FrameworkContentElement, and it should be part of the visual or logical tree (which DataGridColumns are not). Be aware though that there are few exceptions to that requirement (like Freezables defined within resource dictionaries), and more to come with upcoming versions of the framework.

So unfortunately you need to explicitly specify Binding.Source (which should be an instance of your CustomDataGrid class). I can't think of any way of doing it in XAML (Source={x:Reference (...)} is not applicable because you cannot reference an object from within its definition), so I think you'll need to fallback to code-behind (which is not unusual when devising custom controls).

The easiest way is to name your columns:

<DataGrid.Columns>
    <DataGridTextColumn x:Name="Column1" x:FieldModifier="private" Header="Column1" />
    <DataGridTextColumn x:Name="Column2" x:FieldModifier="private" Header="Column2" />
</DataGrid.Columns>

(x:FieldModifier="private" is optional) and then set the bindings upon initialization of your control:

public CustomDataGrid()
{
    InitializeComponent();
    BindingOperations.SetBinding(Column1, DataGridColumn.WidthProperty, new Binding
    {
        Path = new PropertyPath(Column1WidthProperty),
        Source = this,
    });
    BindingOperations.SetBinding(Column2, DataGridColumn.WidthProperty, new Binding
    {
        Path = new PropertyPath(Column2WidthProperty),
        Source = this,
    });
}

You also may want to add Mode = BindingMode.TwoWay to the bindings so that the values on your control stay in sync if the user resizes the columns manually.

Note that I intentionally removed the DataContext = this line because a) it is not necessary and b) it would give you a lot of trouble when using your control (e.g. binding DataGrid.ItemsSource to a view-model property would not work as it usually does).

Upvotes: 1

AaronShockley
AaronShockley

Reputation: 851

You'll need to programmatically insert the fixed columns in your CustomDataGrid class instead of specifying them in the template.

Something like:

public override void OnApplyTemplate()
{
    if (!this.Columns.Any(c => c.Header.ToString() == "Column1"))
    {
        this.Columns.Insert(0,
                            new DataGridTextColumn
                            {
                                Width = this.Column1Width,
                                Header = "Column1"
                            });
    }

    if (!this.Columns.Any(c => c.Header.ToString() == "Column2"))
    {
        this.Columns.Insert(1,
                            new DataGridTextColumn
                            {
                                Width = this.Column2Width,
                                Header = "Column2"
                            });
    }

    base.OnApplyTemplate();
}

I'm not sure OnApplyTemplate() is the right time to do it, there might be a better method to override, but this is the concept I'd go for to make this work.

Upvotes: 2

Related Questions