最終更新 2024.04.01
このページは私の自分用の覚え書きです。以下のサイトに、非常に詳しい 覚え書きがあります(2013.12 の時点)。 教科書的なサイトです 非常に詳しいサイトです。何でも載っています。 教科書とリファレンスが一体化したサイトです リファレンス的なサイトです
A 列で、データが入っている最後の行を取り出す
Range("A65536").End(xlUp).Select
last_row = ActiveCell.Row
空白のセルがあるとき、それ以下は無視するとき
Range("A1").End(xlDown).Select
last_row = ActiveCell.Row
tate = Sheets("sheet1").UsedRange.Rows.Count
yoko = Sheets("sheet1").UsedRange.Columns.Count
Range("A1").CurrentRegion.Select
tate = ActiveCell.CurrentRegion.Rows.Count
yoko = ActiveCell.CurrentRegion.Columns.Count
この関数は要注意。空白の行や列があるときは、そこで途切れる。
メッセージボックス
call MsgBox("i = " + Cstr(i),vbOKOnly)
ret = MsgBox("i = " + Cstr(i),vbOKCancel) 1:OK 2:Cancel
イミディエイトウィンドウに書く
debug.Print "i = ";i
ステータスバー
Application.Statusbar = "文字"
Dim ans As String
ans = InputBox("1 : 選択肢A 2 : 選択肢B")
シートの選択
Sheets("シート名").Select 名前で指定
Sheets(2).Select 番号で指定 1 から始まる
クリア
Cells.Select (全領域を選択)
Range("B8:C18").Select (一部分を選択)
Selection.ClearContents (罫線・塗りつぶしは残る)
Selection.Clear (列幅のみ残る)
Cells.Select
Selection.Delete (列幅もデフォルト)
シートの数
n = Worksheets.Count
新規作成
n = Worksheets.Count
Worksheets.Add After:=Worksheets(n), Count:=2
コピー
Sheets("コピー元").Select
Cells.Select
Selection.Copy
Sheets("コピー先").Select
Cells.Select
ActiveSheet.Paste
名前変更
Sheets("古い名前").Name = "新しい名前"
別のワークブックのシートの一部を値のみコピーする
Workbooks("filename.xls").Sheets(sheet_name).Range("A:F").Copy
ThisWorkbook.Sheets(sheet_name2).Select
Range("A1").PasteSpecial Paste:=xlPasteValues
Dim i As Long, new_fname As String
Dim sheet_name As String, prev_sheet_name As String
Dim orgfile As Object, newfile As Object
Set orgfile = ThisWorkbook
sheet_name = "Sheet1"
orgfile.Sheets(sheet_name).Copy ' before:= と after:= を省略しているので、
' 新規のワークブックが自動的に作成される。
Set newfile = ActiveWorkbook
prev_sheet_name = sheet_name
For i = 2 To 3
sheet_name = "Sheet" + CStr(i) ' Sheet2 Sheet3 もコピーする
orgfile.Sheets(sheet_name).Copy After:=newfile.Sheets(prev_sheet_name)
prev_sheet_name = sheet_name
Next i
new_fname = orgfile.Path + "\sample2.xls"
newfile.SaveAs Filename:=new_fname
newfile.Close
orgfile.Activate
以下のようにも書ける
For i = 1 To num
name = "Sheet" + CStr(i)
If i = 1 Then
ThisWorkbook.Sheets(name).Copy
Else
ThisWorkbook.Sheets(name).Copy After:=ActiveWorkbook.Sheets(i - 1)
End If
Next i
自分でコードを書いてシート内の検索を行うとき、
一旦配列に入れてから検索をした方が圧倒的に速い。
Dim sheet_tmp() As Variant
中略
Workbooks(fname).Activate
Sheets("sheet-name").Select
Range("A1").CurrentRegion.Select
last_row = ActiveCell.CurrentRegion.Rows.Count
ReDim sheet_tmp(1 To sheet_last_row, 1 To column_count)
sheet_tmp = Range("A1:"+column_num_to_str(column_count)+cstr(last_row)).value
Workbooks(fname).Activate
name = WorksheetFunction.VLookup(key_name, Range("sheet-name!a2:b60"), 2, False)
注! VLookup の第 3 引数は、指定した範囲の左端を 1 列目とする
第 2 引数で範囲を指定する。その左端の列中に key_name で示された
値を見つけたとき、第 3 引数で示された列に入っている値を返す。
第 4 引数は false のとき完全に一致する行のみ検索する。
複数の行に一致するとき、最初に見つけた行が返される。
第 4 引数が false で key_name が文字列のときワイルドカードを使える。
? : 任意の 1 文字 * : 任意の文字列
VLookup は文字列と数値を区別する。
一方、If a = b のときは以下のように動作する
・a か b のどちらかが定数のとき
変数の内容が定数と同じ型に変換されてから比較が行われる
型変換できないとき「型が一致しません」
・a : Long, b : String のとき
b が数値に変換可能な場合は数値に変換してから比較が行われる。
b が数値に変換不能な場合は「型が一致しません」のエラーが出てストップ
Rows("4:4").Select
With Selection.Borders(xlEdgeBottom) <---- 下側に引く
.LineStyle = xlContinuous
.Weight = xlThin
.ColorIndex = 5
End With
Columns("A:A").ColumnWidth = 6
Rows("1:1").RowHeight = 29.4
Rows("1:1").Select
Selection.Font.Name = "MS ゴシック"
Range("A65536").End(xlUp).Select
last_row = ActiveCell.Row
Range("2:" + CStr(last_row)).Select
Selection.Font.Name = "MS 明朝"
Range("A1").Interior.Color = RGB(255,220,200)
Range("A1").Interior.ColorIndex = 6 ' カラーインデックスは Excel2003 と 2007 での互換性に問題がある
Range("A2").Interior.Pattern = xlNone ' 塗りつぶしなし
Rows("1:1").Select
Selection.Insert Shift:=xlDown
Sheets("sheet-name").Select
Cells.Select
Selection.Clear
Columns("A:Z").Select ' A 〜 Z 列まで選択
Selection.NumberFormatLocal = "@" ' 文字型
Selection.NumberFormatLocal = "0_ " ' 数値型 (小数点以下の桁数 0)
Selection.NumberFormatLocal = "0" ' ユーザー定義型 (小数点以下の桁数 0)
"0" と "0_ " の違いはセルの右端にスペースが少し入るか否かの違い
num = Asc(LCase(cha)) - Asc("a") + 1
cha = chr(Asc("a") - 1 + num)
WorksheetFunction.Sum(Range("A1:C5"))
Call Application.Run("book1!sub1", 引数, 引数)
Typename(Range("A1").Value)
Typename(a)
ThisWorkbook.Path
Application.ScreenUpdating = False 抑制する Application.ScreenUpdating = True 抑制しない
Application.DisplayAlerts = False Application.DisplayAlerts = True
ThisWorkbook.Close SaveChanges:=False あるいは ThisWorkbook.Saved = True ThisWorkbook.Close
Workbooks.Open filename:="C:\dir\filename.xlsm"
Windows("filename").Activate
last_row = Range("A65536").End(xlUp).row
file_no = FreeFile ' 使えるファイル番号を返す関数
Open ThisWorkbook.path + "\" + filename + ".txt" For Output As file_no
For i = 1 To last_row
If i < 7 Then
Print #file_no, Cells(i, 1)
ElseIf i = 7 Then
Print #file_no, Cells(i, 1) + Space(3) + Cells(i, 2)
ElseIf i > 7 Then
Print #file_no, CStr(Cells(i, 1)) + Space(12 - Len(Trim(Cells(i, 2)))) + CStr(Cells(i, 2))
End If
Next i
Close #file_no
' 豆知識 Print #1, a;b;c のようにセミコロンでつなげる場合、
' 以下のように出力される。
'
' 変数が String の場合、空白は挿入されない
' 変数が Long の場合、前後に 1 個ずつ空白が挿入される
Const rows_per_page As Long = 50 ' 1 ページあたりの行数
'
' 表の整形
'
Sub seikei()
Sheets("sheet name").Select
' フォント 一旦、全てを「MS 明朝」に設定した後「Century」に設定すると、
' 半角文字のみで構成されるセルのみ、Century になる。
Cells.Select
Selection.Font.Name = "MS 明朝"
Selection.Font.Name = "Century"
' 各列の右寄せ・、インデント、列幅(1 単位は半角文字幅よりわずかに広い)
Columns("A:A").HorizontalAlignment = xlCenter
Columns("B:B").HorizontalAlignment = xlLeft
Columns("A:A").ColumnWidth = 14
Columns("A:A").IndentLevel = 3 ' インデント1 で半角 2 文字強の空白を確保する
Columns("B:B").ColumnWidth = 22
' 数値の表現形式
Columns("E:E").NumberFormatLocal = "@"
Columns("K:K").NumberFormatLocal = "00.0"
' 1 行目のフォーマット
Rows("1:1").RowHeight = 56
Rows("1:1").IndentLevel = 0
Rows("1:1").VerticalAlignment = xlTop
Rows("1:1").Orientation = xlVertical
Range("A1").HorizontalAlignment = xlCenter
Range("B1").HorizontalAlignment = xlLeft
' 列幅(1 単位は半角文字幅よりわずかに広い)
Columns("A:A").ColumnWidth = 8
Columns("B:B").ColumnWidth = 7
End Sub
'
' シートを印刷する
'
Sub do_print()
Dim last_row As Long, page As Long, row As Long
Dim left_header As String, center_header As String, center_header2 As String
Dim last_page As Long
' ヘッダ、フッタにおける記号の意味
'
' &P : ページ番号
' &D : 日付
' &8 : フォントサイズ
' &"MS 明朝,太字" : フォント名とボールド体の指定
' "" : 「"」
left_header = "&""MS 明朝""&11" + Chr(10) + "ページ番号 &P" + Chr(10) + "&D"
Sheets("sheet name").Select
Range("A65536").End(xlUp).Select
last_row = ActiveCell.row
center_header = "&""MS 明朝""&11" + _
"1 行目" + Chr(10) + _
"2 行目"
center_header2 = "&""MS 明朝""&11" + _
"1 行目" + Chr(10) + _
"2 行目"
With ActiveSheet.PageSetup
.Orientation = xlLandscape ' 用紙向き
.PaperSize = xlPaperA4 ' 用紙サイズ
.FirstPageNumber = 1 ' 先頭ページ番号
.PrintTitleRows = "$1:$2" ' 全てのページに 1 行目と 2 行目を印刷する
.PrintArea = "A3:Q" + CStr(last_row)
.PrintTitleColumns = ""
.LeftHeader = left_header + " " + CStr(Date)
.CenterHeader = "&A"
.RightHeader = "Page &P"
.TopMargin = Application.CentimetersToPoints(2)
.BottomMargin = Application.CentimetersToPoints(2)
.LeftMargin = Application.CentimetersToPoints(2.5)
.RightMargin = Application.CentimetersToPoints(2.5)
.HeaderMargin = Application.CentimetersToPoints(1.3)
.FooterMargin = Application.CentimetersToPoints(1.3)
.CenterHorizontally = True
.CenterVertically = False
.Zoom = 100 ' False (自動設定) にすると、
' プリンタが変わると倍率が変わる。これは好ましくないので、
' 拡大率は固定値にしてページ区切りを明示的に指定する。
' この倍率は HPageBreak.Add を実行しても不変である。
End With
' ページ区切り位置を明示的に指定
'
' ActiveSheet.HPageBreak.Add を実行すると、なぜか
' 空白しか入っていないセルにページが割り当てられることがある。
' 最終ページを明示する必要がある。
'
last_page = 0
row = 3
Do While True
last_page = last_page + 1
row = row + rows_per_page
If row > last_row Then
Exit Do
End If
ActiveSheet.HPageBreaks.Add Before:=Range("A" + CStr(row))
Loop
' ページ区切りのクリアは
' ActiveSheet.ResetAllPageBreaks
'
' あるいは
' Sheets("シート名").Select
' Cells.Select
' Selection.Delete
' 実行すると、セルに何も入っていない状態になり、
' ページ区切りはリセットされる。
' 1 ページ目とそれ以降でヘッダが異なる
ActiveSheet.PageSetup.CenterHeader = center_header
ActiveSheet.PrintOut To:=1
If last_page >= 2 Then
ActiveSheet.PageSetup.CenterHeader = center_header2
ActiveSheet.PrintOut From:=2, To:=last_page
End If
' ActiveSheet.PrintPreview ' 開発用 プレビュー表示
End Sub
Dim filename As String
Dim line As String, i As Long
i = 1
filename = ThisWorkbook.Path + "\..\memo.txt"
Open filename For Input As #1
Do Until EOF(1) ' (1) は装置番号
Line Input #1, line
Cells(i, 1) = line
i = i + 1
Loop
Close #1
Dim filename As String
Dim line As String, i As Long, last_row As Long
Range("A65536").End(xlUp).Select
last_row = ActiveCell.Row
filename = ThisWorkbook.Path + "\..\aaa.txt"
Open filename For Output As #1
For i = 1 To last_row
line = Cells(i, 1)
Print #1, line
Next
Close #1
Dim buf As Variant
fname = ThisWorkbook.Path + "\出席者04190909.csv"
Open fname For Input As #1
i = 1
Do Until EOF(1)
Line Input #1, line
buf = Split(line, ",") ' buf(0),buf(1).... に格納される
b_start = LBound(buf)
b_end = UBound(buf)
For j = b_start To b_end
Call rm_double_quotation(buf(j))
Cells(i, j + 1) = buf(j)
Next j
i = i + 1
Loop
Close #1
----------------------
'
' 文字列の左端と右端に " がある場合削除
'
Sub rm_double_quotation(word As Variant)
If Left(word, 1) = """" Then ' 先頭が " であれば削除
word = Mid(word, 2)
End If
If Right(word, 1) = """" Then ' 末尾が " であれば削除
word = Mid(word, 1, Len(word) - 1)
End If
End Sub
Type format
field_1 As String * 5
field_2 As String * 20
... 略 ...
crcf As String * 2
End Type
Dim record As format
If Dir(fname) = "" Then
MsgBox "ファイル " + fname + " がありません。作業を中断します。"
End
End If
Open fname For Binary As #1
' Open fname For Random As #1 Len = Len(record) ' 結果はどちらも同じ
nrecord = LOF(1) / Len(record)
If nrecord = 0 Then
Close #1
MsgBox "ファイル " + fname + " の内容が空です。作業を中断します。"
End
End If
If LOF(1) Mod Len(record) <> 0 Then
MsgBox "ファイル " + fname + " の長さが不正です。作業を中断します。"
Close #1
End
End If
'
' 高速化のため一旦 Variant 型変数に読み込む
'
' レコード数が少ないときは Variant 型変数を経由する必要はない。
' 直接セルに読み込めばよい。
'
Dim buf() As Variant
ReDim buf(1 To nrecord, 1 To num_of_elements) As Variant
Application.ScreenUpdating = False
Sheets(sheet_name).Select
Cells.Select
Selection.Clear
Selection.RowHeight = 行幅
'
' セルの型を指定しておく
'
Cells(1, 1).ColumnWidth = 列幅
Cells(1, 1).Value = "列タイトル"
Columns(1).NumberFormatLocal = "@"
Cells(1, 2).ColumnWidth =
Cells(1, 2).Value =
Columns(2).NumberFormatLocal = "0_ "
... 略 ...
' Excel VBA では、セルに代入するときに、セルの表示形式
' によって、自動的な型変換が行われる。
'
' 以下の代入文では Variant 型変数 buf に入れる直前に
' 数値型に関しては CLng CDbl などの関数を用いて
' 正しい型に変換しているが、CLng CDbl を使わなくても
' セルに入る時点で正しい型に変換される。
'
' この場合、buf の中では文字列であるが、セルに入る時点で
' NumberFormatLocal = "0_ " のセルに入るデータは数値に変換される。
For i = 1 To nrecord
If i Mod 1000 = 0 Then
Application.StatusBar = "rec no. = " + CStr(i)
End If
' Get #1, i, record
Get #1, , record ' 結果はどちらも同じ
buf(i, 1) = CLng(record.field_1)
buf(i, 2) = record.field_2
buf(i, 3) = CDbl(record.field_3)
... 略 ...
Next i
Close #1
Range("A2:" + column_num_to_str(num_of_elements) + CStr(nrecord + 1)).Value = buf
Application.StatusBar = "ファイルの読み込みを終了しました"
Application.ScreenUpdating = True
' 読み込み時の注意
'
' 半角文字と全角文字の両方が使われる可能性があるフィールドを Get
' する場合、
'
' format.fieldx = ""
'
' のように、Get する前にそのフィールドをクリアする必要がある。
'
' Excel VBA では string*4 のように宣言した場合、
' 半角文字であろうが全角文字であろうが、4 字格納可能な領域が
' 確保される。
'
' 例えば、「あアイ」を Get し、次の行で「あい」を Get すると
' バッファは "あいイ" となる。これは前に読み込んだ 3 文字目の イ が
' 残っており、全角半角を区別していないことを意味する。
fname = ThisWorkbook.path + "\" + "sample.txt"
If Dir(fname) <> "" Then
MsgBox "ファイル " + fname + " はすでに存在します。上書きします。"
Kill fname
End If
Sheets(sheet_name).Select
Range("A1").CurrentRegion.Select
last_row = Selection.Rows.Count
' 高速化のためシートの内容を buf に移す
'
' データ量が少ない場合は buf に移す必要はない
' 直接セルから record 構造体へ代入すればよい。
Dim buf() As Variant
ReDim buf(1 To last_row, 1 To number_of_elements)
buf = Range("A1:" + column_num_to_str(number_of_elements) + CStr(last_row)).Value
' Open fname For Random As #2 Len = Len(record)
Open fname For Binary As #2
For i = 2 To last_row ' 1 行目はキャプションなので無視
If i Mod 1000 = 0 Then
Application.StatusBar = "rec no. = " + CStr(i)
End If
record.field_1 = Format(buf(i, 1), "00000")
record.field_2 = Format(buf(i, 2), "000000")
record.field_3 = buf(i, 3)
... 略 ...
record.field_n = Format(buf(i, n), "00.0")
record.crcf = Chr$(&HD) + Chr$(&HA)
Put #2, , record
Next i
Close #2
'
' 列番号を列の名前に変換する。文字数 3 桁にも対応
'
' i : 1 2 .....26 27 28 29 .......
' ret : A B ..... Z AA AB AC .......
'
Function column_num2str(i)
num = i
ret = ""
Do While True
low = (num - 1) Mod 26
high = Int((num - 1) / 26)
ret = Chr(Asc("A") + low) & ret
If high = 0 Then
Exit Do
End If
num = high
Loop
column_num2str = ret
End Function
'
' 列の名前を列番号に変換する。文字数 3 桁にも対応
'
' moji_in : A B ..... Z AA AB AC .......
' num : 1 2 .....26 27 28 29 .......
'
Function column_str2num(moji_in)
moji = LCase(Trim(moji_in))
num = 0
kurai = 1
Do While True
leng = Len(moji)
cha = Right(moji, 1)
num = num + kurai * (Asc(cha) - Asc("a") + 1)
If leng = 1 Then
Exit Do
End If
moji = Left(moji, leng - 1)
kurai = kurai * 26
Loop
column_str2num = num
End Function
この WSH ファイルにテキストファイルをドラッグすると、Excel が起動して
sample.xls が読み込まれ、sample.xls の中のマクロ read_txt_file が実行されて、
ドラッグしたテキストファイルを読み込む。
sample.xls は read_txt_file というマクロを持ち、
引数として、読み込みたいファイル名を指定する。
option explicit
dim application, book, command
if trim(wscript.arguments(0)) = "" then
call msgbox("引数がありません")
end if
set application = wscript.createobject("excel.application")
application.visible = true
set book = application.workbooks.open("c:\dir\sample.xls")
if isobject(book) then
command = "sample.xls!read_txt_file("""+wscript.arguments(0)+""")"
application.run command
end if
・VBA エディタで「挿入」→「ユーザーフォーム」で フォームをデザインするモードに入る。フォームを作成し、 その上にコントロールを配置する。 ・フォームをクリックするとツールボックスが表示される。 ツールボックスが表示されない場合は「表示」→「ツールボックス」
◆ ファイルを開いたとき
ThisWorkbook 内の Workbook_Open() と Module? 内の Auto_Open()
順番は Workbook_Open() → Auto_Open()
◆ セルの値を変更したとき
標準モジュールではなく Sheet1(シート名) のような
名前の領域の中に以下のように書く。
Sub worksheet_change(ByVal target As Range)
'
' 複数のセルが選択されている場合(ex. delete 時)を処理するために
' For Each を使う
'
For Each one_cell In target.Cells
Call manage(one_cell)
Next one_cell
End Sub
Sub manage(ByVal target As Range)
Debug.Print target.Row ' 行番号
Debug.Print target.Column ' 列番号
Debug.Print target.Cells ' セルの内容
End Sub
・フォームの開始
UserForm1.Show を実行するとフォームが表示される。
UserForm1 の実行方法は
1. イミディエイトウィンドウで UserForm1.Show と打つ
2. 何らかの関数で UserForm1.Show を実行する
3. UserForm1 をコードウィンドウに表示した状態で
「実行」→「Sub/ユーザーフォームの実行」
・フォームの終了
フォームの上に設置した終了ボタンをクリックした関数内で
Unload Me あるいは Unload UserForm1
・フォーム開始時に自動実行される関数
Private Sub UserForm_Initialize()
・フォーム終了時に自動実行される関数
Private Sub UserForm_Terminate()
・フォームの位置決め
UserForm1.Left = 0 は UserForm1.Show が完了した後でないと効かない。
UserForm_Initialize() の中で位置を指定しても無効である。
UserForm1.Show でフォームを表示するとき、UserForm_Initialize() の
直後に UserForm_Layout() が呼ばれる。
また、UserForm1.Left = 0 のようにフォームのサイズを変更したときも
UserForm_Layout() が呼ばれる。
UserForm1.Left の位置を取得できるのは UserForm_Layout() の中だけの
ようである。
UserForm_Initialize, UserForm_Terminate の中で UserForm1.Left
の値を見ると 0 が入る。
・ShowModal
フォームのプロパティの ShowModal = True に設定すると、フォーム表示中
は Excel に対する操作を受けつけない。False に設定すると、Excel や
Visual Basic Editor を操作することが出来る。
UserForm_Layout は「最初に Form を Show したとき」と
「Form の位置や大きさが変化したとき」に呼ばれる。
UserForm_Layout の中で Form の位置を変更するということは
やってはいけないと思うが、あえて行ってみると
UserForm_Layout が 2 回呼ばれる。
ただし、2 回目に呼ばれたときに 1 回目の実行が終了していない
ときは、2 回目の call は無視されるようだ。
Private Sub UserForm_Layout()
MsgBox "userform1 layout"
UserForm1.Left = 0
End Sub
Private Sub UserForm_Layout()
Debug.Print "userform1 layout"
UserForm1.Left = 0
End Sub
Private Sub UserForm_Layout()
UserForm1.Left = 0
Debug.Print "userform1 layout"
End Sub
以上の 3 つのプログラムではいずれも UserForm_Layout は 2 回実行される。
しかし
Private Sub UserForm_Layout()
UserForm1.Left = 0
MsgBox "userform1 layout"
End Sub
では UserForm_Layout() は 1 回しか実行されない。
(1) プロジェクトの作成 ( VS 2005 の場合 )
「ファイル」→「新規作成」→「プロジェクト」
Visual C++ → Win32 → Win32 プロジェクト
ウィザードに答える過程で dll を選ぶ
プロジェクト名は dll_sample
「プロジェクト」→「dll_sample のプロパティ」→
「構成プロパティ -- 全般」→「文字セット」
においては「マルチバイト文字セットを使用する」を選ぶのが
無難なようだ。
Unicode を選んだ場合、引数に従来の文字列を使用する場合、
MessageBoxA, CreateFileA のように API の末尾に A がついた関数を
呼ばなくてはならない。
----------------
(2) 自動生成された dll_sample.cpp というファイルに
以下のように追記する。
DllMain は dll が初めて呼ばれたときに実行される関数なので、
初期化が必要な処理はこの中に書く。
------------------------------------------
#include <windows.h>
#include <stdio.h>
LONG __stdcall show_message(char msg[], int arg)
{
int len,ret;
char msg2[300];
len = strlen(msg);
sprintf(msg2,"文字列 = |%s| 文字長 = %d 整数型引数 = %d",
msg,len,arg);
MessageBoxA(NULL, msg2, "caption", MB_OK);
return 0;
}
-----------------------------------------------------------
__stdcall を付けるのを怠ると、関数 show_message を
呼び出すことには成功するが、関数からリターンした後、
実行時エラー '49'
dll が正しく呼び出せません。というエラーが発生する。
(3) dll_sample.def というファイルを以下のように記述する。
-------------------------------------------
LIBRARY dll_sample
EXPORTS
show_message
-------------------------------------------
「プロジェクト」→「プロパティ」→「リンカ」→「入力」→
「モジュール定義ファイル」の場所に dll_sample.def と記入する。
VS 2005 の場合、「プロジェクト」→「既存の項目の追加」でソースファイル
に含めても「モジュール定義ファイル」として登録されない。
手作業でモジュール定義ファイルを指定する必要がある。
いくつかの Web サイトには、関数の定義の最初に
__declspec(dllexport)
と記入すると def ファイルは不要であるとの記述があるが、
その方法では export される関数名が ?関数名@@YGHXZ = @ILT+(以下略)
となってしまい、Excel から呼び出せない。
(4) ビルドすると dll_sample.dll と dll_sample.lib が生成
される。
Excel VBA の開発と dll の開発を同時並行して行っている場合、
dll_sample : error PRJ0008 : ファイル 'c:\(略)\dll_sample\Debug\dll_sample.dll' を削除できませんでした。
このファイルが他のプロセスによって使用されていないこと、また書込み禁止になっていないことを確認してください。
というエラーが出ることがある。
その場合は、Excel VBA Editor で「実行」→「リセット」と操作する。
VC++6 の場合は以下のエラーメッセージとなる。
LINK : fatal error LNK1168: 書き込みモードで Debug/dll_sample.dll を開けません
link.exe の実行エラー
(5) 正しくエクスポートされているかチェックする
スタートメニューから「Microsoft Visual Studio 2005」→
「Visual Studio 2005 Tools」→「Visual Studio 2005 コマンドプロンプト」
でコマンドプロンプトを開く。
> dumpbin /exports c:\(省略)\dll_sample\debug\dll_sample.dll
と打ってエントリポイントの名前を確認する。
name の欄に def ファイルで指定した名前があればよい。
(6) Excel VBA 側のコードを以下のように書く
Declare Function show_message Lib "dll_sample.dll" _
(ByVal msg As String, ByVal arg As Long) As Long
Const dll_path As String = "C:\Documents and Settings\user-name\My Documents\..."
Sub dll_test()
Dim msg As String * 10
Dim ret As Long, arg As Long
msg = "abcde"
arg = 50
call ChDir(dll_path)
ret = show_message(msg, arg)
Debug.Print "show message ret =", ret
End Sub
(7) Sub dll_test() を実行する
サンプルプログラム中の ChDir は 1 回だけ実行すればよいので、
Auto_Open の中に書いておいた方が良いかもしれない。
あるいは dll と xls ファイルを同一のディレクトリに入れておき、
「ファイル」→「開く」で xls ファイルを開いた場合は、ChDir は
不要である。
Excel が dll を探す順序は
1. Excel.exe が存在するフォルダ
2. カレントフォルダ
3. Windows システムフォルダ
4. Windows ディレクトリ
5. 環境変数 PATH
なので、環境変数 PATH を設定してもよいようだ。
カレントフォルダは以下のように定まる。
イミディエイトウィンドウで Print CurDir と打つと確認できる。
1. 「ファイル」→「開く」のときは xls ファイルが存在するフォルダ
2. xls ファイルをダブルクリックして xls ファイルを開いたとき
Excel2003 : 「ツール」→「オプション」→「全般」の「カレントフォルダ名」
Excel2007 : 「Officeボタン」→「Excel のオプション」→「保存」の
「規定のファイルの場所」
<注意1>
C++ の void 型の関数を VBA から呼ぶときは
Declare Sub Sleep Lib "kernel32.dll" (ByVal ms As Long)
のように Function ではなく Sub と宣言する。
Function とすると、ハングアップする。
<注意2>
String 型変数の引数の型は ByVal である。
C++ の感覚だと ByRef のように思うが、ByVal である。
以下の記述は http://soudan1.biglobe.ne.jp/qa4064448.html の
記事を参考にしている。
・VBA の String 型変数には、文字列の先頭アドレスと文字列の長さ
が入っていて、本当の文字列データはどこか別の所にある。
・文字型変数を ByVal で渡すとアドレスを渡すことになる。
C++ から見ると &msg[0] を受け取ることになる。
・C++ 側で msg[0] = 'x' のように文字列を変更すると、
VBA 側の String 型変数の内容が書き換わる。
・VBA で As String * 10 と宣言した文字型変数には 10 文字までしか
入らない。ただし、半角文字も全角文字も 1 文字とカウントされる。
C++ で strlen すると、半角文字を 10 文字入れたなら長さ 10 であり、
全角文字を 10 文字入れたなら長さ 20 である。
1.「右クリック」→「セルの書式設定」→「保護」で「ロック」にチェックを 入れる(デフォルトで入っている。) 2. Excel2007 : 「校閲」→「シートの保護」 Excel2007 : 「ツール」→「保護」→「シートの保護」 「シートの保護」を on の状態にすると、ロックにチェックが入っていない セルに関しては、以下の状態になる。 1. 値の代入は可能 2. 色の変更はエラー
ループ変数は j, M 列に得点が入っている
tokuten = Range("M" + CStr(j)).Value
If prev_tokuten = tokuten Then
douten_flag = 1 ' 1 のときは同点継続中
Else
douten_flag = 0
douten_begin = j ' 一連の同点者の最初の者の index
End If
prev_tokuten = tokuten
ソートされた得点が Cells(j, 2) に入っている
順位を Cells(j, 5) に記入する
rank = 1 ' 順位
n = 0 ' 同点者数
For j = 2 To last_row
Cells(j, 5) = rank
If Cells(j + 1, 2) = Cells(j, 2) Then ' 次の行を見る
n = n + 1
Else
rank = rank + n + 1 ' 次の行の人の順位
n = 0
End If
Next j
Excel VBA にディレイ用関数はないようだ。 Win32 API を使用する。 Declare Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long) Sleep (500) 単位はミリセカンド
下の例は全てのシートの枠線色を「黒」に変更する
Dim sheet As Object
Count = 0
For Each sheet In ActiveWorkbook.Sheets
Count = Count + 1
Debug.Print "処理: "; sheet.Name
sheet.Select
ActiveWindow.GridlineColorIndex = 1
Next sheet
' Debug.Print "シートの枚数は " & CStr(Count) & " 枚"
Debug.Print "シートの枚数は " & ActiveWorkbook.Sheets.Count & " 枚"
Sheets(1).Select
コードは Module1 などではなく Sheet1(Sheet1) などに記述する。
コードを記述する領域の上左側の (General) を Worksheet に変更する。
コードを記述する領域の上右側のウィンドウでイベントを選ぶ。
変更したときに呼ばれる関数は Change あるいは SelectionChange である。
Change は 1 回呼ばれ、Target.Row, Target.Column は変更したセルが入る
SelectionChange は 2 回呼ばれ、1 回目のときのTarget.Row, Target.Column は
変更したセル位置、2 回目は変更後にカーソルが移動したセル位置が入る。
以下のようなコードを記述すると動作が確認できる。
Private Sub Worksheet_Change(ByVal Target As Range)
Debug.Print Now(); " change"
Debug.Print Target.Row; Target.Column
End Sub
(参考サイト)
http://excelvba.pc-users.net/fol3/3_6.html
Excel ではデフォルトでセルの「ロック」にチェックが 入っている。 「校閲」→「変更:シートの保護」で以下の現象がおこる。 セルの値の書き換え ロックしたセルは不可 エラー発生 ロックしないセルは可 セルの色の変更 ロックしたセルは不可 エラー発生 ロックしないセルも不可 エラー発生 値の書き換えと、色の変更で、「ロック」の効き方が異なる。 Excel のバグかもしれない。
Set a = Selection
addr = a.Address
cell_list = Split(addr, ",")
start = LBound(cell_list) ' 0 から始まる
last = UBound(cell_list)
For i = start To last
Range(cell_list(i)) = "Select"
Next i
Now (日付と時刻)
Time (時刻のみ)
ex.
Debug.Print "--- " & Now & " ---"
・特定の範囲内の値が変化したら、マクロを実行する
標準モジュールに書いてはいけない。
対象となるシート (たとえば Sheet(Sheet1)) に書く
無限ループを避けるための工夫が必要である。
例:
a2:b3 の範囲内のセルを書き換えると、
計算式により c4 が書き換わり、
c4 の約数を c5 に列挙するコード
Private Sub Worksheet_Change(ByVal Target As Range)
' Sub manage を実行することにより、c5 が書き換わり、
' Worksheet_Change が再度呼ばれる
' 以下の If を省略すると再度、manage を呼ぶことになり、
' 無限ループに陥る
If Intersect(Target, Range("a2:b3")) Is Nothing Then
Exit Sub
Else
Call manage
End If
End Sub
Sub manage()
str = ""
num = Range("c4")
For i = 2 To int(num/2)
If num Mod i = 0 Then
str = str + CStr(i) + " "
End If
Next i
Range("c5") = str
End Sub