views:

76

answers:

2

Hi all,

I'm working on Expression Blend and I'm currently designing a custom control which has a Grid with 5 rows inside, and also has two Dependency properties: "Value", and "Maximum". Three of the rows have fixed height, and what I'm trying to do is set the remaining rows height to "Value/Maximum" and "1-Value/Maximum" respectively. How do I go and do that?

When I set the height to "Value" it seems to react, but when I go and set it to "Value/Maximum" it stops working. I'm still a bit new around WPF, so there must be another way to achieve what I'm intending, but after searching I couln't find my problem elsewhere.

Code:

<Grid x:Name="LayoutRoot" Width="Auto" Background="Transparent">
    <Grid.RowDefinitions>
        <RowDefinition Height="32"/>
        <RowDefinition Height="{Binding Path=(Value/Maximum), ElementName=UserControl, Mode=Default}"/>
        <RowDefinition Height="16"/>
        <RowDefinition Height="{Binding Path=(1-Value/Maximum), ElementName=UserControl, Mode=Default}"/>
        <RowDefinition Height="32"/>
    </Grid.RowDefinitions>
    (...)

By the way, Value is always a not negative double less than or equal to Maximum; so the result of the division will be number between 0.0 a 1.0. I want a "star" instead of "pixel" row height.

+1  A: 

You need a MultiValueConverter. I'm not sure I understand what you're doing, but essentially, for the XAML:

<RowDefinition>
    <RowDefinition.Height>
         <MultiBinding Converter={StaticResource ...}>
             <Binding ElementName=UserControl, Path=Value ... />
             <Binding ElementName=UserControl, Path=Maximum ... />
         </MultiBinding>
    </RowDefinition.Height>
</RowDefinition>

Then in code, you need to declare a class that implements IMultiValueConverter, and in its Convert method, values will contain the list of things that the various normal Bindings returned (0 is the top one (Value here), 1 is the next one down, etc). Finally, you just need to add a StaticResource of your new class to the XAML:

<Grid.Resources>
    <localxmlns:YourNewMultiValueConverter x:Key="Whatever" />
</Grid.Resources>

That should permit you to do what you want.

typing out xaml without intellisense really sucks

JustABill
Thanks,that's what I was looking for!
Pablo
Good answer. One note: It is more efficient to use the singleton pattern for such converers: In your MultiValueConverter create a public static property "Instance" as follows: `public YourNewMultiValueConverter Instance = new YourNewMultiValueConverter();` then in your XAML just reference it using `<MultiBinding Converter="{x:Static localxmns:YourNewMultiValueConverter.Instance}" ...>`. This executes more efficiently and also doesn't require a separate resource in a ResourceDictionary.
Ray Burns
FYI, I just added an answer that explains my comment about the singleton pattern in more detail and also shows a clever way to use star sizing to achieve the goal.
Ray Burns
+1  A: 

Here is a MultiValueConverter that you can use with JustABill's answer:

public class RatioStarSizing : IMultiValueConverter
{
  public static readonly RatioStarSizing Instance = new RatioStarSizing();

  public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
  {
    return new GridLength((double)values[0] / (double)values[1], GridUnitType.Star);
  }
  public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
  {
    throw new NotImplementedException();
  }
}

This will allow you to work with this XAML:

<Grid x:Name="LayoutRoot" Width="Auto" Background="Transparent">    
  <Grid.RowDefinitions>    
    <RowDefinition Height="32"/>    
    <RowDefinition>
      <RowDefinition.Height>
        <MultiBinding Converter="{x:Static RatioStarSizing}">
          <Binding ElementName="UserControl" Path="Value" />
          <Binding ElementName="UserControl" Path="Maximum" />
        </MultiBinding>
      </RowDefinition.Height>
    </RowDefinition> Height="{Binding Path=(Value/Maximum), ElementName=UserControl, Mode=Default}"/>    
    <RowDefinition Height="16"/>    
    <RowDefinition Height="*" />
    <RowDefinition Height="32"/>    
</Grid.RowDefinitions>    
(...)    

Note the clever trick of using star sizing to avoid needing two MultiValueConverters: The first element is set to star of the value/maximum, so if Value is 4 and Maximum is 10, the converter will returh "0.4*" as a size, resulting in the following actual row heights.

  <Grid.RowDefinitions>    
    <RowDefinition Height="32"/>    
    <RowDefinition Height="0.4*"/>
    <RowDefinition Height="16"/>    
    <RowDefinition Height="*" />
    <RowDefinition Height="32"/>    
  </Grid.RowDefinitions>    

As you can see, this will accomplish what you need with a single MultiValueConverter.

As a footnote, I developed a library I plan to open-source soon that allows me to write the whole thing this way with no converter:

  <Grid.RowDefinitions>    
    <RowDefinition Height="32"/>    
    <RowDefinition Height="{edf:ExpressionBinding
                             new GridLength(Value/Maximum, GridUnitType.Star)"/>
    <RowDefinition Height="16"/>    
    <RowDefinition Height="*" />
    <RowDefinition Height="32"/>    
  </Grid.RowDefinitions>    

I'll add a comment here once I release this library for public use.

Ray Burns