我正在开发一个业务应用程序,使用 Silverlight 作为 UI,使用 WCF Web 服务作为后端。在数据库中我有许多查找表。当 WCF 服务返回业务对象时,其中一个属性包含查找表中的整行,而不仅仅是外键,因此在 UI 中我可以显示查找表中的描述等内容,而无需再次调用服务。我目前想做的是提供一个绑定(bind)到整个查找值列表的组合框并使其正确更新。我在本例中处理的业务对象称为 Session,查找称为 SessionType。
下面是组合框的定义。 DataContext 设置为 Session 的实例。我正在设置 ItemTemplate,因为组合框显示的不仅仅是字符串列表。
<ComboBox
x:Name="SessionTypesComboBox"
ItemTemplate="{StaticResource SessionTypeDataTemplate}"
ItemsSource="{Binding Source={StaticResource AllSessionTypes}}"
SelectedItem="{Binding Path=SessionType, Mode=TwoWay}"
/>
业务对象和查找表都通过 Web 服务异步加载。如果我什么都不做,组合框列表将填充 SessionTypes,但不会显示 Session 中的初始 SessionType 值。但是,如果组合框选择发生更改, session 将使用正确的 session 类型进行更新。
似乎发生的情况是 SelectedItem 绑定(bind)无法将 Session 中的 SessionType 与其在 SessionType 列表中的等效项相匹配。对象值相同,但引用不同。
我发现的解决方法是加载 Session 和 SessionTypes 列表,然后使用 SessionTypes 列表中相应的 SessionType 更新 Session 的当前 SessionType。如果我这样做,组合框就会正确显示。然而对我来说,这有一种不好的代码味道。因为所有内容都是异步加载的,所以我必须确定所有内容何时可用。我是这样做的:
在我的 Silverlight 用户控件的代码隐藏中:
// incremented every time we get data back during initial form load.
private volatile int m_LoadSequence = 0;
...
// Loaded event, called when the form is er... loaded.
private void UserControl_Loaded(object sender, RoutedEventArgs e)
{
// load session types
var sessionTypes = this.Resources["AllSessionTypes"] as Lookups.AllSessionTypes;
if (sessionTypes != null)
{
sessionTypes.DataLoadCompleted += (s, ea) =>
{
IncrementLoadSequence();
};
sessionTypes.LoadAsync();
}
// start loading another lookup table, same as above
// omitted for clarity
// set our DataContect to our business object (passed in when form was created)
this.LayoutRoot.DataContext = this.m_Session;
IncrementLoadSequence();
}
// This is the smelly part. This gets called by OnBlahCompleted events as web service calls return.
private void IncrementLoadSequence()
{
// check to see if we're expecting any more service calls to complete.
if (++m_LoadSequence < 3)
return;
// set lookup values on m_Session to the correct one in SessionType list.
// Get SessionType list from page resources
var sessionTypes = this.Resources["AllSessionTypes"] as Lookups.AllSessionTypes;
// Find the matching SessionType based on ID
this.m_Session.SessionType = sessionTypes.Where((st) => { return st.SessionTypeID == this.m_Session.SessionType.SessionTypeID; }).First();
// (other lookup table omitted for clarity)
}
所以基本上我有一个计数器,每次从网络服务获取数据时它都会递增。因为我期待 3 个东西(核心业务对象 + 2 个查找表),所以当计数器达到 3 时,我会匹配引用。
对我来说,这看起来很老套。我宁愿看到组合框指定 ValueMemberPath 和 SelectedValue 以将所选项目与列表中的项目相匹配。
任何人都可以看到更干净的方法吗?这种情况在商业应用程序中很常见,所以我确信一定有一种很好的方法来做到这一点。
最佳答案
杰夫,
为了确认我理解您的问题:数据绑定(bind)基础设施似乎无法识别您认为“相等”的两个对象实际上是相等的 - 因此最初的SelectedItem
设置不正确,因为数据绑定(bind)在 StaticResource
集合中找不到与 Session.SessionType
匹配的引用等于对象。
您可以通过“扁平化”引用来解决这个问题(即,您强制 Session.SessionType
在 Where((st).. .First()
代码。
我们有一个 similar problem .
Silverlight 不会仅仅因为您知道它们代表相同的数据而自动将来自不同“源”的两个对象“等同”,这确实是有道理的。就像您所说的“对象值相同但引用不同”。但是如何使数据绑定(bind)将它们等同起来呢?
我们想到/尝试过的事情:
在类上实现 .Equals()(在您的情况下为
SessionType
)在类上实现运算符 ==(在您的情况下为
SessionType
)实现
IEquatable
在类上(SessionType
在您的情况下)使集合仅包含
String
并绑定(bind)到字符串属性
但最终我们放弃了并使用与您相同的方法 - 从“集合”中“提取”正确的引用等于对象(在加载所有内容之后)并将其插入 SelectedItem
绑定(bind)属性。
我同意你关于代码气味的看法,并且怀疑一定有更好的解决方案。到目前为止,我们在属性访问器和无操作 IValueConverter
中的所有调试都没有找到解决方案 - 但如果我们找到了,我也会将其发布回此处。
关于wcf - 带有对象列表的数据绑定(bind) Silverlight 组合框 - 工作但丑陋,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/475537/