TOC

This article is currently in the process of being translated into Vietnamese (~98% done).

Misc. controls:

The ProgressBar control

WPF đi kèm với một điều khiển tiện dụng để hiển thị tiến trình được gọi là ProgressBar (thanh Tiến trình). Control này hoạt động bằng cách cài đặt giá trị nhỏ nhất và giá trị lớn nhất sau đó tăng dần giá trị, điều này giúp bạn có thể biết được tiến trình hiện tại đang được thực hiện một cách trực quan. Đây là ví dụ đơn giản để giải thích:

<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>

Trong trường hợp này, tôi đã sử dụng cách làm tiêu chuẩn là hiển thị tiến trình theo tỉ lệ phần trăm (0 đến 100%), và cho giá trị ban đầu là 75%. Một cách làm khác là dùng các giá trị nhỏ nhất và giá trị lớn nhất từ danh sách công việc mà bạn đang thực hiện. Ví dụ, khi bạn duyệt qua một danh sách các file đã có và kiểm tra từng cái một, bạn có thể đặt thuộc tính Minimum là 0, Maximum là tổng số file của bạn, sau đó tăng dần lên khi bạn duyệt qua một tệp (như việc bạn giải nén file .rar/.zip, giá trị Minimum sẽ là 0, Maximum là tổng số file, có file nào được giải nén thì giá trị sẽ tăng dần tới Maximum).

Tương tự như các control WPF khác thì giao diện của ProgressBar sẽ bị ảnh hưởng bởi kiểu giao diện của hệ điều hành. Như trên ảnh là kiểu giao diện của hệ điều hành Windows 7.

Hiển thị tiến trình của một tác vụ

Ví dụ trên cho thấy, ta có thể dễ dàng sử dụng ProgressBar như thế nào, đương nhiên khi dùng ProgressBar thì bạn sẽ muốn hiển thị mức độ hoàn thiện của một công việc cụ thể chứ không đơn thuần là một con số tĩnh.

Trong hầu hết các trường hợp, bạn sẽ sử dụng ProgressBar để hiển thị tiến độ cho những tác vụ nặng hoặc kéo dài, và đây là chỗ mà những lập trình viên rất hay gặp vấn đề chung như: nếu bạn thực hiện một tác vụ nặng trên tiến trình UI trong khi đó đồng thời cập nhật ví dụ như ProgressBar trong tiến trình này, bạn sẽ sớm nhận ra rằng bạn không thể làm cả hai đồng thời trên cùng một tiến trình. Hoặc là nói rõ hơn, bạn có thể làm điều đó nhưng ProgressBar sẽ không thay đổi liên tục mỗi lần cập nhật ngay trước khi tác vụ hoàn thành, điều này làm cho ProgressBar trở nên vô dụng.

Để minh họa, bạn có thể xem và trải nghiệm qua ví dụ sau

<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);
			}
		}
	}
}

Đây là một ví dụ đơn giản, khi mà cửa sổ chương trình sẵn sàng, chúng ta thực hiện lặp từ 0 đến 100 và trong mỗi một bước tăng giá trị biến đếm, chúng ta tăng giá trị của ProgressBar. Với sức mạnh các máy tính hiện nay có thể thực hiện việc này trong chớp mắt, vì thế tôi đã thêm bước delay (tạo trễ) trong mỗi bước lặp với thời gian là 100 mili giây. Và thật không may, như đã được mô tả ở trên, chúng ta không thu được gì cả. Nó sẽ trông như thế này khi trong quá trình thực thi tác vụ.

Hãy chú ý rằng con trỏ chuột chỉ ra rằng có điều gì đó đang xảy ra, nhưng ProgressBar vẫn trông giống như lúc bắt đầu (trống, không có gì thay đổi). Ngay sau khi vòng lặp kết thúc, tác vụ được thực hiện xong, ProgressBar sẽ trông như thế này:

Nó thật sự không giúp người dùng của bạn xem được tiến trình đang thực thi. Thay vào đó, chúng ta phải thực hiện tác vụ trên một luồng khác và sau đó đẩy các bản cập nhật lên chuỗi giao diện người dùng, sau đó sẽ có thể ngay lập tức xử lý và hiển thị các bản cập nhật này một cách trực quan. Một công cụ tuyệt vời để xử lý công việc này là lớp BackgroundWorker, mà chúng ta sẽ nói nhiều hơn ở những nơi khác trong hướng dẫn này. Đây là ví dụ tương tự như trên, nhưng lần này sử dụng 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;
		}
	}
}

Như bạn nhìn thấy ảnh chụp màn hình, tiến trình bây giờ được cập nhật trong khi tác vụ đang được thực hiện, và như chuột đang hiển thị, không có công việc nặng trong tiến trình hiển thị UI, điều đó có nghĩa bạn có thể tương tác với giao diện bình thường.

Xin lưu ý rằng mặc dù BackgroundWorker giúp ích rất nhiều cho các vấn đề liên quan đến đa luồng (đa nhiệm), nhưng vẫn có một số điều bạn cần cẩn thận lưu ý, vì vậy các bạn hãy xem các bài viết của BackgroundWorker trước khi thực hiện bất kỳ điều gì nâng cao hơn ví dụ ở trên.

Dạng không xác định

Đối với một số tác vụ, việc thể hiện tiến độ dưới dạng phần trăm là không thể thực hiện được hoặc đơn giản là bạn không biết sẽ mất bao lâu. Đối với những tình huống đó, thanh tiến trình không xác định đã được phát minh, nơi hoạt ảnh cho phép người dùng biết rằng điều gì đó đang xảy ra, đồng thời cho biết rằng không thể xác định được thời gian chạy.

WPF ProgressBar hỗ trợ chế độ này qua thuộc tình IsIndeterminate, nó sẽ được chúng tôi giới thiệu cho bạn qua ví dụ tiếp theo:

<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>

Hãy chú ý rằng thanh tiến trình màu xanh lá cây không gắn ở bên rìa cạnh, thay vào đó nó trôi từ điểm đầu đến điểm cuối và sau đó bắt đầu lại từ đầu.

ProgressBar hiển thị ký tự

Một điều tôi đã thực sự đã bỏ qua trong WPF ProgressBar là khả năng hiển thị ký tự trên tiến trình như thanh tiến trình. Thật may mắn cho chúng ta, WPF giúp chúng ta làm việc này rất dễ dàng, sau đây là ví dụ:

<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>

Chúng ta thực hiện điều trên bằng cách đặt ProgressBar và TextBlock hiển thị phần trăm bên trong của cùng một Grid, mà không chỉ định bất kỳ hàng hoặc cột nào. Điều này sẽ hiển thị TextBlock ở phía trên của ProgressBar, đây chính xác là những gì chúng ta muốn ở đây, vì TextBlock có nền trong suốt theo mặc định.

Chúng ta sử dụng binding (liên kết) để đảm bảo rằng TextBlock hiển thị chính xác giá trị của ProgressBar. Để ý rằng cú pháp đặc biệt StringFormat , cho phép chúng ta hiển thị giá trị với hậu tố dấu phần trăm - nó có thể trông hơi lạ, nhưng vui lòng xem bài viết StringFormat để biết thêm thông tin về nó.


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!