How to Set Grid Column MaxWidth depending on Windo

2020-07-10 03:07发布

问题:

I have a 3 column grid in a window with a GridSplitter on the first column. I want to set the MaxWidth of the first column to a third of the parent Window or Page Width (or ActualWidth) and I would prefer to do this in XAML if possible.

This is some sample XAML to play with in XamlPad (or similar) which shows what I'm doing.

<Page xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:sys="clr-namespace:System;assembly=mscorlib" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" >
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition x:Name="Column1" Width="200"/>
            <ColumnDefinition x:Name="Column2" MinWidth="50" />
            <ColumnDefinition x:Name="Column3" Width="{ Binding ElementName=Column1, Path=Width }"/>
            </Grid.ColumnDefinitions>

        <Label Grid.Column="0" Background="Green" />
        <GridSplitter Grid.Column="0" Width="5" />
        <Label Grid.Column="1" Background="Yellow" />
        <Label Grid.Column="2" Background="Red" />
    </Grid>
</Page>

As you can see, the right column width is bound to the width of the first column, so when you slide the left column using the splitter, the right column does the same :) If you slide the left column to the right, eventually it will slide over half the page/window and over to the right side of the window, pushing away column 2 and 3.

I want to prevent this by setting the MaxWidth of column 1 to a third of the window width (or something like that). I can do this in code behind quite easily, but how to do it in "XAML Only"?

EDIT: David Schmitt suggested to use SharedSizeGroup instead of binding, which is an excellent suggestion. My sample code would look like this then:

<Page xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:sys="clr-namespace:System;assembly=mscorlib" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" >
        <Grid IsSharedSizeScope="True">
            <Grid.ColumnDefinitions>
                <ColumnDefinition x:Name="Column1" SharedSizeGroup="ColWidth" Width="40"/>
                <ColumnDefinition x:Name="Column2" MinWidth="50" Width="*" />
                <ColumnDefinition x:Name="Column3" SharedSizeGroup="ColWidth"/>
            </Grid.ColumnDefinitions>
            <Label Grid.Column="0" Background="Green" />
            <GridSplitter Grid.Column="0" Width="5" />
            <Label Grid.Column="1" Background="Yellow" />
            <Label Grid.Column="2" Background="Red" />
        </Grid>
</Page>

回答1:

I think the XAML-only approach is somewhat circuitous, but here is a way to do it.

<Page xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
      xmlns:sys="clr-namespace:System;assembly=mscorlib"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" >

    <!-- This contains our real grid, and a reference grid for binding the layout-->
    <Grid x:Name="Container">

      <!-- hidden because it's behind the grid below -->
      <Grid x:Name="LayoutReference">
        <Grid.ColumnDefinitions>
          <ColumnDefinition Width="*"/>
          <ColumnDefinition Width="*"/>
          <ColumnDefinition Width="*"/>
        </Grid.ColumnDefinitions>
        <!-- We need the border, because the column doesn't have an ActualWidth -->
        <Border x:Name="ReferenceBorder" 
                Background="Black" />
        <Border Background="White" Grid.Column="1" />
        <Border Background="Black" Grid.Column="2" />
      </Grid>

      <!-- I made this transparent, so we can see the reference -->
      <Grid Opacity="0.9">
          <Grid.ColumnDefinitions>
              <ColumnDefinition x:Name="Column1" 
                                MaxWidth="{Binding ElementName=ReferenceBorder,Path=ActualWidth}"/>
              <ColumnDefinition x:Name="Column2" 
                                MinWidth="50"  />
              <ColumnDefinition x:Name="Column3" 
                                Width="{ Binding ElementName=Column1, Path=Width }"/>
              </Grid.ColumnDefinitions>

          <Label Grid.Column="0" Background="Green"/>
          <GridSplitter Grid.Column="0" Width="5" />
          <Label Grid.Column="1" Background="Yellow" />
          <Label Grid.Column="2" Background="Red" />
      </Grid>
    </Grid>

</Page>


回答2:

Too lazy to actually write it up myself, but you should be able to use a mathematical converter and bind to your parent windows width (either by name, or with a RelativeSource ancestor search).

//I know I borrowed this from someone, sorry I forgot to add a comment from whom
public class ScaledValueConverter : IValueConverter
{
  public Object Convert(Object value, Type targetType, Object parameter, System.Globalization.CultureInfo culture)
  {
     Double scalingFactor = 0;
     if (parameter != null)
     {
        Double.TryParse((String)(parameter), out scalingFactor);
     }

     if (scalingFactor == 0.0d)
     {
        return Double.NaN;
     }

     return (Double)value * scalingFactor;
  }

  public Object ConvertBack(Object value, Type targetType, Object parameter, System.Globalization.CultureInfo culture)
  {
     throw new Exception("The method or operation is not implemented.");
  }
}