TOC

This article has been localized into Polish by the community.

Różne kontrolki:

Kontrolka ProgressBar

WPF zawiera przydatną kontrolkę ProgressBar służącą do wyświetlania postępu przebiegu danego procesu. Działa ona dzięki ustawieniu wartości minimalnej i maksymalnej, a następnie zwiększaniu bieżącej wartości kontrolki. Umożliwia to wizualne przedstawienie postępu przebiegu danego procesu. Oto bardzo podstawowy przykład, aby to zademonstrować:

<Window x:Class="WpfTutorialSamples.Misc_controls.ProgressBarSample"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="ProgressBarSample" Height="100" Width="300">
    <Grid Margin="20">
        <ProgressBar Minimum="0" Maximum="100" Value="75" />
    </Grid>
</Window>

W moim przykładzie użyłem dość standardowego podejścia do wyświetlania postępu w procentach (od 0 do 100%), nadając mu początkową wartość 75%. Innym podejściem jest użycie rzeczywistych wartości minimalnej i maksymalnej jako ilości zadań do wykonania w danym wizualizowanym procesie. Na przykład jeżeli przeglądasz listę zebranych plików, wykonując pewne zadania na każdym z nich, możesz ustawić właściwość Minimum na 0, właściwość Maximum na liczbę plików na liście, a następnie zwiększać stopniowo bieżącą wartość Value kontrolki w trakcie przeglądania listy plików.

Kontrolka ProgressBar, podobnie jak inne standardowe kontrolki WPF, jest renderowana w celu dopasowania jej wyglądu do stylu używanego przez system operacyjny. W systemie Windows 7 ma ona ładny animowany gradient, jak widać na zrzucie ekranu.

Ukazanie postępu przebiegu wykonywania długiego zadania

Powyższy przykład ilustruje, jak proste jest samo użycie kontrolki ProgressBar. Oczywiście chciałbyś jednak pokazać postęp przebiegu rzeczywistego procesu, a nie tylko wyświetlać niezmienną wartość.

W większości sytuacji użyjesz ProgressBar do pokazania postępów przebiegu prac nad jakiemś ciężkim/długim zadaniem, i tutaj większość początkujących programistów napotyka na bardzo powszechny problem: Jeżeli wykonujesz ciężkie/długie zadanie w wątku interfejsu użytkownika, próbując jednocześnie aktualizować np kontrolkę ProgressBar, wkrótce zdasz sobie sprawę, że nie możesz jednocześnie wykonywać obu tych czynności w tym samym wątku. Albo, żeby było bardziej zrozumiale, możesz wykonywać obie te czynności na raz, ale ProgressBar tak naprawdę nie jest w stanie pokazać żadnej z aktualizacji postępu przebiegu prac nad owym ciężkim/długim zadaniem dopóki nie zakończy się jego wykonywanie, co praktycznie czyni nasze wysiłki bezużytecznymi.

Aby to zilustrować możesz użyć poniższego przykładu:

<Window x:Class="WpfTutorialSamples.Misc_controls.ProgressBarTaskOnUiThread"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="ProgressBarTaskOnUiThread" Height="100" Width="300"
        ContentRendered="Window_ContentRendered">
    <Grid Margin="20">
        <ProgressBar Minimum="0" Maximum="100" Name="pbStatus" />
    </Grid>
</Window>
using System;
using System.Threading;
using System.Windows;

namespace WpfTutorialSamples.Misc_controls
{
	public partial class ProgressBarTaskOnUiThread : Window
	{
		public ProgressBarTaskOnUiThread()
		{
			InitializeComponent();
		}

		private void Window_ContentRendered(object sender, EventArgs e)
		{
			for(int i = 0; i < 100; i++)
			{
				pbStatus.Value++;
				Thread.Sleep(100);
			}
		}
	}
}

Bardzo prosty przykład, w którym gdy tylko okno aplikacji jest gotowe do działania, wykonujemy pętlę od 0 do 100 i w każdym przebiegu pętli zwiększamy bieżącą wartość Value kontrolki. Każdy nowoczesny komputer może to zrobić szybciej, niż zdołasz mrugnąć oczami, więc do każdej iteracji pętli dodałem opóźnienie wynoszące 100 milisekund. Niestety, jak już pisałem wyżej, nic się w wyglądzie ProgressBar nie zmieni. Tak to wygląda w środku procesu:

Zauważ, że kursor wskazuje na to, że jakaś operacja się odbywa, ale kontrolka ProgressBar nadal wygląda tak jak na początku (jest pusty). Gdy tylko pętla, która symuluje nasze długie zadanie, zostanie wykonana, pasek postępu będzie wyglądał następująco:

To naprawdę nie pomogło użytkownikom aplikacji zobaczyć postępu przebiegu danej operacji! Zamiast takiego rozwiązania musimy wykonywać nasze ciężkie/długie zadanie w wątku roboczym i z niego wysyłać aktualizacje wartości Value do wątku interfejsu użytkownika, który dzięki temu będzie mógł natychmiast przetworzyć i wizualnie wyświetlić te aktualizacje. Doskonałym narzędziem do wykonania takiego zadania jest klasa BackgroundWorker, o której piszemy znacznie więcej w innym miejscu tego samouczka. Oto ten sam przykład co powyżej, ale tym razem przy użyciu instancji klasy BackgroundWorker:

<Window x:Class="WpfTutorialSamples.Misc_controls.ProgressBarTaskOnWorkerThread"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="ProgressBarTaskOnWorkerThread" Height="100" Width="300"
        ContentRendered="Window_ContentRendered">
    <Grid Margin="20">
        <ProgressBar Minimum="0" Maximum="100" Name="pbStatus" />
    </Grid>
</Window>
using System;
using System.ComponentModel;
using System.Threading;
using System.Windows;

namespace WpfTutorialSamples.Misc_controls
{
	public partial class ProgressBarTaskOnWorkerThread : Window
	{
		public ProgressBarTaskOnWorkerThread()
		{
			InitializeComponent();
		}

		private void Window_ContentRendered(object sender, EventArgs e)
		{
			BackgroundWorker worker = new BackgroundWorker();
			worker.WorkerReportsProgress = true;
			worker.DoWork += worker_DoWork;
			worker.ProgressChanged += worker_ProgressChanged;

			worker.RunWorkerAsync();
		}

		void worker_DoWork(object sender, DoWorkEventArgs e)
		{
			for(int i = 0; i < 100; i++)
			{
				(sender as BackgroundWorker).ReportProgress(i);
				Thread.Sleep(100);
			}
		}

		void worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
		{
			pbStatus.Value = e.ProgressPercentage;
		}
	}
}

Jak widać na zrzucie ekranu, postęp przebiegu operacji jest teraz aktualizowany przez cały czas wykonywania naszego zadania i jak wskazuje kursor, w wątku interfejsu użytkownika nie jest wykonywana żadna ciężka praca, co oznacza, że nadal możesz w tym czasie korzystać z reszty interfejsu aplikacji.

Należy pamiętać, że chociaż klasa BackgroundWorker bardzo pomaga w problemach związanych z wielowątkowością, nadal istnieje kilka rzeczy o których istnieniu powinieneś być świadomym. Dlatego należy zapoznać się z artykułami o klasie BackgroundWorker w tym samouczku, zanim zaczniesz tworzyć bardziej zaawansowane scenariusze niż ten przedstawiony powyżej.

Postęp nieokreślony

W przypadku niektórych zadań wyrażanie w procentach postępu przebiegu danej operacji nie jest możliwe lub po prostu nie wiemy ile czasu zajmie jej wykonanie. Dla takich sytuacji wymyślono nieokreślony pasek postępu, w którym animacja informuje użytkownika, że jakaś operacja jest wykonywana, jednocześnie wskazując że nie można określić czasu do jej zakończenia.

Kontrolka ProgressBar obsługuje ten tryb za pomocą właściwości IsIndeterminate, którą pokazujemy w poniższym przykładzie:

<Window x:Class="WpfTutorialSamples.Misc_controls.ProgressBarIndeterminateSample"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="ProgressBarIndeterminateSample" Height="100" Width="300">
    <Grid Margin="20">
        <ProgressBar Minimum="0" Maximum="100" Name="pbStatus" IsIndeterminate="True" />
    </Grid>
</Window>

Zwróć uwagę, że zielony wskaźnik postępu przebiegu danej operacji nie jest zakotwiczony do żadnej ze stron paska postępu - zamiast tego pływa swobodnie od początku do końca i tak w kółko.

Tekstowa reprezentacja postępu

Jedną rzeczą, której tak naprawdę brakowało mi w standardowej kontrolce ProgressBar, jest możliwość wyświetlania tekstowej reprezentacji postępu równolegle z pokazywanym paskiem postępu. Na szczęście dla nas elastyczność WPF sprawia, że jest to dość łatwe do osiągnięcia. Oto przykład:

<Window x:Class="WpfTutorialSamples.Misc_controls.ProgressBarTextSample"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="ProgressBarTextSample" Height="100" Width="300">
    <Grid Margin="20">
        <ProgressBar Minimum="0" Maximum="100" Value="75" Name="pbStatus" />
        <TextBlock Text="{Binding ElementName=pbStatus, Path=Value, StringFormat={}{0:0}%}" HorizontalAlignment="Center" VerticalAlignment="Center" />
    </Grid>
</Window>

Osiągamy powyższe, umieszczając kontrolkę ProgressBar i kontrolkę TextBlock, pokazującą procentowy postęp, w tej samej komórce panelu Grid, bez określania żadnych wierszy ani kolumn. Spowoduje to wyświetlanie kontrolki TextBlock na wierzchu kontrolki ProgressBar, co jest dokładnie tym czego tutaj oczekujemy, ponieważ TextBlock ma domyślnie przezroczyste tło.

Używamy wiązania danych, aby mieć pewność, że kontrolka TextBlock pokazywać będzie tę samą wartość co kontrolka ProgressBar. Zwróć uwagę na specjalną składnię StringFormat formatującą wyświetlany tekst. Pozwala nam to pokazać wartość paska procesu z przyrostkiem w postaci znaku procentu % - może ów zapis wyglądać nieco dziwnie, ale zapoznaj się z artykułem StringFormat tego samouczka, aby uzyskać więcej informacji na ten temat.


This article has been fully translated into the following languages: Is your preferred language not on the list? Click here to help us translate this article into your language!