【问题标题】:Make attached properties work inside DataTemplate使附加属性在 DataTemplate 中工作
【发布时间】:2017-03-12 18:12:24
【问题描述】:

我有一个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 Lines 或 Ellipses 的类型。这基本上可行,但附加属性 Canvas.LeftCanvas.Top 被忽略,即使使用静态值也是如此。

VS 或 ReSharper 也会通知我附加的属性在当前上下文中无效。

如何在不使用附加属性的情况下将Ellipse 定位在画布上?或者还有什么其他的解决方案是合适的?

【问题讨论】:

  • DataTemplate 中的元素不会成为 Canvas 的直接子元素(因为它们将被用作作为项目容器的 ContentPresenter 的内容)。您只能在 ItemContainerStyle 中设置 Canvas.LeftCanvas.Top,如下所示,例如在this answerThis answer 也可能有帮助。
  • 除此之外,在 ItemsControl 的资源中拥有 ListBoxItem 样式看起来很奇怪。
  • Style 一定是旧代码的遗留物,我将其从示例代码中删除。

标签: wpf datatemplate attached-properties


【解决方案1】:

不幸的是,没有人愿意发布答案。

首先,Clemens 链接很有帮助。这些项目将在 ContentPresenter 内,这就是为什么在 Ellipsis 上设置 Canvas.Left/Top 不起作用的原因。

解决方案 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 放置 &lt;Line&gt; 会产生绑定警告,因为该视图模型没有属性 UpperLeft。尽管如此,它适用于省略号,并且线条由它们的 X1Y1X2Y2 值放置。

解决方案 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);
        }
    }
}

希望有人会发现一个或两个解决方案有用。

【讨论】:

    【解决方案2】:

    请注意,我遇到了与 @ZoolWay 相同的 ReSharper 警告消息,但在我的情况下,它位于数据网格内,我希望右侧的按钮右对齐而不是左对齐:

    附加属性设置“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>
    

    HTH

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2013-07-27
      • 1970-01-01
      • 1970-01-01
      • 2017-02-21
      • 2012-02-27
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多