0

I've ran into a rather strange issue. It seems that as long as a window's SizeToContent is set to Manual and the dimensions and top/left properties are databound, the Presentation Framework won't update the location of the window when it is shown.

I have a Dialog Window-control which I use to display other UserControls within, and the behavior should be controlled by a ViewModel. Some dialogs I wish to display with SizeToContent.WidthAndHeight and ResizeMode.NoResize, and center them on the Owner-window. Other controls I wish to show in a dialog that can be resized, but opened with a specific size.

I made a test-project just to isolate the issue. Here is my Dialog:

<Window x:Class="DialogTests.Dialog"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:local="clr-namespace:DialogTests"
    mc:Ignorable="d"

    ResizeMode="CanResize"
    SizeToContent="WidthAndHeight"
    WindowStartupLocation="CenterOwner"
    Width="{Binding Path=FormWidth, Mode=TwoWay}"
    Height="{Binding Path=FormHeight, Mode=TwoWay}"
    Top="{Binding Path=FormTop, Mode=TwoWay}"
    Left="{Binding Path=FormLeft, Mode=TwoWay}"
    Title="Dialog">

    <StackPanel>
        <Button Height="100">Button 1</Button>
        <Button Width="100">Button 2</Button>
    </StackPanel>
</Window>



public partial class Dialog : Window
{
    public Dialog()
    {
        InitializeComponent();
        this.DataContext = new DialogVm();
    }
}

public class DialogVm : INotifyPropertyChanged
{
    private double _formWidth;
    private double _formHeight;
    private double _formTop;
    private double _formLeft;

    public DialogVm()
    {
        FormWidth = 400;
        FormHeight = 400;
    }

    public double FormWidth
    {
        get { return _formWidth; }
        set
        {
            if (value.Equals(_formWidth)) return;
            _formWidth = value;
            OnPropertyChanged();
        }
    }

    public double FormHeight
    {
        get { return _formHeight; }
        set
        {
            if (value.Equals(_formHeight)) return;
            _formHeight = value;
            OnPropertyChanged();
        }
    }

    public double FormTop
    {
        get { return _formTop; }
        set
        {
            if (value.Equals(_formTop)) return;
            _formTop = value;
            OnPropertyChanged();
        }
    }

    public double FormLeft
    {
        get { return _formLeft; }
        set
        {
            if (value.Equals(_formLeft)) return;
            _formLeft = value;
            OnPropertyChanged();
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;

    [NotifyPropertyChangedInvocator]
    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

The reason for having the Dialog databind to these properties is to save the state (dimensions and position), so that it can be used in cases where certain dialogs should be opened using these previous values.

In my main application I just open the dialog by pressing a button:

private void Button_Click(object sender, RoutedEventArgs e)
{
    var dialog = new Dialog();
    dialog.Owner = this;
    dialog.ShowDialog();
}

If you run this, then everything works as expected. The dialog gets sized to fit the contents, and is placed in the center relative to the main application. The setters of FormWidth, FormHeight, FormTop and FormLeft get called. For the dimensions, it's first called from the constructor, then by the PresentationFramework.

However, if you go back to the Dialog xaml, and change SizeToContent="WidthAndHeight" to SizeToContent="Manual", the Dialog will be shown at the top left (0,0) of the screen. The dialog's dimensions are set in the view model contructor (400 x 400) and doesn't get called again (as expected). The issue is, the Setter methods of FormTop and FormLeft are never called. The expected behaviour is that the 400x400 window should be shown in the center of the Owner window.

If I change the Dialog.xaml to remove the databindings, and explicitly set the dimensions, it works as it should though:

<Window x:Class="DialogTests.Dialog"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:local="clr-namespace:DialogTests"
    mc:Ignorable="d"

    ResizeMode="CanResize"
    SizeToContent="Manual"
    WindowStartupLocation="CenterOwner"
    Width="400"
    Height="400"
    Title="Dialog">
...
</Dialog>

Is this behavior a bug? Or is there something I am missing? Seems so strange that it works fine as long as SizeToContent is set to WidthAndHeight, or if I remove the bindings entirely and set the attributes in the xaml.

Walkingsteak
  • 329
  • 4
  • 16

1 Answers1

0

It depends on when the bindings are resolved and when the position of the window is set.

You could set the DataContext of the Dialog window before you call the InitializeComponent() method:

public Dialog()
{
    this.DataContext = new DialogVm();
    InitializeComponent();
}

Or set the WindowStartupLocation to Manual and calculate the values of your Left and Top source properties:

How do you center your main window in WPF?

Community
  • 1
  • 1
mm8
  • 163,881
  • 10
  • 57
  • 88
  • Switching the order of InitializeComponent and setting DataContext worked for the positioning, but messed up other things that were shown in the dialog, sadly. Guess I'll have to take the route of doing the positioning myself, but hoped I could let WPF handle that. – Walkingsteak Mar 02 '17 at 12:33
  • What "other things" are you referring to? – mm8 Mar 02 '17 at 13:28