代码后台设置用户控件的DataContext

3
这应该很简单,但它会让VS2008出现严重的问题。
我正在尝试使用MVVM的WPF,虽然我已经开发了约15年,拥有计算机科学学位,但对此还是一名新手。在当前客户端,我需要使用VB.Net。
我已经重命名了自己的变量并删除了代码中的一些干扰,所以如果不是100%的语法完美,请原谅我!您可能真的不需要代码来理解问题,但为了帮助您理解,我将其包含在内。
我有一个非常简单的MainView.xaml文件:
<Window x:Class="MyApp.Views.MainView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Main Window" Height="400" Width="800" Name="MainWindow">

<Button Name="Button1">Show Grid</Button>
<StackPanel Name="teststack" Visibility="Hidden"/>

</Window>

我也有一个名为DataView的用户控件,其中包含一个数据网格(DataGrid):
<UserControl x:Class="MyApp.Views.DataView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:WpfToolkit="http://schemas.microsoft.com/wpf/2008/toolkit" >
<Grid>
    <WpfToolkit:DataGrid 
        ItemsSource="{Binding Path=Entries}" SelectionMode="Extended">
    </WpfToolkit:DataGrid>
</Grid>
</UserControl>

DataView用户控件的构造函数通过将其绑定到视图模型来设置DataContext,如下所示:

Partial Public Class DataView

    Dim dataViewModel As ViewModels.DataViewModel

    Public Sub New()
        InitializeComponent()

        dataViewModel = New ViewModels.DataViewModel
        dataViewModel.LoadDataEntries()
        DataContext = dataViewModel
    End Sub

End Class

DataView的视图模型如下所示(ViewModelBase中没有太多内容):

Public Class DataViewModel
    Inherits ViewModelBase

Public Sub New()
End Sub

Private _entries As ObservableCollection(Of DataEntryViewModel) = New ObservableCollection(Of DataEntryViewModel)
Public ReadOnly Property Entries() As ObservableCollection(Of DataEntryViewModel)
    Get
        Return _entries
    End Get
End Property

Public Sub LoadDataEntries()

    Dim dataEntryList As List(Of DataEntry) = DataEntry.LoadDataEntries()
    For Each dataentry As Models.DataEntry In dataEntryList
        _entries.Add(New DataEntryViewModel(dataentry))
    Next

    End Sub
End Class

现在,如果我在XAML中实例化这个UserControl,它可以正常工作。当我运行代码时,网格显示出来并正常填充。
然而,该网格加载数据需要很长时间,我想在按钮点击后通过编程方式创建此用户控件,而不是在XAML中静态实例化网格。我想实例化用户控件,并将其作为StackPanel控件的子代插入其中:
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs) Handles Button1.Click

    Dim dataView As New DataView
    teststack.Children.Add(dataView)

End Sub

当我执行这个操作时,一旦Button1_Click完成,我的应用程序就会锁定,开始消耗内存,并且CPU占用率约为50%。
我没有正确地实例化我的UserControl吗?所有的问题似乎都归结于DataEntry构造函数中的DataContext赋值。如果我注释掉它,应用程序按预期工作(当然,网格中没有任何内容)。
如果我将此代码块移动到Button1_Click中(基本上是将DataEntry的构造函数代码向上移动一级),应用程序仍然会失败:
dataViewModel = New ViewModels.DataViewModel
dataViewModel.LoadDataEntries()
dataView.DataContext = dataViewModel

我被难住了。有人能给我一些提示,告诉我可能哪里出了问题,或者如何调试我的应用程序陷入了无限循环吗?

非常感谢。


网格中有很多数据吗?当锁定后,网格是否完成填充并显示?在滚动时是否有任何延迟(来自虚拟化)? - Will Eddins
网格实际上只有大约4000条记录,但需要大约5或6秒钟才能加载。因此,我不想以声明方式加载它,因为这样用户必须等待数据加载,而且可能永远不会查看它(因为他们只在单击某个按钮时才能看到它)。锁定后,网格没有完成和显示。我必须停止调试。如果我尝试从Button1_Click中实例化DataView,则只会出现此问题。如果我在XAML中创建DataView,则可以正常工作(尽管如上所述,需要大约5或6秒钟来读取数据源并填充数据网格)。 - TimH
3个回答

1
你遇到的问题根本原因可能是你加载的数据量太大,或者加载数据的效率不高。话虽如此,导致应用程序卡死的原因是在加载数据时锁定了界面线程。
我认为,在第一个案例中,数据加载已经被转移到另一个线程来进行。而在第二个案例中,你在界面线程上实例化控件,结果所有的构造函数和加载逻辑都在当前线程(也就是界面线程)上执行。如果你将这个工作转移到另一个线程上,你应该会看到与第一个案例类似的结果。

从XAML实例化DataView,它需要大约5秒钟才能加载(对于我的目的来说有点慢,但仍然不是那么糟糕)。如果我从代码后台实例化DataView,VS2008永远无法成功加载它,会消耗大约500MB的RAM,而且几分钟后我必须停止调试,因为它显然没有做任何事情。所以,我认为我不只是看到“它很慢”的问题。我认为我看到的是“VS2008肯定处于无限循环中,永远不会回来”。您认为使用第二个线程仍然有帮助吗?我真的可以通过这种方式破坏UI吗? - TimH
谢谢Rich。在第二个线程中加载数据不起作用 - 请参见我的评论,开始于“这不起作用”。 - TimH

1

我最终放弃了在UserControl实例化期间(无论是在XAML还是代码中)设置DataContext的尝试。现在,我在UserControl中的一个事件(IsVisibleChanged,我相信)中加载数据并设置UserControl的DataContext。当我在XAML中实例化UserControl时,我将其可见性设置为隐藏。当单击Button1时,我将UserControl的可见性设置为可见。因此,UserControl弹出并加载其数据,并设置DataContext。看起来可以工作,但也似乎非常笨拙。 :-( 感谢大家的帮助!


-1
如果只是因为控件需要很长时间才能填充数据,您应该在另一个线程中填充控件,然后通过委托添加它:
由于我不太擅长编写VB.NET,但这里是C#的等效代码:
private void Button1_Click(Object sender, RoutedEventArgs e)
{
  Thread thr = new Thread(delegate()
  {
    DataView dataView = new DataView();

    this.Dispatcher.BeginInvoke((Action) delegate()
    {
      teststack.Children.Add(dataView);
    });
  });
  thr.Start();
}

感谢您的帮助,Guard。不幸的是,这不仅仅是一个需要很长时间才能填充的问题。只有大约4000条记录,当我运行上述代码时,VS2008会无限旋转,消耗数百MB的RAM并加载CPU。然而,我将尝试您建议的委托方法,并会报告结果。 - TimH
这不起作用。DataView归第二个线程所有,我无法将其添加到UI线程。我尝试了几种方法来跨越它,但没有成功。最后一招,我尝试使用XamlWriter和XamlReader获取XAML并将其添加到第一个线程,但XamlReader抱怨从XamlWriter创建的内容中缺少根元素。我简直不敢相信在运行时实例化用户控件会这么难!!! - TimH
不要通过构造函数加载数据,而是可以通过一个单独的方法加载数据,在另一个线程上运行,然后在主线程上将数据加载到可见控件中。如果在构造函数中同时进行这两个操作,就无法实现这一点。 - Will Eddins
我尝试了那个,但我无法在第二个线程中设置用户控件的数据上下文并使其在主线程中工作。它不断抛出异常。 - TimH
你不能从另一个线程访问控件! 我们都知道,在使用线程时,需要小心访问UI控件。这些控件只能在构建它们的线程上访问,也就是主应用程序线程。 - theSpyCry
这里是解释的链接:http://www.infosysblogs.com/microsoft/2006/10/cross_thread_ui_control_access.html - theSpyCry

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接