构建 Silverlight 企业应用程序的冒险经历 - 第 25 部分
构建 Silverlight 企业应用程序的冒险经历 - 第 25 部分
在前一集中,我讨论了如何使用 enum
来封装数据库中的 char
值,这里以 Gender enum
为例。虽然这很酷,但它确实给 Silverlight 中的数据绑定带来了一个问题,我想与您分享。部分解释将我们带入反射领域(这将是一个很酷的博客标题:)),然后进入一些很酷的绑定技巧,最终以一点失望告终。所以如果你已经感到沮丧,现在就停止阅读吧 :-P。
故事
首先有一个 BussinessObjectBase
。这个类处理很多关于在服务和客户端之间传输数据的事情。它还有助于跟踪属性更改并通知它们。这是一个非常通用的类,它作为我们所有“业务”对象的基类。
在这种情况下,假设有一个 PersonBase
类,它派生自 BusinessObjectBase
。PersonBase
是基于一些元数据生成的,所以我们不想修改这个源代码。PersonBase
类有一个名为 Gender
的属性,其类型为 string
。
然后是 Person
类,它是生成的,但旨在用于自定义代码,所以我们将在那里编写一些代码,使我们的 Gender
可用作 Gender
而不是 string
。
我们可以简单地在 Person
类中编写如下内容
private char GetGender()
{
string gender = base.Gender;
if (gender.Length == 0)
{
return DefaultGender;
}
return gender[0];
}
public Gender GenderValue
{
get
{
return (Gender)GetGender();
}
set
{
base.Gender = ((char)value).ToString();
}
}
我们的意图是隐藏 Gender
属性的基本实现,并拥有我们自己的实现,该实现返回实际的 Gender enum
。到目前为止没有什么特别的。
下一步是在数据绑定场景中使用这个新属性。这是 XAML 中的绑定语句的样子
{Binding Path=Gender, Mode=TwoWay}
现在,如果您尝试使用像这样的绑定语句并运行代码,会发生什么?您会得到一个 AmbiguousMatchException
,原因是绑定引擎无法区分 PersonBase.Gender
和 Person.Gender
。
什么?但我们想要隐藏 PersonBase.Gender
,对吗? 绝对是的,但这两个属性都是 public
,所以实际上都是可用的。
反思“新”属性
为了更好地理解为什么会发生这种情况,我决定编写一些反射代码
object person = newPerson();
object personBase = newPersonBase();
Type personType = person.GetType();
PropertyInfo personGenderProperty = personType.GetProperty("Gender");
MessageBox.Show(personGenderProperty.GetValue(person, null).ToString());
实际上尝试通过反射获取 Gender
属性的值会抛出 AmbiguousMatchException
,就像绑定到它时一样。 实际上从 Visual Studio 的即时窗口调用 personType.GetProperties
会返回两个属性。 然后这个异常突然变得有意义了。
可能看起来有点尴尬的是两个属性都存在。如果我尝试编写 string gender = somePerson.Gender
,其中 somePerson
的类型是 Person
,那么它将无法编译,因为我无法隐式转换它,这证明了基本属性实际上是隐藏的。 尽管如此,仍然需要提供该属性,因为派生类需要能够调用基类中的属性。
仍然尝试绑定到它
尽管如此,我还是需要找到一种方法来绑定到这个属性。我想出了一些简单的解决方案
- 使基本属性受到保护
- 重命名其中一个属性以完全消除问题
- 找到一种方法来区分数据绑定中的两个属性
第一个解决方案对我不起作用,因为我无法修改此代码(记住,它是生成的?)。
第二个解决方案意味着我必须在派生类中重命名该属性,这似乎不太好。
第三个解决方案希望渺茫,但我必须尝试一下。 我实际上找到了用于这种情况的语法,但是它的文档记录不太完善,所以我做了一些实验以更好地理解它。 我开始在 XAML 中编写此内容
{Binding Path=(local:Person.Gender)}
这失败了,并出现一个异常,告诉我这是属性的无效值。 我感到惊讶和困惑,因为我读到其他人正在使用它,并且实际上在文档中是这样的。 进一步的调查告诉我,这仅适用于依赖属性。 我为此构建了一个小例子,它实际上运行得很好,但是......
...拥有一个包含所有管道的依赖属性最好通过从 DependencyObject
派生您的类来完成(其中包含诸如 GetValue
和 SetValue
之类的东西)。 我显然不能从 DependencyObject
派生 Person
或 PersonBase
,因为我已经从另一个类派生了它们。 这意味着我现在必须在派生类中重命名该属性 :(。
您可以在此处找到绑定到新属性的示例。 希望对您有所帮助。 总的来说,这是一次很棒的学习经历。 只是很遗憾它没有为我带来更好的解决方案。