Search code examples
c#wpfxamldatatemplate

WPF How to Create a DataTemplate Which has Canvas As Content By Code?


I have the data template defined by XAML as below:

<DataTemplate x:Key="PointPushPinItemTemplate">
    <mapControl:MapItem mapControl:MapPanel.Location="{Binding}" >
        <Canvas>
            <Path StrokeThickness="2" Fill="Aqua">
                <Path.Data>
                    <EllipseGeometry RadiusX="10" RadiusY="10"/>
                </Path.Data>
            </Path>
        </Canvas>
    </mapControl:MapItem>
</DataTemplate>

I use below code to create it, but failed:

private DataTemplate PushPinPointDataTemplate()
{
    var pointItemFactory = new FrameworkElementFactory(typeof(MapItem));
    pointItemFactory.SetValue(MapPanel.LocationProperty, new Binding());
    var pointCanvas = new Canvas();
    pointCanvas.Children.Add(new Path
    {
        StrokeThickness = 2.0,
        Fill = new SolidColorBrush(Colors.Yellow),
        Data = new EllipseGeometry {RadiusX = 3, RadiusY = 3}
    });

    //below line always throw exception that FrameworkElementFactory.SetValue can't set a visual value
    pointItemFactory.SetValue(MapItem.ContentProperty, pointCanvas);
    return new DataTemplate
    {
        DataType = typeof(Location),
        VisualTree = pointItemFactory
    };
}

How to achieve this? Previousely, I achieved this if there is no Canvas, but for this case, the Canvas to be the content of a content control. ps: mapControl:MapItem is a UI element which inherited from ListBoxItem mapControl:MapPanel.Location is a attached property


Solution

  • You need to create factories for the Canvas and the Path as well:

    private DataTemplate PushPinPointDataTemplate()
    {
        var pointItemFactory = new FrameworkElementFactory(typeof(MapItem));
        pointItemFactory.SetValue(MapPanel.LocationProperty, new Binding("."));
    
        var pathFactory = new FrameworkElementFactory(typeof(Path));
        pathFactory.SetValue(Path.StrokeThicknessProperty, 2.0);
        pathFactory.SetValue(Path.FillProperty, Brushes.Yellow);
        pathFactory.SetValue(Path.DataProperty, new EllipseGeometry { RadiusX = 3, RadiusY = 3 });
    
        var pointCanvasFactory = new FrameworkElementFactory(typeof(Canvas));
        pointCanvasFactory.AppendChild(pathFactory);
    
        pointItemFactory.AppendChild(pointCanvasFactory);
        return new DataTemplate
        {
            DataType = typeof(Location),
            VisualTree = pointItemFactory
        };
    }
    

    Note that using a FrameworkElementFactory is a deprecated way to programmatically create templates though: https://msdn.microsoft.com/en-us/library/system.windows.frameworkelementfactory(v=vs.110).aspx.

    The recommended way to programmatically create a template is to load XAML from a string or a memory stream using the Load method of the XamlReader class.