Markup extension does not work at design time

1k views Asked by At

I have markup extensions to allow me use binding and cell template in GridView at the same time. It works fine at runtime, but it does not work at design time, wondering if there is anything I could do to fix that. I've tested returning simple string instead of DataTemplate just to make sure, that custom markup extensions work in general at design time - and it worked, so it should be somehow related to the fact, that DataTemplate is returned.

[MarkupExtensionReturnType(typeof(DataTemplate))]
public class TemplateBuilderExtension : MarkupExtension
{
    public string Path { get; set; }

    public TemplateBuilderExtension() { }
    public TemplateBuilderExtension(string path)
    {
        Path = path;
    }

    // Here be dirty hack.
    internal static string TagPath { get; private set; }

    public override object ProvideValue(IServiceProvider serviceProvider)
    {
        TagPath = Path;
        var resourceExt = new StaticResourceExtension("GridViewTextCell");

        // This line causes the evaluation of the Tag as the resource is loaded.        
        var baseTemplate = (DataTemplate)resourceExt.ProvideValue(serviceProvider);

        return baseTemplate;
    }
}

[MarkupExtensionReturnType(typeof(BindingExpression))]
public class TemplateBuilderTagExtension : MarkupExtension
{
    public TemplateBuilderTagExtension()
    {           
    }

    public override object ProvideValue(IServiceProvider serviceProvider)
    {
        return new Binding(TemplateBuilderExtension.TagPath);
    }
}

<Window.Resources>
    <DataTemplate x:Shared="false" x:Key="GridViewTextCell">
        <Border BorderBrush="Blue" BorderThickness="1">
            <TextBlock Text="{markupExtensions:TemplateBuilderTag}"></TextBlock>
        </Border>
    </DataTemplate>
</Window.Resources> 
<Grid>      
    <ListView SelectedIndex="5">        
        <ListView.View>
            <GridView>
                <GridViewColumn Header="Id" CellTemplate="{markupExtensions:TemplateBuilder Id}" Width="300"/>
            </GridView>
        </ListView.View>
    </ListView>        
</Grid>

Update: I've simplified code to be as short as possible, in real situation there are multiple GridView's through application, each grid contains multiple columns and those columns should reuse same template and also I can't use DataGrid because of performance issues.

3

There are 3 answers

7
dev hedgehog On

Your extensions do not make much sense. That all can be written like this:

<Window.Resources>
    <sys:String x:Key="path">thatPath<sys:String/>

    <DataTemplate x:Shared="false" x:Key="GridViewTextCell">
        <Border BorderBrush="Blue" BorderThickness="1">
            <TextBlock Text="{Binding Path={StaticResource path}}"></TextBlock>
        </Border>
    </DataTemplate>
</Window.Resources> 
<Grid>      
    <ListView SelectedIndex="5">        
        <ListView.View>
            <GridView>
                <GridViewColumn Header="Id" CellTemplate="{StaticResource GridViewTextCell}" Width="300"/>
            </GridView>
        </ListView.View>
    </ListView>        
</Grid>

Binding itself is also an extension. You sort of trying to extend a extension...

How about you leave it be and use the normal approach instead? :)

0
Giedrius On

So I've decided to use following approach:

<GridViewColumn Header="TestColumn">
    <GridViewColumn.CellTemplate>
        <DataTemplate>
            <ContentPresenter Content="{Binding TestProperty}" ContentTemplate="{StaticResource GridViewTextCell}" />
        </DataTemplate>
    </GridViewColumn.CellTemplate>
</GridViewColumn>

So, wrapping template into ContentPresenter allows to use binding I wish and reuse template.

Following this, it is possible to go further, I have working concept, but decided not to use it (at least yet). Idea is to automatically generate columns, by getting public properties of ItemsSource, which might be further improved by creating attribute, which would define header description and width:

public class ExtendedListView : ListView
{
    public static readonly DependencyProperty AutoColumnsProperty =
               DependencyProperty.Register("AutoColumns", typeof(bool), typeof(ExtendedListView), new FrameworkPropertyMetadata(true, OnAutoColumnsPropertyChanged));

    protected override void OnItemsSourceChanged(IEnumerable oldValue, IEnumerable newValue)
    {
        base.OnItemsSourceChanged(oldValue, newValue);

        OnAutoColumnsPropertyChanged(this, new DependencyPropertyChangedEventArgs(AutoColumnsProperty, false, true));
    }

    private static void OnAutoColumnsPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var newValue = (bool)e.NewValue;

        var dataGrid = (ExtendedListView)d;

        if (newValue)
        {
            dataGrid.AddAutoColumns();
        }
        else
        {
            dataGrid.DeleteAutoColumns();
        }           
    }

    Type GetBaseTypeOfEnumerable(IEnumerable enumerable)
    {
        if (enumerable == null)
        {
            return null;                
        }

        var genericEnumerableInterface = enumerable
            .GetType()
            .GetInterfaces().FirstOrDefault(i => i.GetGenericTypeDefinition() == typeof(IEnumerable<>));

        if (genericEnumerableInterface == null)
        {
            return null;
        }

        var elementType = genericEnumerableInterface.GetGenericArguments()[0];

        if (!elementType.IsGenericType)
        {
            return elementType;
        }

        return elementType.GetGenericTypeDefinition() == typeof(Nullable<>)
            ? elementType.GetGenericArguments()[0]
            : elementType;
    }

    private readonly HashSet<GridViewColumn> autoGeneratedColumns = new HashSet<GridViewColumn>();

    private void AddAutoColumns()
    {
        var gridView = View as GridView;

        if (gridView == null)
        {
            throw new Exception("Not a grid view");
        }

        var itemType = GetBaseTypeOfEnumerable(ItemsSource);

        if (itemType == null)
        {
            throw new Exception("Could not resolve item type");
        }

        var properties = itemType.GetProperties();
        foreach (var property in properties)
        {
            var gridViewColumn = new GridViewColumn
            {
                CellTemplate = CreateTemplate(property.Name),
                Header = property.Name,
                Width = 100
            };

            gridView.Columns.Add(gridViewColumn);

            autoGeneratedColumns.Add(gridViewColumn);
        }           
    }

    private DataTemplate CreateTemplate(string path)
    {
        string xamlTemplate = string.Format("<DataTemplate><ContentPresenter Content=\"{{Binding {0}}}\" ContentTemplate=\"{{StaticResource GridViewTextCell}}\" /></DataTemplate>", path);

        var context = new ParserContext
        {
            XamlTypeMapper = new XamlTypeMapper(new string[0])
        };

        context.XmlnsDictionary.Add("", "http://schemas.microsoft.com/winfx/2006/xaml/presentation");
        context.XmlnsDictionary.Add("x", "http://schemas.microsoft.com/winfx/2006/xaml");

        var template = (DataTemplate)XamlReader.Parse(xamlTemplate, context);

        return template;
    }

    private void DeleteAutoColumns()
    {
        var gridView = View as GridView;

        if (gridView == null)
        {
            throw new Exception("Not a grid view");
        }

        for (int columnIndex = gridView.Columns.Count - 1; columnIndex >= 0; --columnIndex)
        {
            if (autoGeneratedColumns.Contains(gridView.Columns[columnIndex]))
            {
                gridView.Columns.RemoveAt(columnIndex);
            }
        }       
    }

    public bool AutoColumns
    {
        get { return (bool)GetValue(AutoColumnsProperty); }
        set { SetValue(AutoColumnsProperty, value); }
    }
}
2
Thomas Flinkow On

I came across this just very recently as well. Turns out, somehow I had disabled project code in the WPF designer. Enabling project code made the markup extension work as desired.

enter image description here

taken from learn.microsoft.com.