[[20111127110128]] 『VBA教えてください』(あちゃこ) ページの最後に飛ぶ

[ 初めての方へ | 一覧(最新更新順) | 全文検索 | 過去ログ ]

 

 『VBA教えてください』(あちゃこ)

 下記のコードは、デスクトップにあるmというcsvファイル(3万行)から必要な項目だけを
 集計ファイル、Sheet1のABC列に貼り付け、F列以降で集計をします。
 ABC列は3万行あり私の環境では処理時間は2.2秒です。(Excel2003でXP)計算式は
 は先生方の指導をいただき大幅に短縮できるようになりましたが,まだ他でまだ短縮の
 余地はありませんか?

 質問1
 CSVファイルを開く時に時間がかかるのが気になりますが、短縮の方法はありませんか?

 質問2
 宣言の位置宣言の仕方は間違っていませんか?(まだはっきり理解できていません)

 質問3
 所々に自動記録が入っていますが、コードを簡素化できませんか?

 質問4
 その他ご気付きの点、ご意見ご指導お願いいたします。

集計ファイルSheet1

    A      B       C      D    E     F       G    H    I ...........Z      
 1 型式  日付け   個数                      AA    AB   AC
 2 AA    11/2   20              11/1     
 3 AC    11/4   10              11/2
 4 AC    11/4      40              11/3 
 5 AA    11/1      30              11/4
 6 AC    11/6      20              11/5
 7 AB    11/6      10              11/6 

 Sub test1()

 myTime = Timer
     ChDir "C:\Documents and Settings\xxx\デスクトップ"
    Workbooks.Open Filename:="C:\Documents and Settings\xxx\デスク
 トップ\m.csv"

    Dim shtA As Worksheet, shtB As Worksheet
    Set shtA = Workbooks("m.CSV").Worksheets("m")
    Set shtB = Workbooks("集計.xls").Worksheets("Sheet1")
    Dim tbl    As Variant
    Dim Dat()  As Double
    Dim buf    As String
    Dim i      As Long
    Dim j      As Long

     Windows("集計.xls").Activate
    Columns("a:c").Select
    Selection.ClearContents

     With shtA
            .Columns("r").Copy shtB.Columns(1)
            .Columns("o").Copy shtB.Columns(2)
            .Columns("n").Copy shtB.Columns(3)

    Windows("m.csv").Activate
    ActiveWindow.Close

     Columns("B:B").Select
    Selection.TextToColumns Destination:=Range("B1"), DataType:=xlDelimited, _
        TextQualifier:=xlDoubleQuote, ConsecutiveDelimiter:=False, Tab:=True, _
        Semicolon:=False, Comma:=False, Space:=False, Other:=False, FieldInfo _
        :=Array(1, 5), TrailingMinusNumbers:=True

'******************:

  tbl = Worksheets("Sheet1").Range("A1").CurrentRegion.Value
  With CreateObject("Scripting.Dictionary")

    For i = 2 To UBound(tbl, 1)
      buf = Format(tbl(i, 2), "yyyymmdd") & vbTab & tbl(i, 1)
      .Item(buf) = .Item(buf) + tbl(i, 3)
    Next i
    With Worksheets("Sheet1").Range("F1").CurrentRegion
      tbl = .Value

      ReDim Dat(1 To .Rows.Count - 1, 1 To .Columns.Count - 1)
    End With
    For i = 2 To UBound(tbl, 1)
      For j = 2 To UBound(tbl, 2)
        Dat(i - 1, j - 1) = .Item(Format(tbl(i, 1), "yyyymmdd") & vbTab & tbl(1, j))
      Next j
    Next i
  End With
  Worksheets("Sheet1").Range("G2").Resize(UBound(Dat, 1), UBound(Dat, 2)).Value = Dat
MsgBox Format(Timer - myTime, "#,##0.00") & "秒かかりました。"
 End With
End Sub


 要件そのものとコードの内容はまだ読んでいない。
コーディングという観点でのみ、アップされたコードを少しお化粧直し。

 With/End With は大変に有効な記述だけど
・あまり、間隔をあけず、必要なスコープのみに。
・別途、変数規定をしているなら、それを使うということも考慮。

 シート修飾をしたり、しなかったり。これはあまり感心しないね。
コーディングは統一しておこう。

 変数は、それを使う前に記述すれば構文的にはOKだえど、プロシジャの先頭で
全てを規定したほうがわかりやすい。

 Sub test1()
    Dim shtA As Worksheet, shtB As Worksheet
    Dim tbl    As Variant
    Dim Dat()  As Double
    Dim buf    As String
    Dim i      As Long
    Dim j      As Long
    Dim myTime As Double
    Dim dic As Object

    myTime = Timer

    With CreateObject("WScript.Shell")
        Workbooks.Open .SpecialFolders("Desktop") & "\m.csv"
    End With

    Set shtA = Workbooks("m.CSV").Worksheets("m")
    Set shtB = Workbooks("集計.xls").Worksheets("Sheet1")
    Set dic = CreateObject("Scripting.Dictionary")

    shtB.Columns("a:c").ClearContents

    With shtA
        .Columns("r").Copy shtB.Columns(1)
        .Columns("o").Copy shtB.Columns(2)
        .Columns("n").Copy shtB.Columns(3)
    End With

    shtA.Parent.Close savechanges:=False

    shtB.Parent.Activate
    shtB.Select (Selectは好きじゃないけど既存の後続のコードをそのまま使うために)

    Columns("B:B").TextToColumns Destination:=Range("B1"), DataType:=xlDelimited, _
        TextQualifier:=xlDoubleQuote, ConsecutiveDelimiter:=False, Tab:=True, _
        Semicolon:=False, Comma:=False, Space:=False, Other:=False, FieldInfo _
        :=Array(1, 5), TrailingMinusNumbers:=True
    '******************:

    tbl = Range("A1").CurrentRegion.Value

    For i = 2 To UBound(tbl, 1)
        buf = Format(tbl(i, 2), "yyyymmdd") & vbTab & tbl(i, 1)
        dic.Item(buf) = dic.Item(buf) + tbl(i, 3)
    Next i

    With Range("F1").CurrentRegion
        tbl = .Value
        ReDim Dat(1 To .Rows.Count - 1, 1 To .Columns.Count - 1)
    End With

    For i = 2 To UBound(tbl, 1)
        For j = 2 To UBound(tbl, 2)
            Dat(i - 1, j - 1) = dic.Item(Format(tbl(i, 1), "yyyymmdd") & vbTab & tbl(1, j))
        Next j
    Next i

    Range("G2").Resize(UBound(Dat, 1), UBound(Dat, 2)).Value = Dat

    Set shtA = Nothing
    Set shtB = Nothing
    Set dic = Nothing

    MsgBox Format(Timer - myTime, "#,##0.00") & "秒かかりました。"

 End Sub


 ちょっと質問

 ・集計ファイルSheet1 の F列 および G1以降の1行目は、あらかじめ日付や型式がセットされている?
 ・Columns("B:B").TextToColumns ・・・・ これは、具体的には何をしようとしている?

 追記) こちらのcsvファイルのレイアウトがおかしいのかもしれないんだけど
    アップされたコードで実行したとき、Dictionaryのキーマッチングはうまくいってる?

 (ぶらっと)

 ぶらっとさんありがとうございます。
 With CreateObject("WScript.Shell")
        Workbooks.Open .SpecialFolders("Desktop") & "\m.csv"
 ファイルの開き方、閉じ方がよくわかりました。
 宣言がよくわからず、学習したいとおもいます。

 ご質問の
 集計ファイルSheet1 の F列 および G1以降の1行目は、あらかじめ日付や型式
 があらかじめ日付や型式がセットされている?

 F列、G列ともにあらかじめセットしています。

 ・Columns("B:B").TextToColumns ・・・・ これは、具体的には何をしようとしてい
 る?

 日付けが文字列で表記されているのでデータの区切り位置でシリアル値に変えるために
 行いました。
 こちらのcsvファイルのレイアウトがおかしいのかもしれないんだけど
 アップされたコードで実行したとき、Dictionaryのキーマッチングはうまくい
 ってる?

 実行しても異常はありません。うまくいきました。
 (あちゃこ)


 TextToColumns については了解。
Dictionaryキーのマッチングについては、こちらの理解が間違っているんだろうね。

 で、本題の処理効率。
csvから抽出した膨大なデータは、どうしたって処理しなきゃいけない訳なので
・もし、csv側のデータに、過去日付というか、今回分析しようとするF列の日付範囲以外のものが数多く存在するとすれば
 抽出する日付のFrom/Toを与えてフィルターオプションで抜き出し、処理するデータを、少しでも少なくするということが
 効果ありかも。From日付はF2の値、To日付はF列最終行の値 とか。
・もう1つ、現在は
 取り込んだデータを日付+型式でDictionaryに集約
  作表するG2起点の領域の各要素それぞれにDictionaryから値を抽出

 こんな構成だね。

 これを取り込んだデータを、直接、G2起点の領域の各要素に足し込めば、取り込んだデータの処理が終わった時点で
配列も完成しているということになり、少しは処理時間が短縮されるかな?

 (ぶらっと)


 csvから抽出は不要な日付けがたくさん入っています。
 「抽出する日付のFrom/Toを与えてフィルターオプション」やり方を教えていただけ
 ませんか?
(あちゃこ)

 >csvから抽出は不要な日付けがたくさん入っています。

 これは、
[[20111101090418]]

 ここで提示したADOを使えば、可能です。
 いえ、Sqlの指定によっては、最終的な結果まで表示することもできますよ!!

 再度、以下に投稿しますから検討してください。

 新規ブックにて、

 標準モジュール(Module1)にサンプルCSV作成プロシジャー群

 Option Explicit
 Sub mk_csv_file()
    Dim flno As Long
    Dim dat As String
    Dim g0 As Long
    flno = FreeFile
    Open ThisWorkbook.path & "\file.csv" For Output As #flno
    dat = mk_header(95)
    Print #flno, dat
    For g0 = 1 To 30000
       dat = mk_data(95)
       Print #flno, dat
    Next
    Close #flno
 End Sub
 Function mk_header(num As Long) As Variant
    Dim g0 As Long
    ReDim myarray(1 To num)
    For g0 = LBound(myarray) To UBound(myarray)
       myarray(g0) = "項目" & g0
    Next
    mk_header = Join(myarray, ",")

 End Function
 Function mk_data(num As Long) As Variant
    Dim g0 As Long
    ReDim myarray(1 To num)
    For g0 = LBound(myarray) To UBound(myarray)
       If g0 = 18 Then
          myarray(g0) = Chr(Int(Rnd() * 26) + 65)
       ElseIf g0 = 15 Then
          myarray(g0) = Format(CDate(#11/1/2011# + Int(Rnd * 60)), "yyyy/m/d")
       ElseIf g0 = 14 Then
          myarray(g0) = Int(Rnd * 50) + 1
       Else
          myarray(g0) = Int(Rnd * 1000) + 1
       End If
    Next
    mk_data = Join(myarray, ",")
 End Function

一度、適当なフォルダに必ず、保存してから、上記のmk_csv_fileを実行すること。

 項目1から項目95列までデータがあり、30000件あるファイルです。

 尚、この項目18が、型式 項目15が日付 項目14が個数にあたるデータが入っています。

 上記のmk_csv_fileを実行すると、日付(項目15)が2011/11/1〜2011/12/30の
 データがランダムに作成されます。形式は、A〜Z、個数は、適当な数値が
 あてられます。その他の列は、ダミーの数値が作成されます。
 作成されたら、メモ帳辺りで中身を確認してください。

 上記のプロシジャーにて、サンプルCSVファイルが、マクロのがあるブックと同じフォルダ上に作成されます(file.csv)。

 次に上記で作成されたCSVファイルの抽出マクロ。

 別の標準モジュール(Module2)に ADO関連プロシジャー群

 '===============================================================
 Option Explicit
 '=============================================================
 Private cn As Object
 '=============================================================
 Function open_ado_text(path As String) As Long 'adoでテキストにアクセス
    On Error Resume Next
    Dim link_opt As String
    Set cn = CreateObject("adodb.connection")
    link_opt = "Driver={Microsoft Text Driver (*.txt; *.csv)};" & _
                 "DBQ=" & path & ";" & "ReadOnly=0"

    cn.Open link_opt
    open_ado_text = Err.Number
    On Error GoTo 0
 End Function
 '=============================================================
 Sub close_ado()    'クローズ
    On Error Resume Next
    cn.Close
    On Error GoTo 0
 End Sub
 '=============================================================
 Function exec_sql(sql_str, rs As Object) As Long 'Sqlの実行
    On Error Resume Next
    Set rs = cn.Execute(sql_str)
    exec_sql = Err.Number
    If Err.Number <> 0 Then MsgBox Err.Description
    On Error GoTo 0
 End Function
 '==========================================================================
 Function mk_schema_ini(path As String, dat() As String) As Long 
 'schema.iniの作成
    On Error GoTo err_mk_schema_ini
    Dim fno As Long
    Dim didx As Long
    mk_schema_ini = 0
    fno = FreeFile()
    Open path & "\schema.ini" For Output As #fno
    For didx = LBound(dat()) To UBound(dat())
       Print #fno, dat(didx)
       Next
    Close #fno
 ret_mk_schema_ini:
    On Error GoTo 0
    Exit Function
 err_mk_schema_ini:
    MsgBox Err.Description
    mk_schema_ini = Err.Number
    Resume ret_mk_schema_ini
 End Function
 '=============================================================
 Function del_schema_ini(path As String) 'schema_iniの削除
    On Error Resume Next
    Kill path & "\schema.ini"
    On Error GoTo 0
 End Function

 更に別の標準のジュール(Module3)にCSVファイル読み込みコード

 '=====================================================================
Option Explicit
Sub main()
    Dim ret As Long
    Dim g0 As Long
    Dim dat(1 To 99) As String
    Dim rs As Object
    Dim ans As Variant
    Columns("a:cm").Clear
    dat(1) = "[file.csv]"
    dat(2) = "ColNameHeader = true"
    dat(3) = "CharacterSet = oem"
    dat(4) = "Format = CSVDelimited"
    dat(5) = "Col1 = 項目18 char width 255"
    dat(6) = "Col1 = 項目15 char width 255"
    For g0 = 5 To 95
        If g0 = 22 Then
           dat(g0) = "Col" & (g0 - 4) & "=項目" & (g0 - 4) & " Char Width 255"
        ElseIf g0 = 19 Then
           dat(g0) = "Col" & (g0 - 4) & "=項目" & (g0 - 4) & " DateTime"
        Else
           dat(g0) = "Col" & (g0 - 4) & "=項目" & (g0 - 4) & " LONG"
        End If
    Next
    Call mk_schema_ini(ThisWorkbook.path, dat())
    ret = open_ado_text(ThisWorkbook.path)
    If ret = 0 Then
       ret = exec_sql("select 項目18,項目15,項目14 from file.csv where 項目15 between #2011/11/1# and  #2011/11/30#;", rs)
      If ret = 0 Then
          With ActiveSheet
             .Range("a:c").Clear
             .Range("a1:c1").Value = Array("型式", "日付", "個数")
             .Range("a2").CopyFromRecordset rs
          End With
          rs.Close
       Else
          MsgBox Error(ret)
          End If
       close_ado
       End If
    Call del_schema_ini(ThisWorkbook.path)
    Erase dat()
 End Sub

 これで日付が2011/11/1から2011/11/30のデータが読み込まれます。

 SQLを使えば、いきなり結果を表示することも出来ますが、今回は
 CSVファイルの抽出までに留めました。

 前回は、ご返事が頂けませんでしたので、これでADOに関する投稿は最後にしますが、
 ここでは、使えなくても覚えておくと便利ですよ!!

 ichinose


 やっぱりADO使った方が速いんだろうな?

 Splitで分割するので、CSVがダブルクォーツで括られて居る様な物では無くて
 日付がDateValueでシリアル値に成るなら
 CSVをBookで開かずOpenステートメントを使って読んでいます

 Option Explicit

 Public Sub Sample_2()

    Dim i As Long
    Dim j As Long
    Dim lngRows As Long
    Dim lngColumns As Long
    Dim dicIndex As Object
    Dim shtB As Worksheet
    Dim rngList As Range
    Dim rngResult As Range
    Dim strPath As String
    Dim vntData() As Variant
    Dim vntResult() As Variant
    Dim vntColKeys As Variant
    Dim vntRowKeys As Variant
    Dim strKey As String
    Dim strPrompt As String

    Dim myTime As Single

    myTime = Timer

    Set shtB = Workbooks("集計.xls").Worksheets("Sheet1")

    Set rngList = shtB.Range("A1")
    Set rngResult = shtB.Range("F1")

 '   strPath = CreateObject("WScript.Shell").SpecialFolders("Desktop") & "\m.csv"
    strPath = "C:\Documents and Settings\xxx\デスクトップ" & "\m.csv"

    'Dictionaryオブジェクトを取得
    Set dicIndex = CreateObject("Scripting.Dictionary")

    With rngResult
        '行数の取得
        lngRows = .Offset(Rows.Count - .Row).End(xlUp).Row - .Row
        '列数の取得
        lngColumns = .Offset(, Columns.Count - .Column).End(xlToLeft).Column - .Column
        'クロス集計リストの列見出し、行見出しを取得
        vntRowKeys = .Offset(1).Resize(lngRows).Value
        vntColKeys = .Offset(, 1).Resize(, lngColumns).Value
    End With

    '行、列の見出しをKey、位置をItemとしてDictionaryに登録
    With dicIndex
        strKey = Space(8) & vbTab
        For i = 1 To lngRows
            Mid(strKey, 1, 8) = Format(vntRowKeys(i, 1), "yyyymmdd")
            .Item(strKey) = i
        Next i
        For i = 1 To lngColumns
            .Item(vntColKeys(1, i)) = i
        Next i
    End With

    '結果出力用配列を確保
    ReDim vntResult(1 To lngRows, 1 To lngColumns)

    'データをCsvファイルから取得
    GetList strPath, vntData, dicIndex, vntResult

    Application.ScreenUpdating = False

    'データをシートに転記
    With rngList
        .Resize(, UBound(vntData, 2)).EntireColumn.ClearContents
        .Resize(UBound(vntData, 1), UBound(vntData, 2)).Value = vntData
    End With

    '結果を出力
    With rngResult.Offset(1, 1).Resize(lngRows, lngColumns)
        .ClearContents
        .Value = vntResult
    End With

    strPrompt = "処理が完了しました"

 Wayout:

    Application.ScreenUpdating = True

    Set dicIndex = Nothing
    Set rngList = Nothing
    Set rngResult = Nothing
    Set shtB = Nothing

    MsgBox strPrompt & vbLf & Format(Timer - myTime, "#,##0.00") _
                    & "秒かかりました。", vbInformation

 End Sub

 Private Sub GetList(strFileName As String, vntData() As Variant, _
                        dicIndex As Object, vntResult() As Variant)

    Dim i As Long
    Dim dfn As Integer
    Dim bytBuff() As Byte
    Dim vntList As Variant
    Dim vntFields As Variant
    Dim strKey As String
    Dim lngRow As Long
    Dim lngColumn As Long

    '読み込むファイルをOpen
    dfn = FreeFile
    Open strFileName For Binary As dfn

    '全行変数に読み込み
    ReDim bytBuff(1 To LOF(dfn))
    Get #dfn, , bytBuff

    Close #dfn

    '読み込んだファイルを改行コードで分割
    vntList = Split(StrConv(bytBuff, vbUnicode), vbCrLf, , vbBinaryCompare)
    '分割したファイルを調整
    For i = UBound(vntList, 1) To 0 Step -1
        If vntList(i) <> "" Then
            Exit For
        End If
    Next i
    '配列の空白の部分迄を削除
    If i > 0 Then
        ReDim Preserve vntList(i)
    End If

    '必要なデータを配列と結果配列に取得
    ReDim vntData(1 To UBound(vntList, 1) + 1, 1 To 3)
    strKey = Space(8) & vbTab
    For i = 0 To UBound(vntList, 1)
        'データをカンマで分割
        vntFields = Split(vntList(i), ",")
        'R列に当たるフィールドを配列に転記
        vntData(i + 1, 1) = vntFields(17)
        'O列に当たるフィールドを配列に転記
        vntData(i + 1, 2) = vntFields(14)
        '日付と認められる値なら
        If IsDate(vntData(i + 1, 2)) Then
            'シリアル値に変更
            vntData(i + 1, 2) = DateValue(vntData(i + 1, 2))
        End If
        'Nに当たるフィールドを配列に転記
        vntData(i + 1, 3) = Val(vntFields(13))
        With dicIndex
            'Dictionaryから転記する行位置と列位置を取得
            Mid(strKey, 1, 8) = Format(vntData(i + 1, 2), "yyyymmdd") & vbTab
            '結果配列に個数を加算
            If .Exists(strKey) And .Exists(vntData(i + 1, 1)) Then
                lngRow = .Item(strKey)
                lngColumn = .Item(vntData(i + 1, 1))
                vntResult(lngRow, lngColumn) _
                        = vntResult(lngRow, lngColumn) + vntData(i + 1, 3)
            End If
        End With
    Next i

 End Sub

 (Bun)


 こちらで、3万行のCSVファイルを作成して、あちゃこさんの環境より、やや性能の劣る環境でテスト。
結果は、あちゃこさんのオリジナルのコードで5秒前後。以下にアップする、抽出を限定した方法で3.5秒前後。
確実に処理時間は減少しているけど、思ったより時間がかかっている。
やはり、もともとの3万行の負荷は大きいね。
なので、処理時間短縮というテーマであればichinoseさんからアドバイスがある方式かな?

 Sub test3()
    Dim shtA    As Worksheet
    Dim shtB    As Worksheet
    Dim dicX    As Object
    Dim dicY    As Object
    Dim tbl     As Variant
    Dim i       As Long
    Dim j       As Long
    Dim x       As Long
    Dim y       As Long
    Dim c       As Range
    Dim myTime  As Double

    myTime = Timer

    Application.ScreenUpdating = False

    With CreateObject("WScript.Shell")
        Workbooks.Open .SpecialFolders("Desktop") & "\m.csv"
    End With

    Set shtA = Workbooks("m.CSV").Worksheets("m")
    Set shtB = Workbooks("集計.xls").Worksheets("Sheet1")
    Set dicX = CreateObject("Scripting.Dictionary")
    Set dicY = CreateObject("Scripting.Dictionary")

    With shtB

        With .Range("F1").CurrentRegion
            x = .Columns.Count - 1
            y = .Rows.Count - 1
        End With
        For Each c In .Range("G1").Resize(, x)
            dicX(c.Value) = c.Column - 6
        Next
        For Each c In .Range("F2").Resize(y)
            dicY(c.Value) = c.Row - 1
        Next

    End With

    With shtA

        .Columns("A:F").ClearContents  '作業域
        .Range("A1").Value = .Range("R1").Value '型式 抽出域タイトル
        .Range("B1").Value = .Range("O1").Value '日付 抽出域タイトル
        .Range("C1").Value = .Range("N1").Value '個数 抽出域タイトル
        .Range("E1").Value = .Range("O1").Value '日付 抽出条件タイトル
        .Range("E2").Resize(y).Value = shtB.Range("F2").Resize(y).Value
        .Columns("O").TextToColumns Destination:=.Range("O1"), DataType:=xlDelimited, _
            TextQualifier:=xlDoubleQuote, ConsecutiveDelimiter:=False, Tab:=True, _
            Semicolon:=False, Comma:=False, Space:=False, Other:=False, FieldInfo _
            :=Array(1, 5), TrailingMinusNumbers:=True
        .Columns("N:R").AdvancedFilter Action:=xlFilterCopy, _
            CriteriaRange:=.Range("E1").CurrentRegion, CopyToRange:=.Range("A1:C1"), Unique:=False

        ReDim tbl(1 To y, 1 To x)

        For Each c In .Range("A2", .Range("A" & .Rows.Count).End(xlUp))
            i = dicY(c.Offset(, 1).Value)
            j = dicX(c.Value)
            If j > 0 Then tbl(i, j) = tbl(i, j) + c.Offset(, 2).Value
        Next

        .Columns("A:C").Copy shtB.Columns(1)
        shtB.Range("G2").Resize(UBound(tbl, 1), UBound(tbl, 2)).Value = tbl

    End With

    shtA.Parent.Close savechanges:=False

    Application.ScreenUpdating = True

    Set shtA = Nothing
    Set shtB = Nothing
    Set dicX = Nothing
    Set dicY = Nothing

    MsgBox Format(Timer - myTime, "#,##0.00") & "秒かかりました。"

 End Sub

 (ぶらっと)

 ichinoseさん、前回はご丁寧な回答をいただきながら返信もぜず、申し訳ありませんで
 した。csvファイルの抽出うまくいきました。今のところ理解はしきれていませんが今後の
 課題とし、学習したとおもいます。ありがとうがざいました。

 Bunさん、解説もいれていただきありがとうございます。未熟者にとって大変分かり
 わかりやすいです。ありがとうございました。

 ぶらっとさん、いつも対応ありがとうございます。毎回勉強をさせていただき活用
 させていただいています。
(あちゃこ)


コメント返信:

[ 一覧(最新更新順) ]


YukiWiki 1.6.7 Copyright (C) 2000,2001 by Hiroshi Yuki. Modified by kazu.