C#WPFの道#19!ProgressBarの書き方と使い方を解りやすく解説

当サイトではアフィリエイト広告を利用しています。

WPF

ProgressBar(プログレスバー)とは?

ProgressBarとは時間がかかる処理を非同期でさせている最中に,進捗状況を画面に表示するためのコントロールです。

主なプロパティ

  • 進捗状況をValueに設定します。
  • 最小値をMinimum,最大値をMaximumに設定します。パーセントで表示する場合は0から100で設定します。ファイル数等,処理の最大値がわかっている場合はその値を表示することで,どの程度作業が終わっているのかがユーザーに分かりやすくなります。

主なイベント

ValueChanged

Valueが変更されたときに通知されるので,このタイミングでTextBlockのTextプロパティに文字を設定することで,プログレスバーの目盛りだけではなく,文字でも表示することができるので,よりユーザーに分かりやすくなります。

表示形式

最初の画像で示している通り,主な表現の方法は3パターンあります。

パターン1:プログレスバーとテキストを別で表示する

この方法が一番簡単で一般的です。プログレスバーの周りにTextBlockなどを設置して,プログレスバーとテキスト表示を別で表示します。もちろんプログレスバー単体での表示も可能ですが,その場合,ユーザーは大体の値しか把握できなくなります。

<ProgressBar x:Name="AProgressBar"
                 Margin="25,0,0,0"
                 Height="30"
                 Width="180"
                 Minimum="0"
                 Maximum="100"
                 HorizontalAlignment="Left"
                 ValueChanged="AProgressBar_ValueChanged"
                 />
    <TextBlock x:Name="ATextBlock"
               Margin="10,0,0,0"
               HorizontalAlignment="Center"
               VerticalAlignment="Center"
               Text="xxx"
               Width="50"
               FontSize="20"/>
    <Button x:Name="AButton"
            Margin="10,0,0,0"
            Click="AButton_Click"
            Height="30"
            Width="60"
            Content="Run"/>

</StackPanel>
public MainWindow()
{
    InitializeComponent();
    ATextBlock.Text = AProgressBar.Value.ToString() + "%";
}

private void AProgressBar_ValueChanged(object sender, RoutedPropertyChangedEventArgs e)
{
    ATextBlock.Text = AProgressBar.Value.ToString() + "%";
}

private void AButton_Click(object sender, RoutedEventArgs e)
{
    Task.Run(() =>
    {
        for (int i = 0; i < 10; i++)
        {
            System.Threading.Thread.Sleep(500);

            Application.Current.Dispatcher.Invoke(() =>
            {
                AProgressBar.Value += 10;
            });
        }
    });
}
  • AButton_Clickの中では,時間のかかる処理を作るために, 500ミリ秒のSleepを10回実施しています。
  • Sleep 1回実施されるたびにProgressBarのValueを10ずつ増やしています。
  • 非同期処理の中でコントロールを操作できないため,Current.Dispatcher.Invokeで,UIスレッドに戻してから,プログレスバーの値を変更しています。

パターン2:プログレスバーとテキストを重ねて表示する

この表示の仕方も結構ニーズがあると思います。ProgressBar自体にこの方法を実現するプロパティは実装されていないので,自分で実装する必要があります。やり方はProgressBarとTextBlockを同一のGrid上に配置し,同一の列と行の設定にします。同一のGridにこの2つしかコントロールがない場合はどちらも行列がゼロになっているので,あえて行列の設定をする必要はありません。

<StackPanel Margin="5,10,5,5"
            Orientation="Horizontal">
    <Grid>
        <ProgressBar x:Name="BProgressBar"
                 Margin="25,0,0,0"
                 Height="30"
                 Width="180"
                 Minimum="0"
                 Maximum="100"
                 HorizontalAlignment="Left"
                 ValueChanged="BProgressBar_ValueChanged"
                 />
        <TextBlock x:Name="BTextBlock"
               Margin="10,0,0,0"
               HorizontalAlignment="Center"
               VerticalAlignment="Center"
               Text="xxx"
               Width="50"
               FontSize="20"/>
    </Grid>

    <Button x:Name="BButton"
            Margin="10,0,0,0"
            Click="BButton_Click"
            Height="30"
            Width="60"
            Content="Run"/>

</StackPanel>

パターン1とほとんど同じです。違いはProgressBarとTextBlockを同一のGrid上に載せているだけです。

public MainWindow()
{
    InitializeComponent();

    BTextBlock.Text = BProgressBar.Value.ToString() + "%";
}

private void BProgressBar_ValueChanged(object sender, RoutedPropertyChangedEventArgs e)
{
    BTextBlock.Text = BProgressBar.Value.ToString() + "%";
}

private void BButton_Click(object sender, RoutedEventArgs e)
{
    Task.Run(() =>
    {
        for (int i = 0; i < 10; i++)
        {
            System.Threading.Thread.Sleep(500);

            Application.Current.Dispatcher.Invoke(() =>
            {
                BProgressBar.Value += 10;
            });
        }
    });
}

パターン3:終了するタイミングが不明な時

すべての処理が,終了するタイミングがわかるわけではありません。データベースの問い合わせなどは,データベースからの返答がいつになるかわかりません。そういった場合は,処理中であることだけを示し,画面が固まっていないことを表現するためにも,何かしらのアニメーションが動作しているほうが望ましいです。そういった場合にプログレスバーの目盛りが,左から右に永遠と流れるだけの機能が実装されています。やり方はIsIndeterminate

というプロパティをTrueにするだけです。処理が終わったらFalseにすれば止まります。パターン2のTextBlockを重ねる方法を利用して「検索しています…」等と表示すれば,一層の効果があります。

<StackPanel Margin="5,10,5,5"
            Orientation="Horizontal">
    <Grid>
        <ProgressBar x:Name="CProgressBar"
                 Margin="25,0,0,0"
                 Height="30"
                 Width="180"
                 Minimum="0"
                 Maximum="100"
                 HorizontalAlignment="Left"
                 />
        <TextBlock x:Name="CTextBlock"
               Margin="10,0,0,0"
               HorizontalAlignment="Center"
               VerticalAlignment="Center"
               Text=""
               FontSize="14"/>
    </Grid>

    <Button x:Name="CButton"
            Margin="10,0,0,0"
            Click="CButton_Click"
            Height="30"
            Width="60"
            Content="Run"/>

</StackPanel>
private void CButton_Click(object sender, RoutedEventArgs e)
{
    CProgressBar.IsIndeterminate = true;
    CTextBlock.Text = "検索しています...";
}

処理を実行したタイミングでIsIndeterminateをTrueにして,TextBlockに検索中の文言を表示しています。処理が終わったタイミングでIsIndeterminateをFalseにしたり,TextBlockの文言を消す必要があります。

サンプルコード全体

<Window x:Class="WPF019.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WPF019"
        mc:Ignorable="d"
        Title="MainWindow" Height="170" Width="370">
    <Grid>
        <StackPanel>
            <StackPanel Margin="5,10,5,5"
                        Orientation="Horizontal">
                <ProgressBar x:Name="AProgressBar"
                             Margin="25,0,0,0"
                             Height="30"
                             Width="180"
                             Minimum="0"
                             Maximum="100"
                             HorizontalAlignment="Left"
                             ValueChanged="AProgressBar_ValueChanged"
                             />
                <TextBlock x:Name="ATextBlock"
                           Margin="10,0,0,0"
                           HorizontalAlignment="Center"
                           VerticalAlignment="Center"
                           Text="xxx"
                           Width="50"
                           FontSize="20"/>
                <Button x:Name="AButton"
                        Margin="10,0,0,0"
                        Click="AButton_Click"
                        Height="30"
                        Width="60"
                        Content="Run"/>

            </StackPanel>

            <StackPanel Margin="5,10,5,5"
                        Orientation="Horizontal">
                <Grid>
                    <ProgressBar x:Name="BProgressBar"
                             Margin="25,0,0,0"
                             Height="30"
                             Width="180"
                             Minimum="0"
                             Maximum="100"
                             HorizontalAlignment="Left"
                             ValueChanged="BProgressBar_ValueChanged"
                             />
                    <TextBlock x:Name="BTextBlock"
                           Margin="10,0,0,0"
                           HorizontalAlignment="Center"
                           VerticalAlignment="Center"
                           Text="xxx"
                           Width="50"
                           FontSize="20"/>
                </Grid>

                <Button x:Name="BButton"
                        Margin="10,0,0,0"
                        Click="BButton_Click"
                        Height="30"
                        Width="60"
                        Content="Run"/>

            </StackPanel>

            <StackPanel Margin="5,10,5,5"
                        Orientation="Horizontal">
                <Grid>
                    <ProgressBar x:Name="CProgressBar"
                             Margin="25,0,0,0"
                             Height="30"
                             Width="180"
                             Minimum="0"
                             Maximum="100"
                             HorizontalAlignment="Left"
                             />
                    <TextBlock x:Name="CTextBlock"
                           Margin="10,0,0,0"
                           HorizontalAlignment="Center"
                           VerticalAlignment="Center"
                           Text=""
                           FontSize="14"/>
                </Grid>

                <Button x:Name="CButton"
                        Margin="10,0,0,0"
                        Click="CButton_Click"
                        Height="30"
                        Width="60"
                        Content="Run"/>

            </StackPanel>
        </StackPanel>
    </Grid>
</Window>
using System.Threading.Tasks;
using System.Windows;

namespace WPF019
{
    ///
    /// MainWindow.xaml の相互作用ロジック
    /// 
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();

            ATextBlock.Text = AProgressBar.Value.ToString() + "%";
            BTextBlock.Text = BProgressBar.Value.ToString() + "%";
        }

        private void AProgressBar_ValueChanged(object sender, RoutedPropertyChangedEventArgs e)
        {
            ATextBlock.Text = AProgressBar.Value.ToString() + "%";
        }

        private void AButton_Click(object sender, RoutedEventArgs e)
        {
            Task.Run(() =>
            {
                for (int i = 0; i < 10; i++)
                {
                    System.Threading.Thread.Sleep(500);

                    Application.Current.Dispatcher.Invoke(() =>
                    {
                        AProgressBar.Value += 10;
                    });
                }
            });
        }

        private void BProgressBar_ValueChanged(object sender, RoutedPropertyChangedEventArgs e)
        {
            BTextBlock.Text = BProgressBar.Value.ToString() + "%";
        }

        private void BButton_Click(object sender, RoutedEventArgs e)
        {
            Task.Run(() =>
            {
                for (int i = 0; i < 10; i++)
                {
                    System.Threading.Thread.Sleep(500);

                    Application.Current.Dispatcher.Invoke(() =>
                    {
                        BProgressBar.Value += 10;
                    });
                }
            });
        }

        private void CButton_Click(object sender, RoutedEventArgs e)
        {
            CProgressBar.IsIndeterminate = true;
            CTextBlock.Text = "検索しています...";
        }
    }
}