7

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

Frederik Prijck
  • 1,424
  • 10
  • 16
  • 1
    Just a recommendation. Use the actual code in your post (like what you did with the xaml) instead of screenshots of the code. Some of those screenshots are pretty small when resized in SO. – Chris Klepeis Oct 07 '13 at 20:18
  • I used screenshot because I wanted to point out all the issues: Xaml Underline, c# runtime values, ... There's a few screenshots I replaced by code, since you're right. I guess I started using screenshots and forgot to use text where possible. Btw, I added the source project for those reason :-) – Frederik Prijck Oct 07 '13 at 21:22

1 Answers1

1

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}}" />
David Meredith
  • 387
  • 4
  • 10
  • Apparantly, this solution does NOT validate at design time: http://prntscr.com/1w31id As you can see, TitleRow2 does not exist. That's the whole point I wanna achieve. My solution works fine, as I said. The only problem is at design time the developer needs to get feedback. (which does not work for RowDefinitions in my example... ColumnDefinitions work fine) – Frederik Prijck Oct 08 '13 at 18:53
  • My solution works at design time, which yours does not. It seems to be a problem in WPF that the rows are not initialized when your MarkupExtension needs to access them. Your screenshot does not show you using the Converter I recommended, just binding directly to the RowDefinition, which will not work. Make sure you include the converter and create the IValueConverter. – David Meredith Oct 09 '13 at 00:08
  • Oh god, I actually made the Converter but forgot to use it. I just wanted to quickly give it a test, since it's not using MarkupExtension I do not realy see it as a solution to the problem. Apparantly it does work. So now I idd have a working workaround. I'm worrying about performance in this case, I prefer to not use bindings for several reasons, but I guess there's no way this can work without Binding? Thank you so much for your help, it's not that I've never used Value (or multiValue) converters... I just was focussed on the MarkupExtension :( – Frederik Prijck Oct 09 '13 at 05:36
  • Btw I've not marked your answer as Answer yet. I prefer to give it some time to get response about the actual problem. – Frederik Prijck Oct 09 '13 at 05:37
  • Sure. I'm curious to know if anyone has an answer for that, too, but I suspect it's just a quirk of WPF Grid initialization. – David Meredith Oct 09 '13 at 14:01
  • It's so sad to see default code throwing a notimplementedException... This code is actually used in some cases as u can see, which is inacceptable imo. But I'll give it a few more days before accepting ur version :) – Frederik Prijck Oct 10 '13 at 17:15