Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

WPF MarkupExtension and RowDefinition results in NotImplementedException

The scenario: Create a MarkupExtension to replace Grid.Row=”0” by Grid.Row=”{namespace:ClassExtension GridRowName}” (same for column)

Xaml Code:

<Grid>
  <Grid.RowDefinitions>
    <RowDefinition Height="Auto" x:Name="TitleRow" />
    <RowDefinition Height="Auto" x:Name="LastNameRow" />
    <RowDefinition Height="Auto" x:Name="FirstNameRow" />
    <RowDefinition Height="Auto" x:Name="EmailRow" />
  </Grid.RowDefinitions>
  <Grid.ColumnDefinitions>
    <ColumnDefinition x:Name="LabelColumn" />
    <ColumnDefinition x:Name="ValueColumn" />
  </Grid.ColumnDefinitions>

    <Label Grid.Row="{me:GridDefinition Name=TitleRow}" Grid.ColumnSpan="2" FontWeight="Bold" FontSize="14" />
    <Label Grid.Row="{me:GridDefinition Name=LastNameRow}" Grid.Column="{me:GridDefinition Name=LabelColumn}" FontWeight="Bold" FontSize="14" />
</Grid>

The requirement:

  • Show XAML Errors when an incorrent GridRowName (or columnName) is used
  • Show no XAML errors when a correct GridRowName (or columnName) is used
  • When a Valid ColumnName is used for a Row declaration (and vica verca) a XAML error should be shown

The problem: Everything works fine for Grid.Column, but Grid.Row always throws an “Not Implemented Exception” at designtime (grid.row is underlined, grid.column is not). enter image description here

The rows and columns are both named correct, but row always shows an error. If we specify an invalid column name, the column shows an error (which is expected, so Grid.Column works fine!) enter image description here

As you can see, column works fine, but Rows don’t. The problem lies inside the MarkupExtension called GridDefinitionExtension:

[MarkupExtensionReturnType(typeof(int))]
public class GridDefinitionExtension : MarkupExtension
{
    public string Name { private get; set; }

    public override object ProvideValue(IServiceProvider serviceProvider)
    {
        var referenceExt = new Reference(Name);
        var definition = referenceExt.ProvideValue(serviceProvider);

        if (definition is DefinitionBase)
        {
            var grid = (definition as FrameworkContentElement).Parent as Grid;

            if (grid != null && definition is RowDefinition)
                return grid.RowDefinitions.IndexOf(definition as RowDefinition);

            if (grid != null && definition is ColumnDefinition)
                return grid.ColumnDefinitions.IndexOf(definition as ColumnDefinition);
        }

        // This Extension only works for DefinitionBase Elements.
        throw new NotSupportedException();
    }
}

The Exception is trown on the line:

var definition = referenceExt.ProvideValue(serviceProvider);

After looking inside the DLL from which this method is called I have found that the body of this ProvideValue method looks like this:

public override object ProvideValue(IServiceProvider serviceProvider)
{
  if (serviceProvider == null)
    throw new ArgumentNullException("serviceProvider");
  IXamlNameResolver xamlNameResolver = serviceProvider.GetService(typeof (IXamlNameResolver)) as IXamlNameResolver;
  if (xamlNameResolver == null)
    throw new InvalidOperationException(System.Xaml.SR.Get("MissingNameResolver"));
  if (string.IsNullOrEmpty(this.Name))
    throw new InvalidOperationException(System.Xaml.SR.Get("MustHaveName"));
  object obj = xamlNameResolver.Resolve(this.Name);
  if (obj == null)
  {
    string[] strArray = new string[1]
    {
      this.Name
    };
    obj = xamlNameResolver.GetFixupToken((IEnumerable<string>) strArray, true);
  }
  return obj;
}

I’ve simplified this ProvideValue method to only show the code which it is actually using in my scenario:

if (serviceProvider == null)
    throw new ArgumentNullException("serviceProvider");

IXamlNameResolver xamlNameResolver = serviceProvider.GetService(typeof(IXamlNameResolver)) as IXamlNameResolver;

object obj = xamlNameResolver.Resolve(this.Name);
if (obj == null)
{
    var strArray = new string[1]{ this.Name };
    obj = xamlNameResolver.GetFixupToken((IEnumerable<string>)strArray, true);
}
return obj;

Apparantly the Exception is thrown by the GetFixUpToken method, but the cause is the Resolve method. This Resolve method returns a valid object when lookup up the ColumnDefinition by its name, but it returns NULL when doing the exact same thing for a RowDefinition.

The Error thrown by GetFixUpToken is: “NotImplementedException”, which is expected since when looking at the sourcecode of the IXamlNameResolver (which in this case is of Type: XamlNameResolverImpl)

enter image description here

When looking at the source code of this XamlNameResolverImpl, you can see that the method “GetFixUpToken” is empty and throws a NotImplemented exception (look at http://dotnetinside.com/en/framework/Microsoft+Expression/Microsoft.Expression.WpfPlatform/WpfMarkupExtensionValueSetter)

public object GetFixupToken(IEnumerable<string> names, bool canAssignDirectly)
{
      throw new NotImplementedException();
}
public object GetFixupToken(IEnumerable<string> names)
{
      throw new NotImplementedException();
}

But the problem is, as I already said, is the Resolve call, which works fine for columndefinition but fails for rowdefinitions…:

Column: enter image description here

Row: enter image description here

At this point, I don't know what to do anymore...

Source code (example project) available at: http://www.frederikprijck.net/stuff/MarkupExtension.rar

like image 473
Frederik Prijck Avatar asked Oct 07 '13 18:10

Frederik Prijck


1 Answers

Here is a solution that, while different from your implementation, gets the job done.

Instead of a MarkupExtension, create an IValueConverter for use with a Binding:

public class GridDefinitionConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            var definition = value as DefinitionBase;
            int toReturn = 0;
            if (definition != null)
            {
                var grid = (definition as FrameworkContentElement).Parent as Grid;

                if (grid != null && definition is RowDefinition)
                    toReturn = grid.RowDefinitions.IndexOf(definition as RowDefinition);

                if (grid != null && definition is ColumnDefinition)
                    toReturn = grid.ColumnDefinitions.IndexOf(definition as ColumnDefinition);
            }
            return toReturn;
        }

        public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }

Then put this in your XAML:

<Grid.Resources>
    <me:GridDefinitionConverter x:Key="gridDefinitionConverter" />
</Grid.Resources>

And implement it like this:

<Label Grid.Row="{Binding ElementName=TitleRow, Converter={StaticResource gridDefinitionConverter}}" />
like image 50
David Meredith Avatar answered Oct 16 '22 00:10

David Meredith