none
control aus fremden thread ansprechen (mal wieder) RRS feed

  • Frage

  • Ich hab nun verschiedene Möglichkeiten kennengelernt, das zu lösen.

    Die einfachste finde ich:

    Dim chkUpdate As MethodInvoker = Sub() FrmMain.chkRecord.Checked = False
             chkUpdate.Invoke()

    Klappt aber nicht und ich weiß nicht wieso.

    Der code ist aus einem Delegatenaufruf:

    delg.BeginInvoke(rück, AktualSong, output, quality, AddressOf RecordIsReady, Nothing)

    und steht in der function recordIsReady, die auch vorschriftsmäßig durchlaufen wird wenn der delegat fertig ist.

    Lediglich meine checkbox reagiert nicht auf das setzen der .checked-Eigenschaft.

    Liegt das daran, dass dieser ganze code in einem separaten modul liegt, außerhalb der Class Frmmain und generell von da nicht auf die frmmain-controls zugegriffen werden kann?
    Oder stimmt was mit meinem code nicht?

    Gruß und Dank!

    Thomas

    Sonntag, 24. November 2013 13:56

Antworten

  • Hallo,
    außerhalb einer Klasse auf ein Control zuzugreifen ist nicht der beste Stil. Besser wäre für die von außen Setzbaren Eigenschaften auch solche zu erstellen. Sonst könntest du von außen alles frei verändern, was nicht OOP-Gerecht wäre.

    Ich nehme mal an, das FrmMain die Instanz der Form ist. In diesem Fall funktioniert es bei mir:

    Public Class Form1
    
        Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
            Dim t As New Thread(Sub()
                                    modul.XYZ(Me) 'Funktion vom Module aufrufen, Me übergeben um Zugriff auf die Form zu erhalten'
                                End Sub)
            t.Start() 'Thread zum testen'
        End Sub
    End Class
    
    Module modul
        Public Sub XYZ(frm As Form1)
            frm.chkRecord.Invoke(Sub() frm.CheckBox1.Checked = False) 'Über Invoke zugreifen'
        End Sub
    End Module

    Von wo der Aufruf kommt sollte eigentlich keine Rolle spielen, solange die Instanz der Form auch stimmt.

    Wie gesagt, so wäre es besser:

    Public Class Form1
    
        Public Property IsCheckBoxRecordChecked() As Boolean
            Get
                If Me.chkRecord.InvokeRequired Then'Zugriff aus einem anderen Thread heraus?'
                    Dim isChecked As Boolean
                    Me.chkRecord.Invoke(Sub() isChecked = Me.chkRecord.Checked)
                    Return isChecked
                Else
                    Return Me.chkRecord.Checked
                End If
            End Get
            Set(value As Boolean)
                If Me.chkRecord.InvokeRequired Then
                    Me.chkRecord.Invoke(Sub() Me.chkRecord.Checked = value)
                Else
                    Me.chkRecord.Checked = value
                End If
            End Set
        End Property
    
    End Class
    
    Module modul
        Public Sub XYZ(frm As Form1)
            frm.IsCheckBoxRecordChecked = False'Kein Invoke mehr erforderlich, die Form kümmert sich darum'
        End Sub
    End Module

    In diesem Fall ruft die Eigenschaft ggf. Invoke auf, wenn benötigt.


    Koopakiller [kuːpakɪllɐ] (Tom Lambert)
    Webseite | Code Beispiele | Facebook | Twitter | Snippets   C# ↔ VB.NET Konverter
    Markiert bitte beantwortende Posts als Antwort und bewertet Beiträge. Danke.

    Sonntag, 24. November 2013 14:36
    Moderator
  • zu 1.
    Die Form muss übergeben werden. Sonst weiß dein 2. Modul/Klasse nicht um was es geht. Siehe 4.
    Als Beispiel: Du erzeugst die Form1 zwei mal und rufst dann Form1.Title = "Test" auf. Von welcher Form wird der Titel geändert?

    zu 2.
    Siehe 4. Es liegt wieder am 2. Thread und das du die Forminstanz nicht übergeben hast.

    zu 3.
    Siehe 1 und 4.

    zu 4.
    Das ordne ich unter "Eigenheit von VB" ein. Durch die Benutzung einer nicht shared Methode als shared wird eine Instanz erstellt und dann der Code von dieser aufgerufen. Ich denke mal, das es am 2. Thread liegt, das nicht auf die bereits existierende Instanz zugegriffen wird. Solchen Code sollte man aber nicht mehr verwenden, da er nur noch zur Abwärtskompatibilität (VB 6 und früher) funktioniert. Also

    Dim frm As New Form1()
    frm.ShowDialog()
    Anstelle von
    Form1.ShowDialog()
    Gleiches gilt für den Zugriff auf deine CheckBox etc.


    Koopakiller [kuːpakɪllɐ] (Tom Lambert)
    Webseite | Code Beispiele | Facebook | Twitter | Snippets   C# ↔ VB.NET Konverter
    Markiert bitte beantwortende Posts als Antwort und bewertet Beiträge. Danke.

    Sonntag, 24. November 2013 16:29
    Moderator

Alle Antworten

  • Hallo,
    außerhalb einer Klasse auf ein Control zuzugreifen ist nicht der beste Stil. Besser wäre für die von außen Setzbaren Eigenschaften auch solche zu erstellen. Sonst könntest du von außen alles frei verändern, was nicht OOP-Gerecht wäre.

    Ich nehme mal an, das FrmMain die Instanz der Form ist. In diesem Fall funktioniert es bei mir:

    Public Class Form1
    
        Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
            Dim t As New Thread(Sub()
                                    modul.XYZ(Me) 'Funktion vom Module aufrufen, Me übergeben um Zugriff auf die Form zu erhalten'
                                End Sub)
            t.Start() 'Thread zum testen'
        End Sub
    End Class
    
    Module modul
        Public Sub XYZ(frm As Form1)
            frm.chkRecord.Invoke(Sub() frm.CheckBox1.Checked = False) 'Über Invoke zugreifen'
        End Sub
    End Module

    Von wo der Aufruf kommt sollte eigentlich keine Rolle spielen, solange die Instanz der Form auch stimmt.

    Wie gesagt, so wäre es besser:

    Public Class Form1
    
        Public Property IsCheckBoxRecordChecked() As Boolean
            Get
                If Me.chkRecord.InvokeRequired Then'Zugriff aus einem anderen Thread heraus?'
                    Dim isChecked As Boolean
                    Me.chkRecord.Invoke(Sub() isChecked = Me.chkRecord.Checked)
                    Return isChecked
                Else
                    Return Me.chkRecord.Checked
                End If
            End Get
            Set(value As Boolean)
                If Me.chkRecord.InvokeRequired Then
                    Me.chkRecord.Invoke(Sub() Me.chkRecord.Checked = value)
                Else
                    Me.chkRecord.Checked = value
                End If
            End Set
        End Property
    
    End Class
    
    Module modul
        Public Sub XYZ(frm As Form1)
            frm.IsCheckBoxRecordChecked = False'Kein Invoke mehr erforderlich, die Form kümmert sich darum'
        End Sub
    End Module

    In diesem Fall ruft die Eigenschaft ggf. Invoke auf, wenn benötigt.


    Koopakiller [kuːpakɪllɐ] (Tom Lambert)
    Webseite | Code Beispiele | Facebook | Twitter | Snippets   C# ↔ VB.NET Konverter
    Markiert bitte beantwortende Posts als Antwort und bewertet Beiträge. Danke.

    Sonntag, 24. November 2013 14:36
    Moderator
  • Vielen Dank Tom,

    1. ich hatte die Form gar nicht an das sub übergeben. Hatte einfach den Namen der Form (aus den Dateieingenschaften) eingesetzt (siehe Pkt 4). Daher hatte Dein erster code bei mir zunächst nicht funktioniert.

    2. Kannst Du sagen, warum das mit dem MethodInvoker nicht geklappt hat?

    3. Deine 2. Methode klappt auch nur dann, wenn ich die Form als parameter übergebe sonst nicht.

    4. Wenn das ganze nicht in einem anderen Thread abläuft, funktioniert dieser Aufruf aus einem Modul Form1.control.eigenschaft="soundso", auch wenn die Form nicht als Parameter übergeben wurde.

    Da ich das nur als Amateur betreibe ist das für mich von der Theorie schwer nachzuvollziehen. Ich lerne by doing...

    Herzlich

    Thomas

    Sonntag, 24. November 2013 16:03
  • zu 1.
    Die Form muss übergeben werden. Sonst weiß dein 2. Modul/Klasse nicht um was es geht. Siehe 4.
    Als Beispiel: Du erzeugst die Form1 zwei mal und rufst dann Form1.Title = "Test" auf. Von welcher Form wird der Titel geändert?

    zu 2.
    Siehe 4. Es liegt wieder am 2. Thread und das du die Forminstanz nicht übergeben hast.

    zu 3.
    Siehe 1 und 4.

    zu 4.
    Das ordne ich unter "Eigenheit von VB" ein. Durch die Benutzung einer nicht shared Methode als shared wird eine Instanz erstellt und dann der Code von dieser aufgerufen. Ich denke mal, das es am 2. Thread liegt, das nicht auf die bereits existierende Instanz zugegriffen wird. Solchen Code sollte man aber nicht mehr verwenden, da er nur noch zur Abwärtskompatibilität (VB 6 und früher) funktioniert. Also

    Dim frm As New Form1()
    frm.ShowDialog()
    Anstelle von
    Form1.ShowDialog()
    Gleiches gilt für den Zugriff auf deine CheckBox etc.


    Koopakiller [kuːpakɪllɐ] (Tom Lambert)
    Webseite | Code Beispiele | Facebook | Twitter | Snippets   C# ↔ VB.NET Konverter
    Markiert bitte beantwortende Posts als Antwort und bewertet Beiträge. Danke.

    Sonntag, 24. November 2013 16:29
    Moderator
  • OK, das war umfassend. VIELEN DANK.

    Thomas

    Sonntag, 24. November 2013 16:47
  • Hallo Thomas und Tom,

    ich möchte hier einen kleinen Einspruch anmelden ;)

    Die Eigenschaften einer Windows Forms Klasse (vom Steuerlement bis zum Formular) sind nicht ohne Grund in der Vorgabe niemals thread-sicher. So verlockend es sein mag, sämtliche Eigenschaften thread-sicher zu machen, ist das übers Ziel hinausgeschossen und kann sogar behindern.

    Denn i. a. greift man nicht über einen anderen Thread zu und wenn man das tut, so weiß der Entwickler das (oder sollte es zumindest ;) Control.[Begin]Invoke haben einigen Overhead, da sie zum einen das Formular finden müssen, den Ausführungskontext sichern (teuer!) und eine Nachricht über die Meldeschleife verschicken.

    Schon um den zu minimieren, würde / sollte man bei mehreren Eigenschaften (und/oder Methodenzugriffen) diese zusammenfassen - was das "Doppelkodieren" überflüssig machen würde. Gefährlich ist es zudem, da man jedes Mal gucken muss, ob eine Eigenschaft thread-sicher implementiert wurde oder nicht und ein Dritter vom Standard, also nicht-thread-sicher ausgeht.

    Deswegen sollte man dies als (Set-)Methoden bereitstellen, deren Namen ggf. einen Hinweis darauf enthalten - z. B. InvokeRecordChecked(...).

    Nicht zum letzten kann der Zugriff über den SynchronzationContext günstiger sein, da i. a. effizienter bei komplexeren Zugriffen. Der BackgroundWorker tat es schon immer und in neueren .NET Versionen machen es die Task (Async) Klassen ebenso. Was aber auch gegen "zu schlaue Eigenschaften" spricht.

    Gruß Elmar

    P. S.: Das es anfangs nicht funktioniert hat, dürfte an 4. liegen, siehe Von My.Forms und My.WebServices bereitgestellte Standardobjektinstanzen (Visual Basic) - ein Feature, auf das man besser verzichtet hätte, da es wenig nützt aber solche Fehler provoziert und schwer erkennbar macht. Siehe http://stackoverflow.com/questions/4698538/there-is-a-default-instance-of-form-in-vb-net-but-not-in-c-why für eine Erläuterung (Hans Passant) wie es implementiert ist (und die Kommentare warum man besser die Finger davon lässt).

    Sonntag, 24. November 2013 17:24
    Beantworter
  • Macht Sinn ;)
    Danke für die Erklärung.

    Koopakiller [kuːpakɪllɐ] (Tom Lambert)
    Webseite | Code Beispiele | Facebook | Twitter | Snippets   C# ↔ VB.NET Konverter
    Markiert bitte beantwortende Posts als Antwort und bewertet Beiträge. Danke.

    Sonntag, 24. November 2013 17:45
    Moderator