Background


During recent works, I was using the DialogHost control of MaterialDesignTheme to show a dialog instead of opening a window by myself.

The DialogHost use Popup to show the content of it.

My app is set to fullscreen and I wish the dialog will be just a little bit smaller than the screen. Due to the DialogHost has DialogMargin property to limit the dialog content, I just binding the Width and the Height of the dialog content like this

<md:DialogHost.DialogContent>
    <ControlXXX
        Width="{Binding ActualWidth, RelativeSource={RelativeSource FindAncestor, AncestorType=Border}}"
        Height="{Binding ActualHeight, RelativeSource={RelativeSource FindAncestor, AncestorType=Border}}" />
</md:DialogHost.DialogContent>

The ancestor Border has the same size of the screen, everything looks well.

But when run the app and open the dialog, the height of it was smaller than expected, my screen size is 2560X1600 and the height of the dialog was always <=1200 no matter what value I set. It was annoying that I spent a lot of time debugging wpf source code......

Why


Let's see part of the source code of Popup.cs in WPF repository

...
private const double RestrictPercentage = 0.75; // This is how much the max dimensions will be reduced by
...
// Limit size to 75% of maxDimension's area and restrict to be smaller than limitDimension
internal Size RestrictSize(Size desiredSize)
{
    // Make sure screen bounds and limit dimensions are up to date
    Rect targetBounds, screenBounds;
    Size limitSize;
    GetPopupRootLimits(out targetBounds, out screenBounds, out limitSize);

    // Convert from popup's space to screen space
    desiredSize = (Size)_secHelper.GetTransformToDevice().Transform((Point)desiredSize);

    desiredSize.Width = Math.Min(desiredSize.Width, screenBounds.Width);
    desiredSize.Width = Math.Min(desiredSize.Width, limitSize.Width);

    double maxHeight = RestrictPercentage * screenBounds.Width * screenBounds.Height / desiredSize.Width;

    desiredSize.Height = Math.Min(desiredSize.Height, screenBounds.Height);
    desiredSize.Height = Math.Min(desiredSize.Height, maxHeight);
    desiredSize.Height = Math.Min(desiredSize.Height, limitSize.Height);

    // Convert back from screen space to popup's space
    desiredSize = (Size)_secHelper.GetTransformFromDevice().Transform((Point)desiredSize);

    return desiredSize;
}

We can see that, no matter how much available area there are, the size of popup will be limit to 75% of available area, and the limit is set on height.

And we can not change the RestrictPercentage......

Solution


The restriction happens on Measure and was set to PopupRoot which is the child of Popup and really host the popup content.

What I did was set the Height of PopupRoot to expected value and then set the Height and Width of the ControlXXX as same as the ContentPresenter of it after loaded.

private async void ControlXXX_OnLoaded(object sender, RoutedEventArgs e)
{
    var popupRoot = this.FindVisualAncestor(o =>
        o.GetType().ToString() == "System.Windows.Controls.Primitives.PopupRoot");
    if (popupRoot != null)
    {
        //_inDialog = true;
        (popupRoot as FrameworkElement).Height = Application.Current.MainWindow.Height;

        await Task.Yield();
        var presenter = this.FindVisualAncestorByType<ContentPresenter>();
        this.Height = presenter.ActualHeight;
        this.Width = presenter.ActualWidth;
    }
}