Переключение UWP в Style Setter не работает

У меня проблема с созданием элемента управления xaml. Я пишу новый проект в VS 2015 в универсальном приложении. Я хочу создать сетку. В этой сетке я хочу иметь кнопку. В модели я указывается столбец (Уровень) и Строка. это мой код:

<ItemsControl Grid.Row="1" ItemsSource="{Binding Path=TechnologyList}">
    <ItemsControl.ItemsPanel>
        <ItemsPanelTemplate>
            <Grid>
                <Grid.RowDefinitions>
                    <RowDefinition Height="10*"/>
                    <RowDefinition Height="10*"/>
                    <RowDefinition Height="10*"/>
                    <RowDefinition Height="10*"/>
                    <RowDefinition Height="10*"/>
                    <RowDefinition Height="10*"/>
                    <RowDefinition Height="10*"/>
                    <RowDefinition Height="10*"/>
                    <RowDefinition Height="10*"/>
                    <RowDefinition Height="10*"/>
                </Grid.RowDefinitions>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="14*"/>
                    <ColumnDefinition Width="14*"/>
                    <ColumnDefinition Width="14*"/>
                    <ColumnDefinition Width="14*"/>
                    <ColumnDefinition Width="14*"/>
                    <ColumnDefinition Width="14*"/>
                    <ColumnDefinition Width="14*"/>
                </Grid.ColumnDefinitions>
            </Grid>
        </ItemsPanelTemplate>
    </ItemsControl.ItemsPanel>
    <ItemsControl.ItemContainerStyle>
        <Style TargetType="Control">
            <Setter Property="Grid.Column" Value="{Binding Level}" />
            <Setter Property="Grid.Row" Value="{Binding Row}" />
        </Style>
    </ItemsControl.ItemContainerStyle>
    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <Button Content="{Binding Name}"/>
        </DataTemplate>
    </ItemsControl.ItemTemplate>
</ItemsControl>

Я получаю сообщение об ошибке в строке <Setter Property="Grid.Column" Value="{Binding Level}" /> Ошибка: Исключение из HRESULT: 0x8000FFFF (E_UNEXPECTED) находилось в редакторе не в запущенном коде. Что не так? В "старом" WPF все было в порядке, но в Universal App для Windows 10 у меня есть ошибка. Может кто-нибудь мне помочь?

Ответ 1

Как отмечено в разделе Заметки о миграции на странице Setter.Value на MSDN, UWP/Windows Runtime не поддерживает привязки в стиле сеттеры.

Windows Presentation Foundation (WPF) и Microsoft Silverlight поддерживала возможность использования выражения Binding для предоставления значения для сеттера в стиле. Windows Runtime не поддерживает привязку использование для Setter.Value(привязка не будет оцениваться, и сеттер никакого эффекта, вы не получите ошибок, но вы не получите желаемого результата или). Когда вы конвертируете стили XAML из WPF или Silverlight XAML, замените любые выражения привязки выражения строками или объектами, которые устанавливают значения или рефакторинг значений в качестве общей разметки {StaticResource} значения расширения, а не полученные значения привязки.

Обходной путь может быть вспомогательным классом со связанными свойствами для исходных путей привязок. Он создает привязки в коде позади в PropertyChangedCallback свойства helper:

public class BindingHelper
{
    public static readonly DependencyProperty GridColumnBindingPathProperty =
        DependencyProperty.RegisterAttached(
            "GridColumnBindingPath", typeof(string), typeof(BindingHelper),
            new PropertyMetadata(null, GridBindingPathPropertyChanged));

    public static readonly DependencyProperty GridRowBindingPathProperty =
        DependencyProperty.RegisterAttached(
            "GridRowBindingPath", typeof(string), typeof(BindingHelper),
            new PropertyMetadata(null, GridBindingPathPropertyChanged));

    public static string GetGridColumnBindingPath(DependencyObject obj)
    {
        return (string)obj.GetValue(GridColumnBindingPathProperty);
    }

    public static void SetGridColumnBindingPath(DependencyObject obj, string value)
    {
        obj.SetValue(GridColumnBindingPathProperty, value);
    }

    public static string GetGridRowBindingPath(DependencyObject obj)
    {
        return (string)obj.GetValue(GridRowBindingPathProperty);
    }

    public static void SetGridRowBindingPath(DependencyObject obj, string value)
    {
        obj.SetValue(GridRowBindingPathProperty, value);
    }

    private static void GridBindingPathPropertyChanged(
        DependencyObject obj, DependencyPropertyChangedEventArgs e)
    {
        var propertyPath = e.NewValue as string;

        if (propertyPath != null)
        {
            var gridProperty =
                e.Property == GridColumnBindingPathProperty
                ? Grid.ColumnProperty
                : Grid.RowProperty;

            BindingOperations.SetBinding(
                obj,
                gridProperty,
                new Binding { Path = new PropertyPath(propertyPath) });
        }
    }
}

Вы бы использовали их в XAML следующим образом:

<ItemsControl.ItemContainerStyle>
    <Style TargetType="ContentPresenter">
        <Setter Property="local:BindingHelper.GridColumnBindingPath" Value="Level"/>
        <Setter Property="local:BindingHelper.GridRowBindingPath" Value="Row"/>
    </Style>
</ItemsControl.ItemContainerStyle>

Для простого обходного пути для абсолютного позиционирования (т.е. связывания свойств Canvas.Left и canvas.Top) см. этот ответ.

Ответ 2

Хотел добавить мой опыт этой идеи BindingHelper от @clemens. Это аккуратное решение, но я обнаружил, что при нацеливании ListViewItem привязка не будет обращаться к базовой модели представления. После отладки я обнаружил, что мне нужно убедиться, что привязка относится к самому ListViewItem и связанному с ним свойству .Content, чтобы он мог правильно ссылаться на модель представления элементов.

Моим конкретным вариантом использования было установить свойство IsTabStop для ListViewItem на основе значения модели представления:

private static void BindingPathPropertyChanged(DependencyObject obj,
    DependencyPropertyChangedEventArgs e)
{
    if (e.NewValue is string propertyPath)
    {
        var binding = new Binding
        {  
            Path = new PropertyPath($"Content.{propertyPath}"),
            Mode = BindingMode.OneWay,
            RelativeSource = new RelativeSource
            {
                Mode = RelativeSourceMode.Self
            }
        };
        BindingOperations.SetBinding(obj, Control.IsTabStopProperty, binding);
    }
}

Надеюсь, это поможет, если у кого-то еще есть проблема.