我对XAML的数据绑定方式的一个最大的烦恼是,没有强力键入数据绑定的选项。换句话说,在C#中,如果你想访问一个不存在的对象的属性,你将无法获得Intellisense的任何帮助,如果你坚持忽略Intellisense,编译器会抱怨你并赢得'让你继续 - 我怀疑这里的很多人会同意这是一件非常好的事情。但是在XAML数据绑定中,您无需网络即可运行。您可以绑定到任何,即使它不存在。实际上,鉴于XAML数据绑定的奇怪语法,并且根据我自己的经验,绑定到存在的东西比不存在的东西更复杂。我更有可能让我的数据绑定语法错误而不是正确;而我花在排除XAML数据绑定上的比较时间很容易使我花在微软堆栈的任何其他部分上的时间相形见绌(包括尴尬和恼人的WCF,如果你能相信的话)。其中大部分(并非全部)都回到了这样一个事实,即如果没有强类型的数据绑定,我无法从Intellisense或编译器获得任何帮助。
所以我想知道的是:为什么MS至少没有给我们一个选项来拥有强类型的数据绑定:有点像在VB6中,我们可以使任何对象成为变体如果我们真的是受虐狂,但大多数时候使用普通的类型变量是有意义的。 MS有什么理由不能这样做吗?
这是我的意思的一个例子。在C#中,如果属性“UsrID”不存在,如果您尝试这样做,您将收到来自Intellisense的警告和编译器的错误:
string userID = myUser.UsrID;
但是,在XAML中,您可以随心所欲地执行此操作:
<TextBlock Text="{Binding UsrID}" />
智能感知,编译器或(最令人惊讶的)应用程序本身在运行时都不会给你任何提示你做错了什么。现在,这是一个简单的例子,但任何处理复杂对象图和复杂UI的实际应用程序都会有大量等效的场景,这些场景根本不简单,也不易于排除故障。即使您第一次使它正常工作,如果您重构代码并更改C#属性名称,那么您就是SOL。所有东西都会编译,并且它会在没有错误的情况下运行,但是没有任何东西可以工作,让你在整个应用程序中搜寻并啄食,试图弄清楚什么是坏的。
一个可能的建议(在我的头顶,我没有想过)可能会是这样的:
对于逻辑树的任何部分,您可以在XAML中指定它期望的对象的DataType,如下所示:
<Grid x:Name="personGrid" BindingDataType="{x:Type collections:ObservableCollection x:TypeArgument={data:Person}}">
这可能会生成一个强类型的ObservableCollection&lt; Person&gt; .g.cs文件中的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数据绑定的挫败感,以及我认为更好的方法。有什么理由不起作用吗?有没有人知道MS是否计划解决这个问题(按照我的建议,或者希望是更好的问题)?
答案 0 :(得分:10)
Visual Studio 2010中将提供对数据绑定的IntelliSense支持。这似乎是您的投诉真正归结为,因为数据绑定是强类型。你只是在运行时才发现绑定是否成功,而且往往是安静地失败而不是嘈杂的异常。当绑定失败时,WPF会通过调试跟踪转储解释性文本,您可以在Visual Studio输出窗口中看到它。
除了缺乏智能感知支持和一些奇怪的语法问题之外,数据绑定也做得很好(至少在我看来)。有关调试数据绑定的更多帮助,我会查看Bea可爱的文章here。
答案 1 :(得分:9)
这是我对XAML最大的抱怨!没有编译器强制执行有效的数据绑定是一个大问题。我并不关心intellisense,但我关心缺乏重构支持。
在WPF应用程序中更改属性名称或类型是危险的 - 使用内置的重构支持不会更新XAML中的数据绑定。对名称进行搜索和替换是危险的,因为它可能会更改您不打算更改的代码。通过一个查找结果列表是一个痛苦的屁股和耗时。
MVC在一段时间内一直有强类型视图 - MVC contrib项目为MVC1提供了它们,而MVC2本身提供了它们。 XAML将来必须支持这一点,特别是如果它用于应用程序设计随着时间的推移而发展的“敏捷”项目中。我没有看过.NET 4.0 / VS2010,但我希望它的体验远比它好!
答案 2 :(得分:6)
我觉得xaml就像旧日的HTML一样。我无法想象,经过10年多的时间,我正在以这种方式进行编程:手动键入open-close标签,因为我无法使用成熟的GUi来定义样式和绑定以及模板。肯,我强烈支持你的观点。我觉得非常奇怪为什么有这么多人支持MVVM而没有一个抱怨调试xaml的痛苦。命令绑定和数据绑定是非常好的概念,我一直在设计我的winform应用程序。但是,xaml绑定解决方案连同其他一些xaml问题(或缺乏复杂功能)在VS中确实是一个很大的失败。它使开发和调试非常困难,并使代码非常难以理解。
答案 3 :(得分:4)
Ken,C#将受益于一个简洁的语法元素,用于引用PropertyInfo类。 PropertyInfo结构是在编译时定义的静态对象,因此为对象上的每个Property提供唯一键。然后可以在编译时验证属性。
唯一的问题是将对象实例视为数据类型的奇怪之处,因为强制类型是对类型强制执行的,而不是类型的值。传统上,编译器不强制执行数据值,而是依赖运行时代码来检查其数据。大多数情况下甚至不可能在编译时验证数据,但反射是至少可能的边缘情况之一。
或者,编译器可以为每个属性创建新的数据类型。我可以想象创建了很多类型,但这将启用属性绑定的编译时执行。
考虑它的一种方法是CLR引入的反射级别与之前的系统相比是另一个级别。它现在被用来做一些相当令人印象深刻的东西,如数据绑定。但它的实现仍处于元数据级别,编译器会为每种数据类型生成一种报告。我认为增长C#的一种方法是推广元数据以编译时间检查。
在我看来,有人可以开发一个编译工具来添加反射级别验证。新的intellisense就像那样。一般发现与PropertyInfos进行比较的字符串参数会很棘手,但这并非不可能。可以定义一个新的数据类型,如“PropertyString”,可以清楚地识别将来与PropertyInfos进行比较的参数。
无论如何,我感受到了你的痛苦。我已经追查了许多错误拼写的属性名称引用。老实说,WPF中有很多与反射相关的烦恼。一个有用的实用程序是WPF强制检查器,它确保所有静态控件构造函数都已就位,正确定义了属性,绑定是准确的,正确的密钥等。可以执行一长串的验证。
如果我还在为微软工作,我可能会尝试这样做。
答案 4 :(得分:2)
听起来你要求的不需要任何框架更改,并且可能已在VS 2010中修复(我没有安装它)。
当您指定DataType时,XAML设计器需要IntelliSense才能在DataTemplate中进行绑定,这样就可以了:
<DataTemplate DataType="{x:Type data:Person}">
...
</DataTemplate>
我同意在这种情况下使用IntelliSense将是一个有用的更改,但您的其他建议似乎忽略了能够在运行时更改DataContexts并使用DataTemplates用于不同类型来唯一地呈现它们。
答案 5 :(得分:2)
这确实是您想要的解决方案!
但是,它不是一个内置的本机框架解决方案。是的,我认为这就是我们在这里真正想要的。也许我们以后会得到它。
在此期间,如果您已经设定限制类型,这就解决了这个问题!
使用此转换器:
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'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的Source设置为Integer,因此这将始终失败。但它证明了这一点是肯定的。 Silverlight不支持{x:Type}或者我可以做类似ConverterParameter = {x:Type sys:Int16}这样的事情太糟糕了。
另外,XAML应该是非侵入性的,因此您应该能够毫无风险地实现这一点。如果数据类型永远不是您想要的那样,那么调试器将会中断,您可以自行破解自己的规则。 :)
同样,我知道这有点时髦,但它可以做你想要的 - 事实上我在编码时一直在玩它,也许我甚至可以使用它。我喜欢它只是设计时/调试。但是,听着,我不是想把它卖给你。
我只是玩得开心,如果这是太多的语法,只是享受我的努力;)
PS:您还可以创建Type类型的附加属性,您可以使用相同的方式。您可以将它附加到您的CVS或其他任何内容,例如x:RequiredType =“System.Int16”,并且在属性的行为中,您可以重复转换器逻辑。相同的效果,可能是相同数量的代码 - 但如果你是认真的话,另一个可行的选择。
答案 6 :(得分:1)
好的,我无法抗拒,这是附属物的方法:
这是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();
}
}
祝你好运!只是玩得开心。
答案 7 :(得分:0)
另外,如果在调试项目时打开输出窗口,VS会通知您任何数据绑定错误,即Control绑定的属性不存在。
答案 8 :(得分:0)
球员, Ken和Grant试图说的是......
如何关于我可以像p.UserId那样拥有XAMl&gt;其中P是Type Customer的DataContext
答案 9 :(得分:0)