CSVファイルの解析環境構築（その３：データ名シート追加）

## はじめに
前の記事でCSVファイルの列をソートすることができた。
実際にデータ解析をしていると、必要なデータは一部であったり、データに独自の名前を付けたりなどして解析しやすく成型することが多い。
前回までに作成したコードを拡張して、解析するデータの選別や表示順を指定できるようにする。

## 今回やってみたこと
* ほかのExcelファイルからシートをコピーする
* セルマクロを記入し、ほかのセルへコピーする
* ウィンドウ枠の固定
* オートフィルタの追加
* セルの文字を縮小して全体表示を設定する
* 列の幅を変更する

## 今後のレベルアップ項目
* 条件付き書式設定を追加したい

## 追加した機能
* 解析で使用するデータと日本語名、表示順を紐づけた一覧表（シート）を作成して、解析するCSVデータと結合する
* 上記の対応により表示順が個別で指定できるようになる
* ウィンドウ枠の固定とオートフィルタが自動的を設定する

## 実装
### ファイル構成
```dir:フォルダ構成（変更なし）
+---csv2xlsx
    result_code.py　　　 # 共通の戻り値定義
    command_line.py　　　# コマンドライン解析用
    const_excel.py　　　 # Excel VBAの定数定義
    simple_excel.py　　　# 簡易Excel制御クラス
    main.py　　　        # メイン処理
```

### サンプルコード
<details><summary>全コード</summary>

#### result_code.py : 共通の戻り値定義
ソースコード間で戻り値を共通化するために作成。

```python:result_code.py
from enum import IntEnum

class ResultCode(IntEnum):
    """ 戻り値 """
    SUCCESS = 0
    ERROR = -1
```

#### command_line.py : コマンドライン解析用
コマンドラインの解析処理をメイン処理と分離するため別ファイルで処理した。
いつも似たようなコード書いているので、作成するアプリごとに編集して使いまわせればと思っています。

```python:command_line.py
import os
import argparse
from result_code import ResultCode

# カレントディレクトリ・ファイルパス
APP_PATH = os.getcwd()
FILE_PATH = os.path.dirname(__file__)


class CommandParam():
    """ コマンドパラメータクラス """
    path_csvfile: str
    path_output: str


def check_commandline(param: CommandParam) -> ResultCode:
    """
    コマンドラインチェック

    Args:
        param (class)       : (out)コマンドパラメータ

    returns:
        ResultCode.SUCCESS  : 正常
        ResultCode.ERROR    : 引数エラー
    """
    result = ResultCode.SUCCESS

    # コマンドライン パラメータ
    parser = argparse.ArgumentParser(
        description='Aout WinMerge Tool', add_help=True)
    parser.add_argument('arg1', help='CSVファイルのパス')
    parser.add_argument('arg2', help='出力先ファイル名＆パス')
    args = parser.parse_args()

    # パスチェック
    if not os.path.isfile(args.arg1):
        print(f'{args.arg1} が見つかりません')
        result = ResultCode.ERROR

    # コマンドラインパラメータの保存
    if result == ResultCode.SUCCESS:
        param.path_csvfile = args.arg1
        param.path_output = args.arg2

    return result
```

#### const_excel.py：Excel VBAの定数定義
Excel制御で使用する定数を定義する。下記のサイトを参考に必要な定義のみ抜き出しました。（地味に面倒な作業なので、とても助かりました）
・[Python pywin32(win32com) Excel 操作備忘録](https://qiita.com/feo52/items/150745ae0cc17cb5c866)

```python:const_excel.py
# region XlFileFormat定義：ファイル保存フォーマット
xlCSV = 6
xlHtml = 44
xlWorkbookDefault = 51
xlOpenXMLWorkbook = 51
xlOpenXMLWorkbookMacroEnabled = 52
xlWorkbookNormal = -4143
xlCurrentPlatformText = -4158
# endregion

# region XlYesNoGuess定義：Yes/No
xlGuess = 0
xlYes = 1
xlNo = 2
# endregion

# region XlSortOrder定義：ソート順
xlAscending = 1
xlDescending = 2
xlManual = -4135
# endregion

# region XlSortOrientation定義：ソート方向（列・行）
xlSortColumns = 1
xlSortRows = 2
# endregion

# region XlSortMethod定義：ソート条件：ふりがなの使用・不使用
xlPinYin = 1
xlStroke = 2
# endregion

# region XlDeleteShiftDirection, Excel Enum XlInsertShiftDirection
xlShiftUp = -4162
xlShiftDown = -4121
xlShiftToLeft = -4159
xlShiftToRight = -4161
# endregion
```

#### simple_excel.py：簡易Excel制御クラス
Excel制御する処理をクラス化しました。レベルアップ項目もあるので、少しずつ拡張できればと思っています。
ソート機能については、下記のサイトが参考にしつつトライ＆エラーで実現できました。
・[エクセルの神髄 - 第88回.並べ替え（Sort）](https://excel-ubara.com/excelvba1/EXCELVBA388.html)


```python:simple_excel.py
import os
import win32com.client
import csv2xlsx._dust._const_excel as xl
from result_code import ResultCode

# カレントディレクトリ・ファイルパス
APP_PATH = os.getcwd()
FILE_PATH = os.path.dirname(__file__)

# 定数定義
# region デフォルト定義
DEFAULT_VISIBLE = True                      # Excel表示
DEFAULT_FORCED_WRITE = False                # 上書き保存の強制有無
# endregion


class SimpleExcel:
    """
    Excel制御クラス
    """

    # 定数定義
    # region パラメータ定数
    AUTO_FIT = -1                           # 行・高さの自動調整
    MAX_COLUMNS = -1                        # 使用している列の最大値
    MAX_ROWS = -1                           # 使用している行の最大値
    NOTHING = 0                             # 指定しない
    # endregion

    # region コンストラクタ
    def __init__(self,
                 visibled: bool = DEFAULT_VISIBLE):
        """
        コンストラクタ
        """
        self.__xlapp = None
        self.__visibled = visibled
    # endregion

    # region デストラクタ
    def __del__(self):
        """
        デストラクタ
        """
        try:
            # ワークブックを閉じる
            if self.__wbook is not None:
                self.__wbook.Close(SaveChanges=False)

            # Excelを閉じる
            if self.__xlapp is not None:
                self.__xlapp.Quit()
        except Exception as e:
            # すでにExcel終了済みならエラー無視する
            if type(e).__name__ != 'com_error':
                print(f"終了処理に失敗しました\n{e}")
    # endregion

    # region プロパティ
    # region Excel表示On/Off
    @property
    def visibled(self) -> bool:
        """
        Excel表示On/Off
        """
        return self.__visibled

    @visibled.setter
    def visibled(self,
                 value: bool):
        """
        Excel表示On/Off

        Args:
            value               : (in) True = 表示On
        """
        self.__visibled = value
        if self.__xlapp is not None:
            self.__xlapp.Visible = value
    # endregion

    # region アクティブシートで使用されている列数
    @property
    def get_count_columns(self) -> int:
        """
        アクティブシートで使用されている列数
        """
        result = self.__wsheet.UsedRange.Columns(
            self.__wsheet.UsedRange.Columns.Count).Column
        return result
    # endregion

    # region アクティブシートで使用されている行数
    @property
    def get_count_rows(self) -> int:
        """
        アクティブシートで使用されている行数
        """
        result = self.__wsheet.UsedRange.Rows(
            self.__wsheet.UsedRange.Rows.Count).Row
        return result
    # endregion
    # endregion

    # region 公開メソッド
    # region Excelでファイルを開く
    def open(self,
             file_path: str) -> ResultCode:
        """
        Excelでファイルを開く

        Args:
            file_path           : (in) 読込むファイル名&パス

        returns:
            ResultCode.SUCCESS  : 正常
            ResultCode.ERROR    : オープンエラー
        """
        result = ResultCode.SUCCESS

        if not os.path.isfile(file_path):
            print(f"{file_path} が見つかりません")
            result = ResultCode.ERROR
        else:
            try:
                # Excelを起動してファイルを開く
                if self.__xlapp is None:
                    self.__xlapp = win32com.client.Dispatch(
                        'Excel.Application')
                    self.__xlapp.Visible = self.__visibled

                self.__wbook = self.__xlapp.Workbooks.Open(file_path)
                self.select_sheet(1)

            except Exception as e:
                print(f"ファイルオープンに失敗しました\n{e}")
                result = ResultCode.ERROR

        return result
    # endregion

    # region ワークブックを閉じる
    def close(self):
        """
        ワークブックを閉じる

        Args:
            なし

        returns:
            ResultCode.SUCCESS  : 正常
            ResultCode.ERROR    : クローズエラー（未定義）
        """
        result = ResultCode.SUCCESS

        if self.__wbook is not None:
            self.__wbook.Close(SaveChanges=False)

        return result
    # endregion

    # region 名前を付けて保存
    def save_as(self,
                file_path: str,
                format: xl = xl.xlOpenXMLWorkbook,
                forced_write: bool = DEFAULT_FORCED_WRITE) -> ResultCode:
        """
        名前を付けて保存

        Args:
            file_path           : (in) 保存するファイル名&パス
            format              : (in) 保存形式
            forced_write        : (in) True=強制的に上書きする

        returns:
            ResultCode.SUCCESS  : 正常
            ResultCode.ERROR    : 保存エラー
        """
        result = ResultCode.SUCCESS

        # Excelファイル(xlsx)形式で保存する
        if self.__wsheet is not None:
            try:
                if forced_write:
                    # 警告メッセージを非表示にする（強制的に上書きする）
                    self.__xlapp.DisplayAlerts = False

                # 名前を付けて保存する
                self.__wsheet.SaveAs(file_path, FileFormat=format)

                if forced_write:
                    # 警告メッセージ表示を元に戻す
                    self.__xlapp.DisplayAlerts = True

            except Exception as e:
                print(f"ファイルの書き込みに失敗しました\n{e}")
                result = ResultCode.ERROR

        return result
    # endregion

    # region シートを選択する
    def select_sheet(self,
                     name: any) -> any:
        """
        シートを選択する

        Args:
            name                : (in) シート名 or シート番号

        returns:
            シートオブジェクト
        """
        # シート名またはシート番号での指定
        self.__wsheet = self.__wbook.Worksheets(name)
        return self.__wsheet
    # endregion

    # region 列の並び替えをする
    def sort_column(self,
                    key1_row: int,
                    order1: int = xl.xlAscending) -> ResultCode:
        """
        列の並び替えをする

        Args:
            key1_row            : (in) 並べ替えのキーになる行番号（１～）
            order1              : (in) 並べ替え順序

        returns:
            ResultCode.SUCCESS  : 正常
            ResultCode.ERROR    : 並べ替えエラー（未定義）
        """
        result = ResultCode.SUCCESS

        # 並び替えキーのカラム文字作成(行指定）
        col_str = self.conv_r1c1_to_a1(key1_row, 1)
        range_key = self.__wsheet.Range(col_str)

        # 並び替え範囲のカラム文字作成(シートの使用領域全体）
        range_sort = self.covn_range_r1c1_to_a1(
            1, 1, self.get_count_rows, self.get_count_columns)

        # シートの並び替え
        self.__wsheet.Sort.SortFields.Clear
        self.__wsheet.Sort.SortFields.Add(Key=range_key, Order=order1)
        self.__wsheet.Sort.SetRange(self.__wsheet.Range(range_sort))
        self.__wsheet.Sort.Header = xl.xlYes
        self.__wsheet.Sort.Orientation = xl.xlSortRows
        self.__wsheet.Sort.MatchCase = False
        self.__wsheet.Sort.SortMethod = xl.xlPinYin
        self.__wsheet.Sort.Apply()

        return result
    # endregion

    # region カラム文字変換
    # region 列番号 → A1形式のカラム文字に変換
    def conv_colindex_to_addr(self,
                              column: int) -> str:
        """
        列番号 → A1形式のカラム文字に変換

        Args:
            column              : (in) 列番号

        returns:
            A1形式のカラム文字
        """
        return (self.__wsheet.Cells(1, column).Address).split("$")[1]
    # endregion

    # region R1C1指定 → A1形式に変換
    def conv_r1c1_to_a1(self,
                        row: int,
                        column: int) -> str:
        """
        R1C1指定 → A1形式に変換

        Args:
            row                 : (in) 行番号
            column              : (in) 列番号

        returns:
            A1形式
        """
        if row == self.NOTHING:
            # 列指定
            result = f'{self.conv_colindex_to_addr(column)}'
        elif column == self.NOTHING:
            # 行指定
            result = f'{row}'
        else:
            # 行＆列指定
            result = f'{self.conv_colindex_to_addr(column)}{row}'

        return result
    # endregion

    # region R1C1指定 → A1形式の範囲文字に変換
    def covn_range_r1c1_to_a1(self,
                              row1: int,
                              column1: int,
                              row2: int,
                              column2: int) -> str:
        """
        R1C1指定 → A1形式の範囲文字に変換

        Args:
            row1                : (in) 行番号(左上)
            column1             : (in) 列番号(左上)
            row2                : (in) 行番号(右下)
            column2             : (in) 列番号(右下)

        returns:
            A1形式の範囲文字
        """
        # パラメータ指定の場合、変換する
        row1 = self.__conv_param_rows(row1)
        row2 = self.__conv_param_rows(row2)
        column1 = self.__conv_param_columns(column1)
        column2 = self.__conv_param_columns(column2)

        return \
            f'{self.conv_r1c1_to_a1(row1, column1)}:' \
            f'{self.conv_r1c1_to_a1(row2, column2)}'
    # endregion
    # endregion

    # region シートをコピーする
    def sheet_copy(self,
                   sheet_base: any,
                   sheet_after: any) -> ResultCode:
        """
        シートをコピーする

        Args:
            sheet_base          : (in) コピー元のシート
            sheet_after         : (in) コピー先のシート名（またはシート番号）

        returns:
            ResultCode.SUCCESS  : 正常
            ResultCode.ERROR    : コピーエラー（未定義）
        """
        result = ResultCode.SUCCESS

        # シートのコピー
        # ※Beforeを省略すると、Afterも無効になるのか新規ブックが作成される
        sheet_base.Copy(
            Before=None, After=self.__wbook.Worksheets(sheet_after))

        return result
    # endregion

    # region ウィンドウ枠を固定する
    def freeze_window(self,
                      row: int,
                      column: int) -> ResultCode:
        """
        ウィンドウ枠を固定する

        Args:
            row                 : (in) 固定する行番号（NOTHING=指定しない）
            column              : (in) 固定する列番号（NOTHING=指定しない）

        returns:
            ResultCode.SUCCESS  : 正常
            ResultCode.ERROR    : コピーエラー（未定義）
        """
        result = ResultCode.SUCCESS

        # ウィンドウ枠を固定する
        self.__wsheet.Activate()
        range_freeze = self.__wsheet.Range(
            self.covn_range_r1c1_to_a1(row, column, row, column))
        range_freeze.Select()
        self.__wsheet.Parent.Windows(1).FreezePanes = True

        return result
    # endregion

    # region オートフィルタをセットする
    def set_auto_filter(self,
                        row: int) -> ResultCode:
        """
        オートフィルタをセットする

        Args:
            row                 : (in) 定義する行番号

        returns:
            ResultCode.SUCCESS  : 正常
            ResultCode.ERROR    : エラー（未定義）
        """
        result = ResultCode.SUCCESS

        self.__wsheet.Activate()
        range_filter = self.__wsheet.Range(
            self.covn_range_r1c1_to_a1(
                row, 1, self.get_count_rows, self.get_count_columns))
        range_filter.AutoFilter()

        return result
    # endregion

    # region 列の幅を変更する
    def change_column_size(self,
                           size: float,
                           column1: int,
                           column2: int):
        """
        列の幅を変更する

        Args:
            size                : (in) 列の幅
            column1             : (in) 列番号(開始)
            column2             : (in) 列番号(終了)

        returns:
            ResultCode.SUCCESS  : 正常
            ResultCode.ERROR    : エラー（未定義）
        """
        result = ResultCode.SUCCESS

        self.__wsheet.Activate()
        range_width = self.__wsheet.Range(
            self.covn_range_r1c1_to_a1(
                self.NOTHING, column1, self.NOTHING, column2))

        if size == self.AUTO_FIT:
            range_width.EntireColumn.AutoFit()
        else:
            range_width.ColumnWidth = size

        return result
    # endregion

    # region 行の高さを変更する
    def change_row_size(self,
                        size: float,
                        row1: int,
                        row2: int):
        """
        行の高さを変更する

        Args:
            size                : (in) 行の高さ
            row1                : (in) 行番号(開始)
            row2                : (in) 行番号(終了)

        returns:
            ResultCode.SUCCESS  : 正常
            ResultCode.ERROR    : エラー（未定義）
        """
        result = ResultCode.SUCCESS

        self.__wsheet.Activate()
        range_height = self.__wsheet.Range(
            self.covn_range_r1c1_to_a1(row1, self.NOTHING, row2, self.NOTHING))

        if size == self.AUTO_FIT:
            range_height.EntireRow.AutoFit()
        else:
            range_height.RowHeight = size

        return result
    # endregion
    # endregion

    # region 非公開メソッド
    # region 行番号のパラメータ変換
    def __conv_param_rows(self,
                          value) -> int:
        """
        行番号のパラメータ変換

        Args:
            value               : (in) 行番号orパラメータ指定

        returns:
            行番号
        """
        result = value
        if value == self.MAX_ROWS:
            result = self.get_count_rows
        return result
    # endregion

    # region 列番号のパラメータ変換
    def __conv_param_columns(self,
                             value) -> int:
        """
        列番号のパラメータ変換

        Args:
            value               : (in) 列番号orパラメータ指定

        returns:
            列番号
        """
        result = value
        if value == self.MAX_COLUMNS:
            result = self.get_count_columns
        return result
    # endregion
    # endregion
```

#### main.py：メイン処理
```python:main.py
import os
import command_line
from result_code import ResultCode
from command_line import CommandParam
from simple_excel import SimpleExcel, xl

# カレントディレクトリ・ファイルパス
APP_PATH = os.getcwd()
FILE_PATH = os.path.dirname(__file__)

# 定数定義
EXCEL_BASE_FILE = 'test_base.xlsx'
EXCEL_BASE_SHEET_NAME = 'データ名一覧'
EXCEL_CELL_MACRO_NUMBER = f'=IF(ISERROR(VLOOKUP(A$3, ' \
    f'{EXCEL_BASE_SHEET_NAME}!$A:$C, 3, FALSE)), "", ' \
    f'VLOOKUP(A$3, {EXCEL_BASE_SHEET_NAME}!$A:$C, 3, FALSE))'
EXCEL_CELL_MACRO_NAME = f'=IF(ISERROR(VLOOKUP(A$3, ' \
    f'{EXCEL_BASE_SHEET_NAME}!$A:$C, 2, FALSE)), "", ' \
    f'VLOOKUP(A$3, {EXCEL_BASE_SHEET_NAME}!$A:$C, 2, FALSE))'
EXCEL_COLUMN_SIZE = 12.0


def main():
    """
    CSVファイルを読込み、1行目をキーに並べ替えをして
    Excel形式(xlsx)でファイル保存するサンプル
    """
    # コマンドラインチェック
    param = CommandParam()
    ret = command_line.check_commandline(param)

    # CSVファイルを開く
    excel_csv = None
    if ret == ResultCode.SUCCESS:
        # インスタンス生成してファイルを開く
        excel_csv = SimpleExcel()
        ret = excel_csv.open(param.path_csvfile)

    # ベースのExcelファイルを開く
    excel_base = None
    if ret == ResultCode.SUCCESS:
        # インスタンス生成してファイルを開く
        path_base = os.path.join(APP_PATH, EXCEL_BASE_FILE)
        excel_base = SimpleExcel()
        ret = excel_base.open(path_base)

        # ベースファイルのシートをコピーする
        if ret == ResultCode.SUCCESS:
            wsheet_base = excel_base.select_sheet(EXCEL_BASE_SHEET_NAME)
            excel_csv.sheet_copy(wsheet_base, 1)

            # ベースファイルを閉じる
            excel_base.close()

    if ret == ResultCode.SUCCESS:
        # 先頭から2行分の空行を挿入する
        sheet_csv = excel_csv.select_sheet(1)
        sheet_csv.Rows(1).Insert(Shift=xl.xlShiftDown)
        sheet_csv.Rows(1).Insert(Shift=xl.xlShiftDown)

        # ソート順とデータ名のセルマクロを書き込む
        sheet_csv.Range("A1").Value = EXCEL_CELL_MACRO_NUMBER
        sheet_csv.Range("A2").Value = EXCEL_CELL_MACRO_NAME

        # 上記のマクロを横方向へコピーする
        range_dest = sheet_csv.Range(
            excel_csv.covn_range_r1c1_to_a1(1, 2, 2, excel_csv.MAX_COLUMNS))
        sheet_csv.Range("A1:A2").Copy(Destination=range_dest)

        # セルの文字を縮小して全体表示を設定する
        range_data_name = sheet_csv.Range(
            excel_csv.covn_range_r1c1_to_a1(2, 1, 2, excel_csv.MAX_COLUMNS))
        range_data_name.ShrinkToFit = True

    # 1行目をキーに列の並び替えをする
    if ret == ResultCode.SUCCESS:
        ret = excel_csv.sort_column(1, xl.xlDescending)

    # 列の幅を変更する
    if ret == ResultCode.SUCCESS:
        ret = excel_csv.change_column_size(
            EXCEL_COLUMN_SIZE, 1, excel_csv.MAX_COLUMNS)

    # 4行目でウィンドウ枠を固定する
    if ret == ResultCode.SUCCESS:
        ret = excel_csv.freeze_window(4, excel_csv.NOTHING)

    # 3行目にオートフィルタを追加する
    if ret == ResultCode.SUCCESS:
        ret = excel_csv.set_auto_filter(3)

    # Excelファイル(xlsx)形式で保存する
    if ret == ResultCode.SUCCESS:
        ret = excel_csv.save_as(param.path_output, forced_write=False)
        if ret == ResultCode.SUCCESS:
            print(f'{param.path_output} に保存しました')

    # 終了処理
    if excel_base is not None:
        del excel_base

    if excel_csv is not None:
        del excel_csv


if __name__ == "__main__":
    main()
```
</details>

### サンプルコードの詳細
#### シートのコピー

```python:main.py
...

# ベースのExcelファイルを開く
excel_base = None
if ret == ResultCode.SUCCESS:
    # インスタンス生成してファイルを開く
    path_base = os.path.join(APP_PATH, EXCEL_BASE_FILE)
    excel_base = SimpleExcel()
    ret = excel_base.open(path_base)                                 # (※１）

    # ベースファイルのシートをコピーする
    if ret == ResultCode.SUCCESS:
        wsheet_base = excel_base.select_sheet(EXCEL_BASE_SHEET_NAME) # (※２）
        excel_csv.sheet_copy(wsheet_base, 1)                         # (※３）

        # ベースファイルを閉じる
        excel_base.close()
        
...
```
(※１）事前に作っておいたコピー元のシートが含まれたExcelファイルを開く
(※２）シート名を指定してコピー元のシートを取得する
(※３）上記コピー元のシートと、コピー先のシート名を指定してコピーする。今回はCSVファイルを開いており、シート名が一定ではないためシート番号（1)を指定している

```python:simple_excel.py
...

# region シートをコピーする
def sheet_copy(self,
               sheet_base: any,
               sheet_after: any) -> ResultCode:
    """
    シートをコピーする

    Args:
        sheet_base          : (in) コピー元のシート
        sheet_after         : (in) コピー先のシート名（またはシート番号）

    returns:
        ResultCode.SUCCESS  : 正常
        ResultCode.ERROR    : コピーエラー（未定義）
    """
    result = ResultCode.SUCCESS

    # シートのコピー
    # ※Beforeを省略すると、Afterも無効になるのか新規ブックが作成される
    sheet_base.Copy(
        Before=None, After=self.__wbook.Worksheets(sheet_after))

    return result
# endregion

...
```
＜コピー元のシート＞.Copyメソッドを使ってシートをコピーしている。
ここで少し手間取ったのが、コメントに記載している通り、Before=Noneを定義しないと、新規ブックにシートが作られてしまったこと。
Copyメソッドに引数指定しないと新規ブックへシートコピーされる仕様のため、Afterだけ定義してBeforeなしの場合は、そのような動作してしまったのではないかと推測（真因は不明）。

#### ウィンドウ枠を固定する
```python:main.py
...

# 4行目でウィンドウ枠を固定する
if ret == ResultCode.SUCCESS:
    ret = excel_csv.freeze_window(4, -1)

...
```
行と列番号を指定してウィンドウ枠を固定できる。
今回は行のウィンドウ枠を固定したかったため、列番号は「-1」を指定している。

```python:simple_excel.py
...

# ウィンドウ枠を固定する
self.__wsheet.Activate()
range_freeze = self.__wsheet.Range(
    self.covn_range_r1c1_to_a1(row, column, row, column))
range_freeze.Select()
self.__wsheet.Parent.Windows(1).FreezePanes = True

...
```
ウィンドウ枠を固定する列＆行を Select()して、 FreezePanesプロパティを「True」に設定することでウィンドウ枠が固定される。

#### オートフィルタをセットする
```python:main.py
...

# 3行目にオートフィルタを追加する
if ret == ResultCode.SUCCESS:
    ret = excel_csv.set_auto_filter(3)

...
```
行番号を指定してオートフィルタを設定する。

```python:simple_excel.py
...

self.__wsheet.Activate()
range_filter = self.__wsheet.Range(
    self.covn_range_r1c1_to_a1(
        row, 1, self.get_count_rows, self.get_count_columns))
range_filter.AutoFilter()

...
```
オートフィルタを設定する行を選択して、AutoFilter()メソッドを呼び出すことでオートフィルタが設定される。

#### セルマクロの書込み、セルのコピー
セルの書込みなどは、アプリ側固有の処理のため、main.py側で直接期待した。
```python:main.py
...

# 先頭から2行分の空行を挿入する
sheet_csv = excel_csv.select_sheet(1)
sheet_csv.Rows(1).Insert(Shift=xl.xlShiftDown)
sheet_csv.Rows(1).Insert(Shift=xl.xlShiftDown)

# ソート順とデータ名のセルマクロを書き込む
sheet_csv.Range("A1").Value = EXCEL_CELL_MACRO_NUMBER
sheet_csv.Range("A2").Value = EXCEL_CELL_MACRO_NAME

# 上記のマクロを横方向へコピーする
max_columns = excel_csv.get_count_columns
range_dest = sheet_csv.Range(
    excel_csv.covn_range_r1c1_to_a1(1, 2, 2, max_columns))
sheet_csv.Range("A1:A2").Copy(Destination=range_dest)

...
```
Rows(1).Insert や Range("A1").Value など、VBAの記述方法が使用できる。