WPF MarkupExtension и RowDefinition приводят к исключению NotImplementedException

Сценарий: создайте MarkupExtension, чтобы заменить Grid.Row=”0” на Grid.Row=”{namespace:ClassExtension GridRowName}" (то же самое для столбца)

Код XAML:

<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>

Требование:

  • Показать ошибки XAML при использовании неподходящего GridRowName (или columnName)
  • Не показывать ошибок XAML при использовании правильного GridRowName (или columnName)
  • Когда для объявления строки используется допустимое имя столбца (и vica verca), должна отображаться ошибка XAML

Проблема: все отлично работает для Grid.Column, но Grid.Row всегда выдает "Не реализованное исключение" во время разработки (grid.row подчеркнут, grid.column нет).

Строки и столбцы имеют правильные имена, но строка всегда показывает ошибку. Если мы укажем неверное имя столбца, в столбце будет отображена ошибка (что ожидается, поэтому Grid.Column работает отлично!)

Как видите, колонка работает нормально, а строки - нет. Проблема заключается в MarkupExtension под названием 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();
    }
}

Исключение брошено на строку:

var definition = referenceExt.ProvideValue(serviceProvider);

После просмотра библиотеки DLL, из которой вызывается этот метод, я обнаружил, что тело этого метода ProvideValue выглядит следующим образом:

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;
}

Я упростил этот метод ProvideValue, чтобы показать только код, который он фактически использует в моем сценарии:

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;

Очевидно, Исключение выдается методом GetFixUpToken, но причина - метод Resolve. Этот метод Resolve возвращает действительный объект при поиске ColumnDefinition по его имени, но он возвращает NULL, когда делает то же самое для RowDefinition.

Ошибка, выдаваемая GetFixUpToken: "Not ImplementedException", что ожидается после просмотра исходного кода IXamlNameResolver (который в данном случае имеет тип: XamlNameResolverImpl)

Просматривая исходный код этого XamlNameResolverImpl, вы видите, что метод "GetFixUpToken" пуст и выдает исключение Not Implemented (посмотрите 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();
}

Но проблема, как я уже сказал, заключается в вызове Resolve, который прекрасно работает для определения столбцов, но не работает для определений строк…:

Колонка:

Строка:

На данный момент я не знаю, что делать дальше...

Исходный код (пример проекта) доступен по адресу: http://www.frederikprijck.net/stuff/MarkupExtension.rar

1 ответ

Решение

Вот решение, которое, в отличие от вашей реализации, выполняет свою работу.

Вместо MarkupExtension создайте IValueConverter для использования с привязкой:

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();
        }
    }

Затем поместите это в свой XAML:

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

И реализовать это так:

<Label Grid.Row="{Binding ElementName=TitleRow, Converter={StaticResource gridDefinitionConverter}}" />
Другие вопросы по тегам