[M1 Mac, Big Sur 11.6.5, Python 3.10.4]
xlsx変換アプリの実装機能とGUIデザインを検討しました。
ExcelファイルからリストやCSVへの変換、および逆方向の変換を実装する予定です。

[M1 Mac, Big Sur 11.6.5, Python 3.10.4]
xlsx変換アプリの実装機能とGUIデザインを検討しました。
ExcelファイルからリストやCSVへの変換、および逆方向の変換を実装する予定です。
[M1 Mac, Big Sur 11.6.5, Python 3.10.4]
Excelファイルを扱うFLTKアプリの作成に着手しました。GUIはよく使うガワの使い回しです。
まずは前々回から取り組んでいるExcelファイルから列データを取り出してリストにする機能を実装しました。A列の値とセル色をリストにします。色データはRGB16進数の頭にFFが付きます。このFFはいらないのでそのうち除去するようにします。
列数や取り出すデータの種類はいずれ選択できるようにしたいです。
#define PY_SSIZE_T_CLEAN
#include "process.h"
#include </Library/Frameworks/Python.framework/Versions/3.10/include/python3.10/Python.h>
#include <iostream>
#include <string.h>
using std::string;
int XlsxToList(const char* path) {
Py_Initialize();
PyRun_SimpleString("import openpyxl");
string path_str = string(path);
string wb_str = "wb = openpyxl.load_workbook('" + path_str +"')";
PyRun_SimpleString(wb_str.c_str());
PyRun_SimpleString("ws = wb.worksheets[0]");
PyRun_SimpleString("color_name = []");
PyRun_SimpleString("color_code = []");
string for_str = "for cell in ws['A']: color_name.append(cell.value), color_code.append(cell.fill.fgColor.rgb)";
PyRun_SimpleString(for_str.c_str());
PyRun_SimpleString("print(color_name)");
PyRun_SimpleString("print(color_code)");
Py_Finalize();
return 0;
}
[M1 Mac, Big Sur 11.6.5, Python 3.10.4]
前回の続きです。
for文はワンライナーで書けば、PyRun_SimpleString()に使えます。バックスラッシュを入れてもOKです。
リスト内包表記のワンライナーは多用していましたが、if文やfor文の中身をカンマでつなぐとワンライナーになるというのは知りませんでした。
#include </Library/Frameworks/Python.framework/Versions/3.10/include/python3.10/Python.h>
#include <iostream>
#include <string.h>
using std::string;
int main() {
Py_Initialize();
PyRun_SimpleString("import openpyxl");
PyRun_SimpleString("wb = openpyxl.load_workbook('test.xlsx')");
PyRun_SimpleString("ws = wb.worksheets[0]");
PyRun_SimpleString("color_name = []");
PyRun_SimpleString("color_code = []");
string str = "for cell in ws['A']: color_name.append(cell.value), color_code.append(cell.fill.fgColor.rgb)";
// バックスラッシュを使う場合
// string str = "for cell in ws['A']: \
// color_name.append(cell.value), color_code.append(cell.fill.fgColor.rgb)";
PyRun_SimpleString(str.c_str());
PyRun_SimpleString("print(color_name)");
PyRun_SimpleString("print(color_code)");
Py_Finalize();
return 0;
}
[M1 Mac, Big Sur 11.6.5, Python 3.10.4]
C++コードからPythonスクリプトを動かしてExcelを操作してみました。Pythonは公式サイトからダウンロード&インストールしたVer. 3.10.4を使っています。
以下のコードはExcelファイルのA列に入力された文字列とセル色をリストにして出力します。
モジュールではないのでデータの双方向なやりとりは直接できませんが、ファイルを介してなら可能でしょう。
C++でExcelを扱うにはLibXLのような3万円もする有償ライブラリが必要になるので、間接的とはいえopenpyxlで操作できるのであればこれで十分です。
FLTKのような軽量なGUIでopenpyxlやpandasが使えないかと思い、試してみた次第です。TkinterやPyQtといったウィジェットツールキットで重量級GUIにはしたくなかったものですから。
#include </Library/Frameworks/Python.framework/Versions/3.10/include/python3.10/Python.h>
int main() {
Py_Initialize();
PyRun_SimpleString("import openpyxl");
PyRun_SimpleString("wb = openpyxl.load_workbook('test.xlsx')");
PyRun_SimpleString("ws = wb.worksheets[0]");
PyRun_SimpleString("color_name = [cell.value for cell in ws['A']]");
PyRun_SimpleString("color_code = [cell.fill.fgColor.rgb for cell in ws['A']]");
PyRun_SimpleString("print(color_name)");
PyRun_SimpleString("print(color_code)");
Py_Finalize();
return 0;
}
# コンパイラ
COMPILER = clang++
DEBUG = -g
# オプション
CPPFLAGS =
# includeパス(-I)
INCLUDE = -I/Library/Frameworks/Python.framework/Versions/3.10/include/python3.10
# ライブラリパス(-l)
LIBRARY0 = -lpython3.10
# 優先ライブラリパス(-L)
LIBRARY = -L/Library/Frameworks/Python.framework/Versions/3.10/lib
# ソースファイル
SRCDIR = ./src
SRCS = $(SRCDIR)/Openpyxl.cpp
# オブジェクトファイル
OBJDIR = ./obj
OBJS = $(OBJDIR)/Openpyxl.o
# 実行ファイル
TARGETDIR = ./bin
TARGET = Openpyxl
# cppファイルからoファイル作成 $<:依存ファイル
$(OBJDIR)/Openpyxl.o: $(SRCDIR)/Openpyxl.cpp
$(COMPILER) $(CPPFLAGS) $(INCLUDE) $(DEBUG) -o $@ -c $<
# oファイルから実行ファイル作成
$(TARGET):$(OBJS)
$(COMPILER) -o $(TARGETDIR)/$@ $(OBJS) $(LIBRARY0) $(LDFLAGS) $(LIBRARY)
# ファイル削除&実行ファイル作成
.PHONY:all
all: clean $(TARGET)
# ファイル削除
.PHONY:clean
clean:
rm -rf $(OBJS) $(TARGETDIR)/$(TARGET)
[M1 Mac, Big Sur 11.6.5, VSCode 1.67.1]
自製カラーアプリのリファクタリング案(改良案)を作成しました。
これまではTextFieldに保持されたカラーコードで選択色を管理していましたが、専用のクラスSelectColorを新設し変数(基本フォーマットは0x+hex)で管理することにしました。
これにより色フォーマットの変更に対応するコードが複雑にならずに済みそうです。
@startuml
package base {
+class ColorSampleJP {
+{static} gui : JFrame
+{static} tabbedpane : JTabbedPaneEx
+{static} tab1 : JPanel
+{static} tab2 : JPanel
+{static} tab3 : JPanel
+{static} tab4 : JPanel
+{static} tab5 : JPanel
+{static} tab6 : JPanel
+{static} layout : BorderLayout
+{static} default_font : Font
+{static} home : String
-locale_lang : ResourceBundle
-ColorSampleJP() : void
+{static} main(String[] args) : void
+{static} setLAF() : void
+LAF_OS() : String[][]
+{static} size(String key) : int
+{static} makeDir() : void
}
'関連クラス(mainクラスで使用)
+class PanelMake {
}
+class RegistTab {
}
+class SelectColor #00FFFF{
}
+class TabMake {
}
}
package btnAction {
+class ShowButtonAction #DDA0DD{
}
+class AdjustButtonAction {
}
+class AlphaButtonAction {
}
+class RadioButton1Action #DDA0DD{
}
+class RadioButton2Action #DDA0DD{
}
+class RadioButton3Action #DDA0DD{
}
+class RadioButton4Action #DDA0DD{
}
+class RegistButtonAction #DDA0DD{
}
+class TrushButtonAction {
}
+class FormatConverter {
}
}
note left of SelectColor
選択あるいは指定した色を管理する
end note
ColorSampleJP -- PanelMake
ColorSampleJP -left- RegistTab
ColorSampleJP -left- TabMake
TabMake -- SelectColor
SelectColor -- ShowButtonAction
SelectColor -- RadioButton1Action
SelectColor -- RadioButton2Action
SelectColor -- RadioButton3Action
SelectColor -- RadioButton4Action
SelectColor -- RegistButtonAction
left to right direction
PanelMake -- ShowButtonAction
PanelMake -- AdjustButtonAction
PanelMake -- AlphaButtonAction
PanelMake -- RadioButton1Action
PanelMake -- RadioButton2Action
PanelMake -- RadioButton3Action
PanelMake -- RadioButton4Action
RegistTab -- RegistButtonAction
RegistTab -- TrushButtonAction
AdjustButtonAction -- FormatConverter
@enduml
[M1 Mac, Big Sur 11.6.5, VSCode 1.67.1]
自製カラーアプリについてパッケージ間の関係性をクラス図で表現しました。
mainクラスを含むbaseパッケージとbtnActionパッケージのクラス図を描いてみました。関係性が一目瞭然です。
@startuml
package base {
+class ColorSampleJP66 {
+{static} gui : JFrame
+{static} tabbedpane : JTabbedPaneEx
+{static} tab1 : JPanel
+{static} tab2 : JPanel
+{static} tab3 : JPanel
+{static} tab4 : JPanel
+{static} tab5 : JPanel
+{static} tab6 : JPanel
+{static} layout : BorderLayout
+{static} default_font : Font
+{static} home : String
-locale_lang : ResourceBundle
-ColorSampleJP66() : void
+{static} main(String[] args) : void
+{static} setLAF() : void
+LAF_OS() : String[][]
+{static} size(String key) : int
+{static} makeDir() : void
}
'関連クラス(mainクラスで使用)
+class PanelMake {
}
+class RegistTab {
}
}
package btnAction {
+class ShowButtonAction {
}
+class AdjustButtonAction {
}
+class AlphaButtonAction {
}
+class RadioButton1Action {
}
+class RadioButton2Action {
}
+class RadioButton3Action {
}
+class RadioButton4Action {
}
+class RegistButtonAction {
}
+class TrushButtonAction {
}
+class FormatConverter {
}
}
ColorSampleJP66 -- PanelMake
ColorSampleJP66 -left- RegistTab
left to right direction
PanelMake -- ShowButtonAction
PanelMake -- AdjustButtonAction
PanelMake -- AlphaButtonAction
PanelMake -- RadioButton1Action
PanelMake -- RadioButton2Action
PanelMake -- RadioButton3Action
PanelMake -- RadioButton4Action
RegistTab -- RegistButtonAction
RegistTab -- TrushButtonAction
AdjustButtonAction -- FormatConverter
@enduml
[M1 Mac, Big Sur 11.6.5, VSCode 1.67.1]
クラス図をより見やすくするために子クラスを右側に縦並びさせました。デフォルトの横並びを左へ90度回転させるので左側にあったクラスが下になります。書き順を変えても同じです。
まあ見やすくなったのでよしとします。
@startuml
package base {
+class ColorSampleJP66 {
+{static} gui : JFrame
+{static} tabbedpane : JTabbedPaneEx
+{static} tab1 : JPanel
+{static} tab2 : JPanel
+{static} tab3 : JPanel
+{static} tab4 : JPanel
+{static} tab5 : JPanel
+{static} tab6 : JPanel
+{static} layout : BorderLayout
+{static} default_font : Font
+{static} home : String
-locale_lang : ResourceBundle
-ColorSampleJP66() : void
+{static} main(String[] args) : void
+{static} setLAF() : void
+LAF_OS() : String[][]
+{static} size(String key) : int
+{static} makeDir() : void
}
+class Tab1 {
-Tab1(String LAF0) : void
}
+class Tab2 {
-Tab2(String LAF0) : void
}
+class Tab3 {
-Tab3(String LAF0) : void
}
+class Tab4 {
-Tab4(String LAF0) : void
}
+class Tab5 {
-Tab5(String LAF0) : void
}
+class Tab6 {
-Tab6(String LAF0) : void
}
+class MyWindowsListener {
+windowClosing(WindowEvent e) : void
}
left to right direction
ColorSampleJP66 +-- Tab1
ColorSampleJP66 +-- Tab2
ColorSampleJP66 +-- Tab3
ColorSampleJP66 +-- Tab4
ColorSampleJP66 +-- Tab5
ColorSampleJP66 +-- Tab6
ColorSampleJP66 +-- MyWindowsListener
'関連クラス(mainクラスのフィールド)
+class JTabbedPaneEx {
}
}
ColorSampleJP66 -ri- JTabbedPaneEx
@enduml
[M1 Mac, Big Sur 11.6.5, VSCode 1.67.1]
自製カラーアプリのリファクタリングに際し、ソースコードの全体像を把握しやすくするため、クラス図の作成に着手しました。
VSCodeにPlantUMLを導入して使用しています。まずはメインクラスのクラス図を作成しました。入れ子になっているクラスは親クラスの外に出して記述し、+–で入れ子であることを表現します。
私にとっては十分複雑な作りになっていることを再認識しました。今までクラス図なしでよくメンテナンスできたものだと思います。
@startuml
package base {
+class ColorSampleJP66 {
+{static} gui : JFrame
+{static} tabbedpane : JTabbedPaneEx
+{static} tab1 : JPanel
+{static} tab2 : JPanel
+{static} tab3 : JPanel
+{static} tab4 : JPanel
+{static} tab5 : JPanel
+{static} tab6 : JPanel
+{static} layout : BorderLayout
+{static} default_font : Font
+{static} home : String
-locale_lang : ResourceBundle
-ColorSampleJP66() : void
+{static} main(String[] args) : void
+{static} setLAF() : void
+LAF_OS() : String[][]
+{static} size(String key) : int
+{static} makeDir() : void
}
+class Tab1 {
-Tab1(String LAF0) : void
}
+class Tab2 {
-Tab2(String LAF0) : void
}
+class Tab3 {
-Tab3(String LAF0) : void
}
+class Tab4 {
-Tab4(String LAF0) : void
}
+class Tab5 {
-Tab5(String LAF0) : void
}
+class Tab6 {
-Tab6(String LAF0) : void
}
+class MyWindowsListener {
+windowClosing(WindowEvent e) : void
}
ColorSampleJP66 +-- Tab1
ColorSampleJP66 +-- Tab2
ColorSampleJP66 +-- Tab3
ColorSampleJP66 +-- Tab4
ColorSampleJP66 +-- Tab5
ColorSampleJP66 +-- Tab6
ColorSampleJP66 +-- MyWindowsListener
}
@enduml
[M1 Mac, Big Sur 11.6.5, VSCode 1.67.1]
自製カラーアプリをリファクタリングするにあたり、コードの内容をクラス図で整理することにしました。
HomebrewからPlantUMLとGraphviz(クラス図作成に必要)をインストールしました。
brew install graphviz
brew install plantuml
VSCodeのsettings.jsonに以下を追記し、Graphvizのdotファイルをパス設定等します。
"plantuml.commandArgs": [
"-DGRAPHVIZ_DOT=/opt/homebrew/Cellar/graphviz/3.0.0/bin/dot",
"-Xmx2g",
"-DPLANTUML_LIMIT_SIZE=16384",
]
とりあえずネットの作例から簡単なクラス図を表示してみました。
iOSアイコンのAppicon.appiconset内にあるContents.jsonはアイコン作成サイトで作ったものを一部書き換えて使っています。
内容は以下の通りです。
{"images":[{"size":"60x60","expected-size":"180","filename":"180.png","folder":"AppIcon.appiconset/","idiom":"iphone","scale":"3x"},
{"size":"40x40","expected-size":"80","filename":"80.png","folder":"AppIcon.appiconset/","idiom":"iphone","scale":"2x"},
{"size":"40x40","expected-size":"120","filename":"120.png","folder":"AppIcon.appiconset/","idiom":"iphone","scale":"3x"},
{"size":"60x60","expected-size":"120","filename":"120.png","folder":"AppIcon.appiconset/","idiom":"iphone","scale":"2x"},
{"size":"57x57","expected-size":"57","filename":"57.png","folder":"AppIcon.appiconset/","idiom":"iphone","scale":"1x"},
{"size":"29x29","expected-size":"58","filename":"58.png","folder":"AppIcon.appiconset/","idiom":"iphone","scale":"2x"},
{"size":"29x29","expected-size":"29","filename":"29.png","folder":"AppIcon.appiconset/","idiom":"iphone","scale":"1x"},
{"size":"29x29","expected-size":"87","filename":"87.png","folder":"AppIcon.appiconset/","idiom":"iphone","scale":"3x"},
{"size":"57x57","expected-size":"114","filename":"114.png","folder":"AppIcon.appiconset/","idiom":"iphone","scale":"2x"},
{"size":"20x20","expected-size":"40","filename":"40.png","folder":"AppIcon.appiconset/","idiom":"iphone","scale":"2x"},
{"size":"20x20","expected-size":"60","filename":"60.png","folder":"AppIcon.appiconset/","idiom":"iphone","scale":"3x"},
{"size":"1024x1024","filename":"1024.png","expected-size":"1024","idiom":"ios-marketing","folder":"AppIcon.appiconset/","scale":"1x"},
{"size":"40x40","expected-size":"80","filename":"80.png","folder":"AppIcon.appiconset/","idiom":"ipad","scale":"2x"},
{"size":"72x72","expected-size":"72","filename":"72.png","folder":"AppIcon.appiconset/","idiom":"ipad","scale":"1x"},
{"size":"76x76","expected-size":"152","filename":"152.png","folder":"AppIcon.appiconset/","idiom":"ipad","scale":"2x"},
{"size":"50x50","expected-size":"100","filename":"100.png","folder":"AppIcon.appiconset/","idiom":"ipad","scale":"2x"},
{"size":"29x29","expected-size":"58","filename":"58.png","folder":"AppIcon.appiconset/","idiom":"ipad","scale":"2x"},
{"size":"76x76","expected-size":"76","filename":"76.png","folder":"AppIcon.appiconset/","idiom":"ipad","scale":"1x"},
{"size":"29x29","expected-size":"29","filename":"29.png","folder":"AppIcon.appiconset/","idiom":"ipad","scale":"1x"},
{"size":"50x50","expected-size":"50","filename":"50.png","folder":"AppIcon.appiconset/","idiom":"ipad","scale":"1x"},
{"size":"72x72","expected-size":"144","filename":"144.png","folder":"AppIcon.appiconset/","idiom":"ipad","scale":"2x"},
{"size":"40x40","expected-size":"40","filename":"40.png","folder":"AppIcon.appiconset/","idiom":"ipad","scale":"1x"},
{"size":"83.5x83.5","expected-size":"167","filename":"167.png","folder":"AppIcon.appiconset/","idiom":"ipad","scale":"2x"},
{"size":"20x20","expected-size":"20","filename":"20.png","folder":"AppIcon.appiconset/","idiom":"ipad","scale":"1x"},
{"size":"20x20","expected-size":"40","filename":"40.png","folder":"AppIcon.appiconset/","idiom":"ipad","scale":"2x"}]}