Binding to ListBoxItem's ActualWidth
-
07-07-2021 - |
Question
I have a ListBox
, which is basically a canvas. The items shall be displayed as Rectangles
. The position of the rectangles are bound to the items' data via the ListBoxItem's
Canvas.Left
and Canvas.Right
property.
This is the ListBox'
Template
, which generates the Canvas
:
<ListBox.Template>
<ControlTemplate>
<Canvas IsItemsHost="True"/>
</ControlTemplate>
</ListBox.Template>
The ListBox'
ItemContainerStyle
sets the position of the items:
<Style TargetType="ListBoxItem">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBoxItem">
<ContentPresenter/>
</ControlTemplate>
</Setter.Value>
</Setter>
<Setter Property="Canvas.Left">
<Setter.Value>
<MultiBinding Converter="{StaticResource ...}">
...
</MultiBinding>
</Setter.Value>
</Setter>
<Setter Property="Canvas.Right">
<Setter.Value>
<MultiBinding Converter="{StaticResource ...}">
...
</MultiBinding>
</Setter.Value>
</Setter>
</Style>
This actually works. When I surround the ContentPresenter
with a border, the border has the correct position and size.
Now, the rectangle's Width
should equal the ListBoxItem's
actual width. So the ItemTemplate
looks like this:
<DataTemplate>
<Rectangle Height="..."
Width="{Binding ActualWidth,
RelativeSource={RelativeSource FindAncestor, AncestorType=ListBoxItem}}"
Fill="..."/>
</DataTemplate>
But with this binding applied, the rectangle is never displayed. When I set a constant width, everything renders correctly.
Is there a way to set the rectangle's position within the Canvas
?
Update
Using the WPF Tree Visualizer I realized that this is probably a problem of the ContentPresenter's
ActualWidth
. The following screenshot shows that the Canvas.Left
and Canvas.Right
property are set correctly:
However, the ActualWidth
property is set to 4 (the StrokeThickness
of the contained Rectangle
is 4). How can this layout problem be solved?
Furthermore, I have to amend my statement of above. Surrounding the ContentPresenter
with a Border
does not produce a correct result. Instead, the entire layout seems corrupt.
Update 2
The MSDN says the following:
The Canvas.Right offset of a child element does not affect the size of a parent Canvas.
If you specify them, the attached properties Canvas.Top or Canvas.Left take priority over Canvas.Bottom or Canvas.Right properties
So it seems that I have to specify the width explicitly, which should be a minor problem with an appropriate converter.
Solution 2
As stated in the question, it is not possible to define a control's size only with the Canvas.Left
and Canvas.Right
properties.
The wanted behaviour could be achieved with a converter like the following (error handling omitted):
public class CanvasWidthConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
var canvasLeft = (double)values[0];
var canvasRight = (double)values[1];
return canvasRight - canvasLeft;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
Use (add the converter as a resource):
<Style TargetType="ListBoxItem">
<Setter Property="Canvas.Left" Value="{Binding LeftValue}"/>
<Setter Property="Width">
<Setter.Value>
<MultiBinding Converter="{StaticResource CanvasWidthConverter}">
<Binding Path="LeftValue"/>
<Binding Path="RightValue"/>
</MultiBinding>
</Setter.Value>
</Setter>
</Style>
OTHER TIPS
Firstly, you can do this much more easily as follows:
<ListBox>
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<Canvas/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="Canvas.Left" Value="..."/>
<Setter Property="Canvas.Top" Value="..."/>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
Secondly, Rectangle
does not size to its host control. It's likely you just want to use a Border
with a Background
and/or BorderThickness
&BorderBrush
instead.