Restrict the Width of a ContentPresenter's to its Dynamic Content - wpf

I would like to be able to dynamically adjust the size of a content control.
Here's a simple example:
...
<Slider x:Name="width" Minimum="40" Value="100" Maximum="300"/>
...
<ContentPresenter Width="{Binding Value, ElementName=width}" Content="Some value">
<ContentPresenter.ContentTemplate>
<DataTemplate>
<Grid MaxWidth="200" MinWidth="80">
<Rectangle Fill="Wheat" />
<TextBlock Text="{Binding}"/>
</Grid>
</DataTemplate>
</ContentPresenter.ContentTemplate>
</ContentPresenter>
Given this example, I would like to force the width of the content ContentPresenter to stay within the min and max width of its generated child (80 - 200 in this case).
Obviously with a simple example like this I could just change the range of the slider, but my real scenario is more complicated. I'm trying to restrict the size of a popup screen to its generated content. I can't set an explicit range on the popup because I have no idea what the content is going to be like before hand. The content has to be able to restrict itself.
Unfortunately MaxWidth and MinWidth on children are pretty much ignored. MinWidth results in cropping when the parent is set smaller. MaxWidth results in lots of empty space. It looks like I will have to set MaxWidth and MinWidth in the same place that I am dynamically updating the Width value.

My suggestion would be to replace the ContentPresenter with a custom panel (alternately, enclose a custom panel in the ContentPresenter if you cannot replace it) that doles out the exact size you want in the way that you want. It's fairly easy and simple to declare a class that derives from Panel and then override MeasureOverride and ArrangeOverride. In this case you could measure everything with the base MeasureOverride and then just assign the size you want as your available space. In ArrangeOverride just assign the direct child of your panel, to be the final size of the panel (rect = 0,0,width,height).

Related

Why is my Grid's width NaN?

I have the following mark-up in a view. When I get WindowContainer.Width during start-up code for the view, it returns NaN.
<Border BorderThickness="10">
<StackPanel x:Name="Panel" Orientation="Vertical" >
<Grid x:Name="WindowContainer" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Loaded="WindowContainer_OnLoaded">
<delphi:Win32WindowHost x:Name="Host" />
</Grid>
<TextBlock x:Name="InfoTextBlock" HorizontalAlignment="Right" />
</StackPanel>
</Border>
Does Stretch make the grid stretch to accomodate all its content, or to fill its container? I want it to stretch to fill the container, the Border, and have a proper double width I can use to size a floating window to be the same size.
Does Stretch make the grid stretch to accomodate all its content, or to fill its container?
From MSDN about HorizontalAlignment="Stretch":
Child elements are stretched to fill the parent element's allocated layout space. Explicit Width and Height values take precedence.
Why is my Grid's width NaN?
NaN is to mean "not set". FrameworkElement is the base class for many Controls in WPF and if you do not explicitly set the Height and Width properties then in the class constructor will be a default value of NaN.
When I get WindowContainer.Width during start-up code for the view, it returns NaN
In this case try get the ActualWidth, instead of Width, because:
ActualWidth property is a calculated value based on other width inputs, and the layout system. The value is set by the layout system itself, based on an actual rendering pass, and may therefore lag slightly behind the set value of properties such as Width that are the basis of the input change.

WPF ContentControl width grows but doesn't shrink when wrapped in a ScrollViewer

I'm trying to figure out how to make my ContentControl to correctly scroll Horizontally (Vertically its fine at the moment). By correctly i mean i would like to see the content to stretch (expand infinitely) while having minimum sizes to which a scrollbar would appear in order for the content not to overflow behind the ContentControl's area, so here's a quick introduction:
The main window is structured in this way:
Grid (2 columns of .3* and .7*)
Border
Grid (7 rows, one set to * where ContentControl is)
ScrollViewer with StackPanel (purely for test) wrapping a ContentControl that has Auto Width
ContentControl's Template:
Grid (Width set to UserControl's ActualWidth, 6 rows with one set to Auto where ItemsControl go
ItemsControl that describes an ItemTemplate of a type DataTemplate which contains a Grid inside of which i have a DataGrid
The actual problem is that the ContentControl grows as you resize the window, but does not shrink with window resize.
Main View XAML (truncated for clarity):
<ScrollViewer Grid.Row="5" VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto">
<ContentControl Grid.Row="5" Background="Transparent" Focusable="False" Margin="0,5,0,0"
Content="{Binding CurrentSection}" ContentTemplateSelector="{StaticResource templateSelector}/>
</ScrollViewer>
Tempate XAML (truncated for clarity):
<Grid>
...
<ItemsControl Grid.Row="4" ItemsSource="{Binding Data.QualifyingDistributionsDividends}" x:Name="QualifyingItemsControl">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid x:Name="DTLayoutGrid">
...
<Grid Grid.Row="1" x:Name="DataLayout" Width="{Binding ElementName=DTLayoutGrid, Path=ActualWidth}" HorizontalAlignment="Stretch">
...
<DataGrid Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="8" HorizontalScrollBarVisibility="Disabled"
ItemsSource="{Binding Payments}" Style="{StaticResource DataGridStyle}" CellStyle="{StaticResource DataGridNormalCellStyle}">
</DataGrid>
</Grid>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
So what happens? Datagrid assumes width of the entire DataTemplate (well its underlying controls that are set to be DataTemplates size, then * column assumes all empty space. When you try to resize the entire window that holds this code it will grow correctly, expanding the * column but it seems shrinking is not "registered" and it keeps the size you expanded it to, applies a scrollbar over that and forgets about it.
What i've tried so far was to set widths for ItemsControl, its underlying parents like Grid etc, also setting size to ContentControl, StackPanel, ScrollViewer and parent Grid of that.
I've also tried using scrollviewers directly on the Datagrid which produces an epileptic "1 million resizes a second" scenario. I've also played around with HorizontalAlignments
Under certain situations i DID managed to get the horizontal scrollbar to appear correctly but unfortunately that makes my DataGrid's * column to assume Auto Width rather then Star so DataGrid starts having an empty area to the right (unacceptable unfortunately...)
I understand that in order for horizontal scrollbar to work the parent or child of the scrollviewer needs Width set, i guess i can't work out where exactly do i need to restrict it. DataGrids NEED to infinitely expand with the main window while having first column fill all the available space.
Do let me now if you need more information on this and I will gladly answer.
It seems to me that this is just another case of the dreaded StackPanel layout problem. This problem comes up again and again and I confess that I had the very same problem when I started learning WPF. The StackPanel does not take the available size of its parent into consideration whereas other Panels such as a DockPanel or a Grid (yes, that's actually a Panel too) do.
It's explained in the How to: Choose Between StackPanel and DockPanel page on MSDN:
Although you can use either DockPanel or StackPanel to stack child elements, the two controls do not always produce the same results. For example, the order that you place child elements can affect the size of child elements in a DockPanel but not in a StackPanel. This different behavior occurs because StackPanel measures in the direction of stacking at Double.PositiveInfinity; however, DockPanel measures only the available size.
The StackPanel should only really be used to align a number of items, such as Buttons or other controls in a straight line where available space is not a concern. So anyway, the solution should be simple... just remove the StackPanel from the ScrollViewer. It doesn't appear to serve any purpose there anyway.
UPDATE >>>
After looking again, it seems as though you're saying that the problem is inside the DataTemplate, right? You might be able to fix that by setting the ItemsControl.HorizontalContentAlignment property to Stretch. That would ensure that each item remains within the boundary of the ItemsControl.
I'd also remove your Binding on the Grid.Width as you don't need it... a child Grid will take up the full space of a parent Grid by default. If these ideas don't work, just simplify your problem. Seriously, if you follow the advise in the linked page from the Help Center that I gave you in the comments, then you'll either fix the problem, or be able to come back here and provide a complete, but concise example that we could test.
I've found the behavior I was looking for by using a UniformGrid as the ItemsPanel, with its rows bound to the count of the ItemsSource model:
<ScrollViewer>
<ItemsControl ItemsSource="{Binding MyCollection}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Rows="{Binding MyCollection.Count}" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
...
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
As #Sheridan pointed out above, it seems the StackPanel is causing trouble. Also, credit to https://stackoverflow.com/a/23375262/385273 for pointing out the UniformGrid option.

Control the width of child in Stackpanel

I'm trying to implement a certain layout.
I have two elements that I want to stack vertically (I need them to follow each other closely). I am currently trying to achieve it using a Stackpanel.
The problem is that I want the first element to have a limited width and the other to use all the width available in the StackPanel. Ideally, I would like that the first element have a width equals to the width of four columns from the grid that contains the StackPanel, here is my code.
<Grid>
<!-- Colums and Rows definition go here -->
<StackPanel Grid.Column="0" Grid.ColumnSpan="4" Grid.Row="3" Grid.RowSpan="8">
//The first element
<Viewbox Name="viewbox_choix" Margin="160,0,0,0" HorizontalAlignment="Left" VerticalAlignment="Top" Grid.Column="0" Grid.ColumnSpan="4" Grid.Row="3" Grid.RowSpan="4">
//The second element
<StackPanel Grid.Column="0" Grid.ColumnSpan="5">
<Border></Border>
etc...
</StackPanel>
</StackPanel>
</Grid>
The grid attributes are referring to the parent grid of the stackpanel. But the Grid.Column and Grid.ColumnSpans seem to have no effect when I try to use them inside the StackPanel.
The problem of that code is that the first element also uses all the width of the StackPanel but that isn't what I want...
Can anybody help me ? I precise that I'm still learning WPF and I don't really know how bindings work...
In WPF, a StackPanel does not work like a Grid. There is no maximum width... it will happily let content disappear out of its right side. If you want automatic resizing, just replace the StackPanels with `Grid
UPDATE >>>
In the Grid class, there is an attached property called IsSharedSizeScope. Add this to the parent Grid and set it to true. Then in your RowDefinitions, you can add SharedSizeGroup properties to the columns that you require.
These examples may help you:
Grid's SharedSizeGroup and * sizing (SO post)
Grid.IsSharedSizeScope Attached Property (MSDN)
You may need to experiment a bit, but you should be able to get the desired effect using these properties.

image resizing and controls layout shifting

Can you give an idea on xaml layout such that image is on the right and some content on the right.
And when image becomes larger, so the content shifts to the right?
I could probably put a two columns grid and programmatically enlarge image and making column 0 bigger, but there is must be a proper way to do.
Use a control that doesn't automatically adjust the size of its children based on available space, such as a Horizontal StackPanel
<StackPanel Orientation="Horizontal">
<Image ... />
<ContentControl .... />
</StackPanel>
If I misunderstood you and you actually want the content to resize based on available space, then use a control that automatically adjusts the size of its children based on available space, such as a DockPanel
<DockPanel>
<Image DockPanel.Dock="Right" ... />
<ContentControl .... />
</DockPanel>
When I first started working with WPF, I found this article very useful for figuring out what sort of panels I wanted to use to layout my controls
Do you mean image on the left and additional content on the right? Use width = auto for column 0 and width = * for column 1.

What is the difference between Width and ActualWidth in WPF?

I am currently working with Panels in WPF, and I noticed that as regards the Width and Height properties, there are also two other properties called ActualWidth and ActualHeight.
ActualWidth
Gets the rendered width of this
element. This is a dependency
property. (Inherited from
FrameworkElement.)
Width
Gets or sets the width of the element.
This is a dependency property.
(Inherited from FrameworkElement.)
Reference: MSDN
Can anyone point out the differences between the two and when to use either one ?
Width/Height is the requested or layout size. If you set to Auto, then the value is double.NaN when you access the property in code behind.
ActualWidth/ActualHeight and RenderSize.Width/RenderSize.Height both return the element's rendered size, as RenderSize is of type Size. If you want/need the actual size of the item, then use any of these attributes.
I find ActualWidth most useful when I want to bind the width or height of one element to another.
In this simple example I have two buttons arranged side by side and a comment underneath that is constrained to the width of the StackPanel containing the two buttons.
<StackPanel>
<StackPanel Margin="0,12,0,0" Orientation="Horizontal" Name="buttonPanel" HorizontalAlignment="Left" >
<Button Content="Yes - Arm the missile" FontWeight="Bold" HorizontalAlignment="Left"/>
<Button Content="No - Save the world" HorizontalAlignment="Left" Margin="7,0,0,0"/>
</StackPanel>
<TextBlock Text="Please choose whether you want to arm the missile and kill everybody, or save the world by deactivating the missile."
Width="{Binding Path=ActualWidth,ElementName=buttonPanel}" Margin="0,5,0,0" HorizontalAlignment="Left" TextWrapping="Wrap"/>
</StackPanel>
ActualWidth accounts for padding in the value so anytime you need to know that number you can call Actualwidth instead of width and avoid the calculation.
edit: removed Margin b/c it isn't part of ActualWidth.
ActualWidth is set by the rendering system, and may be different depending on the widths of other elements and overall size constraints. As a result, it can not be changed. Width is a property that can be changed, and should be used to increase or decrease the width of the element.
From MSDN:
This property is a calculated value based on other width inputs, and the layout system. The value is set by the layout system itself, based on an actual rendering pass, and may therefore lag slightly behind the set value of properties such as Width that are the basis of the input change.
There is a very good reason not to use the ActualWidth to bind to (obviously ActualHeight accordingly).
When you set the Width of an element, to the ActualWidth of another one you may break the layout chain.
In the best case your element/control needs to be parsed after the layout process of the parent (the binding source) finished. That means additional time.
If it is at the same hierarchy level as the parent the layout process needs two runs (at least) to calculate a definitive size.
For example I had a control which had it's size property overridden in a style that would set it to the TemplatedParent (don't do):
<Rectangle DockPanel.Dock="Top" Width="{TemplateBinding ActualWidth}"
Height="1" Fill="#000000"/>
When resizing the containing window, the control would prevent the container from becoming smaller and brake the layout. Setting it to the Width will resolve the problem (do):
<Rectangle DockPanel.Dock="Top" Width="{TemplateBinding Width}"
Height="1" Fill="#000000"/>
If you have to use the ActualWidth in general something is wrong with your xaml. Better fix that instead of messing up with the final sizes of the layout run.
It's exactly that, the render width != layout width. One is intended to be used for layout the other one is intended for render. It like with WinForms, there was a Size and a ClientSize property, the differ slightly and you should use the Atual/Client size of rendering and the Width/Height for layout.
You can set the Width property, but not the ActualWidth property.
The Width property is used to determine how the panel is rendered, then the ActualWidth is set to the actual width that was used. This may not be the same value as Width, depending on the size of it's child elements and constrictions from it's parent element.
The ActualWidth is not set immediately when setting the Width property, but will be updated (one or more times) during rendering.

Resources