c++ builder XE4, 10.2 Tokyo > Progressフォームの実装

```txt:動作確認
C++ Builder XE4
RAD Studio 10.2 Tokyo Update 2 (追記: 2017/12/28)
```

処理の進捗を表すプログレスバー。キャンセルボタン付きのダイアログで使われる。
PDFExport時のビルトインプログレス表示にてキャンセルボタンの認識ができそうにないので、自作することにした。
関連 http://qiita.com/7of9/items/5a12f5c482bfba552383


## code v0.1

### ProgressUnit

以下のコンポーネントを持つ。

- L_message
- ProgressBar1
- B_cancel

APIはProgressUnit.hを参照。

コードは以下。

```ProgressUnit.dfm
object FormProgress: TFormProgress
  Left = 0
  Top = 0
  BorderStyle = bsDialog
  Caption = 'Progress'
  ClientHeight = 180
  ClientWidth = 378
  Color = clBtnFace
  Font.Charset = DEFAULT_CHARSET
  Font.Color = clWindowText
  Font.Height = -11
  Font.Name = 'Tahoma'
  Font.Style = []
  OldCreateOrder = False
  Position = poScreenCenter
  OnShow = FormShow
  PixelsPerInch = 96
  TextHeight = 13
  object L_message: TLabel
    Left = 16
    Top = 16
    Width = 345
    Height = 41
    AutoSize = False
    Caption = 'L_message'
  end
  object ProgressBar1: TProgressBar
    Left = 16
    Top = 80
    Width = 345
    Height = 25
    TabOrder = 0
  end
  object B_cancel: TButton
    Left = 152
    Top = 128
    Width = 75
    Height = 25
    Caption = 'Cancel'
    TabOrder = 1
    OnClick = B_cancelClick
  end
end
```

```ProgressUnit.h
//---------------------------------------------------------------------------

#ifndef ProgressUnitH
#define ProgressUnitH
//---------------------------------------------------------------------------
#include <System.Classes.hpp>
#include <Vcl.Controls.hpp>
#include <Vcl.StdCtrls.hpp>
#include <Vcl.Forms.hpp>
#include <Vcl.ComCtrls.hpp>
//---------------------------------------------------------------------------
class TFormProgress : public TForm
{
__published:	// IDE で管理されるコンポーネント
	TLabel *L_message;
	TProgressBar *ProgressBar1;
	TButton *B_cancel;
	void __fastcall B_cancelClick(TObject *Sender);
	void __fastcall FormShow(TObject *Sender);
private:	// ユーザー宣言
public:		// ユーザー宣言
	void __fastcall SetMessage(String message);
	void __fastcall SetMaxPosition(int value);
	void __fastcall SetCurrentPosition(int value);
	void __fastcall SetCaption(String caption);

	__fastcall TFormProgress(TComponent* Owner);
};
//---------------------------------------------------------------------------
extern PACKAGE TFormProgress *FormProgress;
//---------------------------------------------------------------------------
#endif
```

```ProgressUnit.cpp
//---------------------------------------------------------------------------

#include <vcl.h>
#pragma hdrstop

#include "ProgressUnit.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TFormProgress *FormProgress;
//---------------------------------------------------------------------------

/*
v0.1 2016/08/28
	- SetCaption()追加
	- B_cancelClick()追加
	- FormShow()追加
	- SetCurrentPosition()追加
	- SetMaxPosition()追加
	- SetMessage()追加
*/


__fastcall TFormProgress::TFormProgress(TComponent* Owner)
	: TForm(Owner)
{
	ProgressBar1->Min = 0;
	ProgressBar1->MarqueeInterval = 1;
}
//---------------------------------------------------------------------------

void __fastcall TFormProgress::SetMessage(String message)
{
	L_message->Caption = message;
}

void __fastcall TFormProgress::SetMaxPosition(int value)
{
	ProgressBar1->Max = value;
}

void __fastcall TFormProgress::SetCurrentPosition(int value)
{
    ProgressBar1->Position = value;
}

void __fastcall TFormProgress::SetCaption(String caption)
{
	this->Caption = caption;
}

//---------------------------------------------------------------------------
void __fastcall TFormProgress::FormShow(TObject *Sender)
{
	this->ModalResult = mrNone;
}

void __fastcall TFormProgress::B_cancelClick(TObject *Sender)
{
	this->ModalResult = mrCancel;
	Close();
}
```

### Unit1

ProgressUnitを使う例。

以下のコンポーネントを持つ。

- TTimer型のTimerProgress
- Button1

```Unit1.dfm
object Form1: TForm1
  Left = 0
  Top = 0
  Caption = 'Form1'
  ClientHeight = 300
  ClientWidth = 404
  Color = clBtnFace
  Font.Charset = DEFAULT_CHARSET
  Font.Color = clWindowText
  Font.Height = -11
  Font.Name = 'Tahoma'
  Font.Style = []
  OldCreateOrder = False
  PixelsPerInch = 96
  TextHeight = 13
  object Button1: TButton
    Left = 168
    Top = 208
    Width = 75
    Height = 25
    Caption = 'Button1'
    TabOrder = 0
    OnClick = Button1Click
  end
  object TimerProgress: TTimer
    Enabled = False
    OnTimer = TimerProgressTimer
    Left = 336
    Top = 24
  end
end
```

```Unit1.h
//---------------------------------------------------------------------------

#ifndef Unit1H
#define Unit1H
//---------------------------------------------------------------------------
#include <System.Classes.hpp>
#include <Vcl.Controls.hpp>
#include <Vcl.StdCtrls.hpp>
#include <Vcl.Forms.hpp>
#include <Vcl.ExtCtrls.hpp>
//---------------------------------------------------------------------------
class TForm1 : public TForm
{
__published:	// IDE で管理されるコンポーネント
	TButton *Button1;
	TTimer *TimerProgress;
	void __fastcall TimerProgressTimer(TObject *Sender);
	void __fastcall Button1Click(TObject *Sender);
private:	// ユーザー宣言
public:		// ユーザー宣言
	__fastcall TForm1(TComponent* Owner);
};
//---------------------------------------------------------------------------
extern PACKAGE TForm1 *Form1;
//---------------------------------------------------------------------------
#endif
```

```Unit1.cpp
//---------------------------------------------------------------------------

#include <vcl.h>
#pragma hdrstop

#include "Unit1.h"
#include "ProgressUnit.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
	: TForm(Owner)
{
}
//---------------------------------------------------------------------------

static const int kMaxPos = 200;
static const int kDivision = 10;
static int s_loop = 0;

void __fastcall TForm1::TimerProgressTimer(TObject *Sender)
{
	if (s_loop == kDivision) {
		TimerProgress->Enabled = false;
		FormProgress->Close();
		return;
	}

	if (FormProgress->ModalResult == mrCancel) {
		TimerProgress->Enabled = false;
		s_loop = 0;
		ShowMessage(L"Canceled");
		return;
	}
	int pos = (s_loop + 1) * kMaxPos / kDivision;
	FormProgress->SetCurrentPosition(pos);
	s_loop++;

}
//---------------------------------------------------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
	FormProgress->SetMessage(L"Exporting to PDF files...");
	FormProgress->SetMaxPosition(kMaxPos);
	FormProgress->SetCurrentPosition(0);
    FormProgress->SetCaption(L"PDF Export");

	FormProgress->Show();

	TimerProgress->Enabled = true;
}
//---------------------------------------------------------------------------
```

## 実行例

![qiita.png](https://qiita-image-store.s3.amazonaws.com/0/32870/0eadc689-a0fb-3535-5f49-fb9ba394e50c.png)

![qiita.png](https://qiita-image-store.s3.amazonaws.com/0/32870/d55206f8-de81-6be4-65e0-4fc90c73edb1.png)

```
if (FormProgress->ModalResult == mrCancel) {
```
にてキャンセルされたか判断する。


## 検討事項

### 100%のクローズ

プログレスが100%になった時にProgressUnitをクローズするのは以下の2つの方法がある。

1. 使用者(Unit1)がクローズする
2. ProgressUnit自身がクローズする

2の方法を取る場合、以下のような検討事項が発生する。

- SetCurrentPosition()でクローズ処理を含めるのか
- クローズ前の100%の表示時間の保持
  - クローズ時のdelay処理の挿入
- AutoCloseフラグの検討: trueの時はProgressUnit自身がクローズする、など。

### Progressを使用するもの

Timerを使ったが、本来はTThreadで使おうとしている。
Timer実装はもっと良い実装がありそう。

最初はButtonClick()時のイベントでループをまわしながらSetCurrentPosition()を繰り返す処理を試した。こちらは、プログレスバーの表示に関してはうまく表示されなかった。
Application->ProcessMessage(); Sleep(100);を入れても、変な場所のプログレスが表示される症状があった。
ボタンクリック時に長時間の処理をすること自体があやまりなので、この変な振る舞いについては追求しない。