1

I am trying to bind a datagrid columns width but not working.

<DataGridTextColumn Binding="{Binding Name}" Width="{Binding GridNameWidth}" Header="Name" />

Here is the backend code:

public int GridNameWidth
{
    get
    {
        return 300;
    }
}

The backend code is never touched. There are no errors but the name field has a default width. I'd like to make width bind to my property. I don't need two-way binding just need the width to be binded when the grid loads. Is this possible in wpf?

Luke101
  • 63,072
  • 85
  • 231
  • 359
  • What does your data structure look like? Does the same object that contains your `Name` property also contain your `GridNameWidth` property? – Rachel May 14 '15 at 16:03
  • Correct, the same object contains my Name object. – Luke101 May 14 '15 at 16:22
  • Setting the width of your visuals via model properties isn't mvvm. –  May 14 '15 at 16:51

3 Answers3

7

The DataGridTextColumn is an abstract object that isn't actually part of the VisualTree, so it doesn't make use of an inherited DataContext like you would expect with other controls.

WPF knows how to parse something like the Binding property correctly and transfer the binding to each Cell, however something like Width simply gets evaluated as-is, and does not evaluate correctly because neither the DataContext nor VisualTree is there as expected.

A common solution is to write your binding using a static data source instead. Here's an example based on this answer, which uses x:Reference to refer to another control from the XAML markup :

{Binding Source={x:Reference MyDataGrid}, Path=DataContext.NameColumnWidth}

Alternatively, you can define your own DataGridCellTemplate with a control that has it's Width bound to whatever the property is on your DataItem

Community
  • 1
  • 1
Rachel
  • 130,264
  • 66
  • 304
  • 490
  • This is probably right but I still don't understand. Are you saying I should not use my 'GridNameWidth' property but instead use another control on the window? I read the other answer but he did not give a clear example. – Luke101 May 14 '15 at 16:39
  • @Luke101 The problem is the DataGridTextColumn doesn't actually exist when the UI gets generated, so the binding doesn't evaluate correctly. As an alternative, we can set the binding to something static that will exist at the the time Cells are being generated. The `Source={x:Reference ...}` means "find the object in XAML named XXX and use it as the source for this binding", so in theory we can use that combined with `Path=DataContext.NameColumnWidth` to tell it to use the value from `MyDataGrid.DataContext.NameColumnWidth` – Rachel May 14 '15 at 17:17
0

As already mentioned the issue here is that DataGridTextColumn isn't in the logical or visual tree. An alternative solution is this approach based on a binding proxy providing a binding. https://stackoverflow.com/a/46787502/5381620

SourceCode

<DataGrid ItemsSource="{Binding Lines}" AutoGenerateColumns="False" >
    <DataGrid.Resources>
        <local:BindingProxy x:Key="proxy" Data="{Binding}"/>
    </DataGrid.Resources>
    <DataGrid.Columns>
        <DataGridTextColumn Header="ProductId1" Binding="{Binding Path=Result[0]}" Width="{Binding Data.Columns[0].Width, Source={StaticResource proxy}, Mode=TwoWay}" />
        <DataGridTextColumn Header="ProductId2" Binding="{Binding Path=Result[1]}" Width="{Binding Data.Columns[1].Width, Source={StaticResource proxy}, Mode=TwoWay}"/>
    </DataGrid.Columns>
</DataGrid>

class BindingProxy : Freezable
{
    //Override of Freezable
    protected override Freezable CreateInstanceCore()
    {
        return new BindingProxy();
    }
    public object Data
    {
        get { return (object)GetValue(DataProperty); }
        set { SetValue(DataProperty, value); }
    }
    public static readonly DependencyProperty DataProperty = DependencyProperty.Register("Data", typeof(object), typeof(BindingProxy), new UIPropertyMetadata(null));
}


public class Column : INotifyPropertyChanged
{

    public event PropertyChangedEventHandler PropertyChanged;
    protected internal void OnPropertyChanged(string propertyname)
    {
        if (PropertyChanged != null)
            PropertyChanged(this, new PropertyChangedEventArgs(propertyname));
    }

    public DataGridLength Width
    {
        get { return m_width; }
        set { m_width = value; OnPropertyChanged("Width"); }
    }
    DataGridLength m_width;
}

For this implementation use a List<Column> or ObservableCollection directly in DataContext else adjust binding.

pedrito
  • 87
  • 9
-1

Your GridNameWidth should be of the type DataGridLength if you want this to work.

mgarant
  • 565
  • 5
  • 18
  • 1
    There is an implicit converter defined for type double. See http://referencesource.microsoft.com/#PresentationFramework/Framework/System/Windows/Controls/DataGridLength.cs,334 – user2697817 May 14 '15 at 15:45
  • Is there a way to get it to work using MVVM? If not how would I do it using your way? – Luke101 May 14 '15 at 15:48
  • You could use a double. Or implement a value converter for the int. – user2697817 May 14 '15 at 15:49
  • Where is your GridNameWidth property? In the code behind or in the ViewModel? – mgarant May 14 '15 at 15:58
  • Have you put a break point on the getter? Is it trying to retrieve the value? – user2697817 May 14 '15 at 16:08
  • Yes, I put a breakpoint in the GridNameWidth property. But it is never hit. – Luke101 May 14 '15 at 16:11
  • This sounds more like a binding error rather that a conversion error. Are you getting any binding errors in output window in vs? – user2697817 May 14 '15 at 16:13
  • 1
    System.Windows.Data Error: 2 : Cannot find governing FrameworkElement or FrameworkContentElement for target element. BindingExpression:Path=GridNameWidth; DataItem=null; target element is 'DataGridTextColumn' (HashCode=738321); target property is 'Width' (type 'DataGridLength') – Luke101 May 14 '15 at 16:16