1

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.

Community
  • 1
  • 1
Nico Schertler
  • 32,049
  • 4
  • 39
  • 70
  • Any reason why you're using a Canvas and not a Grid? Canvas is almost always the wrong choice in WPF as its defeat the layout system (unless you really need free positioning that can't be achieved with margins). – Julien Lebosquain Oct 12 '12 at 16:13
  • Good point. Meanwhile, it is really not necessary to use a canvas because the scenario has changed slightly. – Nico Schertler Oct 12 '12 at 16:19

2 Answers2

1

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.

Kent Boogaart
  • 175,602
  • 35
  • 392
  • 393
  • Thanks for your answer. A `Border` seems to be more appropriate, indeed. However, I narrowed down the problem to the layouting system, which exists regardless of the used control. Could you take a look at the updated question? – Nico Schertler Oct 12 '12 at 14:16
  • Sorry, I assumed something was *in* your `Border`. Since you're in a `Canvas`, the `Border` is basically given the space it asks for, and since it has nothing in it, it won't ask for any space. Put something in your `Border`, like a `TextBlock` with some hard-coded text and you'll then see your items. – Kent Boogaart Oct 12 '12 at 17:09
0

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>
Nico Schertler
  • 32,049
  • 4
  • 39
  • 70