11

I am trying to set the Canvas properties in an ItemsControl DataTemplate with Silverlight 3. According to this post, the only way of doing that is to set it using the ItemsContainerStyle for the ContentPresenter type, since the Canvas properties only take effect on direct children of the Canvas. This doesn't seem to work in SL3, since the ItemsControl doesn't have an ItemsContainerStyle property, so I tried a ListBox as advised by this article, but it still doesn't work. From the XAML below, I would expect to see a green square, with the numbers 10, 30, 50, 70 cascading from "NW" to "SE" direction. Can anyone tell me why they are all stacked on top of eachother in the NW corner?

<UserControl x:Class="TestControl"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:System="clr-namespace:System;assembly=mscorlib" >
    <StackPanel>
        <ListBox>
            <ListBox.ItemsPanel>
                <ItemsPanelTemplate>
                    <Canvas Background="Green" Width="100" Height="100" />
                </ItemsPanelTemplate>
            </ListBox.ItemsPanel>
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <TextBox Text="{Binding}" />
                </DataTemplate>                
            </ListBox.ItemTemplate>
            <ListBox.ItemContainerStyle>
                <Style TargetType="ContentPresenter">
                    <Setter Property="Canvas.Left" Value="{Binding}" />
                    <Setter Property="Canvas.Top" Value="{Binding}" />
                </Style>
            </ListBox.ItemContainerStyle>
            <ListBox.Items>
                <System:Int32>10</System:Int32>
                <System:Int32>30</System:Int32>
                <System:Int32>50</System:Int32>
                <System:Int32>70</System:Int32>
            </ListBox.Items>
        </ListBox>
    </StackPanel>
</UserControl>
Community
  • 1
  • 1
skb
  • 30,624
  • 33
  • 94
  • 146
  • 1
    Thanks skb, this answered my question, which was how to do this in WPF :-) As you say it works great in WPF. – MikeKulls Aug 17 '11 at 03:15

4 Answers4

7

I'm not sure if it will work in your scenario, but I've accomplished this in the past using the RenderTransform.

<ItemsControl>
    <ItemsControl.ItemsPanel>
        <ItemsPanelTemplate>
            <Canvas Background="Green" />
        </ItemsPanelTemplate>
    </ItemsControl.ItemsPanel>
    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <TextBox Text="{Binding}">
                <TextBox.RenderTransform>
                    <TranslateTransform X="100" Y="100" />
                </TextBox.RenderTransform>
            </TextBox>
        </DataTemplate>
    </ItemsControl.ItemTemplate>
    <ItemsControl.Items>
        <System:Int32>10</System:Int32>
        <System:Int32>30</System:Int32>
        <System:Int32>50</System:Int32>
        <System:Int32>70</System:Int32>
    </ItemsControl.Items>
</ItemsControl>

Or in the case of binding you will need to use a converter

<ItemsControl>
    <ItemsControl.ItemsPanel>
        <ItemsPanelTemplate>
            <Canvas Background="Green" />
        </ItemsPanelTemplate>
    </ItemsControl.ItemsPanel>
    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <TextBox Text="{Binding}" RenderTransform="{Binding Converter={StaticResource NumberToTransformGroupConverter}}" />
        </DataTemplate>
    </ItemsControl.ItemTemplate>
    <ItemsControl.Items>
        <System:Int32>10</System:Int32>
        <System:Int32>30</System:Int32>
        <System:Int32>50</System:Int32>
        <System:Int32>70</System:Int32>
    </ItemsControl.Items>
</ItemsControl>

Converter

public void ConvertTo(object value, ...)
{
    int intValue = int.Parse(value.ToString());

    return new TransformGroup()
    {
        Children = new TransformCollection()
        {
            new TranslateTransform { X = intValue, Y = intValue }
        }
    };
}
bendewey
  • 39,709
  • 13
  • 100
  • 125
  • 1
    +1 In the scenario presented this works well enough. Put it in ScrollViewer or Stackpanel that isn't big enough the issues start to surface. But the only other thing you could do is go the whole hog and write a custom panel so that the desired behaviour could be delivered in the Measure and Arrange phases. – AnthonyWJones Mar 05 '10 at 19:28
3

Silverlight4 does not bind to attached properties in style. I suggest using David Anson's approach described here.

    <UserControl.Resources>
    <Style  x:Key="ScreenBindStyle" TargetType="ListBoxItem">
        <Setter Property="Helpers:SetterValueBindingHelper.PropertyBinding">
            <Setter.Value>
                <Helpers:SetterValueBindingHelper>
                    <Helpers:SetterValueBindingHelper Type="Canvas" Property="Left" Binding="{Binding LocationField.Value.X}" />
                    <Helpers:SetterValueBindingHelper Type="Canvas" Property="Top" Binding="{Binding LocationField.Value.Y}" />
                    <Helpers:SetterValueBindingHelper Type="Canvas" Property="ZIndex" Binding="{Binding ZIndex.Value}" />
                </Helpers:SetterValueBindingHelper>
            </Setter.Value>
        </Setter>
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="ListBoxItem">
                    <ContentPresenter/>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</UserControl.Resources>

And in listbox:

ItemContainerStyle="{StaticResource ScreenBindStyle}"
Ben
  • 2,454
  • 26
  • 32
2

Old post but I went into the same problem but now with SL5 that now allows Binding in style setters. I was trying to avoid to use the ListBox (because it handles selection and so on) and the ItemsControl still doesn't have an ItemContainerStyle. So I tried a few things.

I haven't found many subject discussing this problem so let me share my solution (sorry if it duplicates)

In fact, I found a very convenient way to solve the problem by adding an unnamed Style in the ItemsControl resources :

<ItemsControl ItemsSource="{Binding Path=MyData}">
    <ItemsControl.Resources>
        <Style TargetType="ContentPresenter">
            <Setter Property="Canvas.Top" Value="{Binding Path=Bounds.Top}"/>
            <Setter Property="Canvas.Left" Value="{Binding Path=Bounds.Left}"/>
            <Setter Property="Width" Value="{Binding Path=Bounds.Width}"/>
            <Setter Property="Height" Value="{Binding Path=Bounds.Height}"/>
        </Style>
    </ItemsControl.Resources>
    <ItemsControl.ItemsPanel>
        <ItemsPanelTemplate>
            <Canvas/>
        </ItemsPanelTemplate>
    </ItemsControl.ItemsPanel>
    <ItemsControl.ItemTemplate>
        <DataTemplate DataType="my:DataType">
            ...
        </DataTemplate>
    </ItemsControl.ItemTemplate>
</ItemsControl>

Works like a charm in SL5 :)

xum59
  • 841
  • 9
  • 16
0

I can't explain what you are seeing either. Your Xaml is broken in at least a couple of ways.

First the Xaml itself fails because this:-

<Style TargetType="ContentPresenter">

should be

<Style TargetType="ContentControl">

the Item Containers in the case of ListBox are of type ListBoxItem which derive from ContentControl.

Still without that the placing {Binding} in style setters still doesn't work. I guess you were imagining that the style would be applied to each item in turn and get its Value from the current item. However even if binding worked in the style, there would only be one style and it would get its data binding from the ListBox DataContext. This is a different DataContext that applies to each ListBox item (which in this case is each Item in the Items collection).

Still I think Ben has a reasonable solution in place which eliminates this approach.

AnthonyWJones
  • 187,081
  • 35
  • 232
  • 306
  • 1
    Hi Anthony I may be misreading your answer but from what I can tell pretty much everything you've written here is inacurate. First, the xaml doesn't fail because of the target type, in fact it fails if you make the change you suggest. Second, the binding in the style does work. Third, the binding doesn't take its context from the listbox, it takes it from the listbox item. As for there only being one style I suspect that the style gets cloned. The important thing here is the solution presented right at the start DOES work, I am using it in WPF, it just doesn't work in Silverlight. – MikeKulls Aug 17 '11 at 03:13
  • @MikeKulls: Yes you are misreading me ;). Let me start with the second of your objections because that is the real source of the misunderstanding. I state "placing {Binding} in style setters still __doesn't__ work" and "even _if_ binding worked". From then on its all hypothetical because as you say it doesn't work. Hypothetically had the binding been resolved at that point the `Value` property of the Style would be bound to this __common__ value (N.B. _not_ the binding itself) and would be used across all items. Finally even __if__ everything else worked the TargetType __is__ incorrect. – AnthonyWJones Aug 17 '11 at 07:07
  • But the solution does work (where did I say it doesn't work?), the binding works in the style. I am using this solution almost as a copy past in a production app and I can say it most definately does work. Remember that skb states the solution works in WPF, just not in silverlight. – MikeKulls Aug 17 '11 at 23:42