我正在尝试使用MVVM Light创建一个简单页面,用于添加/编辑/删除联系人以及用户从软件的其他部分发送传真。 MainPage有一个简单的标题,包含一些公司信息和两个链接,一个链接到“联系人”页面,另一个链接到“传真历史记录”页面;大多数MainPage页面是一个导航框架,它是加载页面的链接的目标。我使用的是VS 2010 / .NET 4.0 / SL 4.0。
“联系人”页面有两个数据网格,一个用于搜索联系人结果('gridContacts'),另一个用于选定联系人(将接收传真的联系人 - 'gridSelected')。有一个删除按钮,用户可以删除搜索网格中所选行的联系人。还有一个发送传真按钮,用于将传真发送到gridContacts网格中的那些联系人。
问题是,如果我在搜索网格中选择一行,导航到传真历史记录,然后返回,我会在导航发生之前收到StackOverflowException。如果我首先导航到另一页,然后返回,然后选择一行,我将在那时获得异常。我发现当我删除删除按钮时,“发送传真”按钮也会导致相同的异常。在这两个按钮上,我能够解决问题,只需编写代码隐藏的所有内容;显然这不太理想,所以我想学习如何使用MVVM。
当我使用“删除”按钮逐步解决问题的代码时,我看到“删除”按钮绑定到的DelegateCommand中发生了异常。在CanExecute方法中,第一次通过时,object参数正确为Contact(因为网格绑定到了Contacts列表)。它触发CanExecuteChanged(这个,新的EventArgs())作为代码逻辑的一部分 - 但随后返回到函数中,但是传入的参数为null。这是无限循环变得明显的地方:CanExecute在循环中触发并且在传入的参数之间交替使用Contact对象和null。堆栈跟踪显示对CanExecute的调用之间的外部代码,但这是唯一存在的方法。无限循环最终导致StackOverflowException。
以下是相关代码:
public bool CanExecute(object parameter) //parameter alternates between Contact object and null
{
bool temp = _funcCanExecute(parameter); //_funcCanExecute is set to CanDelete in the DelegateCommand constructor.
if (_bCanExecuteCache != temp)
{
_bCanExecuteCache = temp;
if (CanExecuteChanged != null)
{
CanExecuteChanged(this, new EventArgs()); //this line somehow leads to another call into here --> infinite loop
}
}
return _bCanExecuteCache;
}
搜索网格ItemSource通过Locator绑定到VM,如下所示:
<sdk:DataGrid AutoGenerateColumns="False" x:Name="gridContacts" IsTabStop="False" Grid.Row="3" ItemsSource="{Binding Path=SearchContactsViewModel.Contacts, Mode=OneWay, Source={StaticResource Locator}}" SelectionMode="Single">
以下是Delete按钮Command和CommandParameter(整体数据上下文设置为DataContext =“{Binding Source = {StaticResource Locator},Path = SearchContactsViewModel}”):
<Button x:Name="btnDelete" Style="{StaticResource ButtonStyle}" Command="{Binding Path=SearchContactsViewModel.DeleteContactCommand, Source={StaticResource Locator}}" Grid.Row="1" Grid.Column="1"
CommandParameter="{Binding Path=SelectedItem, ElementName=gridContacts}" HorizontalAlignment="Right">
以下是SearchContactsViewModel中的简化代码:
public DelegateCommand DeleteContactCommand
{
get;
private set;
}
private bool CanDeleteContact(object param)
{
Contact c = param as Contact;
if (c == null)
return false;
return true;
}
private void DeleteContact(object param)
{
int contactID = ((Contact)param).ID;
_ServiceAgent.DeleteContact(contactID,
(s, e) =>
{
if (!CheckErrorAndResult(e.Error, e.Result, e.errMsg))
return;
SearchContacts();
MessageBox.Show("Delete successful.");
});
}
以下是在VM中连接命令的代码行:
DeleteContactCommand = new DelegateCommand(DeleteContact, CanDeleteContact);
但是,如果我用“(x)=&gt; true”替换CanDeleteContact进行测试,我不会遇到问题。
如果我能提供更多信息,请告诉我。我想先用删除按钮来解决问题,并希望将相同的解决方案应用于发送传真按钮。
谢谢, Ĵ
***更新9/6/2011:我注意到在Silverlight导航中,每次都会创建一个新的页面实例。那么这会解释为什么这个问题只在导航后发生?为什么在我认为无限循环中处理的值在null和非null值之间交替?也许旧页面现在可以为null,但是新页面正在显示并且是正确的,但两者都绑定到相同的静态VM对象。只是在黑暗中拍摄。
答案 0 :(得分:0)
我认为问题在于您正在混合使用CanExecute和CanExecuteChanged。 CanExecuteChanged肯定会让某人调用CanExecute(调用CanExecuteChanged ......等等......)。
所以那些调用应该是分开的--CanExecute应该只返回一些值(bCanExecuteCache
)。并且应该在特定事件/调用/案例上调用CanExecuteChanged。
以下是Silverlight论坛上可能对您有帮助的一些示例(显示使用CanExecuteChanged):concrete post | whole thread