Negative-margin control get clipped when resizing the window in WPF

1.4k Views Asked by At

I try to understand why a border element get clipped when reducing the width of the main window. Please take a look the code block below.

    <Window x:Class="WpfApplication1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="300" Width="500" Name="MainWin">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition />
            <RowDefinition />
        </Grid.RowDefinitions>

        <Border Background="Blue" Grid.Row="0" BorderBrush="Black" Width="{Binding ElementName=MainWin, Path=Width}" />
        <Grid Grid.Row="1" Margin="0">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="150" />
                <ColumnDefinition Width="150" />
                <ColumnDefinition Width="*" />
            </Grid.ColumnDefinitions>
            <StackPanel Grid.Column="0" Background="Black">
                <Border Background="White" Width="150" Height="150" BorderBrush="Black" BorderThickness="2"
                        Margin="0,-100,0,0">

                    <TextBlock Text="{Binding ElementName=MainWin, Path=Width}" FontSize="14" FontWeight="Bold"
                               HorizontalAlignment="Center"
                               VerticalAlignment="Center" />

                </Border>
            </StackPanel>
            <StackPanel Grid.Column="1" Background="Red" />
            <StackPanel Grid.Column="2" Background="Yellow" />
        </Grid>
    </Grid>
</Window>

Here is what the border appears in the original window width:

Non-resized window

enter image description here

As you can see that the border is displayed outside its container because of the negative top margin, -100 in this case. This is what I expect to have for the border. But when I reduce the main window width to reach the right edge of the red rectangle the outside part of the border get clipped.

Resized window

enter image description here

I have tried to place this border element inside a custom StackPanel which overrides ArrangeOverride, MeasureOverride and GetLayoutClip method but unfortunately these methods are not invoked when the main window is being resized.

I appreciate if somebody can explain me what the reason is and how to work around with this issue. Thanks a lot.

2

There are 2 best solutions below

0
On BEST ANSWER

Based on the explanation of @Marks, here is my solution

  1. Create a custom grid and overrides MeasureOverride method
  2. Replace the inner grid by this custom grid

CustomGrid class

public class CustomGrid : Grid
{
    private double _originalHeight = 0;

    protected override Size MeasureOverride(Size constraint)
    {
        Size? size = null;
        if (constraint.Width <= 300)
        {
            size = new Size(constraint.Width, _originalHeight);
        }
        else
        {
            size = base.MeasureOverride(constraint);
            _originalHeight = constraint.Height;

        }
        return size.Value;
    }

}

XAML code

<Window x:Class="WpfApplication1.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:wpfApplication1="clr-namespace:WpfApplication1"
    Title="MainWindow" Height="300" Width="500" Name="MainWin">
<Grid>
    <Grid.RowDefinitions>
        <RowDefinition />
        <RowDefinition />
    </Grid.RowDefinitions>

    <Border Background="Blue" Grid.Row="0" BorderBrush="Black" Width="{Binding ElementName=MainWin, Path=Width}" />
    <wpfApplication1:CustomGrid Grid.Row="1">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="150" />
            <ColumnDefinition Width="150" />
            <ColumnDefinition Width="*" />
        </Grid.ColumnDefinitions>
        <StackPanel Grid.Column="0" Background="Black">
            <Border Background="White" Width="150" Height="150" BorderBrush="Black" BorderThickness="2"
                    Margin="0,-100,0,0">

                <TextBlock Text="{Binding ElementName=MainWin, Path=Width}" FontSize="14" FontWeight="Bold"
                           HorizontalAlignment="Center"
                           VerticalAlignment="Bottom" />

            </Border>
        </StackPanel>
        <StackPanel Grid.Column="1" Background="Red" />
        <StackPanel Grid.Column="2" Background="Yellow" />
    </wpfApplication1:CustomGrid>
</Grid>

1
On
  1. You're binding the blue border's Width to the MainWindow's Width. For the future: if you want to bind to the width of any FrameworkElement bind to its ActualWidth property.

  2. The order in which WPF draws its stuff is quite dependent on the containing control. I'd say in your case the outer Grid draws its children that need updating in the order they are defined. So you're good to go as long as the inner grid changes along with the border. This is the case as long as the Width of the third column changes. Once it's at 0 there's no more change so it doesn't get updated.

  3. (2) is speculation =)

  4. don't do (1), there's no need for it

  5. Use one Grid

Some XAML:

<Grid>
    <Grid.RowDefinitions>
        <RowDefinition />
        <RowDefinition />
    </Grid.RowDefinitions>        
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="150" />
            <ColumnDefinition Width="150" />
            <ColumnDefinition Width="*" />
        </Grid.ColumnDefinitions>
    <Border Background="Blue" BorderBrush="Black" Grid.ColumnSpan="3"/>
    <StackPanel Grid.Column="0" Grid.Row="1" Background="Black" >
      <Border Background="White" Width="150" Height="150" BorderBrush="Black" BorderThickness="2"  Margin="0,-100,0,0">
        <TextBlock Text="{Binding ElementName=MainWin, Path=ActualWidth}" FontSize="14" FontWeight="Bold"
                           HorizontalAlignment="Center"
                           VerticalAlignment="Center" />
      </Border>
    </StackPanel>
    <StackPanel Grid.Column="1" Grid.Row="1" Background="Red" />
    <StackPanel Grid.Column="2" Grid.Row="1" Background="Yellow" />          
</Grid>