How do I constrain a container's height to the height of a specific content element?
I'm trying to do something which seems like it should be extremely simple and yet I can't see how. I have a very simple layout, a TextBox with an image next to it, similar to the way it might look adorned with an ErrorProvider in a WinForms application. The problem is, I want the image to be no higher than the TextBox it's next to. If I lay it out like this, say:
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBox Grid.Row=开发者_开发百科"0" Grid.Column="0" MinWidth="100"/>
<Image Grid.Row="0" Grid.Column="1" Source="error.png" />
</Grid>
the row will size to the height of the image if the image is taller than the TextBox. This also happens if I use a DockPanel or StackPanel.
The naive solution would be to bind the Height
to the TextBox
's ActualHeight
. I'm sure this is wrong. But what's right?
Edit
Here's an example of what looks wrong to me: In both of these layouts (which are both horizontal StackPanel
s), the FontSize
is the only variable:
You can see that the first TextBox
is constrained to the height of the icon, and as a result has an unnecessary bottom padding under the text. And the icon next to the second is out of scale to the TextBox
it's next to.
As it happens, I found a completely different (and much better) way to approach the problem - originally I was scaling my layout by changing the FontSize
on the Window
, but using a ScaleTransform
is a whole lot easier and seems to work perfectly. But even so, it still seems odd to me that it's so hard to do this.
Name your TextBox, reference the TextBox from the Image as follows.
<TextBox Name="myTextBox"
Grid.Row="0" Grid.Column="0"
MinWidth="100"/>
<Image Grid.Row="0"
Grid.Column="1"
Source="error.png"
Height="{Binding ActualHeight, ElementName=myTextBox}"/>
You want a layout algorithm that measures the other elements with a height constraint equal to the desired height of a specific one. While several of the existing Panel implementations will reduce the available space for remaining elements based on the size used by previous ones, none of them will set the constraint the way you want. If you want the behavior you describe in a single layout pass, you will need to write your own layout algorithm.
For example, you can get close by overriding the behavior of StackPanel like this:
public class SizeToFirstChildStackPanel
: StackPanel
{
protected override Size MeasureOverride(Size constraint)
{
if (Children.Count > 0)
{
var firstChild = Children[0];
firstChild.Measure(constraint);
if (Orientation == Orientation.Horizontal)
{
constraint = new Size(
constraint.Width,
Math.Min(firstChild.DesiredSize.Height, constraint.Height));
}
else
{
constraint = new Size(
Math.Min(firstChild.DesiredSize.Width, constraint.Width),
constraint.Height);
}
}
return base.MeasureOverride(constraint);
}
}
This will constrain the height of all children of the StackPanel to the desired height of the first one (or width if the panel is oriented vertically).
It's still not ideal because the first child will get measured a second time by the base class MeasureOverride, and the second measure will have a constraint. This is extra work, and will cause odd behavior if the first child wants to get larger.
To do it right you would need to implement the entire MeasureOverride method method yourself. I'm not going to do that here because it would be a lot of code that isn't really relevant to the problem, and it depends on how exactly you want the layout to work. The important point is to measure the specific element first and use its DesiredSize to determine the availableSize when you call Measure on the others.
精彩评论