見出し画像

PythonでChatGPTと一緒に住所録を作ろう:機能追加 新規追加、編集、削除

続きです、
今回は、"新規追加" "編集" "削除" の機能を追加します。


1.前回までのコード

import sys
import json
from PyQt6.QtWidgets import (
    QApplication, QMainWindow, QVBoxLayout, QHBoxLayout, QLineEdit, QPushButton,
    QTableWidget, QTableWidgetItem, QHeaderView, QWidget, QLabel, QDialog, QFormLayout
)

DATA_FILE = "address_book.json"

class EditDialog(QDialog):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.setWindowTitle("編集画面")
        self.setGeometry(200, 200, 400, 300)

        layout = QFormLayout()

        self.last_name_input = QLineEdit()
        self.first_name_input = QLineEdit()
        self.last_name_kana_input = QLineEdit()
        self.first_name_kana_input = QLineEdit()
        self.postal_code_input = QLineEdit()
        self.address_input = QLineEdit()
        self.phone_number_input = QLineEdit()
        self.notes_input = QLineEdit()

        layout.addRow("姓", self.last_name_input)
        layout.addRow("名", self.first_name_input)
        layout.addRow("姓フリガナ", self.last_name_kana_input)
        layout.addRow("名フリガナ", self.first_name_kana_input)
        layout.addRow("郵便番号", self.postal_code_input)
        layout.addRow("住所", self.address_input)
        layout.addRow("電話番号", self.phone_number_input)
        layout.addRow("備考", self.notes_input)

        self.save_button = QPushButton("保存")
        self.cancel_button = QPushButton("キャンセル")

        button_layout = QHBoxLayout()
        button_layout.addWidget(self.save_button)
        button_layout.addWidget(self.cancel_button)
        layout.addRow(button_layout)

        self.setLayout(layout)

class AddressBookApp(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("住所録")
        self.setGeometry(100, 100, 800, 600)

        # GUIを構築
        self.setup_ui()

        # データを読み込む
        self.load_data_from_json(DATA_FILE)

    def setup_ui(self):
        """GUIを構築するメソッド"""
        # メインウィジェットとレイアウト
        main_widget = QWidget()
        self.setCentralWidget(main_widget)
        main_layout = QVBoxLayout()
        main_widget.setLayout(main_layout)

        # フィルター入力と実行ボタン
        filter_layout = QHBoxLayout()
        self.filter_input = QLineEdit()
        self.filter_input.setPlaceholderText("フィルターを入力")
        self.filter_button = QPushButton("実行")
        filter_layout.addWidget(self.filter_input)
        filter_layout.addWidget(self.filter_button)
        main_layout.addLayout(filter_layout)

        # 一覧表
        self.table = QTableWidget()
        self.table.setColumnCount(8)
        self.table.setHorizontalHeaderLabels([
            "姓", "姓フリガナ", "名", "名フリガナ", "郵便番号", "住所", "電話番号", "備考"
        ])
        self.table.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeMode.Stretch)
        main_layout.addWidget(self.table)

        # ボタン群
        button_layout = QHBoxLayout()
        self.new_button = QPushButton("新規作成")
        self.edit_button = QPushButton("編集")
        self.delete_button = QPushButton("削除")
        self.save_button = QPushButton("保存")
        self.load_button = QPushButton("読み込み")
        
        button_layout.addWidget(self.new_button)
        button_layout.addWidget(self.edit_button)
        button_layout.addWidget(self.delete_button)
        button_layout.addWidget(self.save_button)
        button_layout.addWidget(self.load_button)
        main_layout.addLayout(button_layout)

        # ボタンの動作
        self.new_button.clicked.connect(self.open_edit_dialog)
        self.edit_button.clicked.connect(self.open_edit_dialog)
        self.save_button.clicked.connect(self.save_data_to_json)

    def load_data_from_json(self, file_path):
        try:
            # JSONデータの読み込み
            with open(file_path, "r", encoding="utf-8") as file:
                address_book = json.load(file)

            # データをテーブルに追加
            self.table.setRowCount(len(address_book))  # 行数を設定
            for row, entry in enumerate(address_book):
                for col, item in enumerate(["姓", "姓フリガナ", "名", "名フリガナ",
                                            "郵便番号","住所", "電話番号", "備考"]):
                    cell = QTableWidgetItem(entry.get(item, ""))
                    self.table.setItem(row, col, cell)
        except FileNotFoundError:
            print("ファイルが見つかりません")
        except json.JSONDecodeError:
            print("JSONデータが壊れています")
    
    def save_data_to_json(self):
        data = []
        for row in range(self.table.rowCount()):
            entry = {
                "姓": self.table.item(row, 0).text() if self.table.item(row, 0) else "",
                "姓フリガナ": self.table.item(row, 1).text() if self.table.item(row, 1) else "",
                "名": self.table.item(row, 2).text() if self.table.item(row, 2) else "",
                "名フリガナ": self.table.item(row, 3).text() if self.table.item(row, 3) else "",
                "郵便番号": self.table.item(row, 4).text() if self.table.item(row, 4) else "",
                "住所": self.table.item(row, 5).text() if self.table.item(row, 5) else "",
                "電話番号": self.table.item(row, 6).text() if self.table.item(row, 6) else "",
                "備考": self.table.item(row, 7).text() if self.table.item(row, 7) else ""
            }
            data.append(entry)
        
        with open(DATA_FILE, "w", encoding="utf-8") as file:
            json.dump(data, file, ensure_ascii=False, indent=4)
        print("データを保存しました")
            
    def open_edit_dialog(self):
        dialog = EditDialog(self)
        dialog.exec()
                
if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = AddressBookApp()
    window.show()
    sys.exit(app.exec())

2.新規追加

新規の追加は、編集画面つまりクラス EditDialog のインスタンスを表示して編集します。

編集画面


そして、編集画面で保存ボタンを押したときに
データを追加して閉じます。

        # ボタンの動作
        self.new_button.clicked.connect(self.open_edit_dialog)

すでに画面を作ったときに住所録のメイン画面で新規ボタンを押すと
編集画面が開くようにコードが書かれていますね。

new_button を押すと open_edit_dialog() が実行されるようにコードが書かれています。

A.キャンセルボタン

簡単なほうから作って行きましょう。
編集画面で"キャンセル"ボタンを押すと何もしないで画面を閉じるようにします。

では、ChatGPTに「(前回までのコードをコピーして)編集画面でキャンセルボタンを押すと何もせずに画面を閉じるにはどのようなメソッドを書けばよいですか?」とプロンプトを送りましょう。


ChatGpt#####
キャンセルボタンを押したときにダイアログを閉じるには、`QDialog.reject()` メソッドを使います。

具体的には、`EditDialog` の `__init__` メソッド内で `self.cancel_button.clicked.connect(self.reject)` を追加すればOKです。
(以下略)


ご#####
EditDialog
の`__init__`メソッド内の最下行に

self.cancel_button.clicked.connect(self.reject)

を追加して実行しました。

うまく行きましたね。

B.保存ボタン

次は、保存ボタンを押すと入力したデータを追加して
画面を閉じるメソッドが必要ですね。

ChatGPTにプロンプトを送りましょう。
「編集画面で保存ボタンを押すと画面の入力内容を元に住所録のデータを一件分作成してQTableWidgetへ追加します。
そして、編集画面を閉じます。以上を実現するメソッドを書いてください。」


ChatGPT#####
EditDialog に保存ボタンを押したときの処理を実装します。

手順としては:

  1. EditDialog に入力されたデータを辞書形式で取得するメソッド get_data() を作成

  2. save_button のクリック時に get_data() で取得したデータを QTableWidget に追加

  3. ダイアログを accept() で閉じる

class EditDialog(QDialog):
    def __init__(self, parent=None):        
        self.setLayout(layout)

        (中略...)

        self.setLayout(layout)

        # ボタンのイベント
        self.cancel_button.clicked.connect(self.reject)
        self.save_button.clicked.connect(self.save_data)

    def get_data(self):
        """入力されたデータを辞書形式で返す"""
        return {
            "姓": self.last_name_input.text(),
            "名": self.first_name_input.text(),
            "姓フリガナ": self.last_name_kana_input.text(),
            "名フリガナ": self.first_name_kana_input.text(),
            "郵便番号": self.postal_code_input.text(),
            "住所": self.address_input.text(),
            "電話番号": self.phone_number_input.text(),
            "備考": self.notes_input.text(),
        }

    def save_data(self):
        """データを取得してダイアログを閉じる"""
        self.accept()  # ダイアログを閉じる

AddressBookApp の修正

ダイアログからデータを取得し、QTableWidget に追加する処理を open_edit_dialog() に追記します。

def open_edit_dialog(self):
    """編集画面を開き、データを取得してテーブルに追加"""
    dialog = EditDialog(self)
    if dialog.exec():  # OK(保存)された場合のみ処理
        new_entry = dialog.get_data() # ダイアログから辞書データ取得
        row_position = self.table.rowCount() # テーブルの行数を返す
        self.table.insertRow(row_position) # テーブルに最下行に1行追加

        for col, key in enumerate(["姓", "名", "姓フリガナ", "名フリガナ", 
                                   "郵便番号", "住所", "電話番号", "備考"]):
            self.table.setItem(row_position, col, QTableWidgetItem(new_entry[key]))

(以下略)


ご#####

EditDialogには、2つのメソッド
get_data() # クラス AddressBookApp から呼ばれデータを返すメソッド 
save_data()
# 画面を閉じるメソッド

self.save_button.clicked.connect(self.save_data) # save_data()を実行する
が追加されました。

AddressBookApp側には、
編集画面を開くだけだったメソッド
open_edit_dialog()
を拡張してEditDialog側で作られた辞書データを
取得してQTableWidgetの最下行へデータを追加するように
書き直しました。

ソース中にコメントとして解説を加えてあります。

以上で、新規データを追加できるようになりました。

新規データを編集
新規 データ追加

3.削除

データの "編集" より "削除" のほうが似た内容で短いコードで書けそうですね。
"削除" するコードを先に作りそれを元に"編集"するコードを理解したほうが良いでしょう。
ですので”削除”するコードを先に作ります。

データを1件削除するには、行を選択して”削除”ボタンを押すという
操作にします。

ChatGPTにプロンプトを送ります。
QTableWidgetの選択した行を削除するメソッドを書いてください。


ChatGpt#####
QTableWidget の選択された行を削除するメソッドを作成します。

実装の流れ:

  1. QTableWidget.selectedItems() を使って選択されたセルを取得

  2. 選択されたセルの行インデックスを特定

  3. QTableWidget.removeRow(row_index) で行を削除

以下のメソッドを AddressBookApp に追加してください。

削除メソッド

def delete_selected_row(self):
    """選択された行を削除する"""
    selected_items = self.table.selectedItems() # 複数の選択された行を取得
    if not selected_items:
        print("削除する行を選択してください")
        return

    # 選択された行のインデックスを取得(重複を防ぐためsetでユニークにする)
    selected_rows = sorted(set(item.row() for item in selected_items), reverse=True)

    # 後ろから削除することで、インデックスのずれを防ぐ
    for row in selected_rows:
        self.table.removeRow(row)

    print("選択した行を削除しました")

ボタンに削除処理を接続

setup_ui() の delete_button に以下を追加してください。

self.delete_button.clicked.connect(self.delete_selected_row)

ご#####

delete_selected_row()
AddressBookApp の最下行に追加しました。

ソース中にコメントとして解説を加えてあります。

    # 選択された行のインデックスを取得(重複を防ぐためsetでユニークにする)
    selected_rows = sorted(set(item.row() for item in selected_items), reverse=True)

set(item.row() for item in selected_items)
の説明をします。
selected_items は、QTableWidget.selectedItems() によって取得された 選択されたセルのリスト を表します。
set(item.row() for item in selected_items) は、各セルの行番号 (row()) を取得し、 重複のないユニークな行番号の集合 を作成します。
これは、同じ行の複数のセルが選択されても、行番号が重複しないようにするため です。

さらに、インデックスがずれることの内容に後ろから削除するために
reverse=True として逆順にしています。

self.delete_button.clicked.connect() を他のボタンのを設定している行の最下行に追加しました。

以上で、複数行を選択して "削除" できるようになりました。

4.編集

行を選択して "編集" 出来る様にします。
複数選択している場合は、一番先頭の行を編集するようにします。

ChatGPTにプロンプトを送ります。
「QTableWidgetの行を選択して編集ボタンを押したときに
選択中のデータを編集画面で編集できるようなメソッドを書いて。
ただし、複数選択されている場合は一番行番号が小さい行のみを
編集します。」


ChatGPT#####

選択された行のデータを `EditDialog` に渡し、編集後に `QTableWidget` に反映するメソッドを作成します。

実装の流れ
1. 選択中の行を取得
`QTableWidget.selectedItems()` を使って選択されたセルを取得。
行番号の `set` を作り、最小の行番号を取得。
複数行選択されていた場合は最小の行のみを編集。

2. `EditDialog` にデータを渡す
`EditDialog` に既存のデータをセットして表示。

3. 編集後のデータを `QTableWidget` に反映
`EditDialog` で「保存」ボタンが押されたら、新しいデータを  
`QTableWidget` に適用。

AddressBookApp の open_edit_dialog() を修正

def open_edit_dialog(self):
    """選択した行を編集する"""
    selected_items = self.table.selectedItems()
    if not selected_items:
        print("編集する行を選択してください")
        return

    # 選択された行のうち、一番小さい行番号を取得
    selected_rows = sorted(set(item.row() for item in selected_items))
    row = selected_rows[0]  # 最小の行番号を選択

    # 既存データを取得
    current_data = {
        "姓": self.table.item(row, 0).text() if self.table.item(row, 0) else "",
        "名": self.table.item(row, 1).text() if self.table.item(row, 1) else "",
        "姓フリガナ": self.table.item(row, 2).text() if self.table.item(row, 2) else "",
        "名フリガナ": self.table.item(row, 3).text() if self.table.item(row, 3) else "",
        "郵便番号": self.table.item(row, 4).text() if self.table.item(row, 4) else "",
        "住所": self.table.item(row, 5).text() if self.table.item(row, 5) else "",
        "電話番号": self.table.item(row, 6).text() if self.table.item(row, 6) else "",
        "備考": self.table.item(row, 7).text() if self.table.item(row, 7) else ""
    }

    # 編集画面を開き、データを渡す
    dialog = EditDialog(self, current_data) # クラスのコンストラクタにデータを取得できるよう設定
    if dialog.exec():  # OK(保存)された場合のみ処理
        updated_data = dialog.get_data()

        # データをテーブルに反映
        for col, key in enumerate(["姓", "名", "姓フリガナ", "名フリガナ", 
                                   "郵便番号", "住所", "電話番号", "備考"]):
            self.table.setItem(row, col, QTableWidgetItem(updated_data[key]))

EditDialog の修正

EditDialog を、既存データを受け取ってフォームにセットするように変更

class EditDialog(QDialog):
    def __init__(self, parent=None, data=None):
        super().__init__(parent)
        self.setWindowTitle("編集画面")
        self.setGeometry(200, 200, 400, 300)

        layout = QFormLayout()

        self.last_name_input = QLineEdit(data["姓"] if data else "")
        self.first_name_input = QLineEdit(data["名"] if data else "")
        self.last_name_kana_input = QLineEdit(data["姓フリガナ"] if data else "")
        self.first_name_kana_input = QLineEdit(data["名フリガナ"] if data else "")
        self.postal_code_input = QLineEdit(data["郵便番号"] if data else "")
        self.address_input = QLineEdit(data["住所"] if data else "")
        self.phone_number_input = QLineEdit(data["電話番号"] if data else "")
        self.notes_input = QLineEdit(data["備考"] if data else "")

        layout.addRow("姓", self.last_name_input)
        layout.addRow("名", self.first_name_input)
        layout.addRow("姓フリガナ", self.last_name_kana_input)
        layout.addRow("名フリガナ", self.first_name_kana_input)
        layout.addRow("郵便番号", self.postal_code_input)
        layout.addRow("住所", self.address_input)
        layout.addRow("電話番号", self.phone_number_input)
        layout.addRow("備考", self.notes_input)

        self.save_button = QPushButton("保存")
        self.cancel_button = QPushButton("キャンセル")

        button_layout = QHBoxLayout()
        button_layout.addWidget(self.save_button)
        button_layout.addWidget(self.cancel_button)
        layout.addRow(button_layout)

        self.setLayout(layout)

        # ボタンのイベント
        self.cancel_button.clicked.connect(self.reject)
        self.save_button.clicked.connect(self.save_data)

    def get_data(self):
        """入力されたデータを辞書形式で返す"""
        return {
            "姓": self.last_name_input.text(),
            "名": self.first_name_input.text(),
            "姓フリガナ": self.last_name_kana_input.text(),
            "名フリガナ": self.first_name_kana_input.text(),
            "郵便番号": self.postal_code_input.text(),
            "住所": self.address_input.text(),
            "電話番号": self.phone_number_input.text(),
            "備考": self.notes_input.text(),
        }

    def save_data(self):
        """データを取得してダイアログを閉じる"""
        self.accept()  # ダイアログを閉じる

ご#####
う~ん、open_edit_dialog() を修正しましたがこれだと
"新規追加" の場合が考慮されていませんね。

実行してみると案の定、行を選択して "新規追加" をすると選択行のデータを編集しようとしますね。

何も選択していないと 「編集する行を選択してください。」 と言ってくるし。

再びChatGPTにプロンプトを送ります。
「これだと新規追加が出来なくなりますね。 新規ボタンと編集ボタンで使うメソッドを分けた方が良いでしょうか?」


ChatGPT#####
はい、新規追加と編集を同じ open_edit_dialog メソッドで処理すると、新規データが渡されないため、編集専用になってしまう という問題があります。

解決策

「新規作成」と「編集」でメソッドを分ける

  • 新規作成 (open_new_dialog)

    • EditDialog に 空のデータ を渡して開く。

    • 入力後、「保存」を押すと 新しい行を追加

  • 編集 (open_edit_dialog)

    • 選択中の行のデータを取得して EditDialog にセット

    • 「保存」を押すと 選択行のデータを更新

(以下略…)


ご#####
という事なので 変更前の open_edit_dialog() open_new_dialog()
名前を変更してコードに追加します。

        # ボタンの動作
        self.new_button.clicked.connect(self.open_new_dialog)

そして "新規追加"ボタンに設定します。

open_edit_dialog() メソッドについて解説
データを編集できるよう修正されたメソッドについて解説します。
ソース中のコメントも参考にしてください。

    def open_edit_dialog(self):
        """選択した行を編集する"""
        selected_items = self.table.selectedItems()
        if not selected_items:
            print("編集する行を選択してください")
            return

        # 選択された行のうち、一番小さい行番号を取得
        selected_rows = sorted(set(item.row() for item in selected_items))
        row = selected_rows[0]  # 最小の行番号を選択
        (続く...)

複数の選択された行から一番行番号の小さい行を一つ選択し
row に設定します。
これが編集する行です。

        # 既存データを取得
        current_data = {
            "姓": self.table.item(row, 0).text() if self.table.item(row, 0) else "",
            "名": self.table.item(row, 1).text() if self.table.item(row, 1) else "",
            "姓フリガナ": self.table.item(row, 2).text() if self.table.item(row, 2) else "",
            "名フリガナ": self.table.item(row, 3).text() if self.table.item(row, 3) else "",
            "郵便番号": self.table.item(row, 4).text() if self.table.item(row, 4) else "",
            "住所": self.table.item(row, 5).text() if self.table.item(row, 5) else "",
            "電話番号": self.table.item(row, 6).text() if self.table.item(row, 6) else "",
            "備考": self.table.item(row, 7).text() if self.table.item(row, 7) else ""
    }

選択行から辞書データを組み立てます。

        # 編集画面を開き、データを渡す
        # dialog = EditDialog(self, current_data) # 修正前 
        dialog = EditDialog(self, data=current_data) # 明示的に書き直した
        if dialog.exec():  # OK(保存)された場合のみ処理
            updated_data = dialog.get_data()

            # データをテーブルに反映
            for col, key in enumerate(["姓", "名", "姓フリガナ", "名フリガナ", 
                                    "郵便番号", "住所", "電話番号", "備考"]):
                self.table.setItem(row, col, QTableWidgetItem(updated_data[key])

クラス EditDialog のコンストラクタの引数を修正してデータを取得できるように変更されています。

EditDialog に引数として編集する行の辞書データを渡しています。

そして dialog(編集画面) が True を返したら、つまり ”保存” された場合
dialog.get_data() でデータを取得して row を元に
編集中の行のデータを更新します。

dialog = EditDialog(self, current_data)

間違いではないですが名前付き引数なのに省略されています。
この書き方は、推奨ではないので data=current_data に書き直しました。

EditDialog について解説

class EditDialog(QDialog):
    def __init__(self, parent=None, data=None):
        super().__init__(parent)
        self.setWindowTitle("編集画面")
        self.setGeometry(200, 200, 400, 300)

コンストラクタ `__init__`でデータを取得できるように
名前付き引数 data を設定しました。

data=None となっていてデフォルト値が None なので
"新規追加" の場合は、Noneが使われます。


        self.last_name_input = QLineEdit(data["姓"] if data else "")
        self.first_name_input = QLineEdit(data["名"] if data else "")
        self.last_name_kana_input = QLineEdit(data["姓フリガナ"] if data else "")
        self.first_name_kana_input = QLineEdit(data["名フリガナ"] if data else "")
        self.postal_code_input = QLineEdit(data["郵便番号"] if data else "")
        self.address_input = QLineEdit(data["住所"] if data else "")
        self.phone_number_input = QLineEdit(data["電話番号"] if data else "")
        self.notes_input = QLineEdit(data["備考"] if data else "")

data から文字列を取得しています。
この値を各 QLineEdit へ設定しています。

そして、"保存"ボタンが押された場合 get_data() によって
AddressBookApp へデータが渡されます。

5.修正

ChatGPTにプロンプトを送信します。
(全コードを張り付け)大きな間違いは、ありますか。

ビックリです。
ChatGPTにデータの並び順について指摘されました。
全く気が付かなかったんですが EditDialog のデータの並び順が
一覧表のデータの並び順が違うことでデータの内容が入違っていました。


ChatGPT#####
テーブルのカラム順に関するミス

open_edit_dialog() で QTableWidget からデータを取得するときのカラム順が 姓, 姓フリガナ, 名, 名フリガナ, 郵便番号, 住所, 電話番号, 備考 になっているのに対し、新規追加時のカラム順が 姓, 名, 姓フリガナ, 名フリガナ, 郵便番号, 住所, 電話番号, 備考 になっています。
このままだと、編集したデータが正しく保存されない可能性があります。

# 修正前
for col, key in enumerate(["姓", "名", "姓フリガナ", "名フリガナ", 
                           "郵便番号", "住所", "電話番号", "備考"]):
 
# 修正後
for col, key in enumerate(["姓", "姓フリガナ", "名", "名フリガナ", 
                           "郵便番号", "住所", "電話番号", "備考"]):

ご#####
という事だったので修正しました。

このような感じでChatGPTは、結構間違ったり
新しい機能を追加するとすでにできている機能に影響を与えるコードを
書いたりするので気をつけないといけないですね。

それでも随分とコードを書くのは楽になるので
使わないという事は考えられませんね。

6.ここまでの全コード

import sys
import json
from PyQt6.QtWidgets import (
    QApplication, QMainWindow, QVBoxLayout, QHBoxLayout, QLineEdit, QPushButton,
    QTableWidget, QTableWidgetItem, QHeaderView, QWidget, QLabel, QDialog, QFormLayout
)

DATA_FILE = "address_book.json"

class EditDialog(QDialog):
    def __init__(self, parent=None, data=None):
        super().__init__(parent)
        self.setWindowTitle("編集画面")
        self.setGeometry(200, 200, 400, 300)

        layout = QFormLayout()

        self.last_name_input = QLineEdit(data["姓"] if data else "")
        self.last_name_kana_input = QLineEdit(data["姓フリガナ"] if data else "")
        self.first_name_input = QLineEdit(data["名"] if data else "")
        self.first_name_kana_input = QLineEdit(data["名フリガナ"] if data else "")
        self.postal_code_input = QLineEdit(data["郵便番号"] if data else "")
        self.address_input = QLineEdit(data["住所"] if data else "")
        self.phone_number_input = QLineEdit(data["電話番号"] if data else "")
        self.notes_input = QLineEdit(data["備考"] if data else "")

        layout.addRow("姓", self.last_name_input)
        layout.addRow("姓フリガナ", self.last_name_kana_input)
        layout.addRow("名", self.first_name_input)
        layout.addRow("名フリガナ", self.first_name_kana_input)
        layout.addRow("郵便番号", self.postal_code_input)
        layout.addRow("住所", self.address_input)
        layout.addRow("電話番号", self.phone_number_input)
        layout.addRow("備考", self.notes_input)

        self.save_button = QPushButton("保存")
        self.cancel_button = QPushButton("キャンセル")

        button_layout = QHBoxLayout()
        button_layout.addWidget(self.save_button)
        button_layout.addWidget(self.cancel_button)
        layout.addRow(button_layout)

        self.setLayout(layout)

        # ボタンのイベント
        self.cancel_button.clicked.connect(self.reject)
        self.save_button.clicked.connect(self.save_data)

    def get_data(self):
        """入力されたデータを辞書形式で返す"""
        return {
            "姓": self.last_name_input.text(),
            "姓フリガナ": self.last_name_kana_input.text(),
            "名": self.first_name_input.text(),
            "名フリガナ": self.first_name_kana_input.text(),
            "郵便番号": self.postal_code_input.text(),
            "住所": self.address_input.text(),
            "電話番号": self.phone_number_input.text(),
            "備考": self.notes_input.text(),
        }

    def save_data(self):
        """データを取得してダイアログを閉じる"""
        self.accept()  # ダイアログを閉じる


class AddressBookApp(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("住所録")
        self.setGeometry(100, 100, 800, 600)

        # GUIを構築
        self.setup_ui()

        # データを読み込む
        self.load_data_from_json(DATA_FILE)

    def setup_ui(self):
        """GUIを構築するメソッド"""
        # メインウィジェットとレイアウト
        main_widget = QWidget()
        self.setCentralWidget(main_widget)
        main_layout = QVBoxLayout()
        main_widget.setLayout(main_layout)

        # フィルター入力と実行ボタン
        filter_layout = QHBoxLayout()
        self.filter_input = QLineEdit()
        self.filter_input.setPlaceholderText("フィルターを入力")
        self.filter_button = QPushButton("実行")
        filter_layout.addWidget(self.filter_input)
        filter_layout.addWidget(self.filter_button)
        main_layout.addLayout(filter_layout)

        # 一覧表
        self.table = QTableWidget()
        self.table.setColumnCount(8)
        self.table.setHorizontalHeaderLabels([
            "姓", "姓フリガナ", "名", "名フリガナ", "郵便番号", "住所", "電話番号", "備考"
        ])
        self.table.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeMode.Stretch)
        main_layout.addWidget(self.table)

        # ボタン群
        button_layout = QHBoxLayout()
        self.new_button = QPushButton("新規作成")
        self.edit_button = QPushButton("編集")
        self.delete_button = QPushButton("削除")
        self.save_button = QPushButton("保存")
        self.load_button = QPushButton("読み込み")
        
        button_layout.addWidget(self.new_button)
        button_layout.addWidget(self.edit_button)
        button_layout.addWidget(self.delete_button)
        button_layout.addWidget(self.save_button)
        button_layout.addWidget(self.load_button)
        main_layout.addLayout(button_layout)

        # ボタンの動作
        self.new_button.clicked.connect(self.open_new_dialog)
        self.edit_button.clicked.connect(self.open_edit_dialog)
        self.save_button.clicked.connect(self.save_data_to_json)
        self.delete_button.clicked.connect(self.delete_selected_row)

    def load_data_from_json(self, file_path):
        try:
            # JSONデータの読み込み
            with open(file_path, "r", encoding="utf-8") as file:
                address_book = json.load(file)

            # データをテーブルに追加
            self.table.setRowCount(len(address_book))  # 行数を設定
            for row, entry in enumerate(address_book):
                for col, item in enumerate(["姓", "姓フリガナ", "名", "名フリガナ",
                                            "郵便番号","住所", "電話番号", "備考"]):
                    cell = QTableWidgetItem(entry.get(item, ""))
                    self.table.setItem(row, col, cell)
        except FileNotFoundError:
            print("ファイルが見つかりません")
        except json.JSONDecodeError:
            print("JSONデータが壊れています")
    
    def save_data_to_json(self):
        data = []
        for row in range(self.table.rowCount()):
            entry = {
                "姓": self.table.item(row, 0).text() if self.table.item(row, 0) else "",
                "姓フリガナ": self.table.item(row, 1).text() if self.table.item(row, 1) else "",
                "名": self.table.item(row, 2).text() if self.table.item(row, 2) else "",
                "名フリガナ": self.table.item(row, 3).text() if self.table.item(row, 3) else "",
                "郵便番号": self.table.item(row, 4).text() if self.table.item(row, 4) else "",
                "住所": self.table.item(row, 5).text() if self.table.item(row, 5) else "",
                "電話番号": self.table.item(row, 6).text() if self.table.item(row, 6) else "",
                "備考": self.table.item(row, 7).text() if self.table.item(row, 7) else ""
            }
            data.append(entry)
        
        with open(DATA_FILE, "w", encoding="utf-8") as file:
            json.dump(data, file, ensure_ascii=False, indent=4)
        print("データを保存しました")
        
    def open_new_dialog(self):
        """編集画面を開き、データを取得してテーブルに追加"""
        dialog = EditDialog(self)
        if dialog.exec():  # OK(保存)された場合のみ処理
            new_entry = dialog.get_data()
            row_position = self.table.rowCount()
            self.table.insertRow(row_position)

            for col, key in enumerate(["姓", "姓フリガナ", "名", "名フリガナ", 
                                    "郵便番号", "住所", "電話番号", "備考"]):
                self.table.setItem(row_position, col, QTableWidgetItem(new_entry[key]))
            
    def open_edit_dialog(self):
        """選択した行を編集する"""
        selected_items = self.table.selectedItems()
        if not selected_items:
            print("編集する行を選択してください")
            return

        # 選択された行のうち、一番小さい行番号を取得
        selected_rows = sorted(set(item.row() for item in selected_items))
        row = selected_rows[0]  # 最小の行番号を選択

        # 既存データを取得
        current_data = {
            "姓": self.table.item(row, 0).text() if self.table.item(row, 0) else "",
            "姓フリガナ": self.table.item(row, 1).text() if self.table.item(row, 1) else "",
            "名": self.table.item(row, 2).text() if self.table.item(row, 2) else "",
            "名フリガナ": self.table.item(row, 3).text() if self.table.item(row, 3) else "",
            "郵便番号": self.table.item(row, 4).text() if self.table.item(row, 4) else "",
            "住所": self.table.item(row, 5).text() if self.table.item(row, 5) else "",
            "電話番号": self.table.item(row, 6).text() if self.table.item(row, 6) else "",
            "備考": self.table.item(row, 7).text() if self.table.item(row, 7) else ""
        }

        # 編集画面を開き、データを渡す
        dialog = EditDialog(self, data=current_data)
        if dialog.exec():  # OK(保存)された場合のみ処理
            updated_data = dialog.get_data()

            # データをテーブルに反映
            for col, key in enumerate(["姓", "姓フリガナ", "名", "名フリガナ", 
                                    "郵便番号", "住所", "電話番号", "備考"]):
                self.table.setItem(row, col, QTableWidgetItem(updated_data[key]))

                
    def delete_selected_row(self):
        """選択された行を削除する"""
        selected_items = self.table.selectedItems()
        if not selected_items:
            print("削除する行を選択してください")
            return

        # 選択された行のインデックスを取得(重複を防ぐためsetでユニークにする)
        selected_rows = sorted(set(item.row() for item in selected_items), reverse=True)

        # 後ろから削除することで、インデックスのずれを防ぐ
        for row in selected_rows:
            self.table.removeRow(row)

        print("選択した行を削除しました")

if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = AddressBookApp()
    window.show()
    sys.exit(app.exec()

まとめ

以上で、"新規追加" "削除" "編集" が出来る様になりました。

次回は、フィルター機能を追加します。

お楽しみに!


いいなと思ったら応援しよう!