页面导航后Silverlight MVVM中的StackOverflowException

时间:2011-08-09 18:18:34

标签: silverlight-4.0

我正在尝试使用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对象。只是在黑暗中拍摄。

1 个答案:

答案 0 :(得分:0)

我认为问题在于您正在混合使用CanExecute和CanExecuteChanged。 CanExecuteChanged肯定会让某人调用CanExecute(调用CanExecuteChanged ......等等......)。

所以那些调用应该是分开的--CanExecute应该只返回一些值(bCanExecuteCache)。并且应该在特定事件/调用/案例上调用CanExecuteChanged。

以下是Silverlight论坛上可能对您有帮助的一些示例(显示使用CanExecuteChanged):concrete post | whole thread