none
Löschen ListBox.Items aus zweitem Thread RRS feed

  • Frage

  • Hallo,

    ich habe eine Powershell-GUI (mittels XAML-Datei) geschrieben, was eine ListBox mit Einträgen enthält. Nun versuche ich die Einträge aus der ListBox aus einem zweiten Thread heraus zu löschen. Der Aufruf:

    $syncHash.GUIObj.ListBox.Dispatcher.invoke([action]{$syncHash.GUIObj.ListBox.Items.Clear()},'Normal')

    schlägt jedoch fehl. Weiss jemand Rat? Danke im Voraus.

    Freitag, 6. Februar 2015 12:15

Antworten

  • Den Dispatcher zu benutzen ist richtig!
    Dein $GuiObj ist völlig überflüssig!

    Du kannst ein WPF Window nich wie XML behandeln!

    Da alle Controls Kinder von dem Window Object (From) sind, spricht man die Controls über das Window (Form) an.

    Lies dir mal den folgenden Blog Post von Boe Prox durch:
    http://learn-powershell.net/2012/10/14/powershell-and-wpf-writing-data-to-a-ui-from-a-different-runspace/

    Hier ein Testcode zum Spielen:

    # Import the Assemblies
    Add-Type -AssemblyName PresentationFramework
    Add-Type -AssemblyName PresentationCore
    Add-Type -AssemblyName WindowsBase
    
    # Create the synchronized-hashtable to hold the thread-shared objects
    $SyncHash = [HashTable]::Synchronized(@{})
    
    # create the runspace to use with the pipeline
    $Runspace =[System.Management.Automation.Runspaces.RunspaceFactory]::CreateRunspace()
    # WPF needs Single Thread Appartment (STA)!
    $Runspace.ApartmentState = 'STA'
    $Runspace.ThreadOptions = [System.Management.Automation.Runspaces.PSThreadOptions]::ReuseThread          
    $Runspace.Open()
    
    # add the $SyncHash as Variable to the runspace so we can use it inside the pipeline
    $Runspace.SessionStateProxy.SetVariable('SyncHash',$SyncHash)          
    
    # create PowerShell pipeline for the GUI thread
    $UiTreadPipeline = [PowerShell]::Create()
    
    # PowerShell pipeline has to use our runspace
    $UiTreadPipeline.Runspace = $Runspace
    
    # add the script to execute with the pipeline
    $UiTreadPipeline.AddScript({   
    [xml]$XAML = @"
    <Window x:Class="WpfApplication1.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            Title="MainWindow" Height="350" Width="525">
        <StackPanel >
            <ListBox Name="Listbox_Disks" ToolTip="Select Disk to Remove" Height="80" Width="150" HorizontalAlignment="Left"/>
        </StackPanel>
    </Window>
    "@
        
        # The the x:class Attribute is added by the Visual Studio designer
        # we remove the x:class Attribute wich is not needed inside PowerShell
        $XAML.Window.RemoveAttribute('x:Class')
        
        # create the XmlNodeReader to use with the XamlReader
        $XmlNodeReader = (New-Object System.Xml.XmlNodeReader $xaml)
        # create the WPF Window from XmlNodeReader
        $SyncHash.Window = [Windows.Markup.XamlReader]::Load($XmlNodeReader)
    
        # Show the WPF window as dialog
        $Null = $SyncHash.Window.ShowDialog()
    
    })
    
    # Start the GUI pipeline asynchronous as new Thread
    $AsyncResult = $UiTreadPipeline.BeginInvoke()
    
    # simulate work inside this main thread
    for ($i = 1; $i -le 10; $i++)
    { 
      Start-Sleep -Milliseconds 200 
      Write-Host "Ich Tu hier wat $i" -ForegroundColor Magenta  
    }
    
    # cross thread call from this thread to the GUI thread to the WPF Window
    # we use the synchronized-hashtable and the dispatcher.Invoke() method to access the WPF Window in a threadsave manner
    # add items to a ListBox
    for ($i = 1; $i -le 15; $i++)
    { 
     $SyncHash.Window.Dispatcher.Invoke(
      'Normal',[action]{($SyncHash.Window.FindName('Listbox_Disks')).Items.Add("$i New Item")}
      )   
    }
    
    # cross thread call from this thread to the GUI thread to the WPF Window
    # we use the synchronized-hashtable and the dispatcher.Invoke() method to access the WPF Window in a threadsave manner
    # remove the item '5 New Item' from ListBox
     $SyncHash.Window.Dispatcher.Invoke(
      'Normal',[action]{($SyncHash.Window.FindName('Listbox_Disks')).Items.Remove('5 New Item')}
      )   
    
    # simulate work inside this main thread
    for ($i = 10; $i -le 20; $i++)
    { 
      Start-Sleep -Milliseconds 200 
      Write-Host "Ich Tu noch was hier $i" -ForegroundColor Magenta  
    }


    PowerShell Artikel, Buchtipps und kostenlose PowerShell Tutorials + E-Books
    auf der deutschsprachigen PowerShell Community

    Mein 21 Teiliger PowerShell Video Grundlehrgang
    Deutsche PowerShell Videos auf Youtube
    Folge mir auf:
    Twitter | Facebook | Google+

    Montag, 9. Februar 2015 08:36

Alle Antworten

  • Schau mal ob dir der Thread hier weiterhilft. Ansonsten müsstest du mal das Script, bzw. den relevanten Teil davon posten.
     
    Grüße, Denniver


    Blog: http://bytecookie.wordpress.com

    Kostenloser Powershell Snippet Manager v3: Link
    (Schneller, besser + einfacher scripten.)

    Hilf mit und markiere hilfreiche Beiträge mit dem "Abstimmen"-Button (links) und Beiträge die eine Frage von dir beantwortet haben, als "Antwort" (unten).
    Warum das Ganze? Hier gibts die Antwort.


    Samstag, 7. Februar 2015 01:28
    Moderator
  • Jedes Controll sollte (MUSS) im XAML einen Namen bekommen, damit man das Control über seinen Namen Ansprechen kann.
    Heißt deine Listbox wirklich "ListBox"? Kein sinnvoller Name.....

    Zeig uns mal den XAML Code (Ausschnitt) in dem du deine Listbox definierst.


    PowerShell Artikel, Buchtipps und kostenlose PowerShell Tutorials + E-Books
    auf der deutschsprachigen PowerShell Community

    Mein 21 Teiliger PowerShell Video Grundlehrgang
    Deutsche PowerShell Videos auf Youtube
    Folge mir auf:
    Twitter | Facebook | Google+

    Montag, 9. Februar 2015 06:55
  • Hallo Peter,

    vielleicht habe ich auch ein Verständnisproblem...Mein Verständnis war, dass ich die Dispatcher-Eigenschaft benutzen muss, um ein Objekt thread-übergreifend zu ändern, also schreibend darauf zugreife.

    Anbei ei Auszug aus meiner XAML-Datei:

    <GroupBox Name="GroupBox_Guest" Header="Guest Options" HorizontalAlignment="Center" Margin="11,119,0,0" VerticalAlignment="Top" Height="210" Width="475">
                            <Grid Name="Grid_Guest" HorizontalAlignment="Center" Height="200" VerticalAlignment="Top" Width="470" Margin="0,0,-7,-12">
                                <GroupBox Name="GroupBox_Disks" Header="Additional Disk(s)" HorizontalAlignment="Left" Margin="194,14,0,0" VerticalAlignment="Top" RenderTransformOrigin="0.423,-1.818" Height="170" Width="255">
                                    <Grid Name="Grid_Disks" HorizontalAlignment="Center" Height="150" Margin="0" VerticalAlignment="Top" Width="255">
                                        <ListBox Name="Listbox_Disks" HorizontalAlignment="Left" Height="90" Margin="13,40,0,0" VerticalAlignment="Top" Width="55" RenderTransformOrigin="0.545,0.667" ToolTip="Select Disk to Remove"/>
                                        <Label Name="lbl_DiskList" Content="Disks to add" HorizontalAlignment="Left" Margin="8,10,0,0" VerticalAlignment="Top" Width="80" Height="25"/>
                                        <Label Name="lbl_DiskSize" Content="DiskSize in GB" HorizontalAlignment="Left" Margin="115,10,0,0" VerticalAlignment="Top" RenderTransformOrigin="0.395,0.385" Width="90"/>
                                        <TextBox Name="txtbx_DiskSize" HorizontalAlignment="Left" Height="25" Margin="120,40,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="80" ToolTip="Enter DiskSize"/>
                                        <Button Name="btn_Remove_Disk" Content="Remove" HorizontalAlignment="Left" Margin="80,80,0,0" VerticalAlignment="Top" Width="75" RenderTransformOrigin="0.4,0.5" ToolTip="Click to remove disk from list"/>
                                        <Button Name="btn_Add_Disk" Content="Add" HorizontalAlignment="Left" Margin="160,80,0,0" VerticalAlignment="Top" Width="75" ToolTip="Click to add disk to list"/>
                                    </Grid>
                                </GroupBox>

    Aus der XML-Datei erzeuge ich ein Object für die Controls:

    $GUIObj = New-Object -TypeName PSObject
    $XAML.SelectNodes('//*[@Name]') | ForEach-Object {
      Add-Member -InputObject $GUIObj -Name ($_.Name) -Value $Form.FindName($_.Name) -MemberType NoteProperty -ErrorAction Stop
          }

    Das Object wird über $syncHash an den zweiten Thread übergeben. Aus diesem zweiten Thread versuche ich nun die ListBox "Listbox_Disks" zu leeren.

    Montag, 9. Februar 2015 08:15
  • Den Dispatcher zu benutzen ist richtig!
    Dein $GuiObj ist völlig überflüssig!

    Du kannst ein WPF Window nich wie XML behandeln!

    Da alle Controls Kinder von dem Window Object (From) sind, spricht man die Controls über das Window (Form) an.

    Lies dir mal den folgenden Blog Post von Boe Prox durch:
    http://learn-powershell.net/2012/10/14/powershell-and-wpf-writing-data-to-a-ui-from-a-different-runspace/

    Hier ein Testcode zum Spielen:

    # Import the Assemblies
    Add-Type -AssemblyName PresentationFramework
    Add-Type -AssemblyName PresentationCore
    Add-Type -AssemblyName WindowsBase
    
    # Create the synchronized-hashtable to hold the thread-shared objects
    $SyncHash = [HashTable]::Synchronized(@{})
    
    # create the runspace to use with the pipeline
    $Runspace =[System.Management.Automation.Runspaces.RunspaceFactory]::CreateRunspace()
    # WPF needs Single Thread Appartment (STA)!
    $Runspace.ApartmentState = 'STA'
    $Runspace.ThreadOptions = [System.Management.Automation.Runspaces.PSThreadOptions]::ReuseThread          
    $Runspace.Open()
    
    # add the $SyncHash as Variable to the runspace so we can use it inside the pipeline
    $Runspace.SessionStateProxy.SetVariable('SyncHash',$SyncHash)          
    
    # create PowerShell pipeline for the GUI thread
    $UiTreadPipeline = [PowerShell]::Create()
    
    # PowerShell pipeline has to use our runspace
    $UiTreadPipeline.Runspace = $Runspace
    
    # add the script to execute with the pipeline
    $UiTreadPipeline.AddScript({   
    [xml]$XAML = @"
    <Window x:Class="WpfApplication1.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            Title="MainWindow" Height="350" Width="525">
        <StackPanel >
            <ListBox Name="Listbox_Disks" ToolTip="Select Disk to Remove" Height="80" Width="150" HorizontalAlignment="Left"/>
        </StackPanel>
    </Window>
    "@
        
        # The the x:class Attribute is added by the Visual Studio designer
        # we remove the x:class Attribute wich is not needed inside PowerShell
        $XAML.Window.RemoveAttribute('x:Class')
        
        # create the XmlNodeReader to use with the XamlReader
        $XmlNodeReader = (New-Object System.Xml.XmlNodeReader $xaml)
        # create the WPF Window from XmlNodeReader
        $SyncHash.Window = [Windows.Markup.XamlReader]::Load($XmlNodeReader)
    
        # Show the WPF window as dialog
        $Null = $SyncHash.Window.ShowDialog()
    
    })
    
    # Start the GUI pipeline asynchronous as new Thread
    $AsyncResult = $UiTreadPipeline.BeginInvoke()
    
    # simulate work inside this main thread
    for ($i = 1; $i -le 10; $i++)
    { 
      Start-Sleep -Milliseconds 200 
      Write-Host "Ich Tu hier wat $i" -ForegroundColor Magenta  
    }
    
    # cross thread call from this thread to the GUI thread to the WPF Window
    # we use the synchronized-hashtable and the dispatcher.Invoke() method to access the WPF Window in a threadsave manner
    # add items to a ListBox
    for ($i = 1; $i -le 15; $i++)
    { 
     $SyncHash.Window.Dispatcher.Invoke(
      'Normal',[action]{($SyncHash.Window.FindName('Listbox_Disks')).Items.Add("$i New Item")}
      )   
    }
    
    # cross thread call from this thread to the GUI thread to the WPF Window
    # we use the synchronized-hashtable and the dispatcher.Invoke() method to access the WPF Window in a threadsave manner
    # remove the item '5 New Item' from ListBox
     $SyncHash.Window.Dispatcher.Invoke(
      'Normal',[action]{($SyncHash.Window.FindName('Listbox_Disks')).Items.Remove('5 New Item')}
      )   
    
    # simulate work inside this main thread
    for ($i = 10; $i -le 20; $i++)
    { 
      Start-Sleep -Milliseconds 200 
      Write-Host "Ich Tu noch was hier $i" -ForegroundColor Magenta  
    }


    PowerShell Artikel, Buchtipps und kostenlose PowerShell Tutorials + E-Books
    auf der deutschsprachigen PowerShell Community

    Mein 21 Teiliger PowerShell Video Grundlehrgang
    Deutsche PowerShell Videos auf Youtube
    Folge mir auf:
    Twitter | Facebook | Google+

    Montag, 9. Februar 2015 08:36
  • Hallo Peter,

    vielen Dank für deine Antwort, nach dem ich das Skript umgebaut habe, funktioniert es so, wie es soll.

    Freitag, 13. Februar 2015 15:43