Option Explicit

'===========================================================
' ■部品_範囲設定_行可変 / SetRange_VariableRows
'   ・開始行から、指定した列の最終行までを、行数可変の範囲（Range）にして返す
'     → Returns a Range from the start row to the last row of the specified column (the row count varies).
'
'   ・最終行の取得と、範囲を作ることを、1つにまとめた部品（内部で GetLastRow と SetRange_Fixed を使う）
'     → Combines the last-row lookup and the range creation (uses GetLastRow and SetRange_Fixed inside).
'
'   ・開始行の下にデータがない場合は、Nothing を返す
'     → Returns Nothing if there is no data from the start row down.
'
'   ★ この部品は Sub（プロシージャ）ではなく「Function（関数）」で、Range を返す
'     → ★ This component is a Function (not a Sub) that returns a Range.
'===========================================================

'===========================================================
' ■区分 / Classification
'   ・基礎区分：応用 ／ 用途区分：取得 ／ 操作対象区分：範囲
'     → Base: application / Purpose: get / Target: range
'
' ■バージョン / Version
'   ・Version 1.0（2026-10-29）
'===========================================================

'===========================================================
' ■URL（WordPress部品ページ）
'   https://feelcodelab.com/vba_parts/setrange_variablerows/
'===========================================================

'===========================================================
' ■前提条件（AI誤動作防止の最重要項目） / Preconditions
'   ★【最重要】ブック（vWb）とシート名（vSheetName）は、必ず引数で指定する（省略できない）
'     → ThisWorkbook ＝ このマクロが入っているブック ／ ActiveWorkbook ＝ いまアクティブなブック
'     → ファイルを開く部品の後は、ActiveWorkbook が、開いたファイルに切り替わる。
'       開いた直後に、ActiveWorkbook を変数へ控えて、その変数を渡す
'     → ★ [MOST IMPORTANT] The workbook (vWb) and the sheet name (vSheetName) must always be specified
'        (they cannot be omitted). After the "open file" component runs, ActiveWorkbook switches to the opened
'        file, so keep it in a variable right after opening and pass that variable.
'
'   ★【最重要】戻り値は Range（オブジェクト）なので、受け取るときは必ず Set を使う
'     → ★ [MOST IMPORTANT] The return value is an object, so the caller must receive it with Set.
'
'   ★【最重要】データがないときは Nothing が返るので、受け取った直後に Nothing かどうかを確認する
'     → Nothing のまま後続の部品へ渡すと、範囲消去などが、Nothing のエラー（1014）で止まる
'     → ★ [MOST IMPORTANT] Nothing is returned when there is no data, so check for Nothing right after
'        receiving the result; passing Nothing on stops later components with error 1014.
'
'   ・最終行は、キー列（vKeyCol）の、最後にデータが入っている行で決まる
'     → 途中に空白行があっても、キー列の最後のデータの行までが範囲になる
'     → キー列は、空白のない列（番号や名前など）を指定する
'     → The last row is the last row with data in the key column (vKeyCol); blank rows in between are included.
'        Specify a column without blanks (such as an ID or a name) as the key column.
'
'   ・開始行が 1 行目で、シートが空の場合は、GetLastRow が 1 を返すため、Nothing ではなく、1 行の範囲が返る
'     → 見出し行がある表では、開始行を 2 以上にして使う
'     → If the start row is row 1 and the sheet is empty, GetLastRow returns 1, so a one-row range is returned
'        instead of Nothing; for tables with a header, use a start row of 2 or more.
'
'   ・開始行・開始列・終了列・キー列は、すべて 1 以上の整数で、終了列は開始列以上であること
'     → 設定の誤りは、Nothing や False ではなく、原因を示すエラーで止まる（Err.Raise）
'     → データがないときでも、設定の誤りは、必ず検出する
'     → All numbers must be integers of 1 or more, and the end column must not be before the start column.
'        A setting mistake stops with an error naming the cause (Err.Raise), even when there is no data.
'
'   ・中で呼んでいる部品（最終行取得・範囲設定_固定）が出すエラーは、この部品の名前を付けて、出し直される
'     → エラー番号は、そのまま変わらない。エラーの説明の先頭に、
'       「部品_範囲設定_行可変 の中（呼んでいた部品名）：」が付く
'     → Errors raised by the components called inside are raised again with this component's name;
'        the error number does not change.
'
'   ・最終行から、但し書きなどの行を除きたいときは、この部品ではなく、
'     GetLastRow の結果から行を引いて SetRange_Fixed に渡す
'     → To exclude trailing note rows, use GetLastRow and SetRange_Fixed directly instead of this component.
'
'   ・Offset / Find / Resize / With は禁止
'     → Offset, Find, Resize, and With statements are prohibited.
'===========================================================

'===========================================================
' ■依存関係（部品同士） / Component Dependencies
'   ・f_GetLastRow（部品_最終行取得）が必要
'     → Requires f_GetLastRow (GetLastRow component).
'       ・キー列の最終行の取得に使用
'         → Used to get the last row of the key column.
'
'   ・f_SetRange_Fixed（部品_範囲設定_固定）が必要
'     → Requires f_SetRange_Fixed (SetRange_Fixed component).
'       ・範囲を作ることと、行番号・列番号の確認に使用
'         → Used to make the range and to check the row and column numbers.
'
'   ・組み合わせ：f_SetRange_VariableRows → f_ClearRange_Contents / f_ClearRange_All など
'     （作った範囲を、範囲を操作する部品へ渡す）
'     → Typical combination: [SetRange_VariableRows] -> [ClearRange_Contents], [ClearRange_All], etc.
'===========================================================

'===========================================================
' ■依存関係（シート・表） / Sheet Dependencies
'   ・特になし（マッピング表等は不要）
'     → No sheet-level dependencies (no mapping table etc. required).
'===========================================================

'===========================================================
' ■技術解説 / Technical Notes
'
'   ・ブックとシート名を、必ず引数で受け取る理由
'       → シート名だけでは、どのブックのシートか決まらず、Worksheets("名前") は、そのときの
'          ActiveWorkbook のシートを指してしまう。ファイルを開くと ActiveWorkbook が切り替わるため、
'          取り違えが起きやすい。ブックを必ず引数で渡す形にすると、呼び出しの行に
'          「どのブックの、どのシートか」が書かれ、読むだけで意図が分かる。
'       → A sheet name alone does not identify the workbook, and Worksheets("name") would refer to the current
'          ActiveWorkbook, which switches when a file is opened. Always passing the workbook makes the call
'          itself say "which sheet of which workbook".
'
'   ・データがないときに、エラーではなく Nothing を返す理由
'       → 見出しだけで、データがない状態は、設定の誤りではなく、普通に起こる結果のため。
'          呼び出し側が、Nothing かどうかで分岐できるようにしている。
'       → No data below the header is a normal outcome, not a setting mistake, so Nothing is returned and the
'          caller can branch on it.
'
'   ・データがなくても、設定の誤りを検出する理由
'       → データがあるときだけ検出されると、空のシートで動かしたときだけ誤りが隠れ、
'          データが入った後に、突然エラーで止まるため。
'          データがない場合も、検証のために、SetRange_Fixed を一度呼んでから、Nothing を返している。
'       → Otherwise a mistake would stay hidden on an empty sheet and suddenly stop the macro once data is
'          entered; SetRange_Fixed is called once for validation before Nothing is returned.
'
'   ・On Error GoTo で、中の部品のエラーを、出し直す理由
'       → 中の部品（最終行取得・範囲設定_固定）のエラーを、そのまま通すと、どの部品を呼んだときのエラーかが
'          分かりにくい。そこで、この部品が受けて、エラー番号はそのままに、この部品の名前と、呼んでいた部品名を
'          付けて出し直す。握りつぶさず、必ず呼び出し側へ届ける。実行時の失敗（False）は、この部品にはない。
'
'   ・呼んでいた部品名を、変数（stepName）に控える理由
'       → エラーを出した部品が Err.Raise で付けた名前（Err.Source）は、VBA や Excel が出した実行時エラーでは、
'          部品名にならない。呼ぶ直前に、部品名を変数へ入れておけば、どの場合でも、どの部品を呼んでいたかが分かる。
'       → Err.Source is not a component name for VBA/Excel run-time errors; keeping the name of the component being
'          called in a variable works in every case.
'       → Errors from the components called inside would be hard to place if passed through as they are; this
'          component catches them and raises them again with the same number plus its own name and the origin.
'
'   ・開始行・開始列・終了列・キー列の番号の誤りを、この部品でも、先に確認する理由
'       → 中の部品に任せると、呼び出し側が渡していない「終了行」の値が、メッセージに出てしまい、分かりにくいため。
'          この部品の引数の言葉で、確認する（1007・1008）。
'       → Left to the inner component, the message would show an end row the caller never passed.
'
'   ・Offset / Find / Resize / With を禁止する理由
'       → 生成AIが誤って不正な範囲操作を行う可能性が高く、行数可変の処理で誤動作を引き起こしやすいため。
'       → These statements often cause misaligned or ambiguous range operations when generated by AI.
'
'   ・本部品が「応用」である理由
'       → 土台の部品（最終行取得・範囲設定_固定）を参照するため。
'       → It references foundation components (GetLastRow and SetRange_Fixed).
'===========================================================

'---------------------------------------------
' ■引数（型＋意味） / Arguments
'   vWb        : 対象のブック（Workbook）※必須（省略できない）
'                → Target workbook (Workbook); required.
'
'   vSheetName : 範囲を作るシートのシート名（String）
'                → Name of the sheet in which the range is made (String).
'
'   vStartRow  : 開始行の行番号（Long）
'                → Start row number (Long).
'
'   vStartCol  : 開始列の列番号（Long）
'                → Start column number (Long).
'
'   vEndCol    : 終了列の列番号（Long）
'                → End column number (Long).
'
'   vKeyCol    : 最終行を判定するキー列の列番号（Long）
'                → Column number of the key column that determines the last row (Long).
'---------------------------------------------

'---------------------------------------------
' ■戻り値（型＋意味） / Return Value
'   Range : 開始行から、キー列の最終行まで、開始列から終了列までの範囲
'           開始行の下にデータがない場合は Nothing
'           → The range from the start row to the last row of the key column, from the start to the end column;
'             Nothing if there is no data from the start row down.
'---------------------------------------------

'---------------------------------------------
' ■エラー / Errors (Err.Raise)
'   ・vbObjectError + 1010：ブック（vWb）が Nothing
'   ・vbObjectError + 1011：シート（vSheetName）が、ブックに存在しない
'   ・vbObjectError + 1007：開始行・開始列・終了列・キー列のどれかが 1 未満
'   ・vbObjectError + 1008：終了列が開始列より小さい
'   ・vbObjectError + 1009：終了列またはキー列が、シートの上限（列数）を超えている
'   （1007・1008 は、この部品が止める。ほかは、依存先の部品が止め、この部品の名前を付けて出し直す）
'---------------------------------------------

'===========================================================
' ■禁止事項（AI向け） / Prohibitions
'   ・ブックとシート名を省略することの禁止（必ず引数で指定する）
'     → Do not omit the workbook and the sheet name; always specify them.
'
'   ・戻り値を Set なしで受け取ることの禁止
'     → Do not receive the return value without Set.
'
'   ・Nothing かどうかを確認せずに、戻り値を後続の部品へ渡すことの禁止
'     → Do not pass the return value to the following components without checking for Nothing.
'
'   ・空白のある列を、キー列に指定することの禁止（最終行が、途中までになる）
'     → Do not specify a column with blanks as the key column (the last row would stop short).
'
'   ・セル参照の直書き禁止（Range("A1") など）
'     → Hard-coded cell references are prohibited; use Cells with row/column numbers.
'
'   ・Offset禁止
'     → Offset may cause misaligned row calculations.
'
'   ・Find禁止
'     → Find leads to unpredictable search behavior.
'
'   ・Resize禁止
'     → Resize may unintentionally expand ranges.
'
'   ・With禁止
'     → With blocks cause ambiguous object references.
'===========================================================

'===========================================================
' ■使用例（サンプルコード） / Example
'   ＜例1：見出し（2行目）の下から、最終行までの A列〜B列 / Example 1: from below the header (row 2) to the last row, columns A-B＞
'   Dim rng As Range
'
'   Set rng = 部品_範囲設定_行可変(ThisWorkbook, "転記元", 3, 1, 2, 1)   '3行目から、1列目の最終行まで / row 3 to the last row of column 1
'   If rng Is Nothing Then
'       MsgBox "データがありません"
'       Exit Sub
'   End If
'
'   ＜例2：転記先の表データを消す / Example 2: clear the data of a destination table＞
'   Set rng = 部品_範囲設定_行可変(ThisWorkbook, "転記先", 3, 1, 4, 1)   'A3〜D列の最終行 / A3 to the last row of column D
'   If Not rng Is Nothing Then
'       If 部品_範囲消去_値のみ(rng) = False Then MsgBox "消去に失敗しました": Exit Sub
'   End If
'
'   ＜例3：但し書きの2行を除く場合は、この部品を使わない / Example 3: to exclude 2 trailing note rows, do not use this component＞
'   lastRow = 部品_最終行取得(ThisWorkbook, "帳票", 1)
'   lastRow = lastRow - 2
'   Set rng = 部品_範囲設定_固定(ThisWorkbook, "帳票", 3, 1, lastRow, 5)
'===========================================================

'===========================================================
' ■ラッパー関数（日本語名） / Wrapper Function
'   ・辞書の日本語名で呼び出すための窓口
'     → Entry point callable by the Japanese dictionary name.
'
'   ・内部では英語名の本体処理を呼び出し、結果（Range）をそのまま返す
'     → Internally calls the English-named core function and returns its result (Range) as it is.
'===========================================================
Function 部品_範囲設定_行可変( _
        ByVal vWb As Workbook, _
        ByVal vSheetName As String, _
        ByVal vStartRow As Long, _
        ByVal vStartCol As Long, _
        ByVal vEndCol As Long, _
        ByVal vKeyCol As Long) As Range

    '本体処理（英語名）を呼び出し、結果をそのまま返す / Call core function and return its result
    Set 部品_範囲設定_行可変 = f_SetRange_VariableRows(vWb, vSheetName, vStartRow, vStartCol, vEndCol, vKeyCol)

End Function

'===========================================================
' ■本体処理（英語名） / Core Function
'   ・キー列の最終行を取得し、開始行から最終行までの範囲（Range）を作って返す
'     → Gets the last row of the key column and returns the Range from the start row to that row.
'
'   ・開始行の下にデータがない場合は、Nothing を返す
'     → Returns Nothing if there is no data from the start row down.
'===========================================================
Function f_SetRange_VariableRows( _
        ByVal vWb As Workbook, _
        ByVal vSheetName As String, _
        ByVal vStartRow As Long, _
        ByVal vStartCol As Long, _
        ByVal vEndCol As Long, _
        ByVal vKeyCol As Long) As Range

    Dim lastRow As Long
    Dim endRow As Long
    Dim rng As Range
    Dim errNum As Long
    Dim stepName As String
    Dim errDesc As String

    Set f_SetRange_VariableRows = Nothing

    '---------------------------------------------
    ' ■番号の誤りを、この部品の引数の言葉で確認（誤りの場合はエラーで止める）
    '   / Check the numbers in this component's own words (stop with an error if wrong)
    '---------------------------------------------
    If vStartRow < 1 Or vStartCol < 1 Or vEndCol < 1 Or vKeyCol < 1 Then
        Err.Raise vbObjectError + 1007, "f_SetRange_VariableRows", _
                  "行番号・列番号は 1 以上を指定してください" & _
                  "（開始行=" & vStartRow & "、開始列=" & vStartCol & _
                  "、終了列=" & vEndCol & "、キー列=" & vKeyCol & "）。"
    End If

    If vEndCol < vStartCol Then
        Err.Raise vbObjectError + 1008, "f_SetRange_VariableRows", _
                  "終了列が、開始列より前になっています（開始列=" & vStartCol & "、終了列=" & vEndCol & "）。"
    End If

    '---------------------------------------------
    ' ■中の部品のエラーは、この部品の名前を付けて、出し直す（ErrWrap へ）
    '   / Errors from the inner components are raised again with this component's name (to ErrWrap)
    '---------------------------------------------
    On Error GoTo ErrWrap

    '---------------------------------------------
    ' ■キー列の最終行を取得（ブック・シート・列番号の誤りは、GetLastRow が、エラーで止める）
    '   / Get the last row of the key column (GetLastRow stops with an error on a wrong workbook, sheet or column)
    '---------------------------------------------
    stepName = "最終行取得"
    lastRow = f_GetLastRow(vWb, vSheetName, vKeyCol)

    '---------------------------------------------
    ' ■範囲を作る（行番号・列番号の誤りは、SetRange_Fixed が、エラーで止める）
    '   データがないときも、設定の誤りを検出するため、開始行を終了行として、一度作る
    '   / Make the range (SetRange_Fixed stops with an error on wrong numbers); even with no data it is made once
    '     with the start row as the end row, so that setting mistakes are always detected
    '---------------------------------------------
    endRow = lastRow
    If endRow < vStartRow Then endRow = vStartRow

    stepName = "範囲設定_固定"
    Set rng = f_SetRange_Fixed(vWb, vSheetName, vStartRow, vStartCol, endRow, vEndCol)

    '---------------------------------------------
    ' ■データがあれば、範囲を返す（開始行の下にデータがなければ、Nothing のまま）
    '   / Return the range if there is data (Nothing if there is no data from the start row down)
    '---------------------------------------------
    If lastRow >= vStartRow Then Set f_SetRange_VariableRows = rng
    Exit Function

ErrWrap:
    errNum = Err.Number
    errDesc = Err.Description
    Err.Raise errNum, "f_SetRange_VariableRows", _
              "部品_範囲設定_行可変 の中（" & stepName & "）：" & errDesc

End Function
