gologiusの巣

プログラミングなどの技術メモです。誰かの役に立てるとうれしいです。

Vueっぽく階層式プルダウンを作ったが、もっとスマートに書きたいという話

※Vue始めて3か月くらいです。

とりあえず作ったものを下記リンクに置いておく。

Vueでプルダウンテスト

ソースは下記に置いておく。JSなら動くのでgithubio便利。

gologius.github.io/test/PULLDOWN_SAMPLE at master · gologius/gologius.github.io · GitHub

リンク先を見るのが面倒な人向けにGIFも貼っておく

f:id:gologius:20200606152036g:plain
プルダウンのGIF

なぜ作ったのか

  • 3階層、4階層以降に対応したサンプルがなかった(気がする)
  • 超適当に調べたが、微妙にやりたいこととずれたものだった(気がする)
  • こんなんサイトちゃんと見なくても自力実装できるわwwwww(所感)

感想

1

HTMLはそこそこ綺麗かなと思いました

<div id="pulldown">
    <div v-for="(choices,layer) in selectable_items">

        <div v-bind:id="'layer' + layer" v-show="choices.length >= 1">    
            <h2>第{{layer+1}}階層</h2>
            <select v-bind:id="'pd-'+ layer" v-model="selected_stack[layer]">
                <option v-for="item in choices" v-bind:value="item.id">
                    {{item.name}}
                </option>
            </select>
        </div>

    </div>
</div>

<script src="pulldown.js"></script>

2

現状の選択に紐づく選択肢をJSでフィルターして渡してるんだけど、 HTML側のフィルターで頑張るべきなのか、JS側で頑張るべきなのか、ライブラリの思想としてどっちがいいのかがよくわからない

//現状のユーザー選択(selected_stack)に対して、表示すべき選択肢を取得
selectable_items: function () {
    
    var results = [];
    var layer = 0;
    for (layer = 0; layer <= this.selected_stack.length; layer++) {
        
        //上位層の選択結果を取得
        before_selected_id  = ''
        if (layer >= 1 ) {
            before_selected_id = this.selected_stack[layer-1];
        }

        //該当層かつ、上位層の選択肢に合致するもののみ抽出
        layer_result = MENU_M.filter(function (value) {
            return value.layer === layer && value.before_id === before_selected_id;
        });

        results.push(layer_result);
    }
    return results;
},

3

JS側が汚い。初期化処理とかキモイ。watch使っているんだけど謎ラッパーかまさないとListの検知ができない

computed: {
    //watch関数をまともに動作させるためのラッパー
    //参考:https://qiita.com/haruyanagi17/items/d74c0b9546719ff88c63
    watch_selected_stack: function () {
        return Object.assign({}, this.selected_stack); // ディープコピーしたものを返す
    },
},
watch: {
    //選択肢が入力された際の動作定義
    watch_selected_stack: function (newval, oldval) {
        
        //変更箇所を検知
        var update_flag = false;
        var layer = 0;
        for(layer=0; layer < this.selected_stack.length; layer++){
            
            //変更箇所より後の選択肢は初期化する
            if(update_flag) {
                this.selected_stack[layer] = '';
            }

            //変更されている箇所があれば、フラグを立てる
            if (newval[layer] !== oldval[layer]) {
                update_flag = true;
            }
        }
    },      
}

今後

もうちょいきれいに書く方法探しますわ

MySQLで住所CSVをDBテーブルにロードする

概要

住所情報をDBに入れて、検索させて候補表示したりすることはよくあると思います。

なので、その準備方法の一つとして、方法を記載しておきます。

なお、今回使うのは住所.jp様のCSVです。

項目の意味等は別途下記サイトから調べてください。

住所データCSV【住所.jp】

日本郵政にも同じようなCSVファイルがありましたが、読みが半角カナで変換が面倒だったので、 こちらを使用。

環境

Ubuntu 18.04

MySQL 14.14

テーブルを作る

下記SQLでテーブルを作成します。命名がダサいとおもった方は適宜変えてください。

 CREATE TABLE JUSYO(
        JUSYO_CD VARCHAR(10),
        TODOFUKEN_CD VARCHAR(10),
        SIKU_CD VARCHAR(10),
        CHOU_CD VARCHAR(10),
        ZIP VARCHAR(8),
        JIGYOSHO_FLG VARCHAR(1),
        HAISI_FLG VARCHAR(1),
        TODOFUKEN VARCHAR(100),
        TODOFUKEN_KANA VARCHAR(100),
        SIKU VARCHAR(100),
        SIKU_KANA VARCHAR(100),
        CHOU VARCHAR(100),
        CHOU_KANA VARCHAR(100),
        CHOU_HOSOKU VARCHAR(100),
        KYOTO_TORINA VARCHAR(100),
        AZA VARCHAR(100),
        AZA_KANA VARCHAR(100),
        HOSOKU VARCHAR(100),
        JIGYOSHO_NAME VARCHAR(100),
        JIGYOSHO_KANA VARCHAR(100),
        JIGYOSHO_JUSYO VARCHAR(100),
        NEW_JUSYO_CD VARCHAR(100)
    );

ファイルをサーバーに置く

適当な場所でよいです。

もしかしたらサーバーとDBの設定を変えれば、リモート越しでもできるかもしれません。

CSVロード

該当ファイルがある場所でmysql にログイン

先ほど作ったテーブルが存在するDBを選択し、下記SQLを実行 (ファイル名は zenkoku.csv を想定)

LOAD DATA LOCAL INFILE "zenkoku.csv"
INTO TABLE JUSYO
CHARACTER SET SJIS
FIELDS TERMINATED BY ','
OPTIONALLY ENCLOSED BY '"';
IGNORE 1 LINES;

参考: 【MySQL】CSVファイルをデータベースにインポートする | (株)シャルーン

エラー対処法

住所.jp様からDLできるCSVファイルはSJISなので、 DBがUTF8 かつ CHARACTER SET SJIS がない場合、下記のようなエラーで落ちる

ERROR 1300 (HY000): Invalid utf8 character string: '"' 

INTO TABLE *** の直後に CHARACTER SET SJIS をもってこないと、下記エラーになる模様

ERROR 1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version 
for the right syntax to use near 'character SET sjis IGNORE 1 LINES' at line 1

おまけ

全部連結したテーブルを作っておけば、 サジェストなどに使いやすい形になると思います。

こんな感じのデータになります。

郵便番号 住所 住所読み
060-0042 北海道札幌市中央区大通西1丁目 ホッカイドウサッポロシチュウオウクオオドオリニシ01チョウメ

作成したければ、下記のようなSQLで、SELECT結果からTABLEを作成することができます。

create table JUSYO_MIN
as 
SELECT 
zip, 
replace(concat(TODOFUKEN,SIKU,CHOU,AZA,JIGYOSHO_NAME,JIGYOSHO_JUSYO)," ", "") as JUSYO_ALL, 
replace(concat(TODOFUKEN_KANA,SIKU_KANA,CHOU_KANA,AZA_KANA,JIGYOSHO_KANA), " ", "") as JUSYO_KANA_ALL 
FROM JUSYO;

MySQL+Python(Flask) でdatetimeが返却される

MySQLのDATETIME型を、SELECT文で取得すると、 pythonのdatetime型で返却される事象があるようです。

私も下記記事のようになりました

Python - PythonでMySQLのTIME型をSELECTする方法|teratail

例コード

import mysql.connector
DB_HOST = "192.168.0.XXX"
DB_NAME = "your_db_name"
DB_USER = "your_db_user"
DB_PW = "your_user_pw"
conn = mysql.connector.connect(user=DB_USER, password=DB_PW, host=DB_HOST, database=DB_NAME)

cur = conn.cursor(dictionary=True) #引数指定すると、辞書型で返してくれる
sql = "SELECT ADD_DATE FROM USER;"
cur.execute(sql)
data = cur.fetchall(); 

print(data)

>>[ { "ADD_DATE": datetime.datetime(2019, 08, 08) } ]

ちなみに、APIを実装するために、Flaskの関数 flask.jsonify() なんかでJSON変換して送信すると、 文字列がMon Mar 22 05:06:07 GMT 1999 のようになってしまう。

素直にYYYYMMDD hh:mm:ss で表示してほしい・・・

解決方法

MySQLならDATE_FORMAT関数があるので、そちらを使ってフォーマットされた文字列を出力する。

ただしそのままSELECTすると、Pythonで結果をfetchする際に、辞書型のキー名がDATE_FORMAT(****)になっていまいます。

なので、SQL文を

sql = 'SELECT DATE_FORMAT(ADD_DATE, %s) AS ADD_DATE  FROM USER;'
cur.execute(sql, ('%Y年%m月%d日 %h時%i分%s秒',))

のようにASを使用してあげるとよいと思います。

ダメな例

なお、Pythonパッケージmysql-connector-pythonを使用する場合、 リファレンス通りのSQLを作るとエラーになります。

MySQL :: MySQL 5.6 リファレンスマニュアル :: 12.7 日付および時間関数

NG例

sql = 'SELECT  DATE_FORMAT(LAST_UPDATE_DATE,'%Y年%m月%d日 %h時%i分%s秒') FROM USER;"'
cur.execute(sql)

恐らくSQL Statementで使用する'%'がエラーで引っかかるのだと思います。

Use mysql DATE_FORMAT in Python - CodeProject

以上

PythonでMySQLに接続、操作する(2019年風)

(追記 2019/08/21) 私のググり方が悪かったのか、

'rt'ではない方のパッケージ使っている方はちょくちょくいますね・・・

====

PythonMySQLに接続と操作をしようとしました。ググると下記記事が出てきます。

qiita.com

上記記事によると、 mysql-connector-python-rfmysql-connector-python の二つのパッケージが存在し、上記の記事は'rf'の方のパッケージについて記載されています。 rfはサポート終了してるようですので、今後はもう一つのパッケージを使うべきたと思います。

(追記8/13)また、上記記事ではSQL結果を辞書型で扱えないという旨が記載されていますが、 cursorの引数指定で扱うことが可能です。

というわけでmysql-connector-pythonを使用して、MySQLを触っていきます。

ぶっちゃけ大して使い方は変わっていないようです

ソースと参考文献

今回のソースまとめ

https://gist.github.com/gologius/b210556b7ce1f4b5ac33277bd1629e49

公式ドキュメント(詳細はここを読んでください ※英語)

MySQL :: MySQL Connector/Python Developer Guide :: 5.1 Connecting to MySQL Using Connector/Python

前準備

環境はPython実行側が Win10、Python3.7、DBサーバーはUbuntu18.04です。 MySQLの設定等はここでは触れません。

DBは下記のような構造を想定しています。

項目名 TYPE
id int
username varchar
createtime datetime

パッケージインストール

pip install mysql-connector-python

これだけ

接続

import mysql.connector

DB_HOST = "192.168.0.XXX"
DB_NAME = "your_db_name"
DB_USER = "your_db_user"
DB_PW = "your_user_pw"

conn = mysql.connector.connect(user=DB_USER, password=DB_PW, host=DB_HOST, database=DB_NAME)

INSERT

# 1レコードのみ
cur = conn.cursor()
insert_sql = "INSERT INTO USER () VALUES(%s, %s, %s);"
insert_value = (random.randint(1,100), 'testusername', datetime.datetime.now())
cur.execute(insert_sql, insert_value) #第二引数はタプル
conn.commit()

# 複数レコードある場合
insert_values = []
for i in range(10):
    v =  (random.randint(1,100), 'testusername', datetime.datetime.now())
    insert_values.append(v);

cur.executemany(insert_sql, insert_values) #第二引数はタプルリスト
conn.commit()

SELECT

(追記08/13)引数指定することで、辞書型で返却できる旨を追記

#シンプルなSQL実行
cur = conn.cursor(dictionary=True) #引数指定すると、辞書型で返してくれる
select_sql = "SELECT * FROM USER;"
cur.execute(select_sql)

# 辞書型リストを取得
data = cur.fetchall(); 

# 変数埋め込み
cur = conn.cursor() #引数に何も指定しない場合、タプルのリストが返却される
select_sql = "SELECT * FROM USER where id >= %s ORDER BY ID;"
where_value = random.randint(1,100)
cur.execute(select_sql , (where_value,)) #第二引数はタプル ※条件が一つの場合でもタプル型にする必要がある

#ループでも取り出し可能
for id, username, createtime in cur:
    print(id, username, createtime)

切断

cur.close()
conn.close()

あとがき

mysql-connector-python-rfと使い勝手はあまり変わらない気も・・・

随時追記します

【Python】SQLiteでSQLエラーが発生する

SQLiteプレースホルダー関連でエラーが発生するので解決砲を記載。ハマる人はハマるのではと思ったり。

なお、下記のソースはそのままでは動かないので適宜改変してください。

その1 ''で囲んでいる

ソース

sql = "SELECT NAME FROM MEMBERS WHERE NAME='?'"  #<<<<<<これ
name = "testname"

try :
   #SQL実行
   conn = sqlite3.connect("your db path.db")
   cur = conn.cursor()
   cur.execute(sql,(name, ))

   cur.close()
   conn.close()
except:
   print(traceback.format_exc())
   return DBResult.SQL_ERROR

結果

Traceback (most recent call last):
sqlite3.ProgrammingError: Incorrect number of bindings supplied. The current statement uses 0, and there are 1 supplied.

原因

?を''で囲んだせいで、?が文字列として認識されてしまっているようです。 よって、プレースホルダ(?)が0個なのに、値(name)を一個プレスホルダに入れようとしていてエラーになっています。

SQLをかじった人なら、SQL内で文字列比較する際には''囲みをすることを把握しているかと思います。 それにより引き起こされるミスですね。

解決策

下記のようにする

sql = "SELECT NAME FROM MEMBERS WHERE NAME=?"

その2 タプルで渡していない

ソース

sql = "INSERT INTO MEMBERS (NAME, GROUP) VALUES (?, ?)"
values = ["testname", "A"] #<<<<<<これ

try :
   #SQL実行
   conn = sqlite3.connect("your db path.db")
   cur = conn.cursor()
   cur.execute(sql, values) #<<<<<<これ

   cur.close()
   conn.close()
except:
   print(traceback.format_exc())

エラー

sqlite3.ProgrammingError: Incorrect number of bindings supplied. The current statement uses 1, and there are 2 supplied.

原因

execute()の引数はタプル、executemany()の引数はタプルのリストとなります。 実際の内部処理はよくわかりませんが、プレースホルダ(?)が2に対して、引数がリストでまとめて1つ、 と判断されているのだと思います。

上記のエラーでとりあえずググれば出てくる、有名な話ですね。

解決策

タプルに変換すればいいです

values = ("testname", "A") #タプルにしてあげる
values = ("testname",) #要素が一つの場合は、末尾カンマも忘れずに

#executemanyを使用する場合、タプルのリストに変換してあげる
value_list = ["testname1","testname2","testname3"]
tuple_list = []
   for name in value_list:
       tuple_list.append((name,))

その3 executemanyをselectで使用している

ソース

sql = "SELECT NAME FROM MEMBERS WHERE NAME=?"

name_list = ["aaa","bbb","ccc"]
tuple_list = []
   for name in name_list:
       name_list.append((name,)) #引数がlist[tuple]でないと受け付けないため、変換する

try :
   #SQL実行
   conn = sqlite3.connect("your db path.db")
   cur = conn.cursor()
   cur.executemany(sql, tuple_list)

   cur.close()
   conn.close()
except:
   print(traceback.format_exc())

エラー

sqlite3.ProgrammingError: executemany() can only execute DML statements.

原因

executemanyはDML文でしか使用できません。

DML文とは、表の値を操作したり、削除したりと、「表に変更を与えるようなSQL」です。 最後にcommit()が必要な文ですね。

docs.oracle.com

SELECTは読み取るだけなので、表に変化は与えません。 なのでexecutemanyが使用できないみたいです。

解決策

条件文を駆使して、SQLの構文を用いてまとめて取得しましょう。 下記はIN文での例です

name_list = ["aaa","bbb","ccc"]
sql = "SELECT NAME FROM MEMBERS WHERE NAME IN (" + ",".join(name_list) + ")"

try :
   #SQL実行
   conn = sqlite3.connect("your db path.db")
   cur = conn.cursor()
   cur.execute(sql)

   cur.close()
   conn.close()
except:
   print(traceback.format_exc())

もしくはSQLを都度発行する方法もあります。 パフォーマンス的にはあまりよくないのかもしれませんが・・・

name_list = ["aaa","bbb","ccc"]

try :
    sql = "SELECT NAME, GROUP FROM MEMBER WHERE NAME = ?"    
    conn = sqlite3.connect("your db path.db")
    cur = conn.cursor()

    for name in name_list:    
        cur.execute(sql, (name,))
        records = cur.fetchall()
        print(records)

    cur.close()
    conn.close()
except:
    print(sql)
    print(traceback.format_exc())
    return DBResult.SQL_ERROR

まとめ

最近SQL全然使ってないのでハマった。

画像ビューワーを作る(.NET C# Windows Form)

晦日なので画像ビューワーを作りましょう(核爆)(錯乱)

完成するとこんな感じになります。 圧縮ファイルの中身を自動展開します。

f:id:gologius:20181231142756g:plain

今回のプロジェクトはこちら。

github.com

※「Windowsフォーム」は技術的に古い、みたいな話がネット上に散見されます。 本当に「Windowsフォームで作成すべきか」は一度検討してみてもよいと思います。

事前準備と画面の説明

開発環境は「Win10、Visual Studio 2017」です。

Visual Studioのインストール方法は割愛します。

プロジェクトを作成する

Visual Studioを開いて、右上の「ファイル」→「新規作成」→「プロジェクト」 でプロジェクト作成します。 f:id:gologius:20181231142929p:plain 今回は「Windows フォーム アプリケーション」を選択します。

フォルダを開くためにNugetからダウンロード

今回やることに必要なパッケージをNuGetからDLします。 今回使うのは下記のパッケージです。

  • Microsoft.WindowsAPICodePack.Core (フォルダを開くダイアログ)
  • Microsoft.WindowsAPICodePack.Shell(フォルダを開くダイアログ)
  • SharpCompress (圧縮ファイルの操作)

  • プロジェクト→NuGetパッケージの管理→参照

  • パッケージソースを「すべて」にして、検索ボックスで上記のパッケージを検索+DL!

f:id:gologius:20181231143112p:plain

f:id:gologius:20181231143045p:plain

(2017になって微妙にUIが変更されててビビった)

迷ったらDL数が多いものを選んでおけばよいのではないでしょうか。

画面の説明

f:id:gologius:20181231144230p:plain

  • 各ウインドウの位置は人によって違うと思います
  • ツールボックスが表示されていない人は上バーの「表示」から表示できます。

ここで覚えておいてほしいのは

ですです。

デザイナー上でコンポーネントをダブルクリックすると、勝手に関数が作成されます。 害はありませんが、コードが汚染されるので私は嫌いです。

フォルダを選択できるようにする

ツールボックスからコンポーネントを追加

ツールボックスからMenuStripを探して、D&Dで追加してください。 追加後、ダブルクリックで項目を追加できます。 ここでは「フォルダを開く」という項目を追加します。 f:id:gologius:20181231144840p:plain

変数名の話

プロパティにて変数名を変更できます。

GUI表示に使用されるのはTextです。 f:id:gologius:20181231144909p:plain

変更しないとコード内に日本語変数が紛れ込みます。 関数名に日本語が混ざります。 動作上問題はないのですが気持ち悪い・・・ f:id:gologius:20181231145001p:plain

イベント追加

MenuStripのイベントClickに対して、関数を設定します。 Clickを選択してダブルクリックで勝手に関数が作成されます。 f:id:gologius:20181231145307p:plain

フォルダを開く コードの追加

private void ****ToolStripMenuItem_Click(object sender, EventArgs e) という関数が自動生成されると思います。

下記のコードのように、クリックしたらフォルダ選択ダイアログが表示されるように実装します。

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using Microsoft.WindowsAPICodePack.Dialogs; //これを追加しないと動かない

namespace Viewer
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void DirOpenToolStripMenuItem_Click(object sender, EventArgs e)
        {
            //「フォルダを開く」ダイアログの設定
            var dialog = new CommonOpenFileDialog();
            dialog.IsFolderPicker = true;  // フォルダーを開く設定にする
            dialog.EnsureReadOnly = true;
            dialog.AllowNonFileSystemItems = false;
            dialog.DefaultDirectory = Application.StartupPath;

            //フォルダを開く
            var Result = dialog.ShowDialog();
            if (Result == CommonFileDialogResult.Ok)
            {
              //開いた後の処理
              this.Text = dialog.FileName; //タイトル名変更
              //updateFileList(select_path); //ファイルリスト更新 ※後程使用します
            }
        }
    }
}

※Form1.csがプログラムとして開けない人は、下記の方法で開いてみてください。

Form1.Designer.cs → Form→Form1()をクリック f:id:gologius:20181231150948p:plain

コンパイル

ここまでいろいろやったのでコンパイルして、動くか確認しておきましょう。

コンパイル後、「フォルダを開く」ボタン→「フォルダ選択ダイアログ」が開き、 選択後にフォームのタイトルが「選択したパス」になっていればOKです。

f:id:gologius:20181231145640p:plain

f:id:gologius:20181231145643p:plain

画像ビューワーに仕立て上げる

ガワはできたので、ここから作りこんでいきます。

ここから説明する関数は全てForm1クラスのメンバ関数です。また、適宜イベントを設定する必要があります。

コンポーネント追加

下記の部品を追加します。

  • ListView
  • PictureBox

f:id:gologius:20181231145720p:plain

ListViewはプロパティ「View」を「List」にします(見た目が変わります)。 f:id:gologius:20181231150047p:plain

PictureBoxはプロパティ「SizeMode」を「Zoom」にする(画像の表示方法です) f:id:gologius:20181231150110p:plain

以下のURLに詳しく記載されています。

PictureBoxコントロールに簡単に画像を表示する - .NET Tips (VB.NET,C#...)

画像ビューワーっぽくなってきましたね。

ファイル一覧を表示

ListViewに、選択したフォルダ内のファイル一覧を表示させます。

//ファイルリストを更新する
private void updateFileList(string path)
{
   var fullpaths = System.IO.Directory.GetFiles(path, "*");
   foreach (string filename in fullpaths)
   {
       listView1.Items.Add(filename);
   }
}

※先ほどのDirOpenToolStripMenuItem_Click()//updateFileList(select_path);コメントアウトを外してください

圧縮ファイルを開いて画像を登録する

ListViewの項目をクリックしたら、圧縮ファイル内の「画像ファイル」のみを読み込むようにします。

※読み込みの待ち時間や、IO負荷などはあまり考慮していません。

IArchive archive = null; //圧縮ファイルの実体
List<IArchiveEntry> imgs = null; //画像ファイル群   
int lookPage = 0; //現在閲覧しているページ

//ファイルリストの項目をクリックした時
 private void listView1_Click(object sender, EventArgs e)
 {
     //項目が一つもない場合
     if (listView1.SelectedItems.Count == 0)
     {
         return;
     }

     //選択している最初の行の、最初の列の値を取得
     ListViewItem item = listView1.SelectedItems[0];
     string path = item.SubItems[0].Text;

     bool result = registerImage(path);
     if (result)
     {
         lookPage = 0;
         showImage(lookPage);
     }
 }

 //指定された圧縮ファイル内の画像を表示する
 private bool registerImage(string path)
 {
     Console.WriteLine(path);
     //初期化
     if (archive != null)
     {
         archive.Dispose();
         archive = null;
     }

     //圧縮ファイルから画像ファイルのみ取り出す
     try
     {
         archive = ArchiveFactory.Open(path);
         var entries = archive.Entries.Where(e =>
             e.IsDirectory == false && (
             Path.GetExtension(e.Key).Equals(".jpg") ||
             Path.GetExtension(e.Key).Equals(".jpeg") ||
             Path.GetExtension(e.Key).Equals(".png") ||
             Path.GetExtension(e.Key).Equals(".bmp")));

         imgs = entries.ToList();
     }
     catch (Exception e)
     {
         MessageBox.Show(path + " " + e.ToString(), "ファイル展開エラー", MessageBoxButtons.OK, MessageBoxIcon.Error);

         if (archive != null)
         {
             archive.Dispose();
         }
         archive = null;
         return false;
     }

     //ソート
     imgs.Sort((a, b) => { return a.Key.CompareTo(b.Key); });

     return true;
 }

画像を表示させる+ページめくり機能をつける

いよいよ、画像表示機能を実装します。 といっても、登録している画像を表示させるだけですが・・・

ついでに、矢印キーでページめくりができるようにしておきます。 これは「ListView」に対して「KeyDown」イベントを設定します

//指定ページの画像を表示する
private bool showImage(int index)
{
    if (imgs.Count() == 0)
    {
        return false;
    }

    //圧縮ファイル内のファイル指定
    var entry = imgs[index];
    try
    {
        //ファイルを読み込みビューワーにセット
        pictureBox1.Image = Image.FromStream(entry.OpenEntryStream());
    }
    catch (Exception e)
    {
        MessageBox.Show(e.ToString(), "正常な画像ファイルではありません", MessageBoxButtons.OK, MessageBoxIcon.Error);
        return false;
    }

    return true;
}

//矢印キーでのページめくり
private void listView1_KeyDown(object sender, KeyEventArgs e)
{
    if (archive == null)
    {
        return;
    }

    if (e.KeyCode == Keys.Left)
    {
        lookPage--;
        if (lookPage < 0)
        {
            lookPage = imgs.Count() - 1;
        }
        showImage(lookPage);
    }
    else if (e.KeyCode == Keys.Right)
    {
        lookPage++;
        if (lookPage >= imgs.Count())
        {
            lookPage = 0;
        }
        showImage(lookPage);
    }
}

全てのソースを合体させると、下記のようになります

Viewer/Form1.cs at master · gologius/Viewer · GitHub

まとめ

画像ビューワーを作成しました。

特に SharpCompress の使い方が分からず、最初は苦労しました。

常に圧縮ファイル内の全画像を持つようなコードになっているので、 負荷が気になるところです(といっても私のPCだと全く問題ありませんが・・・)

また、Windowsフォームも技術的には古いらしいので、他の手法も調査もしないとなぁと思いました。

おしまい。

github.com

f:id:gologius:20181231142756g:plain