#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()