数据模板中的WPF绑定不适用于自定义类

时间:2017-10-17 02:31:10

标签: c# wpf xaml data-binding combobox

使用显示可用图块背景列表的ComboBox。这只是一个简单的ComboBox,其ItemSource设置为MapTileBackground对象的集合。

MapTileBackground类完全使用属性定义:

public partial class MapTileBackground
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Description { get; set; }
    public byte[] Content { get; set; }
    public Nullable<int> Color { get; set; }
    public int StrokeColor { get; set; }
    public byte StrokeThickness { get; set; }
}

在一个单独的库中定义,我宁愿不改变它。

我已经定义了一个简单的形状扩展来绘制背景::

public class MapTileBackgroundPreview : Shape
{
    public static readonly DependencyProperty SizeProperty = DependencyProperty.Register("Size", typeof(Point), typeof(MapTileBackgroundPreview));
    public static readonly DependencyProperty TileBackgroundProperty = DependencyProperty.Register("TileBackground", typeof(MapTileBackground), typeof(MapTileBackgroundPreview));
    public MapTileBackgroundPreview()
    {
        layout = new Hex.Layout(Hex.Orientation.Flat, new Hex.Point(8, 8), new Hex.Point(4, 4));
        Size = new Point(8, 8);
        TileBackground = null;
    }

    private Hex.Layout layout;
    protected override Geometry DefiningGeometry
    {
        get
        {
            var points = layout.HexCorners(0, 0).ToArray();
            var path = new PathFigure();
            path.StartPoint = points[5].ToWin();
            for (var i = 0; i < 6; i++)
                path.Segments.Add(new LineSegment(points[i].ToWin(), true));

            var geo = new PathGeometry();
            geo.Figures.Add(path);
            return geo;
        }
    }
    public Point Size
    {
        get
        {
            return (Point)GetValue(SizeProperty);
        }
        set
        {
            SetValue(SizeProperty, value);
            layout.Size = value.ToHex();
            layout.Origin = new Hex.Point(layout.Size.X / 2, layout.Size.Y / 2);
        }
    }

    public MapTileBackground TileBackground
    {
        get
        {
            return (MapTileBackground)GetValue(TileBackgroundProperty);
        }
        set
        {
            SetValue(TileBackgroundProperty, value);

            if (value == null)
            {
                Fill = Brushes.Transparent;
                Stroke = Brushes.Black;
                StrokeThickness = 1;
            }
            else
            {
                Stroke = value.Stroke();
                StrokeThickness = value.StrokeThickness();
                Fill = value.Fill(layout.Orientation);
            }
        }
    }
}

布局只是屏幕像素坐标和六边形系统之间的转换实用程序。 DefiningGeometry只需添加十六进制的6个线段。当给定非空MapTileBackground时,TileBackground setter会在背景定义时更新Stroke和Fill。我已经成功测试了这个控件(在组合框数据模板之外)。

说到:

<DataTemplate x:Key="TileListItemRenderer">
    <Grid Width="225">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="24"/>
            <ColumnDefinition Width="75"/>
            <ColumnDefinition />
        </Grid.ColumnDefinitions>
        <local:MapTileBackgroundPreview Grid.Row="0" Grid.Column="0" Size="12,12" VerticalAlignment="Center" HorizontalAlignment="Center" TileBackground="{Binding /}"/>
        <Label Grid.Row="0" Grid.Column="1" Content="{Binding Name}" HorizontalAlignment="Left" VerticalAlignment="Center" FontWeight="Bold"/>
        <TextBlock Grid.Row="0" Grid.Column="2" Text="{Binding Description}" HorizontalAlignment="Stretch" VerticalAlignment="Top" TextWrapping="Wrap" />
    </Grid>
</DataTemplate>

所以我只创建一个形状和两个标签,将形状绑定到当前的MapTileBackground对象(组合框ItemSource是MapTileBackground对象的集合),标签绑定到Name和Description。

我的问题是形状总是被绘制为空(如在TileBackground中为null)并且永远不会调用setter。名称标签和描述TextBlock都按预期运行(显示正确的文本)。在我的调试尝试期间,我在预览对象上创建了一个id属性,该属性又调用TileBackground Setter并将其绑定到Id属性(避免当前对象绑定),同样,从不调用TileBackgroundId setter。我甚至添加了一个绑定到Id的新标签,看看它是否正常工作,并按预期显示id。以下是那些再次无效的变化。打开下拉列表时,永远不会设置TileBackgroundId或TileBackground属性。

    <DataTemplate x:Key="TileListItemRenderer">
        <Grid Width="225">
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto"/>
            </Grid.RowDefinitions>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="24"/>
                <ColumnDefinition Width="75"/>
                <ColumnDefinition />
            </Grid.ColumnDefinitions>
            <local:MapTileBackgroundPreview Grid.Row="0" Grid.Column="0" Size="12,12" VerticalAlignment="Center" HorizontalAlignment="Center" TileBackgroundId="{Binding Id}"/>
            <Label Grid.Row="0" Grid.Column="0" Content="{Binding Id}" HorizontalAlignment="Left" VerticalAlignment="Center" FontWeight="Bold"/>
            <Label Grid.Row="0" Grid.Column="1" Content="{Binding Name}" HorizontalAlignment="Left" VerticalAlignment="Center" FontWeight="Bold"/>
            <TextBlock Grid.Row="0" Grid.Column="2" Text="{Binding Description}" HorizontalAlignment="Stretch" VerticalAlignment="Top" TextWrapping="Wrap" />
        </Grid>
    </DataTemplate>

    public static readonly DependencyProperty TileBackgroundIdProperty = DependencyProperty.Register("TileBackgroundId", typeof(int), typeof(MapTileBackgroundPreview));

    public int TileBackgroundId
    {
        get
        {
            return (int)GetValue(TileBackgroundIdProperty);
        }
        set
        {
            SetValue(TileBackgroundIdProperty, value);
            TileBackground = TMapTileBackgroundTool.Get(value);
        }
    }

TMapTileBackgroundTool.Get()根据Id返回正确的对象。

我还在数据模板外测试了MapTileBackgroundPreview设置TileBackgroundId的实例。

有关正在发生的事情的任何想法?

1 个答案:

答案 0 :(得分:1)

不应该设置依赖项属性的CLR包装器的setter,因为WPF绑定引擎直接调用GetValueSetValue方法:

Setters not run on Dependency Properties?

Why are .NET property wrappers bypassed at runtime when setting dependency properties in XAML?

CLR包装器属性的getter和setter应分别调用GetValueSetValue方法。

如果要在设置依赖项属性时执行某些操作,则应注册回调:

public static readonly DependencyProperty TileBackgroundIdProperty = DependencyProperty.Register("TileBackgroundId", typeof(int), typeof(MapTileBackgroundPreview),
    new PropertyMetadata(0, new PropertyChangedCallback(TileBackgroundIdChanged)));

public int TileBackgroundId
{
    get
    {
        return (int)GetValue(TileBackgroundIdProperty);
    }
    set
    {
        SetValue(TileBackgroundIdProperty, value);
    }
}

private static void TileBackgroundIdChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
    MapTileBackgroundPreview ctrl = (MapTileBackgroundPreview)d;
    ctrl.TileBackground = TMapTileBackgroundTool.Get((int)e.NewValue);
}