Окно WPF не обновляется из PowerShell

#wpf #powershell #progress-bar

#wpf #powershell #индикатор выполнения

Вопрос:

У меня возникли проблемы с тем, что WPF-window продолжает обновлять из PowerShell, а также не зависает.

Идея в том, что когда я нажимаю кнопку, должна запускаться какая-то функция, которая обновляет значение для панели прогресса. Но какой бы метод или руководство я ни использовал, я не могу заставить его работать. Панель выполнения обновляется и заполняется на 100%, когда задача выполнена. Но во время операции окно зависает. Единственный способ, которым я могу получить окно, которое не зависает, — это если я выполню простое добавление значений в updateblock . Но мне нужно иметь возможность запускать различные функции и обновлять панель выполнения снова и снова.

Что мне нужно сделать, чтобы запустить функцию или блок сценариев из события щелчка, чтобы окно не зависало и панель прогресса обновлялась?

PowerShell-code:

 Add-Type -Assembly PresentationFramework

$syncHash = [hashtable]::Synchronized( @{} )
$syncHash.Counter = 0.0
$syncHash.Do = {
    param ( $syncHash, $ToDo )
    $rsR = [runspacefactory]::CreateRunspace()
    $rsR.ApartmentState = "STA"
    $rsR.ThreadOptions = "ReuseThread"
    $rsR.Open()
    $rsR.SessionStateProxy.SetVariable( "syncHash", $syncHash )
    $c = [powershell]::Create().AddScript( $ToDo )
    $c.Runspace = $rsR
    $h = $c.BeginInvoke()
    do { start-sleep -millisecond 10 } until ( $h.IsCompleted -eq $true )
    $c.EndInvoke( $h )
    $rsR.Close()
    $rsR.Dispose()
}

$syncHash.Window    = [Windows.Markup.XamlReader]::Load( ( New-Object System.Xml.XmlNodeReader ( [xml]( Get-Content .tt.xml ) ) ) )
$syncHash.Button    = $syncHash.Window.FindName( "Button" )
$syncHash.Progress  = $syncHash.Window.FindName( "Progress" )
$Timer = New-Object System.Windows.Threading.DispatcherTimer

$Task = { 1..100 | % { start-sleep -millisecond 10; $syncHash.Counter = $_ } }
$syncHash.Button.Add_Click( { Something } )
$syncHash.Window.Add_SourceInitialized( {
    $Timer.Start()
    $Timer.Add_Tick( $UpdateBlock )
    $Timer.Interval = [TimeSpan]"0:0:0.01"
} )

function Something
{
    amp; $syncHash.Do $syncHash $Task
}

$UpdateBlock =`
{
    $syncHash.Window.Resources["Progress"] = [double]$syncHash.Counter
    $syncHash.Window.Resources["PbText"] = "$( $syncHash.Counter ) %"
}
[void]$syncHash.Window.ShowDialog()
  

XAML-код:

 <Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:system="clr-namespace:System;assembly=mscorlib"
        Topmost="True"
        WindowStartupLocation="CenterScreen"
        SizeToContent="WidthAndHeight">
   <Window.Resources>
      <system:Double x:Key="Progress">0.0</system:Double>
      <system:String x:Key="PbText"></system:String>
   </Window.Resources>

    <Grid>
    <Grid.RowDefinitions>
        <RowDefinition/>
        <RowDefinition/>
        <RowDefinition/>
    </Grid.RowDefinitions>
        <Button Grid.Row="0" Content="Click" Name="Button"/>
        <Grid Grid.Row="1">
            <ProgressBar Name="Progress" Minimum="0" Value="{DynamicResource Progress}"  Width="230"/>
            <TextBlock Text="{DynamicResource PbText}" HorizontalAlignment="Center" VerticalAlignment="Center"/>
        </Grid>
    </Grid>
</Window>
  

Комментарии:

1. У вас уже есть дополнительный поток (runspace), но вы просто не используете его в полной мере. Вы открываете другой поток, а затем отправляете свой основной поток / GUI в режим ожидания до завершения задачи… Вместо этого сохраните прогресс в вашем $syncHash объекте, который имеет INotifyPropertyChanged , чтобы вы могли запускать события для обновления панели выполнения при ее изменении, вместо того, чтобы ждать / спать, пока это произойдет.

2. Я пытался получить это, но я либо не понимаю, что вы пытаетесь объяснить, либо не понимаю, как это реализовать:(

Ответ №1:

Я наконец нашел, что делать. Пришлось переосмыслить некоторые вещи и полностью удалить функциональность runspace.

Благодаря привязке все стало проще.

Вот к чему я пришел, также добавлена привязка для максимального значения progressbar:

 Add-Type -Assembly PresentationFramework

$syncHash = @{}
$syncHash.Window    = [Windows.Markup.XamlReader]::Load( ( New-Object System.Xml.XmlNodeReader ( [xml]( Get-Content .tt.xml ) ) ) )
$syncHash.Button    = $syncHash.Window.FindName( "Button" )
$syncHash.Progress  = $syncHash.Window.FindName( "Progress" )

$Something = { param( $d )
    function SomethingElse { gci }

    $d[1] = ( SomethingElse ).Count
    [system.windows.messagebox]::show($d[1])
    foreach ( $i in ( 1..$d[1] ) ) { Start-Sleep -MilliSeconds 300; $d[0] = [double]( ( $i/$d[1] ) * 100 ) }
}

$syncHash.Button.Add_Click( { ( [powershell]::Create().AddScript( $Something ).AddArgument( $DataContext ) ).BeginInvoke() } )

$DataContext = New-Object System.Collections.ObjectModel.ObservableCollection[Object]
$DataContext.Add( 0.0 )
$DataContext.Add( 0.0 )
$syncHash.Progress.DataContext = $DataContext

$Binding = New-Object System.Windows.Data.Binding -ArgumentList "[0]"
$Binding.Mode = [System.Windows.Data.BindingMode]::OneWay
[void][System.Windows.Data.BindingOperations]::SetBinding( $syncHash.Progress, [System.Windows.Controls.ProgressBar]::ValueProperty, $Binding )

$Binding = New-Object System.Windows.Data.Binding -ArgumentList "[1]"
$Binding.Mode = [System.Windows.Data.BindingMode]::OneWay
[void][System.Windows.Data.BindingOperations]::SetBinding( $syncHash.Progress, [System.Windows.Controls.ProgressBar]::MaximumProperty, $Binding )

[void]$syncHash.Window.ShowDialog()