Подтвердить что ты не робот

Сильно типизированная привязка данных в WPF/Silverlight/XAML?

Одно из моих самых больших домашних животных заставляет задуматься о том, как привязка данных к работе с XAML заключается в том, что нет возможности сильно набирать ваши данные. Другими словами, в С#, если вы хотите получить доступ к объекту на объекте, которого не существует, вы не получите никакой помощи от Intellisense, и если вы настаиваете на игнорировании Intellisense, компилятор схватит вас и выиграет ' я позволю вам продолжить - и я подозреваю, что многие люди здесь согласятся с тем, что это очень хорошая вещь. Но в привязке данных XAML вы работаете без сети. Вы можете привязываться ко всему, даже если он не существует. Действительно, учитывая странный синтаксис привязки данных XAML и, учитывая мой собственный опыт, гораздо сложнее связать с чем-то, что существует, чем с чем-то, что этого не происходит. У меня гораздо больше шансов, что мой синтаксис привязки данных неправильный, чем для правильной работы; и сравнительное время, которое я провожу для устранения неполадок с привязкой к базе данных XAML, легко затмевает время, которое я трачу с любой другой частью стека Microsoft (включая неудобный и раздражающий WCF, если вы можете в это поверить). И большинство из них (не все) восходит к тому, что без строго типизированных привязок данных я не могу получить никакой помощи ни от Intellisense, ни от компилятора.

Итак, что я хочу знать: почему MS не дает нам возможности иметь строго типизированные привязки данных: вроде как в VB6, мы могли бы сделать любой объект вариантом, если бы мы были действительно мазохистскими, но большую часть времени имело смысл использовать обычные типизированные переменные. Есть ли причина, почему MS не может этого сделать?

Вот пример того, что я имею в виду. В С#, если свойство "UsrID" не существует, вы получите предупреждение от Intellisense и ошибку от компилятора, если вы попробуете это:

string userID = myUser.UsrID;

Однако в XAML вы можете сделать все, что хотите:

<TextBlock Text="{Binding UsrID}" />

И ни Intellisense, ни компилятор, ни (самое удивительное) приложение во время выполнения не даст вам никакого намека на то, что вы сделали что-то не так. Теперь это упрощенный пример, но любое приложение реального мира, которое имеет дело со сложными графами объектов и сложным интерфейсом, будет иметь множество эквивалентных сценариев, которые не являются простыми и не являются простыми для устранения неполадок. И даже после того, как вы впервые сработали правильно, вы SOL, если вы реорганизуете свой код и измените свои имена свойств С#. Все будет скомпилировано, и оно будет работать без ошибок, но ничего не получится, оставив вас охотиться и проталкивать свой путь через все приложение, пытаясь выяснить, что сломалось.

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

Для любой части логического дерева вы можете указать в XAML DataType объекта, который он ожидает, например:

<Grid x:Name="personGrid" BindingDataType="{x:Type collections:ObservableCollection x:TypeArgument={data:Person}}">

Это могло бы создать строго типизированный ObservableCollection <Person> Свойство TypedDataContext в файле .g.cs. Итак, в вашем коде:

// This would work
personGrid.TypedDataContext = new ObservableCollection<Person>(); 

// This would trigger a design-time and compile-time error
personGrid.TypedDataContext = new ObservableCollection<Order>(); 

И если вы получили доступ к этому TypedDataContext через элемент управления в сетке, он будет знать, к какому объекту вы пытались получить доступ.

<!-- It knows that individual items resolve to a data:Person -->
<ListBox ItemsSource="{TypedBinding}">
    <ListBox.ItemTemplate>
       <DataTemplate>
           <!--This would work -->
           <TextBlock Text="{TypedBinding Path=Address.City}" />
           <!-- This would trigger a design-time warning and compile-time error, since it has the path wrong -->
           <TextBlock Text="{TypedBinding Path=Person.Address.City} />
       </DataTemplate>
    </ListBox.ItemTemplate>
</ListBox>

Я сделал запись в блоге здесь, которая объясняет больше о моих разочарованиях с привязкой данных WPF/XAML, и, я думаю, это будет значительно лучший подход. Есть ли причина, почему это не может работать? И кто-нибудь знает, планирует ли MS решить эту проблему (в соответствии с моим предложением или, надеюсь, лучше)?

4b9b3361

Ответ 1

Кен, С# выиграет от сжатого синтаксического элемента для ссылки на класс PropertyInfo. Структуры PropertyInfo являются статическими объектами, определенными во время компиляции, и поэтому предоставляют уникальный ключ для каждого свойства объекта. Затем свойства могут быть проверены во время компиляции.

Единственная проблема с этим - странность обращения с экземпляром объекта как типа данных, при условии, что сильная типизация применяется для типов, а не для значений типа. Традиционно компиляторы не применяют значения данных и вместо этого полагаются на код времени выполнения для проверки своих данных. В большинстве случаев его даже невозможно проверить данные во время компиляции, но отражение является одним из тех крайних случаев, когда это возможно.

В качестве альтернативы, компилятор может создать новый тип данных для каждого свойства. Я мог представить себе множество типов, которые будут созданы, но это позволит скомпилировать время принудительного связывания свойств.

Один из способов подумать о том, что CLR представил уровень отражения, который был еще одним уровнем по сравнению с системами, которые ему предшествовали. Теперь он используется для создания довольно впечатляющих материалов, таких как привязка данных. Но его реализация по-прежнему находится на уровне метаданных, генерируется отчет от компилятора для каждого типа данных. Я предположил, что одним из способов роста С# было бы продвижение метаданных для компиляции проверки времени.

Мне кажется, что кто-то может разработать инструмент компиляции, который добавит эту проверку уровня отражения. Новый intellisense подобен этому. Было бы сложно определить параметры строки, которые предназначены для сравнения с PropertyInfos, но это не невозможно. Можно определить новый тип данных, например "PropertyString", который четко идентифицирует параметры, которые будут сопоставляться с PropertyInfos в будущем.

В любом случае, я чувствую вашу боль. Я преследовал множество ссылок на названия с орфографическими ошибками. Честно говоря, в WPF есть много раздражений, связанных с размышлением. Полезной утилитой будет проверка правильности WPF, которая гарантирует, что все ваши конструкторы статического контроля на месте, ваши атрибуты правильно определены, привязки точны, правильные ключи и т.д. Существует длинный список проверок, которые могут быть выполнены.

Если бы я все еще работал в Microsoft, я бы, наверное, попытался это сделать.

Ответ 2

В Visual Studio 2010 будет поддержка IntelliSense для привязки данных. Похоже, что ваша жалоба действительно сводится к тому, что привязка данных строго типизирована. Вы просто не узнаете до тех пор, пока не успеете привязать привязку, и чаще всего это происходит не так, как если бы это было тихо, а не с шумным исключением. Когда сбой не выполняется, WPF выдает пояснительный текст через трассировки отладки, которые вы можете видеть в окне вывода Visual Studio.

Помимо отсутствия поддержки IntelliSense и нескольких странных проблем с синтаксисом, привязка данных довольно хорошо (по крайней мере, на мой взгляд). Для получения дополнительной поддержки отладки привязок данных я бы посмотрел прекрасную статью Bea здесь.

Ответ 3

Это моя самая большая проблема с XAML! Отсутствие компилятора для обеспечения правильной привязки данных является большой проблемой. Меня не волнует intellisense, но я НЕ забочусь об отсутствии поддержки рефакторинга.

Изменение имен или типов свойств опасно в приложении WPF - использование встроенной поддержки рефакторинга не будет обновлять привязки данных в XAML. Выполнение поиска и замены по имени опасно, так как оно может изменить код, который вы не намеревались изменить. Прохождение списка результатов поиска - это боль в заднице и много времени.

MVC имеет строго типизированные представления в течение некоторого времени - проект MVC contrib, предоставленный им для MVC1, и MVC2 предоставляет их изначально. XAML должен поддерживать это в будущем, особенно если он используется в "гибких" проектах, где дизайн приложения развивается с течением времени. Я не смотрел .NET 4.0/VS2010, но надеюсь, что опыт намного лучше, чем есть!

Ответ 4

Я чувствую, что xaml является sth как old-days html. я не могу себе представить, что через 10 лет я программирую таким образом: вводя теги open-close вручную, потому что у меня нет зрелого GUI для определения стилей, привязки и шаблонов. Я решительно поддерживаю ваше мнение, Кен. Мне очень странно, почему так много людей поддерживают MVVM без единого жалования на боль в отладке xaml. Связывание команд и привязка данных - очень хорошие концепции, и я таким образом разрабатывал свои приложения winform. Однако решение привязки xaml вместе с некоторой другой проблемой xaml (или отсутствием сложной функции) действительно является большим провалом в VS. Это сделало разработку и отладку очень сложной, и сделал код очень нечитаемым.

Ответ 5

Похоже, то, о чем вы просите, не потребует каких-либо изменений в каркасе и, возможно, уже исправлено в VS 2010 (у меня его нет).

Дизайну XAML требуется IntelliSense для привязок в DataTemplate, когда вы указываете DataType, который уже возможен следующим образом:

<DataTemplate DataType="{x:Type data:Person}">
...
</DataTemplate>

Я согласен с тем, что IntelliSense в этом случае будет полезным изменением, но ваши другие предложения, похоже, не позволяют определить DataContexts во время выполнения и использовать DataTemplates для разных типов, чтобы сделать их уникальными.

Ответ 6

Это действительно решение того, что вы хотите!

Но это не встроенное, родное, рамочное решение. Да, я думаю, что мы все действительно хотим здесь. Может быть, мы это получим позже.

В промежуточный период, если вы мертвы для ограничения типа, это решает это!

Использование этого преобразователя:

public class RequireTypeConverter : System.Windows.Data.IValueConverter
{
    public object Convert(object value, Type targetType, 
        object parameter, System.Globalization.CultureInfo culture)
    {
        if (value == null)
            return value;

        // user needs to pass a valid type
        if (parameter == null)
            System.Diagnostics.Debugger.Break();

        // parameter must parse to some type
        Type _Type = null;
        try
        {
            var _TypeName = parameter.ToString();
            if (string.IsNullOrWhiteSpace(_TypeName))
                System.Diagnostics.Debugger.Break();
            _Type = Type.GetType(_TypeName);
            if (_Type == null)
                System.Diagnostics.Debugger.Break();
        }
        catch { System.Diagnostics.Debugger.Break(); }

        // value needs to be specified type
        if (value.GetType() != _Type)
            System.Diagnostics.Debugger.Break();

        // don't mess with it, just send it back
        return value;
    }

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

Затем ограничьте свой DataType следующим образом:

<phone:PhoneApplicationPage.Resources>

    <!-- let pretend this is your data source -->
    <CollectionViewSource x:Key="MyViewSource" Source="{Binding}"/>

    <!-- validate data type - start -->
    <converters:RequireTypeConverter x:Key="MyConverter" />
    <TextBlock x:Key="DataTypeTestTextBlock" 
        DataContext="{Binding Path=.,
            Source={StaticResource MyViewSource},
            Converter={StaticResource MyConverter}, 
            ConverterParameter=System.Int16}" />
    <!-- validate data type - end -->

</phone:PhoneApplicationPage.Resources>

Посмотрите, как мне требуется, чтобы CollectionViewSource имел System.Int16? Конечно, вы даже не можете установить Source CVS в Integer, так что это всегда будет терпеть неудачу. Но это точно доказывает. Это слишком плохо, что Silverlight не поддерживает {x: Тип}, или я мог бы сделать что-то вроде ConverterParameter = {x: Тип sys: Int16}, что было бы хорошо.

Кроме того, XAML должен быть неинтрузивным, поэтому вы можете реализовать это без какого-либо риска. Если тип данных когда-либо был тем, чего вы не хотите, тогда отладчик сломается, и вы можете взломать себя за нарушение собственного правила.:)

Опять же, я знаю, что это немного напуганно, но он делает то, что вы хотите - на самом деле я играл с ним во время его кодирования, и, возможно, я даже использовал его. Мне нравится, что это только дизайн-время/debuggin. Но, послушай, я не пытаюсь это продать тебе.

Я просто развлекаюсь, если это слишком синтаксис, просто наслаждайся моими усилиями;)

PS: вы также можете создать прикрепленное свойство типа Type, которое вы можете использовать таким же образом. Вы можете прикрепить его к CVS или чему-либо еще, например x: RequiredType = "System.Int16", а в поведении свойства вы можете просто повторить логику конвертера. Тот же эффект, вероятно, такой же объем кода, но еще один жизнеспособный вариант, если вы серьезно.

Ответ 7

Хорошо, я не удержался, здесь подход Attached Property:

Здесь XAML:

<phone:PhoneApplicationPage.Resources>

    <!-- let pretend this is your data source -->
    <CollectionViewSource 
        x:Key="MyViewSource" Source="{Binding}"
        converters:RestrictType.Property="Source"                  
        converters:RestrictType.Type="System.Int16" />

</phone:PhoneApplicationPage.Resources>

Здесь код свойства:

public class RestrictType
{
    // type
    public static String GetType(DependencyObject obj)
    {
        return (String)obj.GetValue(TypeProperty);
    }
    public static void SetType(DependencyObject obj, String value)
    {
        obj.SetValue(TypeProperty, value);
        Watch(obj);
    }
    public static readonly DependencyProperty TypeProperty =
        DependencyProperty.RegisterAttached("Type",
        typeof(String), typeof(RestrictType), null);

    // property
    public static String GetProperty(DependencyObject obj)
    {
        return (String)obj.GetValue(PropertyProperty);
    }
    public static void SetProperty(DependencyObject obj, String value)
    {
        obj.SetValue(PropertyProperty, value);
        Watch(obj);
    }
    public static readonly DependencyProperty PropertyProperty =
        DependencyProperty.RegisterAttached("Property",
        typeof(String), typeof(RestrictType), null);

    private static bool m_Watching = false;
    private static void Watch(DependencyObject element)
    {
        // element must be a FrameworkElement
        if (element == null)
            System.Diagnostics.Debugger.Break();

        // let not start watching until each is set
        var _PropName = GetProperty(element);
        var _PropTypeName = GetType(element);
        if (_PropName == null || _PropTypeName == null)
            return;

        // we will not be setting this up twice
        if (m_Watching)
            return;
        m_Watching = true;

        // listen with a dp so it is a weak reference
        var _Binding = new Binding(_PropName) { Source = element };
        var _Prop = System.Windows.DependencyProperty.RegisterAttached(
            "ListenToProp" + _PropName,
            typeof(object), element.GetType(),
            new PropertyMetadata((s, e) => { Test(s); }));
        BindingOperations.SetBinding(element, _Prop, _Binding);

        // run now in case it is already set
        Test(element);
    }

    // test property value type
    static void Test(object sender)
    {
        // ensure element type (again)
        var _Element = sender as DependencyObject;
        if (_Element == null)
            System.Diagnostics.Debugger.Break();

        // the type must be provided
        var _TypeName = GetType(_Element);
        if (_TypeName == null)
            System.Diagnostics.Debugger.Break();

        // convert type string to type
        Type _Type = null;
        try
        {
            _Type = Type.GetType(_TypeName);
            if (_Type == null)
                System.Diagnostics.Debugger.Break();
        }
        catch { System.Diagnostics.Debugger.Break(); }

        // the property name must be provided
        var _PropName = GetProperty(_Element);
        if (string.IsNullOrWhiteSpace(_PropName))
            System.Diagnostics.Debugger.Break();

        // the element must have the specified property
        var _PropInfo = _Element.GetType().GetProperty(_PropName);
        if (_PropInfo == null)
            System.Diagnostics.Debugger.Break();

        // the property value Type must match
        var _PropValue = _PropInfo.GetValue(_Element, null);
        if (_PropValue != null)
            if (_PropValue.GetType() != _Type)
                System.Diagnostics.Debugger.Break();
    }
}

Удачи! Просто развлекайтесь.

Ответ 8

Кроме того, если вы открываете окно вывода при отладке своего проекта, VS сообщит вам о любых ошибках привязки данных, то есть о том, что свойство, связанное с элементом управления, не существует.

Ответ 9

Ребята,  Что Кен и грант пытаются сказать...

Как использовать XAMl, где я могу сделать, например p.UserId) > где P - это DataContext клиента типа