Invoke или BeginInvoke не могут быть вызваны для элемента управления, пока дескриптор окна не был создан ошибка - PullRequest
1 голос
/ 24 февраля 2012

Я бился головой об этом уже несколько дней. Я разрабатываю com объект в vb.net для отображения заставки. Это будет вызываться из скрипта vb и Powershell для предоставления статуса пользователям, в то время как в скрипте выполняются другие инструкции. Я получаю эту ошибку периодически, любая помощь будет оценена.

Вот ссылка, которую я использовал для разработки своего приложения. http://msdn.microsoft.com/en-us/library/ms233639.aspx

Public Class Progress

Private WithEvents appContext As ApplicationContext
Private frmSplash As frmProgress
Private Delegate Sub FormDelegate()
Private ShowDelegate As FormDelegate
Private UnLdDelegate As FormDelegate
Private t As Thread

Public Sub New()

    MyBase.New()

    If IsNothing(appContext) Then
        frmSplash = New frmProgress
        appContext = New ApplicationContext(frmSplash)
        t = New Thread(AddressOf StartMessageLoop)
        t.IsBackground = True
        t.SetApartmentState(ApartmentState.STA)
        t.Start()
        ShowDelegate = New FormDelegate(AddressOf frmSplash.Show)
        HideDelegate = New FormDelegate(AddressOf frmSplash.Hide)
        UnLdDelegate = New FormDelegate(AddressOf frmSplash.Close)
    End If

 End Sub

MessageLoop

 Private Sub StartMessageLoop()
    Application.Run(appContext)
 End Sub

ShowForm

Public Sub Show()
    If frmSplash.InvokeRequired Then
        If frmSplash.IsHandleCreated = True Then
            appContext.MainForm.Invoke(ShowDelegate)
        End If
    End If
End Sub

Выгрузка

 Public Sub Unload()
    If frmSplash.InvokeRequired Then
        If frmSplash.IsHandleCreated Then
            appContext.MainForm.Invoke(UnLdDelegate)
        End If
    End If
End Sub

Событие выхода из темы

 Private Sub ac_ThreadExit(ByVal sender As Object, ByVal e As System.EventArgs) Handles appContext.ThreadExit

    appContext.MainForm.Dispose()
    appContext.MainForm = Nothing
    appContext.Dispose()
    appContext = Nothing
    frmSplash.Dispose()
    frmSplash = Nothing

End Sub

Ошибка обычно возникает, когда я вызываю метод Show из сценария powershell. Любая помощь будет принята с благодарностью. Спасибо

Полный код

Private ShowLock As New ManualResetEvent(False)
Private WithEvents appContext As ApplicationContext
Private frmSplash As frmProgress

Private Delegate Sub FormDelegate()
Private Delegate Sub DisplayDelegate(ByVal msg As String)
Private Delegate Sub FormSettings(ByVal val As Boolean)

Private ShowDelegate As FormDelegate
Private HideDelegate As FormDelegate
Private UnLdDelegate As FormDelegate

Public Sub New()

    MyBase.New()
    frmSplash = New frmProgress

End Sub

Public Sub SetDisplayText1(ByVal msg As String)
    If frmSplash.InvokeRequired Then
        Dim d As New DisplayDelegate(AddressOf SetDisplayText1)
        frmSplash.Invoke(d, New Object() {[msg]})
    Else
        frmSplash.Label1.Text = msg
        frmSplash.Label1.Refresh()
    End If
End Sub


Public Sub SetDisplayText2(ByVal msg As String)
    If frmSplash.InvokeRequired Then
        Dim d As New DisplayDelegate(AddressOf SetDisplayText2)
        frmSplash.Invoke(d, New Object() {[msg]})
    Else
        frmSplash.Label2.Text = msg
        frmSplash.Label2.Refresh()
    End If

End Sub

Public Sub SetTitle(ByVal msg As String)
    If frmSplash.InvokeRequired Then
        Dim d As New DisplayDelegate(AddressOf SetTitle)
        frmSplash.Invoke(d, New Object() {[msg]})
    Else
        frmSplash.Text = msg
    End If
End Sub


Public WriteOnly Property AlwaysOnTop() As Boolean

    Set(ByVal value As Boolean)
        SetTop(value)
    End Set
End Property


Public Sub Show()

    AddHandler frmSplash.HandleCreated, AddressOf frm_HandleCreated

    If IsNothing(appContext) Then
        Dim t As Thread
        appContext = New ApplicationContext(frmSplash)
        t = New Thread(AddressOf StartMessageLoop)
        t.IsBackground = True
        t.SetApartmentState(ApartmentState.STA)
        t.Start()
    Else
        ShowLock.WaitOne()
        If frmSplash.InvokeRequired Then
            If frmSplash.IsHandleCreated = True Then
                ShowDelegate = New FormDelegate(AddressOf Show)
                appContext.MainForm.Invoke(ShowDelegate)
            End If
        Else
            frmSplash.Show()
        End If

    End If
End Sub

Public Sub SetTop(ByVal val As Boolean)
    If frmSplash.InvokeRequired Then
        Dim d As New DisplayDelegate(AddressOf SetTop)
        frmSplash.Invoke(d, New Object() {[val]})
    Else
        frmSplash.TopMost = val
        frmSplash.TopLevel = val
    End If
End Sub

Public Sub Hide()

    If frmSplash.InvokeRequired Then
        If frmSplash.IsHandleCreated Then
            HideDelegate = New FormDelegate(AddressOf Hide)
            appContext.MainForm.Invoke(HideDelegate)
        End If
    Else
        frmSplash.Hide()
    End If

End Sub

Public Sub Unload()

    If frmSplash.InvokeRequired Then
        If frmSplash.IsHandleCreated Then
            UnLdDelegate = New FormDelegate(AddressOf Unload)
            appContext.MainForm.Invoke(UnLdDelegate)
        End If
    Else
        frmSplash.Close()
    End If

End Sub

Private Sub StartMessageLoop()
    Application.Run(appContext)
End Sub

Private Sub ac_ThreadExit(ByVal sender As Object, ByVal e As System.EventArgs) Handles appContext.ThreadExit

    appContext.MainForm.Dispose()
    appContext.MainForm = Nothing
    appContext.Dispose()
    appContext = Nothing
    frmSplash.Dispose()
    frmSplash = Nothing

End Sub

Protected Overrides Sub Finalize()
    MyBase.Finalize()
End Sub

Public Sub frm_HandleCreated()
    ShowLock.Set()
End Sub

Мои строки отладки заставят меня поверить, что здесь происходит сбой

 Public Sub Show()

    AddHandler frmSplash.HandleCreated, AddressOf frm_HandleCreated

    If IsNothing(appContext) Then
        Dim t As Thread
        appContext = New ApplicationContext(frmSplash)
        t = New Thread(AddressOf StartMessageLoop)
        t.IsBackground = True
        t.SetApartmentState(ApartmentState.STA)
        t.Start()

Спасибо за всю помощь.

Ответы [ 2 ]

3 голосов
/ 24 февраля 2012

После создания MainForm получите доступ к его свойству Handle в том же потоке, который в конечном итоге станет "потоком пользовательского интерфейса". Назначьте его какой-нибудь одноразовой переменной, суть в том, чтобы просто получить к ней доступ. WinForms лениво инициализирует Handle, поэтому он недействителен до тех пор, пока не получен доступ к свойству или его первый показ. InvokeRequired работает, глядя на поток, в котором был создан Handle, поэтому, если попытаться лениво инициализировать его в фоновом потоке, могут произойти плохие вещи.

См. этот вопрос и ответ для более подробной информации.

Может быть, это и не так, но это звучит как то, что я ударил раньше. Надеюсь, это поставит вас на правильный путь.

2 голосов
/ 24 февраля 2012

ОК, я вижу, в чем проблема.Я предполагаю, что вы вызываете один из SetDisplayText методов из сценария PS.Вам нужно защитить тех, у кого есть проверки на InvokeRequired и IsHandleCreated, плюс вам необходимо обеспечить синхронизацию при начальном отображении формы.Мой VB.NET немного заржавел, но я приложу все усилия:

Сначала добавьте это как локальную переменную:

Private ShowLock As New ManualResetEvent(False);

Затем добавьте обработчик для HandleCreated событие в frmSplash, и в этом обработчике:

Public Sub Form_HandleCreated(...)
     ShowLock.Set
End Sub

Затем в вашем методе ShowForm подождите, пока этот парень не установится:

Public Sub Show()
    ShowLock.WaitOne
    If frmSplash.InvokeRequired Then
        If frmSplash.IsHandleCreated = True Then
            appContext.MainForm.Invoke(ShowDelegate)
        End If
    End If
End Sub

Таким образом, вы уверены, чтодескриптор формы был создан до того, как первый поток попытается показать его (или сделать что-нибудь еще).

Наконец, защитите эти методы SetXXX (делайте это для всего, что касается формы и может быть вызвано изСкрипт):

Public Sub SetDisplayText1(ByVal msg As String)
    If frmSplash.InvokeRequired Then
        Dim d As New SetDisplayText1Delegate(AddressOf SetDisplayText1)
        frmSplash.Invoke(d, New Object() {[msg]})
    Else
        frmSplash.Label1.Text = msg
        frmSplash.Label1.Refresh()
    End If
End Sub

РЕДАКТИРОВАТЬ: Попробуйте это - я взял ваш код сверху и немного переработал его.

Private ShowLock As New ManualResetEvent(False)
Private WithEvents appContext As ApplicationContext
Private frmSplash As frmProgress

Private Delegate Sub FormDelegate()
Private Delegate Sub DisplayDelegate(ByVal msg As String)
Private Delegate Sub FormSettings(ByVal val As Boolean)

Private ShowDelegate As FormDelegate
Private HideDelegate As FormDelegate
Private UnLdDelegate As FormDelegate

Public Sub New()

    MyBase.New()
    frmSplash = New frmProgress
    AddHandler frmSplash.HandleCreated, AddressOf frm_HandleCreated
    Dim t As Thread
    appContext = New ApplicationContext(frmSplash)
    t = New Thread(AddressOf StartMessageLoop)
    t.IsBackground = True
    t.SetApartmentState(ApartmentState.STA)
    t.Start()

End Sub

Public Sub SetDisplayText1(ByVal msg As String)
    ShowLock.WaitOne()
    If frmSplash.InvokeRequired Then
        Dim d As New DisplayDelegate(AddressOf SetDisplayText1)
        frmSplash.Invoke(d, New Object() {[msg]})
    Else
        frmSplash.Label1.Text = msg
        frmSplash.Label1.Refresh()
    End If
End Sub


Public Sub SetDisplayText2(ByVal msg As String)
    ShowLock.WaitOne()
    If frmSplash.InvokeRequired Then
        Dim d As New DisplayDelegate(AddressOf SetDisplayText2)
        frmSplash.Invoke(d, New Object() {[msg]})
    Else
        frmSplash.Label2.Text = msg
        frmSplash.Label2.Refresh()
    End If

End Sub

Public Sub SetTitle(ByVal msg As String)
    ShowLock.WaitOne()
    If frmSplash.InvokeRequired Then
        Dim d As New DisplayDelegate(AddressOf SetTitle)
        frmSplash.Invoke(d, New Object() {[msg]})
    Else
        frmSplash.Text = msg
    End If
End Sub

Public WriteOnly Property AlwaysOnTop() As Boolean
    Set(ByVal value As Boolean)
        SetTop(value)
    End Set
End Property

Public Sub Show()
    ShowLock.WaitOne()
    If frmSplash.InvokeRequired Then
        If frmSplash.IsHandleCreated = True Then
            ShowDelegate = New FormDelegate(AddressOf Show)
            appContext.MainForm.Invoke(ShowDelegate)
        End If
    Else
        frmSplash.Show()
    End If
End Sub

Public Sub SetTop(ByVal val As Boolean)
    ShowLock.WaitOne()
    If frmSplash.InvokeRequired Then
        Dim d As New DisplayDelegate(AddressOf SetTop)
        frmSplash.Invoke(d, New Object() {[val]})
    Else
        frmSplash.TopMost = val
        frmSplash.TopLevel = val
    End If
End Sub

Public Sub Hide()
    ShowLock.WaitOne()    
    If frmSplash.InvokeRequired Then
        If frmSplash.IsHandleCreated Then
            HideDelegate = New FormDelegate(AddressOf Hide)
            appContext.MainForm.Invoke(HideDelegate)
        End If
    Else
        frmSplash.Hide()
    End If

End Sub

Public Sub Unload()
    ShowLock.WaitOne()
    If frmSplash.InvokeRequired Then
        If frmSplash.IsHandleCreated Then
            UnLdDelegate = New FormDelegate(AddressOf Unload)
            appContext.MainForm.Invoke(UnLdDelegate)
        End If
    Else
        frmSplash.Close()
    End If

End Sub

Private Sub StartMessageLoop()
    Application.Run(appContext)
End Sub

Private Sub ac_ThreadExit(ByVal sender As Object, ByVal e As System.EventArgs) Handles appContext.ThreadExit

    appContext.MainForm.Dispose()
    appContext.MainForm = Nothing
    appContext.Dispose()
    appContext = Nothing
    frmSplash.Dispose()
    frmSplash = Nothing

End Sub

Protected Overrides Sub Finalize()
    MyBase.Finalize()
End Sub

Public Sub frm_HandleCreated()
    ShowLock.Set()
End Sub
...