Заставьте вложенные свойства работать внутри DataTemplate

Я получил ItemsControl который использует Canvas как ItemsPanel и его элементы отображаются в различных формах WPF в зависимости от типа привязки, в основном так:

<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>

Поэтому я в основном добавляю объекты в PreviewShapes модели представления и в зависимости от типа они отображаются в WPF Lineс или Ellipses. Это в основном работает, но прикрепленные свойства Canvas.Left а также Canvas.Top игнорируются даже при использовании статических значений.

Также VS или ReSharper уведомляет меня, что присоединенное свойство не имеет никакого эффекта в текущем контексте.

Как я могу расположить Ellipse на холсте без использования прикрепленных свойств? Или какое другое решение было бы уместным?

2 ответа

Решение

К сожалению, никто не хотел публиковать ответ.

Во-первых, полезны ссылки Клеменса. Предметы будут внутри ContentPresenter что является причиной, почему установка Canvas.Left/Top на Ellipsis не работает.

Решение 1

Добавив стиль в контейнер элементов, можно установить привязки для позиции:

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

Это работает, но DataTemplate размещение <Line> будет выдавать обязательные предупреждения, потому что эта модель представления не имеет свойства UpperLeft, Тем не менее, это работает для многоточия и линии размещены их X1, Y1, X2 а также Y2 ценности.

Решение 2

Если вы хотите использовать более детальный подход к управлению, вы можете установить Canvas свойства к ContentPresenter проксируя их с пользовательским поведением / прикрепленным свойством. Давайте назовем его CanvasPointProxyBehavior, вы можете использовать его для Ellipse как это:

<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>

Line не понадобится Код для этого присоединенного свойства может выглядеть следующим образом:

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);
        }
    }
}

Надеясь, кто-то найдет одно или оба решения полезными.

Обратите внимание, что я столкнулся с тем же предупреждением ReSharper, что и ZoolWay, но в моем случае это было в сетке данных, где я хотел, чтобы кнопка справа была выровнена по правому краю, а не по левому краю:

Параметр вложенного свойства "Grid.Column" не действует в текущем контексте и может быть удален.

Вот код, где у меня было предупреждение, на 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>

Вот как я исправил предупреждение, куда я переместил кнопку под первым 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>

НТН

Другие вопросы по тегам