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: Screenshot of Tree Visualizer

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.

Was it helpful?

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.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top