tags:

views:

148

answers:

2

I'm writing an application in WPF using the MVVM pattern. In my application I've got an IPopupWindowService which I use to create a popup dialog window.

So to show a ViewModel in a popup window you'd do something like this:

var container = ServiceLocator.Current.GetInstance<IUnityContainer>();
var popupService = container.Resolve<IPopupWindowService>();
var myViewModel = container.Resolve<IMyViewModel>();
popupService.Show((ViewModelBase)myViewModel);

This is all well and good. What I want to do is be able to set the MinHeight and MinWidth on the View bound to myViewModel and have the popup window use those settings so that a user cannot make the window smaller than its contents will allow. At the moment when the user shrinks the window the contents stops resizing but the window doesn't.

EDIT:

I map my Views to my ViewModels in ResourceDictionarys like so:

<DataTemplate DataType="{x:Type ViewModels:MyViewModel}">
     <Views:MyView />
</DataTemplate>

And my popup view looks like this:

<Window x:Class="TheCompany.Cubit.Shell.Views.PopupWindowView"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        SizeToContent="WidthAndHeight"
        WindowStartupLocation="CenterOwner">
<DockPanel x:Name="panelContent">
    <StackPanel HorizontalAlignment="Right" DockPanel.Dock="Bottom" Orientation="Horizontal" Visibility="{Binding RelativeSource={RelativeSource AncestorType=Window},Path=ButtonPanelVisibility}">
        <Button Width="75" IsDefault="True" x:Uid="ViewDialog_AcceptButton" Click="OnAcceptButtonClick" Margin="4">OK</Button>
        <Button Width="75" IsCancel="True" x:Uid="ViewDialog_CancelButton" Click="OnCancelButtonClick" Margin="0,4,4,4">Cancel</Button>
    </StackPanel>
    <ContentPresenter Content="{Binding}" />
</DockPanel>

+2  A: 

You can define MinHeight and MinWidth properties on your ViewModel and use databinding to bind the View to those properties in XAML:

<...
    MinHeight="{Binding Path=MinHeight}"
    MinWidth="{Binding Path=MinWidth}"
.../>
Mark Seemann
I thought about that option, but I was hoping to keep that sort of information out of my ViewModel. It may be the only way though...
Jon Mitchell
I agree with Mark then. Jon, if you don't want to put this information in the view model, alternatively you can use Settings class, and bind this particular properties to it...
Anvaka
Why would you want to keep that out of your ViewModel? A ViewModel is a class that models the *View*.
Mark Seemann
The reason why I'm not keen on putting the size in the ViewModel is because its view dependent. I might want to completely change the layout of the form and therefore change minimum/maximum size I want it to be. This would mean that I would have to change the ViewModel to accommodate the change in view. I might even create a SilverLight specific version with a different layout that therefore different sizes. Hopefully you can see where I'm coming from.
Jon Mitchell
You wouldn't have to change the ViewModel, only the values of those properties. This is excactly one of the benefits of ViewModels: that you can put such logic into a POCO class where you can implement as simple or complex logic as you need.
Mark Seemann
Okay, I'm going to go with your suggestion and add properties onto my ViewModelBase class for this.However I'm still not sure how I can have two different Views bound to two the same ViewModel and not have the View declare what size it wants to be. I don't want to put logic in my ViewModel that know about the types of Views it might have bound to it.
Jon Mitchell
Now I'm confused. I thought that you wanted to base this decision upon the type of ViewModel. If you don't want the ViewModel to control these, the only alternative I can think of are the Views themselves - does it make sense to simply set them in XAML for each View?
Mark Seemann
The way I see it is that a designer can create one or more Views based off a single ViewModel and set what they deem to be the minimum size for the Views to still be displayed correctly. I have a PopupWindowView that I want to 'inherit' the MinWidth/MinHeight properies of the content it is displaying. So when the window appears the user cannot shrink it to be less than the designer specified.
Jon Mitchell
That sounds a lot to me like you would want the values encoded in XAML, then?
Mark Seemann
Yeah, don't know how though.
Jon Mitchell
Let me see if I understand this: Your data templates map ViewModels to Views, and the implementer of the View (UserControl) has assigned some value for MinWidth and MinHeight, and now you want to reuse these values for the hosting window? If this is the case, you might be able to databind using RelativeSource...
Mark Seemann
I'll give it a go. Thanks!!
Jon Mitchell
+1  A: 

I designed exactly the same generic modal dialog control (using Type-targeted DataTemplates) and also stumbled into this problem.

  • Using a RelativeSource does not work because you can only find ancestors that way (afaik).

  • Another possible solution was to name the ContentPresenter and bind to properties on that using ElementName binding. However, the ContentPresenter does not "inherit" the MinHeight and MinWidth properties from the Visual child it renders.

The solution eventually was to use VisualTreeHelper to get at the resolved View associated with the ViewModel at runtime:

        DependencyObject lObj = VisualTreeHelper.GetChild(this.WindowContent, 0);
        if (lObj != null && lObj is FrameworkElement)
        {
            lWindowContentMinHeight = ((FrameworkElement)lObj).MinHeight;
            lWindowContentMinWidth = ((FrameworkElement)lObj).MinWidth;
        }

I put this code in a OnActivated() override in the code-behind of the ModalDialogView (in OnInitialized the View can't be resolved yet).

The only remaining issue is to correct these minimums so that the window width and button panel height is taken into account.

UPDATE

Below is the code I use in my generic modal dialog. It solves the following additional problems:

  • It centers on the owner window correctly
  • It doesn't do anything if the content doesn't have MinWidth and MinHeight set

    private bool _MinSizeSet = false;
    
    
    public ModalDialogView(object pDataContext)
        : this()
    {
        this.DataContext = pDataContext;
        this.LayoutUpdated += new EventHandler(ModalDialogView_LayoutUpdated);
    }
    
    
    void ModalDialogView_LayoutUpdated(object sender, EventArgs e)
    {
        if (System.Windows.Media.VisualTreeHelper.GetChildrenCount(this.WindowContent) > 0)
            SetInitialAndMinimumSize();
    }
    
    
    private void SetInitialAndMinimumSize()
    {
        FrameworkElement lElement = VisualTreeHelper.GetChild(this.WindowContent, 0) as FrameworkElement;
        if (!_MinSizeSet && lElement != null)
        {
            if (lElement.MinWidth != 0 && lElement.MinHeight != 0)
            {
                double lHeightDiff = this.ActualHeight - this.WindowContent.ActualHeight;
                double lWidthDiff = this.ActualWidth - this.WindowContent.ActualWidth;
                this.MinHeight = lElement.MinHeight + lHeightDiff;
                this.MinWidth = lElement.MinWidth + lWidthDiff;
                this.SizeToContent = SizeToContent.Manual;
                this.Height = this.MinHeight;
                this.Width = this.MinWidth;
                this.Left = this.Owner.Left + (this.Owner.ActualWidth - this.ActualWidth) / 2;
                this.Top = this.Owner.Top + (this.Owner.ActualHeight - this.ActualHeight) / 2;
            }
            _MinSizeSet = true;
        }
    }
    
Andre Luus
I wanted to comment that I also feel that these View specific properties (MinHeight and MinWidth) don't belong in the ViewModel.
Andre Luus