4

I got an ItemsControl which uses a Canvas as ItemsPanel and its items are rendered to different WPF shapes depending on the bound type, basically like this:

<ItemsControl  ItemsSource="{Binding PreviewShapes}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
    <ItemsControl.Resources>
        <DataTemplate DataType="{x:Type local:UiPreviewLineViewModel}">
            <Line X1="{Binding Start.X}" Y1="{Binding Start.Y}"
                        X2="{Binding End.X}" Y2="{Binding End.Y}" 
                        StrokeThickness="0.75" Stroke="{Binding Brush}" x:Name="Line" ToolTip="{Binding Text}">
            </Line>
        </DataTemplate>
        <DataTemplate DataType="{x:Type local:UiPreviewEllipsisViewModel}">
            <Ellipse Canvas.Left="{Binding UpperLeft.X" Canvas.Top="{Binding UpperLeft.Y}" 
                     Width="{Binding Width}" Height="{Binding Height}" 
                     StrokeThickness="0.75" Stroke="{Binding Brush}" x:Name="Ellipse" ToolTip="{Binding Text}">
            </Ellipse>
        </DataTemplate>
    </ItemsControl.Resources>
    <ItemsControl.ItemsPanel>
        <ItemsPanelTemplate>
            <Canvas IsItemsHost="True" HorizontalAlignment="Center" VerticalAlignment="Center" x:Name="SketchCanvas" ClipToBounds="False">
            </Canvas>
        </ItemsPanelTemplate>
    </ItemsControl.ItemsPanel>
</ItemsControl>

So I basically add objects to PreviewShapes of the viewmodel and depending on the type they are rendered to WPF Lines or Ellipses. That basically works but the attached properties Canvas.Left and Canvas.Top are ignored, even when using static values.

Also VS or ReSharper notifies me that the attached property has no effect in the current context.

How can I position the Ellipse on the canvas without using the attached properties? Or what other solution would be appropiate?

ZoolWay
  • 5,411
  • 6
  • 42
  • 76
  • 2
    The elements in the DataTemplate won't become direct children of the Canvas (because they will be used as Content of a ContentPresenter that serves as item container). You can only set `Canvas.Left` and `Canvas.Top` in an ItemContainerStyle, as shown e.g. in [this answer](http://stackoverflow.com/a/22325266/1136211). [This answer](http://stackoverflow.com/a/40190793/1136211) may also be helpful. – Clemens Oct 30 '16 at 14:12
  • Besides that, having a ListBoxItem Style in the Resources of an ItemsControl looks odd. – Clemens Oct 30 '16 at 14:18
  • That `Style` must be a relict from older code, I remove it from the sample code. – ZoolWay Nov 10 '16 at 20:24

2 Answers2

4

Unfortunately nobody felt like posting an answer.

First, Clemens links are helpful. The items will be inside a ContentPresenter which is the reason why setting Canvas.Left/Top on the Ellipsis does not work.

Solution 1

By adding a style to the item container the bindings for the position can be set there:

<ItemsControl.ItemContainerStyle>
    <Style TargetType="ContentPresenter">
        <Setter Property="Canvas.Left" Value="{Binding UpperLeft.X}" />
        <Setter Property="Canvas.Top" Value="{Binding UpperLeft.Y}" />
    </Style>
</ItemsControl.ItemContainerStyle>

This works but the DataTemplate placing <Line> will produce binding warnings because that view model does not have a property UpperLeft. Nevertheless it works for the ellipsis and the lines are placed by their X1, Y1, X2 and Y2 values.

Solution 2

If you would like to use a more fine grained control approach you can set the attached Canvas properties to the ContentPresenter by proxing them with a custom behaviour / attached property. Let's name it CanvasPointProxyBehavior, you could use it for the Ellipse like this:

<DataTemplate DataType="{x:Type local:UiPreviewEllipsisViewModel}">
    <Ellipse behaviors:CanvasPointProxyBehavior.Point="{Binding UpperLeft}"
             Width="{Binding Width}" Height="{Binding Height}" 
             StrokeThickness="0.75" Stroke="{Binding Brush}" x:Name="Ellipse" ToolTip="{Binding Text}">
    </Ellipse>
</DataTemplate>

The Line will not need it. The code for this attached property might look like this:

public class CanvasPointProxyBehavior
{
    public static readonly DependencyProperty PointProperty = DependencyProperty.RegisterAttached("Point", typeof(Point), typeof(CanvasPointProxyBehavior), new UIPropertyMetadata(null, PointChangedCallback));

    public static void SetPoint(DependencyObject depObj, Point point)
    {
        depObj.SetValue(PointProperty, point);
    }

    public static Point GetPoint(DependencyObject depObj)
    {
        return depObj.GetValue(PointProperty) as Point;
    }

    private static void PointChangedCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
    {
        UIElement uiElement = (dependencyObject as UIElement);
        if (uiElement == null) return;
        UIElement elementToBePositioned = uiElement;
        var visualParent = VisualTreeHelper.GetParent(uiElement);
        if (visualParent is ContentPresenter)
        {
            elementToBePositioned = visualParent as ContentPresenter;
        }

        var point = e.NewValue as Point;
        if (point != null)
        {
            Canvas.SetLeft(elementToBePositioned, point.X);
            Canvas.SetTop(elementToBePositioned, point.Y);
        }
    }
}

Hoping someone will find one or both solution useful.

ZoolWay
  • 5,411
  • 6
  • 42
  • 76
0

Please note that I encountered the same ReSharper warning message as @ZoolWay, but in my case it was within a data grid, where I wanted the button on the right to be right-aligned instead of left-aligned:

Attached property setting "Grid.Column" has no effect in current context and can be removed.

Here is the code where I had the warning, on the Button Grid.Column="2":

        <Border VerticalAlignment="Center" Style="{DynamicResource InspectionsCustomDataGridHeader}">
            <Grid>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="Auto"/>
                    <ColumnDefinition Width="Auto" />
                    <ColumnDefinition Width="10*"/>
                </Grid.ColumnDefinitions>
                <StackPanel  Orientation="Horizontal" VerticalAlignment="Top">
                    <Label Name="InspectionsDataGridTitle" Background="White" Content="{Binding InspectionsCollectionView.View.Count, ConverterParameter={x:Static resx:Resources.InspectionsDataGridTitle}, Converter={StaticResource DataGridHeaderCountConverter}}" Style="{DynamicResource InspectionsCustomDataGridHeaderLabel}" MouseDown="InspectionsDataGridTitle_MouseDown" />
                    <!-- more labels, etc... -->
                    <Button Grid.Column="2" Width="30" Height="30" Margin="0,0,10,0" Background="Transparent" BorderThickness="0" HorizontalAlignment="Right" Command="{Binding CreatePDFCommand,Mode=OneWay}" >
                        <StackPanel Orientation="Horizontal" Background="Transparent">
                            <Image Width="30" Height="30" Source="/Inspections;component/Icons/pdf_btn.png" />
                    </StackPanel>
                    </Button>
                </StackPanel>
            </Grid>
        </Border>

This is how I fixed the warning, where I moved the button below the first StackPanel:

        <Border VerticalAlignment="Center" Style="{DynamicResource InspectionsCustomDataGridHeader}">
            <Grid>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="Auto"/>
                    <ColumnDefinition Width="*" />
                    <ColumnDefinition Width="Auto"/>
                </Grid.ColumnDefinitions>
                <StackPanel  Orientation="Horizontal" VerticalAlignment="Top">
                    <Label Name="InspectionsDataGridTitle" Background="White" Content="{Binding InspectionsCollectionView.View.Count, ConverterParameter={x:Static resx:Resources.InspectionsDataGridTitle}, Converter={StaticResource DataGridHeaderCountConverter}}" Style="{DynamicResource InspectionsCustomDataGridHeaderLabel}" MouseDown="InspectionsDataGridTitle_MouseDown" />
                    <!-- more labels, etc... -->
                </StackPanel>
                <Button Grid.Column="2" Width="30" Height="30" Margin="0,0,10,0" Background="Transparent" BorderThickness="0" HorizontalAlignment="Right" Command="{Binding CreatePDFCommand,Mode=OneWay}" >
                    <StackPanel Orientation="Horizontal" Background="Transparent">
                        <Image Width="30" Height="30" Source="/Inspections;component/Icons/pdf_btn.png" />
                    </StackPanel>
                </Button>
            </Grid>
        </Border>

HTH

user8128167
  • 6,929
  • 6
  • 66
  • 79