VB.Net App中的奇怪WPF显示:所有窗口属性更新正确,但未显示某些更改

时间:2014-01-23 01:05:27

标签: wpf vb.net

我遇到了一个令人困惑的VB线程和WPF显示问题。我是线程新手,但问题是我的代码完全按照预期执行;问题是WPF控制它影响不正确更新。我已经在互联网上寻找潜在的修复方法,但我仍然空手而归。

我正在尝试更新MainWindow的Initialized子中的原始SplashScreenWindow。有相当多的初始化代码 - 我在这里注释了大部分 - 所以我想让用户知道他们所处的加载阶段并提供ProgressBar作为视觉辅助。这些控件的简单WPF代码和SplashScreenWindow出现在此代码示例的末尾,而启动屏幕本身则在应用程序范围内引用。在Initialized子程序中,我对启动屏幕的Dispatcher.Invoke方法进行了几次同步调用,该方法由Application类中的UpdateSplashScreenWindow委托处理。其中一些调用是在TableClass类的构造函数中进行的,该类根据我注释掉的一些代码中的指示,一次处理一个数十个数据类的处理。

我可以在任何断点处验证ProgressBar和LoadingTextBox的值是否完全按照我的指示进行更新。另一方面,在第一次调用Dispatcher.Invoke之后,控件的显示通常只更新一次;我通常看到的是SplashScreen冻结了一条文本消息,说“正在初始化数据集”和一个只移动了一个微笑的ProgressBar。然后在Initialized代码完成之前没有进一步的更新,MainWindow打开并且SplashScreenWindow被丢弃。然而,如果在Try ... Catch中捕获到错误,则进度条和文本框将突然显示正确的值。我可以在代码中的任何位置设置断点 - 甚至在SplashScreenWindow中的WindowLoadingProgressBar_ValueChanged事件中 - 并验证Text和Value属性在任何时候都设置不正确。它虽然变得更加怪异。正如您在UpdateSplashScreenWindow中看到的那样,我在每次传递时将字母s添加到SplashScreenWindow的标题中,作为可视化测试。我看到标题按预期滚动到屏幕顶部的右侧,但是它下面的子控件只更新一次,尽管两者都在同一个例行程序中。

在控件或其父窗口上调用InvalidateMeasure,InvalidateVisual和UpdateLayout是无效的解决方法之一。我不认为这是一个线程问题,因为启动屏幕线程正如预期的那样同步执行。这些似乎是我在网上看到的WPF视觉更新问题的其他参考文献中常见的罪魁祸首,但我认为我已将其排除在外。如果有人有任何想法,我会感激一些意见。我现在并不太担心切向问题,例如在应用程序范围内引用启动画面是否是良好的设计实践,或调查其他方式的线程WPF窗口,如BackgroundWorker。在达到这个目标并最终使我的代码在理论上工作之后,我想弄清楚是什么阻止它在实践中正确显示,以防我或其他任何人再次遇到问题。

Private Sub MainWindow_Initialized(sender As Object, e As System.EventArgs) Handles Me.Initialized

        Try
            InFormLoadedBoolean = True

            Application.SplashScreenWindow.Dispatcher.Invoke(New Application.UpdateSplashScreenDelegate(AddressOf Application.UpdateSplashScreenWindow), Application.SplashScreenWindow, "Initializing Datasets", 5)

 ‘set some dataset properties here
                Application.SplashScreenWindow.Dispatcher.Invoke(DispatcherPriority.Send, New Application.UpdateSplashScreenDelegate(AddressOf Application.UpdateSplashScreenWindow), Application.SplashScreenWindow, "Initializing Data Classes", 5)

‘initialize several dozen data-bound classes here, one at a time
            Application.SplashScreenWindow.Dispatcher.Invoke(New Application.UpdateSplashScreenDelegate(AddressOf Application.UpdateSplashScreenWindow), Application.SplashScreenWindow, "Initializing Control Resources", 5)

‘initialize a long list of WPF resources here
‘add some event handlers

Application.FormLoadedBoolean = True
        Catch MyException As Exception
            ErrorCheckerDebugVersion(MyException)
        End Try

Application.SplashScreenWindow.LoadingTextBox.Text = "Finished Loading"

        Me.Width = 1680
        Me.Height = 1050

        Application.SplashScreenWindow.Visibility = Windows.Visibility.Hidden
        Application.SplashScreenWindow = Nothing

    End Sub

    Public Sub New(TempName As String, TempViewSource As CollectionViewSource, FillOnInitializeBoolean As Boolean, TempDataGrid As DataGrid, TempPopup As Primitives.Popup, AdapterTypeNameString As String, TempDataRowTypeNameString As String)

‘do some initialization work for each data class here, all of which executes as expected

        Application.SplashScreenWindow.Dispatcher.Invoke(New Application.UpdateSplashScreenDelegate(AddressOf Application.UpdateSplashScreenWindow), Application.SplashScreenWindow, "Initializing Data Classes", 20)

    End Sub

Class Application

    Friend Shared SplashScreenWindow As New DashboardApplication.SplashScreenWindow
    Public Delegate Sub UpdateSplashScreenDelegate(sender As SplashScreenWindow, LoadingTextBoxText As String, Amount As Int32)

    Public Shared Sub UpdateSplashScreenWindow(sender As SplashScreenWindow, LoadingTextBoxText As String, Amount As Int32)

        sender.WindowLoadingProgressBar.Value = sender.WindowLoadingProgressBar.Value + Amount
        sender.LoadingTextBox.Text = LoadingTextBoxText
        sender.Title = sender.Title + "s"
        If sender.IsActive = False Then sender.Show()
        sender.InvalidateVisual()
        sender.InvalidateMeasure()
        sender.WindowLoadingProgressBar.InvalidateVisual()
        sender.WindowLoadingProgressBar.InvalidateMeasure()

        sender.WindowLoadingProgressBar.UpdateLayout()
        sender.LoadingTextBox.UpdateLayout()
    End Sub

Public Class SplashScreenWindow
 Private Sub Window_Loaded(sender As System.Object, e As System.Windows.RoutedEventArgs) Handles MyBase.Loaded   
    End Sub

    Private Sub WindowLoadingProgressBar_ValueChanged(sender As Object, e As System.Windows.RoutedPropertyChangedEventArgs(Of Double)) Handles WindowLoadingProgressBar.ValueChanged   
        WindowLoadingProgressBar.UpdateLayout()
        WindowLoadingProgressBar.InvalidateMeasure()
        WindowLoadingProgressBar.InvalidateVisual()
    End Sub
End Class

1 个答案:

答案 0 :(得分:0)

好的,我借助于我在其他网站上发现的两篇宝贵文章解决了这个问题,Suchit Khanna的“在专用线程上创建WPF窗口”[链接](http://www.c-sharpcorner.com/uploadfile/suchit_84/creating-wpf-windows-on-dedicated-threads/和Reed Copsey Jr.的“发布一个单独线程中的WPF窗口,第1部分“link

我基本上是用火线进行了洗礼;显然我想要完成的事实上对于像我这样的线程新手来说实际上有点太先进了。通常情况下,我不需要从我的MainWindow调用BackgroundWorker或其他线程并在那里工作,我需要反向执行主线程中的工作,而是将其显示在另一个线程中。这需要在自己的线程中启动另一个窗口,这在上面的两个链接中有解释。它们是用C#编写的,所以我不得不将它们的代码示例转换为VB并应用其他一些修改。我已在此处发布,以防其他人遇到同样的问题:

'我将以下代码添加到Application类,以确保SplashScreenWindow在其自己的专用线程上运行

导入System.Threading.SynchronizationContext 朋友共享SplashScreenThread为System.Threading.Thread

Public Shared Sub StartSplashThread()

    SplashScreenThread = New System.Threading.Thread(AddressOf ThreadWork)
    SplashScreenThread.SetApartmentState(ApartmentState.STA)
    SplashScreenThread.IsBackground = True
    SplashScreenThread.Priority = ThreadPriority.AboveNormal
    SplashScreenThread.Start()

End Sub

Public Shared Sub ThreadWork()

    SynchronizationContext.SetSynchronizationContext(New System.Windows.Threading.DispatcherSynchronizationContext(System.Windows.Threading.Dispatcher.CurrentDispatcher))

    SplashScreenWindow = New DashboardApplication.SplashScreenWindow
    SplashScreenWindow.Show()

    System.Windows.Threading.Dispatcher.Run()
End Sub

'我还将它添加到Application类中的UpdateSplashScreenWindow例程,该例程在SplashScreenWindow中调用下面的SplashScreenWindow_Closed例程。这可以防止线程在窗口关闭后保持活动状态

    If LoadingTextBoxText = "Opening Main Window" Then
        SplashScreenWindow.Close()
        SplashScreenWindow = Nothing
    End If

Private Sub SplashScreenWindow_Closed(sender as Object,e As System.EventArgs)处理Me.Closed         System.Windows.Threading.Dispatcher.CurrentDispatcher.BeginInvokeShutdown(DispatcherPriority.Background)

End Sub