Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Binding to the width of a ColumnDefinition

I need some kind of table for user input. So I created a grid with defined columns and rows. The table is not to show data, it's purpose is just to structure inputs.

To structure all elements, like text boxes etc. I defined all columns with a width. There will be several grids aligned in a stackpanel.

The column definitions in XAML looks like:

<Grid.ColumnDefinitions>
  <ColumnDefinition x:Name="widthLoopID" Width="55" />
</Grid.ColumnDefinition>

I referenced that from other grids like:

<Grid.ColumnDefinitions>
  <ColumnDefinition Width="{Binding ElementName=widthLoopID, Path=Width}" />
</GridColumnDefinition>

That works pretty well.

Now the difficult part. How do I do this in code-behind?

I tried the following:

// define columns
ColumnDefinition loopIdColumn = new ColumnDefinition();
// setup databinding for columns
Binding loopIdBinding = new Binding();
// set binding for width
loopIdBinding.ElementName = "widthLoopID";
loopIdBinding.Path = new PropertyPath("Width");
loopIdColumn.SetBinding(Grid.WidthProperty, loopIdBinding);
// add definition of column
loopGrid.ColumnDefinitions.Add(loopIdColumn);

But when executed I get the following error

System.Windows.Data Error: 1 : Cannot create default converter to perform 'one-way' conversions between types 'System.Windows.GridLength' and 'System.Double'. Consider using Converter property of Binding. BindingExpression:Path=Width; DataItem='ColumnDefinition' (HashCode=37457626); target element is 'ColumnDefinition' (HashCode=7871588); target property is 'Width' (type 'Double')

System.Windows.Data Error: 5 : Value produced by BindingExpression is not valid for target property.; Value='55' BindingExpression:Path=Width; DataItem='ColumnDefinition' (HashCode=37457626); target element is 'ColumnDefinition' (HashCode=7871588); target property is 'Width' (type 'Double')

So I need to convert "Width", but how do I do that and why that can't be performed by the default converter as it does in XAML?

I'm pretty new to WPF and may not have understood every concept, so please give me a hint.

Thanks in advance Benny

EDIT:

I tried the hint of the first answer. It doesn't work, because GridLengthConverter isn't from type IValueConverter.

So I wrote a DoubleToGridLengthConverter, which converts a GridLength to a Double and vice versa.

Now the value gets converted but the result is not the same as the result that the code does in XAML. I have the feeling that it doesn't work at all from code behind. Really strange.

Here is the code of the converter:

using System;
using System.Windows;
using System.Windows.Data;
using System.Windows.Media;
using System.Globalization;
using System.Diagnostics;

namespace editor
{
   [ValueConversion(typeof(Double), typeof(GridLength))]
   class DoubleToGridLengthConverter : IValueConverter
   {
       public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
       {
           // check whether a value is given
           if (value != null)
           {
              Debug.WriteLine("value is: " + value + " type: " + value.GetType());
              return (Double)((GridLength)(value)).Value;
           }
           else
           {
               throw new ValueUnavailableException();
           }
       }

       public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
       {
           // check whether a value is given
           if (value != null)
           {
               return new GridLength((Double)value);
           }
           else
           {
               throw new ValueUnavailableException();
           }
        }
    }
}

Any further hints? I can imagine that someone else already have had this problem.

Thanks again Benny

like image 875
Benny Avatar asked Sep 05 '11 05:09

Benny


3 Answers

You're very close, and the error is a big hint. You need to use a GridLengthConverter to convert the original type (System.Double) to a GridLength type (which is what column widths are defined as).

Try this (untested):

loopIdBinding.Converter = new System.Windows.GridLengthConverter();

In fact, that should be all you have to do. That should tell the binding to run the value through the converter to get the type you need.

Edit

So yes, GridLengthConverter is not an IValueConverter so you can't do this. Instead, you'll need to make a new class that implements IValueConverter like this:

public class GridLengthValueConverter : IValueConverter
{
    GridLengthConverter _converter = new GridLengthConverter();

    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        return _converter.ConvertFrom(value);
    }

    public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        return _converter.ConvertTo(value, targetType);
    }
}

... then assign a new instance of this as your converter:

loopIdBinding.Converter = new System.Windows.GridLengthConverter();

Again this is untested. Let me know how it goes.

like image 153
Matt Hamilton Avatar answered Nov 04 '22 10:11

Matt Hamilton


I think I misunderstood the question, I didn't realise you were binding between 2 grids. However, I have left my previous answer as it might be relevant. It would be worth considering, if you need to add columns in code quite often you are doing something wrong. Every situation is different though :-)

Anyway, the answer to your question is that you don't need a converter. You have just bound to the wrong property in this line

loopIdColumn.SetBinding(Grid.WidthProperty, loopIdBinding);

just change that to

loopIdColumn.SetBinding(ColumnDefinition.WidthProperty, loopIdBinding);

and it all works just fine.

like image 37
MikeKulls Avatar answered Nov 04 '22 09:11

MikeKulls


If all you need to do is sync (share) column sizing across separate grids, have you checked out the SharedSizeGroup property on the ColumnDefinition? Sounds like it may make this a bit easier.

SharedSizeGroup Property MSDN

Code sample from MSDN link:

<StackPanel>
<Grid ShowGridLines="True" Margin="0,0,10,0">
  <Grid.ColumnDefinitions>
    <ColumnDefinition SharedSizeGroup="FirstColumn"/>
    <ColumnDefinition SharedSizeGroup="SecondColumn"/>
  </Grid.ColumnDefinitions>
  <Grid.RowDefinitions>
    <RowDefinition Height="Auto" SharedSizeGroup="FirstRow"/>
  </Grid.RowDefinitions>

    <Rectangle Fill="Silver" Grid.Column="0" Grid.Row="0" Width="200" Height="100"/>
    <Rectangle Fill="Blue" Grid.Column="1" Grid.Row="0" Width="150" Height="100"/>

    <TextBlock Grid.Column="0" Grid.Row="0" FontWeight="Bold">First Column</TextBlock>
    <TextBlock Grid.Column="1" Grid.Row="0" FontWeight="Bold">Second Column</TextBlock>
</Grid>

<Grid ShowGridLines="True">
  <Grid.ColumnDefinitions>
    <ColumnDefinition SharedSizeGroup="FirstColumn"/>
    <ColumnDefinition SharedSizeGroup="SecondColumn"/>
  </Grid.ColumnDefinitions>
  <Grid.RowDefinitions>        
    <RowDefinition Height="Auto" SharedSizeGroup="FirstRow"/>
  </Grid.RowDefinitions>

    <Rectangle Fill="Silver" Grid.Column="0" Grid.Row="0"/>
    <Rectangle Fill="Blue" Grid.Column="1" Grid.Row="0"/>

    <TextBlock Grid.Column="0" Grid.Row="0" FontWeight="Bold">First Column</TextBlock>
    <TextBlock Grid.Column="1" Grid.Row="0" FontWeight="Bold">Second Column</TextBlock>
</Grid>
</StackPanel>
like image 1
TheZenker Avatar answered Nov 04 '22 10:11

TheZenker