I am using WPF MVVM. I have the following code:
<ScrollViewer VerticalScrollBarVisibility="Auto">
<ListView ItemsSource="{Binding ItemCollection}" Height="160" Width="810">
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Width="500" Height ="150" ItemWidth="100" ItemHeight="30"/>
</ItemsPanelTemplate>
</ListView.ItemsPanel>
<ListView.ItemTemplate>
<DataTemplate>
<CheckBox IsChecked="{Binding Checked}">
<TextBlock Text="{Binding Label}"/>
</CheckBox>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</ScrollViewer>
The code shows that each CheckBox has a width of 100. Each line in the WrapPanel can contain at most 5 CheckBoxes due to its size and the ItemWidth (500 / 100).
I have multiple CheckBox with different widths.
Most of the check boxes have a width <=100
One check box has a width equal to 280
In the future I might have a check box with a width >300 and <=400
I do not want to set the ItemWidth explicitly to 280, because most of the items are smaller. Instead, I want each CheckBox to take up the space of a multiple of 100 that can display all of its content. If the current line in the WrapPanel does not have enough space, then move it to next line.
For the sample widths above I expect this.
Width <= 100: Occupy 1 item space (Width = 100)
Width <= 280: Occupy 3 item spaces (Width = 300)
Width > 300 and <= 400: Occupy 4 item spaces (Width = 400)
How can I achieve that?
The ItemWidth explicitly defines the width for all items.
A Double that represents the uniform width of all items that are contained within the WrapPanel. The default value is NaN.
Do not set an ItemWidth. Then each item occupies its individual size.
A child element of a WrapPanel may have its width property set explicitly. ItemWidth specifies the size of the layout partition that is reserved by the WrapPanel for the child element. As a result, ItemWidth takes precedence over an element's own width.
Now, if you do not explicitly define the widths of your items, they are sized to fit their content, but do not align with a multiple of 100. Scaling items up to a multiple of a defined size automatically is not supported in WrapPanel.
If you want to enable this kind of dynamic sizing, you will have to create a custom wrap panel or you can write a custom behavior. I show you an example of the latter, as it is reusable and more flexible. I use the Microsoft.Xaml.Behaviors.Wpf NuGet package that contains base classes for that.
public class AlignWidthBehavior : Behavior<FrameworkElement>
{
public static readonly DependencyProperty AlignmentProperty = DependencyProperty.Register(
nameof(Alignment), typeof(double), typeof(AlignWidthBehavior), new PropertyMetadata(double.NaN));
public double Alignment
{
get => (double)GetValue(AlignmentProperty);
set => SetValue(AlignmentProperty, value);
}
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.LayoutUpdated += OnLayoutUpdated;
}
protected override void OnDetaching()
{
base.OnDetaching();
AssociatedObject.LayoutUpdated -= OnLayoutUpdated;
}
private void OnLayoutUpdated(object sender, EventArgs e)
{
var size = AssociatedObject.ActualWidth;
var alignment = Alignment;
var isAligned = size % alignment < 10E-12;
if (!double.IsNaN(alignment) && !isAligned)
AssociatedObject.Width = Math.Ceiling(size / alignment) * alignment;
}
}
This behavior is triggered, when the layout of an item changes. It then checks, if the width of the associated item is aligned with the value given by the Alignment property and adapts it if not.
<DataTemplate>
<CheckBox IsChecked="{Binding Checked}">
<b:Interaction.Behaviors>
<local:AlignWidthBehavior Alignment="100"/>
</b:Interaction.Behaviors>
<TextBlock Text="{Binding Label}"/>
</CheckBox>
</DataTemplate>
You have to attach the behavior in XAML as shown above and set the Alignment to your desired value and do not forget to remove the ItemWidth property from WrapPanel.
Related
I'm doing some layouting on a toolbar-like control and need to hide texts of buttons when there's not enough space. I've successfully done this in Windows Forms already and now I've ported this logic to WPF. But there is a huge problem here: For my algorithm to work properly, I need to know the desired width of a container control (to know what size would be required if everything was visible) and the actual width of the control (to know how wide it really is and whether there's enough space for the desired width). The first one is available, albeit a bit backwards at times. (If there's more space available than required, the DesiredSize increases to fill it all out, although less would be fine.) The latter one is entirely unavailable!
I've tried with ActualWidth, but if the Grid is wider than the window, the ActualWidth is more than is actually visible. So this must be wrong already. I've then tried the RenderSize, but it's the same. Using Arrange after my Measure call leads to more weirdness.
I need to know how wide the control really is, and not how wide it believes itself to be. How can I determine that size?
Update: Okay, here's some code. It's already quite long for this question and still incomplete. This is from the Window's code-behind.
private void ToolGrid_LayoutUpdated(object sender, EventArgs e)
{
AutoCollapseItems();
}
private void AutoCollapseItems()
{
if (collapsingItems) return;
if (ToolGrid.ActualWidth < 10) return; // Something is wrong
try
{
collapsingItems = true;
// Collapse toolbar items in their specified priority to save space until all items
// fit in the toolbar. When collapsing, the item's display style is reduced from
// image and text to image-only. This is only applied to items with a specified
// collapse priority.
Dictionary<ICollapsableToolbarItem, int> collapsePriorities = new Dictionary<ICollapsableToolbarItem, int>();
// Restore the display style of all items that have a collpase priority.
var items = new List<ICollapsableToolbarItem>();
EnumCollapsableItems(ToolGrid, items);
foreach (var item in items)
{
if (item.CollapsePriority > 0)
{
item.ContentVisibility = Visibility.Visible;
collapsePriorities[item] = item.CollapsePriority;
}
}
// Group all items by their descending collapse priority and set their display style
// to image-only as long as all items don't fit in the toolbar.
var itemGroups = from kvp in collapsePriorities
where kvp.Value > 0
group kvp by kvp.Value into g
orderby g.Key descending
select g;
foreach (var grp in itemGroups)
{
//ToolGrid.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
//ToolGrid.Arrange(new Rect(ToolGrid.DesiredSize));
//ToolGrid.UpdateLayout();
System.Diagnostics.Debug.WriteLine("Desired=" + ToolGrid.DesiredSize.Width + ", Actual=" + ToolGrid.ActualWidth);
if (ToolGrid.DesiredSize.Width <= ToolGrid.ActualWidth) break;
foreach (var kvp in grp)
{
kvp.Key.ContentVisibility = Visibility.Collapsed;
}
}
//ToolGrid.UpdateLayout();
}
finally
{
collapsingItems = false;
}
}
More code: Here's part of the Window XAML:
<Window>
<DockPanel>
<Grid Name="ToolGrid" DockPanel.Dock="Top" LayoutUpdated="ToolGrid_LayoutUpdated">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
...
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
</Grid>
From what I understood you are using Grid but you set the columns width to Auto, how about you use * for the Width of your Grid.Column istead of Auto. If Auto then Grid stretches its Width and Height to fit its content hence why your Grid.Width is greater than windows width. When you use * the column wont care about content but it will always be inside the windows boundaries.
Now after implementing *, you use the column.width/height, which is inside window boundaries as your final width/height and inside the Grid you can measure the desized size of your nested innner controls. Thats how you get the final size and the desized size of controls.
Show some more code/xaml and we will be able to help you furthermore.
Edited:
<Window>
<DockPanel x:Name="dockyPanel>
<Grid Name="ToolGrid" DockPanel.Dock="Top" LayoutUpdated="ToolGrid_LayoutUpdated">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
...
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
</Grid>
var itemGroups = from kvp in collapsePriorities
where kvp.Value > 0
group kvp by kvp.Value into g
orderby g.Key descending
select g;
double x = 0.0;
foreach (var grp in itemGroups)
{
// x will be increased by the sum of all widths of items
x += grp.SumOfAllWidthOfGroup;
// if x greater than available space then this group needs to collaps its items
if(x > this.dockyPanel.ActualWidth)
{
foreach (var kvp in grp)
{
kvp.Key.ContentVisibility = Visibility.Collapsed;
}
}
}
How about this? Will my pseudocode help you any further?
It's turned out that WPF won't give me predictable sizes of the Grid that contains all the auto-sized columns with the button elements in them. But the parent element of this Grid, no matter what it is and how it is layouted, provides usable information on that. So my solution is basically to insert another level of Grid container between what was already there and my actual toolbar layout grid and compare the different sizes of both. The central test to find out whether the grid would fit in the given space is now this:
foreach (var grp in itemGroups)
{
InnerToolGrid.UpdateLayout();
if (InnerToolGrid.RenderSize.Width - extentWidth <= ToolGrid.ActualWidth) break;
foreach (var kvp in grp)
{
kvp.Key.ContentVisibility = Visibility.Collapsed;
}
}
It iterates through all priority classes of elements that may be collapsed together (whereever they are located on the grid) and collapses all elements in a class (group). Then the inner grid's layout is updated to reflect the changes of the buttons and the inner grid's RenderSize is compared with the outer grid's ActualWidth. If it's smaller, it fits in and no more items need to be collapsed.
All of this is invoked from the LayoutUpdated event of the inner grid, still preventing recursion through a lock variable. This allows me to react on size changes of any button on the toolbar, for example when a text was updated.
Since the LayoutCompleted event seems to be triggered asynchronously, the lock variable must remain set until the next layout run has completed, and cannot be reset again at the end of the LayoutUpdated handler:
private bool collapsingItems;
private void InnerToolGrid_LayoutUpdated(object sender, EventArgs e)
{
if (collapsingItems) return;
// Prevent further calls before the layouting is completely finished
collapsingItems = true;
Dispatcher.BeginInvoke(
(Action) (() => { collapsingItems = false; }),
System.Windows.Threading.DispatcherPriority.Loaded);
// ...
}
I have a ListBox with a ItemsPanelTemplate of Canvas. I know the ScrollViewer will not work with the Canvas unless its given a height and width. I DO NOT want to give the canvas a height and width because it will not always be constant. Is there any other work around or tricks anyone has gotten to work for this situation. I know I can't be the only one with this problem. Thanks in advance here is my code so far.
Another problem is I am unable to place the ScrollViewer inside the ItemsPanelTemplate because it can only have one element nested inside.
This also restricts me from placing the canvas inside a grid to get positioning.
XAML:
<!--Core Viewer-->
<ScrollViewer x:Name="scrollViewer"
VerticalScrollBarVisibility="Hidden"
HorizontalScrollBarVisibility="Hidden">
<ListBox x:Name="objCoreViewer"
ItemsSource="{Binding ItemsSource}"
Background="LightGray"
SelectionChanged="objCoreViewer_SelectionChanged"
ItemTemplateSelector="{DynamicResource CoreViewerDataTemplateSelector}"
ItemContainerStyleSelector="{DynamicResource ItemContainerStyleSelector}"
PreviewMouseWheel="objCoreViewer_PreviewMouseWheel">
<!-- Core Map Canvas -->
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<Canvas x:Name="objCoreViewerCanvas"
Background="Transparent">
<Canvas.LayoutTransform>
<ScaleTransform ScaleX="{Binding Path=Value, ElementName=ZoomSlider}"
ScaleY="{Binding Path=Value, ElementName=ZoomSlider}" />
</Canvas.LayoutTransform>
</Canvas>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
</ListBox>
</ScrollViewer>
To get the canvas to grow based on the the child elements inside of it you will need to override the Canvas's MeasureOverride event in a custom class that inherits from Canavas. This code works great for me:
protected override System.Windows.Size MeasureOverride(System.Windows.Size constraint)
{
Size toReport = new Size();
foreach (UIElement element in this.InternalChildren)
{
//Get the left most and top most point. No using Bottom or Right in case the controls actual bottom and right most points are less then the desired height/width
var left = Canvas.GetLeft(element);
var top = Canvas.GetTop(element);
left = double.IsNaN(left) ? 0 : left;
top = double.IsNaN(top) ? 0 : top;
element.Measure(constraint);
Size desiredSize = element.DesiredSize;
if (!double.IsNaN(desiredSize.Width) && !double.IsNaN(desiredSize.Height))
{
//left += desiredSize.Width;
//top += desiredSize.Height;
toReport.Width = toReport.Width > left +desiredSize.Width ? toReport.Width : left + desiredSize.Width;
toReport.Height = toReport.Height > top+desiredSize.Height ? toReport.Height : top + desiredSize.Height;
}
}
//Make sure scroll includes the margins incase of a border or something
toReport.Width += this.Margin.Right;
toReport.Height += this.Margin.Bottom;
return toReport;
}
I know we can stretch item to the right or down using ColumnSpan and RowSpan
However I got an Item I want to display in grid cell and have it centered according to this cell center, so it will span both right and left in case of text, here I provide an image of how it currently looks, "12345" text can't fit the column and is displayed as "234", is there a way to make 1 & 5 visible leaving all columns untouched?
Text can be changed to anything and has to be centered correctly, I can hack a way to display 12345 this way , but need a solution that will work with any text, just in case,and textBlock grid.Column has to be left untouched 1 (or 2 to be precise for my case) , not 0 for this picture case.
Solution 4
So what you actually want is a way to center the text dynamically if I now understand correctly. You don't want to have to work out fiddling with the padding yourself, you want a number that works for everything? I sure hope you appreciate this! :)
/// <summary>
/// Takes a FrameworkElement that is spanned across
/// multiple columns and RETURNS a Thickness that
/// can be applied to the Padding property to
/// centre it across the spanned columns
/// </summary>
/// <param name="fe">The FrameworkElement to be centred</param>
/// <param name="centerColumn">The index of the desired centre column</param>
/// <returns>A Thickness that will center the FrameworkElement</returns>
private Thickness GetCenteringPadding(FrameworkElement fe, int centerColumn)
{
Grid parentGrid = fe.Parent as Grid;
if (parentGrid == null)
throw new ArgumentException();
// Variables
int firstColumn = (int)fe.GetValue(Grid.ColumnProperty);
int columnSpan = (int)fe.GetValue(Grid.ColumnSpanProperty);
double totalWidth = 0.0;
double leftWidth = 0.0;
double leftPaddingWidth = 0.0;
// Total the width for all the spanned columns
for (int i = firstColumn; i < columnSpan + firstColumn; i++)
{
// This part can be dangerous, especially if you're using a '*'
totalWidth += parentGrid.ColumnDefinitions[i].ActualWidth;
}
// Get the total width from the left side of the first
// spanned column, to the center of the 'centerColumn'
for (int j = firstColumn; j <= centerColumn; j++)
{
if (j != centerColumn)
leftWidth += parentGrid.ColumnDefinitions[j].ActualWidth;
else // Only take half the width for the center column
leftWidth += parentGrid.ColumnDefinitions[j].ActualWidth / 2;
}
// Calculate the padding width
// (Abbr. rightWidth = tW - lW, lPW = lW - rW)
leftPaddingWidth = 2*leftWidth - totalWidth;
// Check whether the padding needs to be on the left or the right
if (leftPaddingWidth > 0.0)
{
// The excess space is on the left
return new Thickness(leftPaddingWidth, 0.0, 0.0, 0.0);
}
else
{
// The excess space is on the right
return new Thickness(0.0, 0.0, -leftPaddingWidth, 0.0);
}
}
A couple of notes. It uses the .ActualWidth of the columns in the Grid. You can change this of course to .Width, however you may run into problems when you're using ColumnDefinition Width="30*" because I'm quite sure you won't get a double as the result then. Secondly, this function doesn't set the padding on the object you pass for two reasons:
- You can take the returned Thickness and add it to the .Padding already applied to a Control so that you don't destroy it's formatting.
- You can pass any FrameworkElement, whereas only Controls have padding. Perhaps you could add the returned thickness to the .Margin property instead, or even create a container and apply padding within that?
Finally, you could easily combine the two for loops within the function, but I left them seperate from clarity.
Cheers.
I'm not sure I fully understand the restrictions on your problem, so it's possible that neither of these solutions will work, but here's two quick suggestions. (Personally, I like Solution 2 better.)
<Edit: Additional Thought>
Solution 0
If you're able to change the Grid.ColumnSpan of the textblock, you can do solution 2 without the container. Just span the textblock over a couple of columns and adjust the padding of the textblock to center the contents!
</Edit>
Solution 1
First off, if your current XAML structure looks something like this:
<Grid>
<TextBlock Grid.Column="2" Text="12345" />
</Grid>
Consider changing it to something like this?
<Grid>
<!-- Draw up columns so that the textblock looks as you wish -->
<TextBlock Grid.Column="2" Text="12345" />
<!-- Span an inner grid across all the columns/rows in the outer grid -->
<Grid Grid.ColumnSpan=".."
Grid.RowSpan="..">
<!-- All the other stuff in the Grid -->
</Grid>
</Grid>
Solution 2
If that can't work, consider housing the textblock in another container such as a Label, and spanning that across the columns.
<Grid>
<!-- Use the left/right padding on the label to center the textbox -->
<sdk:Label Grid.Column="0"
Grid.ColumnSpan="3"
Grid.Row="1"
HorizontalAlignment="Center"
Padding="13,0,0,0">
<!-- In my example, setting the left pading to 13 centered the textbox -->
<TextBlock HorizontalAlignment="Center"
Text="......!......" />
<!-- Text like the test above, really helped find the correct padding -->
</sdk:Label>
</Grid>
I hope this works within your restrictions, and if not, helps you figure out a solution yourself!
Why does this work so well in wpf
<ScrollViewer HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
<Canvas x:Name="MyDesigner">
</Canvas>
</ScrollViewer>
Now when I do the same thing in silverlight and load an control "that can be dragged" the scrollbars are not triggered, when I drag out of view, nothing happens... but in wpf it automatically shows them...
As a quick check against the normal 'gotcha' have you explicitly set the canvas height / width properties?
If I knock up some xaml for test purposes and run it:
<ScrollViewer HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
<Canvas x:Name="test" Background="Beige">
<TextBlock Canvas.Left="2000" Canvas.Top="200" Text="test"/>
</Canvas>
</ScrollViewer>
Will not show a scroll bar, even though I have explicitly created content in the canvas 2000 to the left, the canvas width not being set means the scroll viewer has no range to bind to so to speak. The canvas without a width is considered is just infinitely wide from what I can see. Whilst this is not the same as dragging the concept of putting a piece of content outside of the current view is there.
As soon as you add a width, it defines a finite area to scroll on and the scroll bar shows up.
Here is the solution I found.
The canvas can grow dynamically, but you will need to explicitly set the height to a new value.
So, if you have 20 textblocks with a height of 21, you need to set the canvas height as:
Canvas.Height = 22.0 * 100;
for the scrollviewer to pick up the new height.
MainPage.xaml
<Canvas Canvas.Left="5" Canvas.Top="25" >
<ScrollViewer Width="300" Height="700" x:Name="CanSummaryScroller">
<Canvas x:Name="canSummaryCells" >
</Canvas>
</ScrollViewer>
</Canvas>
MainPage.xaml.cs
boxDevSumList is a List of TextBoxes
for (int i = 0; i < 100; i++)
{
boxDevSumList.Add(new Cell());
(boxDevSumList.ElementAt(i)).Width = 271;
(boxDevSumList.ElementAt(i)).Height = 21;
(boxDevSumList.ElementAt(i)).SetValue(FontFamilyProperty, new FontFamily("Calibri"));
(boxDevSumList.ElementAt(i)).FontSize = 12;
(boxDevSumList.ElementAt(i)).Text = "this is a test";
if (i.Equals(1) || i.Equals(3) || i.Equals(5) || i.Equals(7) || i.Equals(9))
(boxDevSumList.ElementAt(i)).Background = lgBrush;
(boxDevSumList.ElementAt(i)).Opacity = .9;
canSummaryCells.Children.Add(boxDevSumList.ElementAt(i));
boxDevSumList.ElementAt(i).SetValue(Canvas.TopProperty, (double)(i * 21) + 45);
boxDevSumList.ElementAt(i).SetValue(Canvas.LeftProperty, 4.0);
canSummaryCells.Height = 22.0 * 100;
}
I've created a ListBox to display items in groups, where the groups are wrapped right to left when they can no longer fit within the height of the ListBox's panel. So, the groups would appear similar to this in the listbox, where each group's height is arbitrary (group 1, for instance, is twice as tall as group 2):
[ 1 ][ 3 ][ 5 ]
[ ][ 4 ][ 6 ]
[ 2 ][ ]
The following XAML works correctly in that it performs the wrapping, and allows the horizontal scroll bar to appear when the items run off the right side of the ListBox.
<ListBox>
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical"/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.GroupStyle>
<ItemsPanelTemplate>
<WrapPanel Orientation="Vertical"
Height="{Binding Path=ActualHeight,
RelativeSource={RelativeSource
FindAncestor,
AncestorLevel=1,
AncestorType={x:Type ScrollContentPresenter}}}"/>
</ItemsPanelTemplate>
</ListBox.GroupStyle>
</ListBox>
The problem occurs when a group of items is longer than the height of the WrapPanel. Instead of allowing the vertical scroll bar to appear to view the cutoff item group, the items in that group are simply clipped. I'm assuming that this is a side effect of the Height binding in the WrapPanel - the scrollbar thinks it does not have to enabled.
Is there any way to enable the scrollbar, or another way around this issue that I'm not seeing?
By setting the Height property on the WrapPanel to the height of the ScrollContentPresenter, it will never scroll vertically. However, if you remove that Binding, it will never wrap, since in the layout pass, it has infinite height to layout in.
I would suggest creating your own panel class to get the behavior you want. Have a separate dependency property that you can bind the desired height to, so you can use that to calculate the target height in the measure and arrange steps. If any one child is taller than the desired height, use that child's height as the target height to calculate the wrapping.
Here is an example panel to do this:
public class SmartWrapPanel : WrapPanel
{
/// <summary>
/// Identifies the DesiredHeight dependency property
/// </summary>
public static readonly DependencyProperty DesiredHeightProperty = DependencyProperty.Register(
"DesiredHeight",
typeof(double),
typeof(SmartWrapPanel),
new FrameworkPropertyMetadata(Double.NaN,
FrameworkPropertyMetadataOptions.AffectsArrange |
FrameworkPropertyMetadataOptions.AffectsMeasure));
/// <summary>
/// Gets or sets the height to attempt to be. If any child is taller than this, will use the child's height.
/// </summary>
public double DesiredHeight
{
get { return (double)GetValue(DesiredHeightProperty); }
set { SetValue(DesiredHeightProperty, value); }
}
protected override Size MeasureOverride(Size constraint)
{
Size ret = base.MeasureOverride(constraint);
double h = ret.Height;
if (!Double.IsNaN(DesiredHeight))
{
h = DesiredHeight;
foreach (UIElement child in Children)
{
if (child.DesiredSize.Height > h)
h = child.DesiredSize.Height;
}
}
return new Size(ret.Width, h);
}
protected override System.Windows.Size ArrangeOverride(Size finalSize)
{
double h = finalSize.Height;
if (!Double.IsNaN(DesiredHeight))
{
h = DesiredHeight;
foreach (UIElement child in Children)
{
if (child.DesiredSize.Height > h)
h = child.DesiredSize.Height;
}
}
return base.ArrangeOverride(new Size(finalSize.Width, h));
}
}
Here is the slightly modified code - all credit given to Abe Heidebrecht, who previously posted it - that allows both horizontal and vertical scrolling. The only change is that the return value of MeasureOverride needs to be base.MeasureOverride(new Size(ret.width, h)).
// Original code : Abe Heidebrecht
public class SmartWrapPanel : WrapPanel
{
/// <summary>
/// Identifies the DesiredHeight dependency property
/// </summary>
public static readonly DependencyProperty DesiredHeightProperty = DependencyProperty.Register(
"DesiredHeight",
typeof(double),
typeof(SmartWrapPanel),
new FrameworkPropertyMetadata(Double.NaN,
FrameworkPropertyMetadataOptions.AffectsArrange |
FrameworkPropertyMetadataOptions.AffectsMeasure));
/// <summary>
/// Gets or sets the height to attempt to be. If any child is taller than this, will use the child's height.
/// </summary>
public double DesiredHeight
{
get { return (double)GetValue(DesiredHeightProperty); }
set { SetValue(DesiredHeightProperty, value); }
}
protected override Size MeasureOverride(Size constraint)
{
Size ret = base.MeasureOverride(constraint);
double h = ret.Height;
if (!Double.IsNaN(DesiredHeight))
{
h = DesiredHeight;
foreach (UIElement child in Children)
{
if (child.DesiredSize.Height > h)
h = child.DesiredSize.Height;
}
}
return base.MeasureOverride(new Size(ret.Width, h));
}
protected override System.Windows.Size ArrangeOverride(Size finalSize)
{
double h = finalSize.Height;
if (!Double.IsNaN(DesiredHeight))
{
h = DesiredHeight;
foreach (UIElement child in Children)
{
if (child.DesiredSize.Height > h)
h = child.DesiredSize.Height;
}
}
return base.ArrangeOverride(new Size(finalSize.Width, h));
}
}
Thanks for answering, David.
When the binding is removed, no wrapping occurs. The WrapPanel puts every group into a single vertical column.
The binding is meant to force the WrapPanel to actually wrap. If no binding is set, the WrapPanel assumes the height is infinite and never wraps.
Binding to MinHeight results in an empty listbox. I can see how the VerticalAlignment property could seem to be a solution, but alignment itself prevents any wrapping from occurring. When binding and alignment are used together, the alignment has no effect on the problem.
I would think that you are correct that it has to do with the binding. What happens when you remove the binding? With the binding are you trying to fill up at least the entire height of the list box? If so, consider binding to MinHeight instead, or try using the VerticalAlignment property.