質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

87.49%

Openpyxlでセルの色情報を取得する方法とTreeviewのイベントの設定の仕方について

解決済

回答 2

投稿 編集

  • 評価
  • クリップ 0
  • VIEW 4,793

score 1353

前提・実現したいこと

現在Excel情報を取得し、取得した情報をTkinter.Treeviewに反映するGUIを作成しております。
Excelファイル内は以下の画像の様な形式とデータになっており
GUIでは、一つのIDを指定しopenpyxlでデータを抽出し画像の様にTreeviewで表示しております。

今回目的の実装したい項目は

  • Excel内の塗りつぶし情報のTreeviewへの反映
    (Treeview内でもbgを同じ色で反映したいです)

  • Treeviewへのイベントの設定

Excelデータサンプル
GUI動作画像

該当のソースコード

    #-------Excel Window-------
    def ExcelProcess(self):
        # NewWindow create.
        self.submaster = tk.Toplevel()
        self.submaster.geometry('700x400')
        self.submaster.grab_set()
        self.submaster.attributes('-topmost', True)

        ttk.Button(self.submaster, text='データ表示', command=self.ExcelDataGet).pack(pady=10)

        # Treeview create. set data of index and anchor together for tree.
        tvframe = ttk.Frame(self.submaster)
        tvframe.pack()
        self.tree = ttk.Treeview(tvframe)
        self.tree['column'] = tuple([i for i in range(7)])
        tree_info = {'INDEX1': [70, tk.CENTER], 'INDEX2': [50, tk.CENTER], 'INDEX3': [70, tk.CENTER], 
                    'INDEX4': [60, tk.E], 'INDEX5': [150, tk.W], 'INDEX6': [50, tk.W], 'INDEX7': [120, tk.W]}
        for i, [header, [width, anchor]] in enumerate(tree_info.items()):
            self.tree.column(i, width=width, anchor=anchor)
            self.tree.heading(i, text=header)
        self.tree["show"] = "headings"
        self.tree.grid(row=0, column=0)

        # Scrollbar create.
        ysb = tk.Scrollbar(tvframe, orient=tk.VERTICAL, width=16, command=self.tree.yview)
        self.tree.configure(yscrollcommand=ysb.set)
        ysb.grid(row=0, column=1, sticky='nsew')


    def ExcelDataGet(self):
        # Ecelfile open.
        wb = px.load_workbook(self.excel_path)
        sheet = wb['Sheet1']

        target = 1000001
        self.data = []
        # Data create. Read line, Datetime attribute are convert and replace if blank.
        for row in sheet.iter_rows():
            if row[1].value == target:
                tmp = []
                for cell in row:
                    celldata = cell.value
                    if type(celldata) == datetime:
                        celldata = cell.value.strftime('%Y/%m/%d')
                    elif celldata == None:
                        celldata = ''

                    tmp.append(celldata)
                self.data.append(tmp)

        # If data has target tree delete it.
        if self.tree:
            tree_data = self.tree.get_children()
            for td in tree_data:
                self.tree.delete(td)
        # Set data for tree
        for d in self.data:
            self.tree.insert('', 'end', values=d)

試したこと

以下でExcelを読み込みながらセル内の塗りつぶしに関するプロパティの取得は出来ました。
if文などで塗りつぶされている色が赤だった場合~と条件の分岐をさせ、Treeview上に反映を
させる為のデータの成形を考えておりましたが、色を指定した分岐方法がわかりません。

import openpyxl as px

wb = px.load_workbook(file)
sheet = wb['Sheet1']

for row in sheet.iter_rows():
    for cell in row:
        print(cell.fill.fgColor)
        print(type(cell.fill.fgColor))

>>> <openpyxl.styles.colors.Color object>
>>> Parameters:
>>> rgb='FFFF0000', indexed=None, auto=None, theme=None, tint=0.0, type='rgb'
>>> <class 'openpyxl.styles.colors.Color'>
.
.


イベントの設定についてですが、初めにイベントの内容として
itemをダブルクリックで、現在塗りつぶしが無い場合は赤に、赤ならば黄色に
黄色ならば塗りつぶしを無しに~とTreeview上の表示を変更していくつもりです。
その場合にself.tree.bind('<Double-1>', self.function)とすべきなのか
self.tree.bind('<<TreeviewSelect>>', self.function)とすべきなのかがわからず
また、いずれの場合にはイベントの内容をどの様に組み立てれば良いのかがわからず今回質問を致しました。

補足情報(FW/ツールのバージョンなど)

python3.8
widnows10

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 2

checkベストアンサー

+1

Excel内の塗りつぶし情報のTreeviewへの反映

ttk.Treeview は、ライブラリ元の tcl/tk 側の不具合があって
背景色変更に対応するにはパッチ(もしくはダウングレード)が必要です。

FIX_TTK_TREEVIEW_STYLE = """
ttk::style map Treeview \
    -foreground {disabled SystemGrayText \
                 selected SystemHighlightText} \
    -background {disabled SystemButtonFace \
                 selected SystemHighlight}
"""

import tkinter as tk
root = tk.tk()
root.tk.eval(FIX_TTK_TREEVIEW_STYLE)
  • tree.tag_configure でタグに対して background等のオプションを設定
  • 色変えは任意の行に対して タグ を付ける事で実現できます
  • タグは insert 時の tags に複数付けられます。
  • 選択時の色等は、タグで管理するのではなく、ウィジェットのスタイルで指定できます。

ツリービューをカスタマイズする

Treeviewへのイベントの設定

ダブルクリックかどうかは任意なので、好きな方で良いのですが
<<TreeviewSelect>> イベントはキーボードで移動しただけでも発呼されます。
リスト要素のactivate に対応するなら、<Double-1> と <Return>が適切ですね。

何方の場合も、イベント内では全体がクリックされた・選択されたしか通知されないので、
Treeview の selection() メソッドで選択中の行を所得する必要があります。
デフォルトでは単一選択ですが、複数選択の場合の扱いにのみ注意。

# 塗りつぶしの色をタグ付けしておく
tree.tag_configure("red", background="red")
tree.insert("", tk.END, values=[...], tags=["red"])

def onActivated(event):
    tree = event.widget
    for iid in tree.selection():
        tags = tree.item(iid)["tags"]
        if "red" in tags: # タグに赤が含まれる場合・・・
            print(tags)

        break # 単一行選択の場合、最初の要素のみ処理することを明示する為の break

tree.bind('<Double-1>', onActivated)
tree.bind('<Return>', onActivated)

追記: タグの他に、非表示のcolumnにデータを持たせる方法もあります。
タグをリセットする等で、タグでは管理が面倒になる場合。
例: columns=["A", "B", "C"], displaycolumns=["A", "C"]
とすれば、"B" は非表示領域として活用可能。

この方法で管理しておくと、displaycolumns切り替えにより
デバッグ時のみ情報を表示する等が簡単になります。

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2020/09/27 13:13 編集

    ご回答ありがとうございます。
    無事、ダブルクリックによるbgの変更は実装する事が出来ました。
    たしかに不使用であるカラムを使用するのもありでしたが、掲題のfor row in sheet.iter_rows():にて、辞書型として色情報も含めデータを管理していく予定で考えておりますので、今回は無難な形での実装となりました。

    ちなみになのですが、ダブルクリックを行った際、フォーカスの色が上に被ってしまい
    正常に色が変更出来ているかどうかの視認が困難になってしまっています。
    (他のitemを選択し直さないと色が変更されている事が確認出来ない)

    この場合、フォーカスの色を無色(または変更した色と同じ色)にすべきなのか
    もしくは、自動でフォーカスを外す処理を施すのか
    nextを指定してselection_setするべきか(その場合のスクロールバーを追従させる方法がわかりません)
    はたまたその他に処理があるのか。
    何れが自然な処理になるでしょうか?

    キャンセル

  • 2020/09/27 19:23

    今実際に試せないので、解る範囲で

    可能なら無色(無効にする)がよさそうですが、出来るのかな…
    次点で、同じ色にする -> 行によって変える必要があるので少し手間ですね。
    フォーカスを外すのはキーボード上下での選択操作が出来なくなりそうです。

    >スクロールバーを追従
    see(iid) メソッドで指定の行に移動できます。スクロールだけなら yview
    ですが、selection_set は選択した時点で色が変わるので問題解決にはなりません。

    キャンセル

  • 2020/09/27 19:42

    使用上、キーボード操作もおそらくは行われない為(使用者がマウス操作のみの為)
    フォーカスアウトする事で済ませました。
    追従についてもフォーカスアウトで追従の必要性をなくせました。


    自己解決についての回答に、大まかな修正後のコードを掲載致しました。
    おそらくはこれで目的の動作は得られておりますので、細かい点はこれから修正していく予定です。

    キャンセル

+1

セルの色情報の出力について(自己解決)

質問内容 - 試したこと に掲題のコードまで出来ており
そこから更にrgbを指定する事でカラーコードの抽出が可能でした。

print(cell.fill.fgColor.rgb)

>>> 00000000

修正後

諸々を修正し以下の様な形となり、自身としては問題の解決に至りました。

    #-------Excel Window-------
    def ExcelProcess(self):
        # NewWindow create.
        self.submaster = tk.Toplevel()
        self.submaster.geometry('700x400')
        self.submaster.grab_set()
        self.submaster.attributes('-topmost', True)

        ttk.Button(self.submaster, text='データ表示', command=self.ExcelDataGet).pack(pady=10)
        self.BTN = ttk.Button(self.submaster, text='リセット', state='disable', command=self.TreeviewReset)
        self.BTN.pack()

        # Treeview create. set data of index and anchor together for tree.
        tvframe = ttk.Frame(self.submaster)
        tvframe.pack()
        self.tree = ttk.Treeview(tvframe)
        self.tree['column'] = tuple([i for i in range(7)])
        tree_info = {'INDEX1': [70, tk.CENTER], 'INDEX2': [50, tk.CENTER], 'INDEX3': [70, tk.CENTER], 
                    'INDEX4': [60, tk.E], 'INDEX5': [150, tk.W], 'INDEX6': [50, tk.W], 'INDEX7': [120, tk.W]}
        for i, [header, [width, anchor]] in enumerate(tree_info.items()):
            self.tree.column(i, width=width, anchor=anchor)
            self.tree.heading(i, text=header)
        self.tree["show"] = "headings"
        self.tree.grid(row=0, column=0)

        # Scrollbar create.
        ysb = tk.Scrollbar(tvframe, orient=tk.VERTICAL, width=16, command=self.tree.yview)
        self.tree.configure(yscrollcommand=ysb.set)
        ysb.grid(row=0, column=1, sticky='nsew')

    def ColorChange(self, event):
        tree = event.widget
        for itemid in tree.selection():
            tags = tree.item(itemid)['tags']
            num = tags[0]    # num for tag.
            # For each color as specify the i, set bg in color & Rewring tags.
            if 'white' in tags:
                self.tree.tag_configure(num, background='red')
                self.tree.item(itemid, tags=[num, 'red'])
                break
            elif 'red' in tags:
                self.tree.tag_configure(num, background='yellow')
                self.tree.item(itemid, tags=[num, 'yellow'])
                break
            elif 'yellow' in tags:
                self.tree.tag_configure(num, background='white')
                self.tree.item(itemid, tags=[num, 'white'])
                break

        self.tree.selection_set()    # focus out, this is optimal...for me.

        if str(self.BTN['state']) == 'disable':    # a little strange. str??? whatever, its fine:(
            self.BTN['state'] = 'normal'

    def TreeviewReset(self):
        if self.tree:    # treeitems delete.
            self.tree.delete(*self.tree.get_children())
        for i, [value, color] in enumerate(self.data):    # Rebuild.
            self.tree.insert('','end', values=value, tags=[i, color])
            self.tree.tag_configure(i, background=color)
        self.BTN['state'] = 'disable'    # Resetbutton has state change.

    def ExcelDataGet(self):
        # Ecelfile open.
        wb = px.load_workbook(self.excel_path)
        sheet = wb['Sheet1']

        target = 1000001
        color_dict = {'00000000': 'white', 'FFFF0000': 'red', 'FFFFFF00': 'yellow'}
        self.data = []
        # Data create. Read line, Datetime attribute are convert and replace if blank.
        for row in sheet.iter_rows():
            if row[1].value == target:
                tmp = []
                for cell in row:
                    celldata = cell.value
                    if type(celldata) == datetime:
                        celldata = cell.value.strftime('%Y/%m/%d')
                        cellcolor = color_dict[cell.fill.fgColor.rgb]    # 8 digit Colorcord get for here
                    elif celldata == None:
                        celldata = ''

                    tmp.append(celldata)
                self.data.append([tmp, cellcolor])

        # If data has target tree delete it.
        if self.tree:
            self.tree.delete(*self.tree.get_children())
        # Set data for tree
        for i, [value, color] in enumerate(self.data):
            self.tree.insert('','end', values=value, tags=[i, color]) # [num, colorcorde] addition to tags.
            self.tree.tag_configure(i, background=color)    # Specify the i, set bg in color.
        self.BTN['state'] = 'disable'    # Resetbutton has state change.

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 87.49%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る