WPF: Making child elements' MinWidth/MinHeight constrain the Window - wpf

I have a WPF Window that contains a UserControl with a MinWidth and MinHeight. How can I prevent the user from resizing the Window down to a point where that UserControl's minimum size is violated?
Here's a simplified version of a Window I'm working on. My real app's UserControl is replaced here by a Border:
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<DockPanel>
<StackPanel DockPanel.Dock="Bottom" Orientation="Horizontal">
<Button Content="OK"/>
<Button Content="Cancel"/>
</StackPanel>
<Border BorderBrush="Green" BorderThickness="10"
MinWidth="200" MinHeight="150"/>
</DockPanel>
</Window>
If I shrink the window small enough, the Border's right and bottom edges are cut off. I want to prevent the window from getting that small -- I want my window's minimum size to be exactly the point at which the Border is at its minimum size. Some frameworks (like the Delphi VCL) automatically aggregate child controls' minimum sizes up to the window; I expected WPF to do the same, but clearly it does not.
I could always explicitly set the Window's MinWidth and MinHeight, but to calculate those correctly, I would have to factor in the Buttons' ActualHeight, which would mean waiting for at least one layout pass (or calling Measure manually). Messy.
Is there any better way to keep the Window from resizing too small for its content?

The simplest way I've found is to tell the Window to size to its content:
<Window ... SizeToContent="WidthAndHeight" ...>
and then, once it's done sizing (which will take the child elements' MinWidth and MinHeight into account), run some code that sets MinWidth and MinHeight to the window's ActualWidth and ActualHeight. It's also a good idea to turn SizeToContent back off at this point, lest the window resize when its content changes.
The next question is, where to put this code? I finally settled on OnSourceInitialized:
protected override void OnSourceInitialized(EventArgs e)
{
base.OnSourceInitialized(e);
MinWidth = ActualWidth;
MinHeight = ActualHeight;
ClearValue(SizeToContentProperty);
}
I also tried the Loaded event, but in my case, that was too soon -- Loaded happens before databindings have been evaluated, and I had databindings that affected my layout (a Label with a binding for its Content -- its size changed after the binding took effect). Moving the code into OnSourceInitialized, which fires after databinding, corrected the problem.
(There were also other events that fired after binding, but before the window was shown -- SizeChanged and LayoutUpdated -- but they both fire multiple times as the window is shown, and again later if the user resizes the window; OnSourceInitialized only fires once, which I consider ideal.)

Have you tried using an ElementName style binding from the Window to the UserControl at hand? It seems like it would be feasible to bind the Window's MinWidth/Height to those of the Button. Still not pretty, but it shouldn't require extra passes once the binding is in place and will adapt if you change the values on the Button.

Related

WPF what determines whether scrollviewer and stackpanel give infinite size to a child as opposed to a set size?

During Measure, I'm still really confused because this results in stackpanel's child (signalgraph) being given (292,Infinity) as the availableSize in Measure:
<Window x:Class="paneltesting.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:wpfExp="clr-namespace:WpfExperimental"
Title="Window1" Height="300" Width="300">
<StackPanel
>
<wpfExp:SignalGraph/>
</StackPanel>
</Window>
While with a canvas of a set size the child is given (infinity, infinity) for its available size. I assume that this has something to do with an interaction with the window, but I'm really confused as to what is going on.
<Canvas Width="100" Height="100">
<StackPanel SizeChanged="ScrollViewer_SizeChanged"
>
<wpfExp:SignalGraph/>
</StackPanel>
</Canvas>
The same thing occurs when a scrollviewer is used. I just wanted to be able to understand how scrollviewer gives sizes to its children. I was wondering if changing the panels in its controltemplate might result in a scrollviewer that just passes the scrollviewer size as the availableSize to its child instead of infinite space.
I've already coded a work around, but I just want to understand this, so that I'm certain that there wasn't some much easier way to get the behavior I wanted.
StackPanel measures its children with infinity in the direction of its Orientation, so an infinite height when the Orientation is Vertical; the other orientation is usually given the constraint it got.
Canvas always measures its children with an infinite width and height.
For a ScrollViewer, if the Content implements IScrollInfo (i.e. it provides the scrolling functionality) and its CanContentScroll is true then the child is measured based on the constraint it is given, possibly minus the amount needed for the scrollbar(s) if they are shown. Of course then it is responsible for handling the scrolling.

AdornerDecorator - what does it matter where they are placed?

With some xaml like this:
<Grid Name="grid">
<AdornerDecorator>
<TextBox Height="23" HorizontalAlignment="Left" Name="textBox1" Width="120" />
</AdornerDecorator>
</Grid>
The WPF Snoop utility indicates textBox1 is a child of AdornerDecorator (as you would expect) but also that the AdornerLayer that AdornerDecorator creates is also a child. As a custom adorner added to the AdornerLayer can be displayed 'outside' the textbox, the AdornerLayer's drawing surface must stretch outside too (presumably all over the window).
So, what real significance does the placement of AdornerDecorator have (given we bind a UI element to the custom adorner, which we place in the AdornerLayer)? I know AdornerLayer.GetAdorner(textBox1) will get the first adorner layer in the visual tree up from textbox1, but what does it matter where that is (as the custom ardorner gets added to the layer and the custom ardoner knows which element it is bound to)?
The short answer is that it matters when you start to deal with controls that overlap other controls (in the z-index plane) and you care about whether or not a particular adorner layer shows on top of the overlapping controls. For example, when you use an ErrorTemplate, its contents get rendered in an adorner layer and if you don't supply an <AdornerDecorator> in your app (meaning that you just use the one provided by most Window templates), then you can end up with this happening.
By placing the <AdornerDecorator> where we want, we can control how this overlapping behaves.

WPF ListBox scrolls to top when I change status message or show wait screen

I'm developing an opensource application named Media Assistant. I used a ListBox to show the library. ItemsSource is bound to a list of LibraryItem. Here is the XALM.
<ListBox Name="Tree" DockPanel.Dock="Top"
ItemsSource="{Binding DataSource.OrderedLibraryItems}"
Background="{StaticResource LibraryBackground}"
Width="220" HorizontalAlignment="Left"
BorderThickness="0"
VirtualizingStackPanel.IsVirtualizing="True"
VirtualizingStackPanel.VirtualizationMode="Standard"
ScrollViewer.IsDeferredScrollingEnabled="True"
ItemTemplate="{StaticResource ListLibraryItemTemplate}"
SelectionMode="Single"
MouseDoubleClick="HandleMouseDoubleClick"
/>
The problem is when I show any status message at the bottom of my window from a thread by using Dispatcher.
Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Background,new ParameterizedThreadStart(action), state);
The ListBox scrolls to at the top. If I don't show any status message then it works just fine. The datacontext or list items or focus has not been changed. I could not found any reason why it's doing that. It happens when I display any wait screen which is a non modal window. I could not recreate it in a different project. Here is the source code of Media Assistant.
You can easily re-create it by un-commenting the return statement of method SetStatusMessage at BackgroundScanner class.
I found the reason behind this, so the solution.
I used a DockPanel to layout my UI. I put my status bar at the bottom, the ListBox on the Left and other items are on middle and top. There is a TextBlock in my StatusBar which has width and Height set to Auto. So, when I changed text of my StatusBar TextBlock it's width and height gets recalculated and It's parent's recalculates it's layout. Hence the ListBox gets invoked to Measures and Arrange. Even though it's size does not gets changed it resets it's scroll position to top. It happens only if I use ScrollViewer.CanContentScroll="True" at the ListBox. By default it is True. So, even though I did not set this value It was resetting the scroll position. If I disable it by using ScrollViewer.CanContentScroll="False" then it works fine.
<ListBox Name="Tree" DockPanel.Dock="Top"
ItemsSource="{Binding DataSource.OrderedLibraryItems}"
Background="{StaticResource LibraryBackground}"
Width="220" HorizontalAlignment="Left"
BorderThickness="0"
VirtualizingStackPanel.IsVirtualizing="True"
VirtualizingStackPanel.VirtualizationMode="Standard"
ScrollViewer.IsDeferredScrollingEnabled="True"
ScrollViewer.CanContentScroll="False"
ItemTemplate="{StaticResource ListLibraryItemTemplate}"
SelectionMode="Single"
MouseDoubleClick="HandleMouseDoubleClick"
/>
But setting ScrollViewer.CanContentScroll="False" disables virtualization and I want to use virtualization to my ListBox so I set fixed Height and Width to the TextBlock. So, the DockPanel does not re-arrange it's children if I change the status message.
May be it's a bug at ScrollViewer. It should not change the scroll position if the size has not changed.
In reply to #user904627's answer, here is an enhanced version of his workaround. The issue with just fixing Width and Height is the ListBox keeps the same position even if the user resizes the Window. This is not acceptable.
This is why I created this tiny behavior which fixes Width and Height but listens to the parent element's SizeChanged event to let the ListBox resize itself when the container's size changes.
The code is here: VirtualizedListBoxFixBehavior
When the parent element is resized, I restore Width and Height to double.NaN (so the control can resize itself) and I queue the bit of code which fixes the size properties to actual values in the Dispatcher for later execution.
But this still is an ugly working workaround...
Try this:
listBox1.ScrollIntoView(listBox1.Items.GetItemAt(listBox1.Items.Count - 1));
Since the layout is being reset, it is expected that the list box to select the first item (0).
Can you try to set the selected item to the number of existing items in the list box:
Tree.SelectedIndex = Tree.Items.Count;
I did not test this solution on your code but I have used it in another project of mine where I had a similar problem.
Hope it helps.

How to retrieve Canvas dimensions in the viewmodel

I've got a Canvas element in a resizable window; on this canvas are a number of Image and ArcElements that are connected together. I'm trying to get the position of the images to adjust relatively in response to a resize of the window/canvas, but for some reason I can't read the dimensions of the canvas.
The main window is defined as:
<Page>
<DockPanel LastChildFill="True">
<TextBox DockPanel.Dock="Top">Message</TextBox>
<Canvas></Canvas>
</DockPanel>
</Page>
I've hooked up MvvMLight's EventToCommand so that I can route the Canvas's LayoutUpdated or
SizeChanged events to my viewmodel; I tried databinding the Canvas's Width and Height properties, but the dimensions always came out as zero, which meant that all the images on the canvas would appear dead centre rather than positioned as desired.
It turns out I was heading in the right direction by using MvvmLight's EventToCommand; there is an attribute PassEventArgsToCommand that when set to True sends the event args to the appropriate RelayCommand in the viewmodel. So in the viewmodel, I initialised the command thusly:
Commands.ResizeCommand = new RelayCommand<SizeChangedEventArgs>(action => RecalculateObjectPositions(action));
and in the RecalculateObjectPositions method, I can access e.NewSize to find the new size of the canvas.

Problem with ContentControl resizing

I've been hitting my head against a wall with this one for a while now.
I've got a custom control that derives from ContentControl - it's working perfectly EXCEPT that it won't resize to fit its parent, either declaratively OR programatically.
The custom control is parented within (ultimately) a content presenter, and that's correctly sized, but my control just will NOT size itself out to fit.
(sample.MyControl) - 400x267
- (System.Windows.Controls.ContentPresenter) - 979x569
Even when I explicitly set the width and height (at the right moment), the size doesn't "stick"
Debug.WriteLine(string.Format("MyControl {0}: Size is {1}x{2} ({3}/{4})",
GetHashCode(), this.ActualWidth, this.ActualHeight,
this.HorizontalAlignment, this.VerticalAlignment));
Debug.WriteLine(string.Format("Parent is {0}x{1} ({2}/{3})",
parent.ActualWidth, parent.ActualHeight,
parent.HorizontalAlignment, parent.VerticalAlignment));
this.Width = parent.ActualWidth;
this.Height = parent.ActualHeight;
Debug.WriteLine(string.Format("MyControl {0}: New size is {1}x{2}",
GetHashCode(), this.ActualWidth, this.ActualHeight));
The above code gives:
MyControl 36022757: Size is 400x267 (Stretch/Stretch)
Parent is 979x569 (Stretch/Stretch)
MyControl 36022757: New size is 400x267
Why! Oh Gods Why!
Anyone got any ideas?
You need just to add HorizontalContentAlignment="Stretch" and VerticalContentAlignment="Stretch" to your control:
<ContentControl HorizontalContentAlignment="Stretch" VerticalContentAlignment="Stretch">
...
</ContentControl>
In such cases Silverlight Spy tool can be helpful for debugging layout in runtime.
Try to run your application in this tool and you will be able to view all xaml elements and even change some properties in runtime.

Resources