Welcome to Pete Brown's 10rem.net

First time here? If you are a developer or are interested in Microsoft tools and technology, please consider subscribing to the latest posts.

You may also be interested in my blog archives, the articles section, or some of my lab projects such as the C64 emulator written in Silverlight.

(hide this)

Custom Placement within an ItemsControl in WPF

Pete Brown - 16 September 2010

I've been tripped up on a problem in Shoebox Scan for several weeks now. I'll pick it up for an hour or two on a Friday, get nowhere, and put it down until later the next week. Today, I finally figured out what the problem is. (Yes, I have been piddling with this application since early this year. I'm pretty sure Scott has a special ulcer just for me and Shoebox scan <g>)

Background

I rewrote Shoebox Scan so I could make the code follow modern WPF coding and design practices. There were some areas of really tight coupling between some of the UI elements and the backing store that were a constant source of churn whenever I made a change. I won't say it was a disaster, but it was a mess. The part of the code had to do with the selection of regions in the image for purposes of rotating and cropping.

Here's a work-in-progress screenshot showing two selection regions on a scanned image. The adorners are turned off for the moment because I thought they were the problem. Without the adorners, you can move the regions around. With the adorners, you can move everything around, plus rotate and resize to select a region from the image to save to a file.

image

Refactoring

So, I refactored it to use MVVM with binding in the UI. Rather than have these other controls injecting everything via code, I was able to move the UI into XAML. Rather than have code which added and removed regions, I was able to use a plain old ItemsControl  to represent them on-screen, and a data template to define the appearance. I'll probably retemplate a listbox or derive my own control from Selector later.

image

You wouldn't normally think of an ItemsControl in this context. ItemsControls are usually thought of for lists of data in some structured format. This is free form: each item can be a different size, rotation and location.

Inside the data template, I bound the root grid of the template to the Left, Top, Width, and Height of the SelectionRegion.

The Problem

While the ItemsControl is actually perfectly suited to this, The problem I ran into was that no matter what I did, I could not get the regions to move from the top left. I could see that the properties were correct in the viewmodel.

In the DataTemplate, I had set up a root Grid, and bound the Canvas.Left and Canvas.Top properties to the appropriate properties on the SelectionRegion model object. In the debugger, I verified that the properties were being updated, that there were no binding problems. I also did the usual replacement of the ItemsPanel with a canvas set to stretch in both directions. Still no go. Despite the frustration of seeing everything correct in the debugger, the visuals weren't correct.

The Debugging (well, the last bit of it, anyway)

I cracked open Snoop and looked at the tree. It occurred to me that the problem might be with the ContentPresenter's positioning or sizing.

image

Sure enough, when I looked at the ActualHeight and ActualWidth, I saw that the content presenter was sized to the individual item. This seems obvious in retrospect, but it took a while to find it.

The Solution

The solution, of course, was to set the ItemContainerStyle and bind that to the Left, Top, Width and Height. The resulting ItemsControl with the appropriate templates, looks like this:

<ItemsControl x:Name="Selections"
              ItemsSource="{Binding Regions}"
              HorizontalAlignment="Stretch"
              VerticalAlignment="Stretch">
    <ItemsControl.ItemsPanel>
        <ItemsPanelTemplate>
            <Canvas HorizontalAlignment="Stretch"
                    VerticalAlignment="Stretch"/>
        </ItemsPanelTemplate>
    </ItemsControl.ItemsPanel>

    <ItemsControl.ItemContainerStyle>
        <Style TargetType="ContentPresenter">
            <Setter Property="Canvas.Left"
                    Value="{Binding CalculatedNormalLeft}" />
            <Setter Property="Canvas.Top"
                    Value="{Binding CalculatedNormalTop}" />
            <Setter Property="Width"
                    Value="{Binding Width}" />
            <Setter Property="Height"
                    Value="{Binding Height}" />
        </Style>
    </ItemsControl.ItemContainerStyle>
    
    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <Grid HorizontalAlignment="Stretch"
                  VerticalAlignment="Stretch">

            <controls:MoveThumb Cursor="SizeAll"
                                Template="{StaticResource MoveThumbTemplate}" />

                <Rectangle Fill="Blue"
                           Opacity="0.1"
                           IsHitTestVisible="False"
                           Stretch="Fill" />

                <TextBlock Foreground="#ffffffff"
                           FontSize="24"
                           FontWeight="Bold"
                           FontFamily="Segoe UI"
                           Text="{Binding Caption}"
                           IsHitTestVisible="False"
                           Margin="10,0,10,0"
                           HorizontalAlignment="Center"
                           VerticalAlignment="Center">
                    <TextBlock.Effect>
                        <DropShadowEffect Opacity="1"
                                          BlurRadius="4"
                                          Color="Black"
                                          ShadowDepth="0" />
                    </TextBlock.Effect>
                </TextBlock>

                <!--<controls:DesignerItemDecorator x:Name="ItemDecorator"
                                                ShowDecorator="True" />-->

                <Grid.RenderTransform>
                    <RotateTransform CenterX="0.5"
                                     CenterY="0.5"
                                     Angle="{Binding RotationAngle}" />
                </Grid.RenderTransform>

            </Grid>

        </DataTemplate>
    </ItemsControl.ItemTemplate>
</ItemsControl>

I'm posting this in case someone else tries to bend an ItemsControl in a similar way, and runs into problems with positioning. Now I can go back to solving the hard problems around rotation and cropping :)

       
posted by Pete Brown on Thursday, September 16, 2010
filed under:        

3 comments for “Custom Placement within an ItemsControl in WPF”

  1. chadbrsays:
    FWIW, I'm doing something similar (x-compiled to SL and WPF) like this:

    <ScrollViewer x:Name="scrollContainer"
    Grid.Row="1"
    HorizontalScrollBarVisibility="Auto"
    VerticalScrollBarVisibility="Auto"
    VerticalAlignment="Top"
    HorizontalAlignment="Left">
    <Canvas x:Name="canvasView">
    <ItemsControl x:Name="items">
    <ItemsControl.ItemTemplate>
    <DataTemplate>
    <Canvas>
    <Grid Canvas.Left="{Binding Left}"
    Canvas.Top="{Binding Top}"
    Background="{Binding BackColor}">
    <Border BorderBrush="{Binding BorderColor}"
    BorderThickness="1">
    <TextBlock FontSize="{Binding FontSize}"
    Margin="2,0"
    Text="{Binding Text}"
    Foreground="{Binding TextColor, FallbackValue=#00000000}"
    Width="{Binding Width}"
    MouseLeftButtonUp="TextBlock_MouseLeftButtonUp" />
    </Border>
    </Grid>
    </Canvas>
    </DataTemplate>
    </ItemsControl.ItemTemplate>
    </ItemsControl>
    </Canvas>
    </ScrollViewer>

Comment on this Post

Remember me