03_メインウィンドウ__mainwindow__

Chapter 3: メインウィンドウ (MainWindow)

前の章、第2章: 勤務表データモデル (Timesheet Data Models)では、プログラムが様々な形式の勤務表データを統一的に扱うための「設計図」について学びましたね。異なる形式のファイルでも、プログラム内では同じ「形」のデータとして扱える仕組みでした。

さて、今回は、いよいよユーザーである皆さんが直接触れることになる、このアプリケーションの「顔」とも言える部分、メインウィンドウ (MainWindow) について学んでいきましょう。

メインウィンドウ (MainWindow) とは? - アプリケーションの「玄関口」

メインウィンドウは、この「勤務表比較ツール」を起動したときに最初に表示される画面です。まさに、アプリケーションの玄関口であり、ユーザーが様々な操作を行うための主要なインターフェースを提供します。

家で例えるなら、全ての部屋(機能)につながるリビングルームのような存在です。このメインウィンドウから、ファイルを指定したり、「比較して!」と指示を出したり、比較結果を見たりといった、様々な機能へアクセスすることになります。

皆さんがこのツールを使うとき、まず目にするのがこの画面であり、ここでの操作が全ての始まりとなります。

メインウィンドウの見た目と主な機能

では、実際にメインウィンドウがどのような見た目で、どんなことができるのかを見てみましょう。(実際の見た目はバージョンによって多少異なる場合があります)

+--------------------------------------------------------------+
| 勤務表比較ツール                                             |
+--------------------------------------------------------------+
| [社内勤務表]                                                 |
| +----------------------------------------------------------+ |
| | [フォルダ] [ ここにファイルをドロップまたはファイル選択 ]  | |
| +----------------------------------------------------------+ |
|                                                              |
| [客先勤務表]                                                 |
| +----------------------------------------------------------+ |
| | [フォルダ] [ ここにファイルをドロップまたはファイル選択 ]  | |
| +----------------------------------------------------------+ |
|                                                              |
| [許容時間]                                                   |
| +-------------------+                                        |
| | 稼働時間: [ 5分 ▼] |                                        |
| +-------------------+                                        |
|                                                              |
|                              [実行(比較)] [キャンセル]       |
+--------------------------------------------------------------+

(これは画面の構成を簡易的に表現したものです)

このメインウィンドウには、主に以下の機能エリアがあります。

  1. ファイル選択エリア (社内勤務表 / 客先勤務表):
  2. 許容時間設定エリア:
  3. 操作ボタンエリア:

このように、メインウィンドウはシンプルな見た目ながら、ファイル選択から比較実行の指示まで、ツールを使う上での基本的な操作を集約しています。

メインウィンドウはどのように他の部品と連携するのか?

メインウィンドウは「リビングルーム」のようなものだと説明しました。つまり、他の専門的な機能を持つ「部屋」(部品/モジュール)と連携して動作します。ユーザーがメインウィンドウで行った操作は、適切な部品に伝えられ、処理が進められます。

ユーザーが「実行(比較)」ボタンを押したときの、大まかな処理の流れをシーケンス図で見てみましょう。

sequenceDiagram
    participant User as ユーザー
    participant MW as メインウィンドウ (MainWindow)
    participant FS as ファイル選択 (FileSelector)
    participant TC as 勤務表比較 (TimesheetComparator)
    participant RV as 結果表示 (ResultViewer)

    User->>MW: 社内勤務表ファイルを選択
    MW->>FS: 社内ファイルパスを保持
    User->>MW: 客先勤務表ファイルを選択
    MW->>FS: 客先ファイルパスを保持
    User->>MW: 許容時間を設定 (例: 5分)
    User->>MW: 「実行(比較)」ボタンをクリック
    MW->>FS: 選択されたファイルパスを取得
    FS-->>MW: ファイルパス (社内, 客先)
    MW->>TC: compare(客先データ, 社内データ, 許容時間) を呼び出す
    Note right of TC: 比較処理を実行中...
    TC-->>MW: 比較結果 (ComparisonResult) を返す
    MW->>RV: 比較結果を表示するよう指示
    RV-->>User: 比較結果を画面に表示
  1. ユーザーはメインウィンドウを通じて、社内と客先の勤務表ファイルを指定します (ファイル選択 (FileSelector) が担当)。
  2. ユーザーは許容時間を設定します。
  3. ユーザーが「実行(比較)」ボタンをクリックすると、メインウィンドウは FileSelector から選択されたファイルの情報を取得します。
  4. メインウィンドウは、取得したファイル情報と設定された許容時間を使って、勤務表比較 (TimesheetComparator) に比較処理を依頼します (compare メソッド呼び出し)。
  5. TimesheetComparator は比較処理を行い、結果をメインウィンドウに返します。
  6. メインウィンドウは、受け取った比較結果を 結果表示 (ResultViewer) に渡し、画面に表示するように指示します。
  7. 最終的に、ユーザーは ResultViewer によって整形された比較結果を見ることができます。

このように、メインウィンドウはユーザーからの指示を受け付け、各専門部品へ適切に処理を依頼し、最終的な結果をユーザーに提示する、司令塔のような役割を果たしているのです。

コードの中身を覗いてみよう (少しだけ)

メインウィンドウの裏側は、src/ui/main_window.py というファイルに書かれています。ここでは、その一部を簡単に見てみましょう。(実際のコードはもっと複雑ですが、ここでは概念を理解するために簡略化しています。)

UI部品のセットアップ

メインウィンドウが表示されるとき、まず _setup_ui というメソッド(関数のようなもの)が呼ばれて、ボタンやラベルなどの画面部品が配置されます。

# src/ui/main_window.py より (簡略化)
from PySide6.QtWidgets import QMainWindow, QPushButton, QLabel, QComboBox
from .file_selector import FileSelector # ファイル選択部品
from .result_viewer import ResultViewer # 結果表示部品
from ..core.timesheet_comparator import TimesheetComparator # 比較ロジック

class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("勤務表比較ツール") # ウィンドウのタイトル設定

        # 各部品の準備
        self.file_selector = FileSelector()
        self.comparator = TimesheetComparator()
        self.result_viewer = ResultViewer()

        # UIのセットアップを実行
        self._setup_ui()

    def _setup_ui(self):
        # --- ファイル選択エリアの部品を作成・配置 ---
        # (社内用)
        self.file_selector.internal_file_label = QLabel("ここにファイルをドロップ...", self)
        self.file_selector.internal_file_button = QPushButton("選択", self)
        # ボタンがクリックされたら self.file_selector._select_internal_file を呼ぶように接続
        self.file_selector.internal_file_button.clicked.connect(self.file_selector._select_internal_file)

        # (客先用)
        self.file_selector.client_file_label = QLabel("ここにファイルをドロップ...", self)
        self.file_selector.client_file_button = QPushButton("選択", self)
        self.file_selector.client_file_button.clicked.connect(self.file_selector._select_client_file)

        # --- 許容時間設定エリア ---
        self.allowable_work_time_comboBox = QComboBox(self)
        self.allowable_work_time_comboBox.addItems(["0分", "5分", "10分", "..."]) # 選択肢を追加

        # --- 操作ボタン ---
        self.compare_button = QPushButton("実行(比較)", self)
        # 比較ボタンがクリックされたら self._execute_comparison を呼ぶように接続
        self.compare_button.clicked.connect(self._execute_comparison)

        self.cancel_button = QPushButton("キャンセル", self)
        self.cancel_button.clicked.connect(self._execute_cancel)

        # ... 他のUI部品の配置や設定 ...

このコードでは、QLabel (文字表示) や QPushButton (ボタン)、QComboBox (ドロップダウンリスト) といった部品を作成し、それぞれのボタンがクリックされたときにどの処理 (_select_internal_file_execute_comparison など) を呼び出すかを設定しています (.clicked.connect(...) の部分)。

「実行(比較)」ボタンが押された時の処理

ユーザーが「実行(比較)」ボタンを押すと、_execute_comparison メソッドが呼び出されます。

# src/ui/main_window.py より (簡略化)
from PySide6.QtWidgets import QMessageBox # メッセージ表示用
from PySide6.QtCore import QThread, Signal # 非同期処理用

# --- 時間のかかる処理を別スレッドで行うためのクラス ---
class WorkerThread(QThread):
    finished_signal = Signal(object) # 処理完了を知らせるシグナル

    def __init__(self, file_selector, comparator, allowable_minutes):
        super().__init__()
        self.file_selector = file_selector
        self.comparator = comparator
        self.allowable_minutes = allowable_minutes

    def run(self): # このメソッドが別スレッドで実行される
        try:
            # ファイル選択部品からデータモデルを取得
            client_data = self.file_selector.get_client_timesheet()
            internal_data = self.file_selector.get_internal_timesheet()

            # 比較を実行!
            result = self.comparator.compare(
                client_data, internal_data, self.allowable_minutes
            )
            self.finished_signal.emit(result) # 結果をシグナルで通知
        except Exception as e:
            # エラーが発生したら None を通知
            self.finished_signal.emit(None)

# --- MainWindow クラスの中に戻る ---
class MainWindow(QMainWindow):
    # ... ( __init__, _setup_ui などは省略 ) ...

    def _execute_comparison(self):
        # 1. ファイルが両方選択されているかチェック
        if not self.file_selector.is_files_selected():
            QMessageBox.warning(self, "警告", "両方のファイルを選択してください。")
            return # 処理を中断

        # 2. 許容時間を取得 (例: "5分" -> 5)
        allowable_str = self.allowable_work_time_comboBox.currentText()
        allowable_minutes = int(allowable_str.replace("分", ""))

        # 3. 処理中ダイアログを表示し、比較処理を別スレッドで開始
        self.start_progress_dialog(allowable_minutes) # 処理中... を表示

    def start_progress_dialog(self, allowable_minutes):
        # ... (処理中ダイアログ表示の準備) ...

        # WorkerThreadを作成して開始
        self.worker = WorkerThread(
            self.file_selector, self.comparator, allowable_minutes
        )
        # 処理が終わったら on_progress_finished を呼ぶように接続
        self.worker.finished_signal.connect(self.on_progress_finished)
        self.worker.start() # 別スレッドで run() を実行開始

    def on_progress_finished(self, result): # 比較処理が終わったときに呼ばれる
        # ... (処理中ダイアログを閉じる) ...

        if result is None:
            QMessageBox.critical(self, "エラー", "比較中にエラーが発生しました。")
        elif not result: # 結果が空リストの場合
             QMessageBox.warning(self, "警告", "比較対象データが見つかりませんでした。")
        else:
            # 4. 結果表示部品に結果を渡して表示
            self.result_viewer.show_result(result)

_execute_comparison の主な流れは以下の通りです。

  1. まず、ファイル選択 (FileSelector) を使って、社内と客先の両方のファイルがちゃんと選ばれているか確認します。もし選ばれていなければ、警告メッセージを表示して処理を止めます。
  2. 次に、ユーザーが設定した「許容時間」を画面のドロップダウンリストから取得します。
  3. 重要な点: 勤務表の比較は、ファイルの内容によっては少し時間がかかることがあります。もし比較処理中に画面が固まってしまうと、ユーザーは不安になりますよね?そこで、WorkerThread という別の「働き手」(スレッド)に実際の比較処理 (comparator.compare(...)) を任せます。これにより、比較中もメインウィンドウは応答可能な状態を保てます。 start_progress_dialog で「処理中…」のような表示を出し、WorkerThread を開始します。
  4. WorkerThread での比較処理が終わると、finished_signal という合図が送られ、on_progress_finished メソッドが呼び出されます。
  5. on_progress_finished では、まず処理中ダイアログを閉じます。そして、比較結果 (result) を受け取り、エラーがなかったか、結果が空でないかなどを確認します。
  6. 問題がなければ、その比較結果を 結果表示 (ResultViewer) に渡し、show_result メソッドを呼び出して画面に表示させます。

少し複雑に見えるかもしれませんが、メインウィンドウがユーザー操作を受け付け、時間のかかる処理は別の働き手に任せ、最終的に結果を表示部品に渡す、という流れを掴んでいただければ大丈夫です。

まとめ

この章では、timesheet_compare アプリケーションの「玄関口」であり「リビングルーム」でもあるメインウィンドウ (MainWindow) について学びました。

これで、アプリケーション全体の操作の流れと、メインウィンドウがその中でどのような役割を果たしているか、イメージが湧いたのではないでしょうか。

次の章では、メインウィンドウの一部であり、ユーザーが比較したいファイルを指定するための機能である「ファイル選択」について、もう少し詳しく見ていきます。

次の章: 第4章: ファイル選択 (FileSelector)


Generated by AI Codebase Knowledge Builder