代码之家  ›  专栏  ›  技术社区  ›  Ken Smith

WPF/Silverlight/XAML中的强类型数据绑定?

  •  28
  • Ken Smith  · 技术社区  · 15 年前

    对于数据绑定如何与XAML一起工作,我最大的不满之一是没有强类型数据绑定的选项。换句话说,在c中,如果你想访问一个不存在的对象上的属性,你不会从intellisense得到任何帮助,如果你坚持忽略intellisense,编译器会对你发牢骚,不会让你继续——我怀疑这里的很多人会同意这是一件非常好的事情。但是在XAML数据绑定中,您的操作没有网络。您可以绑定到 任何东西 即使它不存在。事实上,考虑到XAML数据绑定的奇怪语法,以及我自己的经验,绑定到确实存在的东西比绑定到不存在的东西要复杂得多。我更可能把数据绑定语法弄错,而不是把它弄好;而且,我花在排除XAML数据绑定故障上的比较时间很容易使e我花在微软堆栈的任何其他部分上的时间(包括笨拙和烦人的WCF,如果你能相信的话)。其中大部分(不是全部)都要追溯到这样一个事实:如果没有强类型的数据绑定,无论是IntelliSense还是编译器都无法提供任何帮助。

    所以我想知道的是:为什么微软不给我们一个 选项 要有强类型的数据绑定:就像在vb6中一样,如果我们真的是受虐狂,我们可以将任何对象变为变量,但大多数时候使用普通类型的变量是有意义的。有什么理由微软不能这样做吗?

    这是我的意思的一个例子。在C中,如果属性“usrid”不存在,则IntelliSense将发出警告,如果尝试此操作,则编译器将发出错误:

    string userID = myUser.UsrID;
    

    但是,在XAML中,您可以随心所欲地执行以下操作:

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

    无论是intellisense、编译器,还是(最令人惊讶的是)运行时的应用程序本身,都不会给您任何提示,表明您做错了什么。现在,这是一个简单的例子,但是任何处理复杂对象图和复杂用户界面的实际应用程序都会有很多等价的场景,这些场景一点也不简单,也不容易排除故障。即使在你第一次让它正常工作之后,如果你重构你的代码并改变你的C属性名,你就是索尔。所有的东西都会编译,运行时不会出错,但是什么都不会工作,让您在整个应用程序中搜索和轻敲,试图找出损坏的地方。

    一个可能的建议(从我的头顶上,我还没有考虑过)可能是这样的:

    对于逻辑树的任何部分,可以在XAML中指定它所期望的对象的数据类型,如下所示:

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

    这可能会在.g.cs文件中生成一个强类型的ObservableCollection<Person>typedDataContext属性。所以在你的代码中:

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

    我发了篇博文 here 这就解释了我对WPF/XAML数据绑定的不满,以及我认为是一种更好的方法。有什么理由不能这样做吗?有人知道微软是否计划解决这个问题(按照我的建议,或者希望是更好的建议)?

    10 回复  |  直到 13 年前
        1
  •  4
  •   Grant BlahaErath    15 年前

    ken,c将受益于引用propertyinfo类的简明语法元素。propertyinfo结构是在编译时定义的静态对象,因此为对象上的每个属性提供唯一键。然后可以在编译时验证属性。

    唯一的问题是将对象的实例视为数据类型的奇怪,因为强类型是对类型强制执行的,而不是对类型的值。传统上,编译器不强制数据值,而是依赖运行时代码来检查数据。大多数情况下,甚至不可能在编译时验证数据,但反射是其中一种边缘情况,至少在这种情况下是可能的。

    或者,编译器可以为每个属性创建一个新的数据类型。我可以想象创建了许多类型,但这将启用属性绑定的编译时强制。

    一种思考的方法是,与之前的系统相比,CLR引入了另一个级别的反射。现在它被用来做一些相当令人印象深刻的事情,比如数据绑定。但它的实现仍处于元数据级别,生成了编译器针对每种数据类型的一种报告。我认为增长C的一种方法是促进元数据的编译时检查。

    在我看来,有人可以开发一个编译工具来添加反射级验证。新的智能感知就是这样。一般来说,要发现注定要与propertyinfos进行比较的字符串参数是很困难的,但这并非不可能。可以定义一个新的数据类型,如“propertystring”,它清楚地标识将来将与propertyinfos进行比较的参数。

    不管怎样,我感觉到你的痛苦。我追查了很多拼错的房产名称参考资料。老实说,WPF中有很多与反射有关的恼怒。一个有用的实用程序是一个WPF强制检查程序,它确保所有静态控件构造函数都已就位、属性定义正确、绑定准确、键正确等。可以执行的验证列表很长。

    如果我还在为微软工作,我可能会尝试这样做。

        2
  •  10
  •   Charlie    15 年前

    在Visual Studio 2010中,将对数据绑定提供IntelliSense支持。这似乎就是你抱怨的真正原因,因为数据绑定 强类型。您只是在运行时才知道绑定是否成功,而且常常是安静地失败,而不是出现异常。当绑定失败时,WPF通过调试跟踪转储解释性文本,您可以在Visual Studio输出窗口中看到这些跟踪。

    除了缺乏智能感知支持和一些奇怪的语法问题,数据绑定也做得很好(至少在我看来)。要获得调试数据绑定的更多帮助,我将查看BEA的可爱文章。 here .

        3
  •  9
  •   Dale Anderson    13 年前

    这是我对XAML最大的抱怨!不让编译器强制有效的数据绑定是一个大问题。我并不真正关心IntelliSense,但我确实关心缺乏重构支持。

    在WPF应用程序中更改属性名或类型是危险的-使用内置的重构支持不会更新XAML中的数据绑定。对名称进行搜索和替换是危险的,因为它可能会更改您不打算更改的代码。浏览查找结果的列表是一件非常痛苦和耗时的事情。

    MVC有一段时间具有强类型的视图——MVC Contrib项目为MVC1提供了这些视图,而MVC2则以本机方式提供了这些视图。XAML将来必须支持这一点,特别是在“敏捷”项目中使用时,应用程序的设计会随着时间的推移而发展。我还没有看过.NET 4.0/VS2010,但我希望这次体验远比现在好!

        4
  •  6
  •   fluxray    14 年前

    我觉得XAML有点像以前的HTML。我无法想象在10多年之后,我会用这种方式进行编程:手工输入open-close标签,因为我没有一个成熟的GUI来定义样式、绑定和模板。我强烈支持你的观点,肯。我觉得很奇怪,为什么有那么多人支持MVVM而没有抱怨调试XAML的痛苦。命令绑定和数据绑定是非常好的概念,我一直以这种方式设计我的WinForm应用程序。然而,Xaml绑定解决方案和其他一些Xaml问题(或者缺少复杂的功能)确实是一个很大的失败,与之相比,它使开发和调试非常困难,并且使代码非常不可读。

        5
  •  2
  •   Bryce Kahle    15 年前

    听起来您所要求的不会需要任何框架更改,而且可能已经在Vs2010中修复了(我没有安装它)。

    当指定数据类型时,XAML设计器需要IntelliSense才能在DataTemplate中进行绑定,该数据类型已经可以这样做:

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

    我同意在这种情况下使用IntelliSense将是一个有益的更改,但您的其他建议似乎忽略了能够在运行时更改数据上下文并使用不同类型的数据模板来唯一地呈现它们的要点。

        6
  •  2
  •   Jerry Nixon    13 年前

    这真的是一个解决你想要的问题的方法!

    但是,它不是一个内置的、本机的框架解决方案。是的,我想这就是我们在这里真正想要的。也许我们以后会得到。

    在这个过程中,如果你对类型进行了严格的限制,这就解决了这个问题!

    使用此转换器:

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

    然后,像这样限制数据类型:

    <phone:PhoneApplicationPage.Resources>
    
        <!-- let's 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吗?当然,您甚至不能将cvs的源代码设置为整数,所以这将永远失败。但这确实证明了这一点。太糟糕了,Silverlight不支持x:type,或者我可以做一些类似converterParameter=x:type sys:int16的事情,这会很好。

    另外,该XAML应该是非侵入性的,因此您应该能够在没有任何风险的情况下实现它。如果数据类型是您不希望的类型,那么调试器将中断,您可以因为破坏了自己的规则而自责。:)

    同样,我知道这有点奇怪,但它可以满足你的需要——事实上,我在编码的时候一直在玩它,也许我甚至可以使用它。我喜欢,现在只是设计/调试时间。但是,听着,我不是想把它卖给你。

    我只是在玩,如果这句话太多,那就尽情享受我的努力吧;)

    PS:您还可以创建一个类型为的附加属性,您可以使用相同的方法。您可以将它附加到您的cvs或其他任何东西上,比如x:requiredType=“System.Int16”,在属性的行为中,您可以重复转换器逻辑。同样的效果,可能同样数量的代码-但如果你认真的话,另一个可行的选择。

        7
  •  1
  •   Jerry Nixon    13 年前

    好吧,我无法抗拒,这是附属性方法:

    以下是XAML:

    <phone:PhoneApplicationPage.Resources>
    
        <!-- let's 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's 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's value's Type must match
            var _PropValue = _PropInfo.GetValue(_Element, null);
            if (_PropValue != null)
                if (_PropValue.GetType() != _Type)
                    System.Diagnostics.Debugger.Break();
        }
    }
    

    祝你好运!只是玩得开心。

        8
  •  0
  •   user112889    15 年前

    此外,如果在调试项目时打开了输出窗口,则VS将通知您任何数据绑定错误,即控件绑定到的属性不存在。

        9
  •  0
  •   satish    13 年前

    伙计们, 肯和格兰特想说的是……

    如果有一个XAML,我可以像p.userid)那样做,其中p是customer类型的dataContext