エクセルクイズ 回答コード
ネタ元はこちら
暗号で会話ができれば、本当のチャレンジ成功か!?
— わえなび the Theory of Word-Excel (@waenavi_jp) October 11, 2019
問題
Excelを用いて、次の暗号を解読して、暗号で答えてください。
BMT NX YMJ HZWWJSY RNSNXYJW TK IJKJSXJ#エクセルクイズ pic.twitter.com/qTYr7ZmVWG
皆さん、CONCAT 関数で回答されていたのですが、
うちのExcelは、2013 なので、
CONCAT 関数が使えない
くやしいので、VBAでやったやつがこれ。
Public Enum ConvertMode cmDecode cmEncode End Enum Public Function convertText(ByVal sText As String, Optional ByVal mode As ConvertMode = cmDecode) As String Dim sResult As String Dim lLength As Long Dim i As Long lLength = Len(sText) sResult = Space(lLength) For i = 1 To lLength If Mid$(sText, i, 1) <> " " Then If mode = cmDecode Then Mid$(sResult, i, 1) = decode(Mid$(sText, i, 1)) Else Mid$(sResult, i, 1) = encode(Mid$(sText, i, 1)) End If End If Next i convertText = sResult End Function Private Function decode(ByVal s As String) As String Const CONV_TEXT As String = "VWXYZABCDEFGHIJKLMNOPQRSTU " Dim lBaseCode As Long Dim lIndex As Long lBaseCode = Asc(UCase(Left$(s, 1))) lIndex = (lBaseCode - Asc("A") + Len(CONV_TEXT)) Mod Len(CONV_TEXT) + 1 decode = Mid$(CONV_TEXT, lIndex, 1) End Function Private Function encode(ByVal s As String) As String Const CONV_TEXT As String = "FGHIJKLMNOPQRSTUVWXYZABCDE " Dim lBaseCode As Long Dim lIndex As Long lBaseCode = Asc(UCase(Left$(s, 1))) lIndex = (lBaseCode - Asc("A") + Len(CONV_TEXT)) Mod Len(CONV_TEXT) + 1 encode = Mid$(CONV_TEXT, lIndex, 1) End Function
FYFWFXMNN UH YT TKKNHJ MTXMNN
ExcelのVBAで使えるDLLを、C++(Visual Studio 2017)で作る。・・・その3(文字列の受け渡し)
その1、その2と書いたものの
1年以上放置して、いまさらの第3段。
今回は、文字列の受け渡しです。
初めに
初回に書いた設定に要修正箇所があります。
「プロジェクトの設定」の
3.「構成プロパティ」-「全般」-「プロジェクトの既定値」-「文字セット」を
"Unicode 文字セットを使用する"に変更して下さい。
使用する文字範囲が、Shift-JIS の範囲で限定されるような場合には、
マルチ バイト文字セットを使用する
でも良いのですが、Unicode 文字や、JIS 第三水準、第四水準の文字が含まれる可能性が排除できない場合には、
Unicode 文字セットを使用する
としておいた方が、後でトラブルになりにくいと思います。
文字列を受け渡しするためのデータ型
VBAでは、文字列を扱うデータ型は、String です。
これまで、ネットで見つかるコードを見ると、DLL側は、
- char*
- const char*
- wchar_t*
- BSTR
- BSTR*
といった型での受け渡しをしているものが大半を締めていたようです。
しかし、今回の記事では、これらの型を使いません。
使うのは、VARIANT です。
理由は、以下の通りです。
VBA の String は、
ByVal 渡しの場合はバイト文字列 BSTR 構造体へのポインターとして渡されます。
ByRef 渡しの場合はポインターへのポインターとして渡されます。
文字列を格納している VBA の Variant は、
https://docs.microsoft.com/ja-jp/office/client-developer/excel/how-to-access-dlls-in-excel
ByVal 渡しの場合は Unicode ワイド文字文字列 BSTR 構造体へのポインターとして渡されます。
ByRef 渡しの場合はポインターへのポインターとして渡されます。
Excel は、ワイド文字 Unicode 文字列を使用して内部で動作しています。VBA ユーザー定義関数が String 引数を取るように宣言されている場合、Excel は指定した文字列をロケール固有の方法でバイト文字列に変換します。関数に Unicode 文字列を渡す場合、VBA ユーザー定義関数は String 引数の代わりにバリアント型を受け入れる必要があります。その後、DLL 関数は、VBA から バリアント BSTR ワイド文字列を受け入れることができます。
DLL から VBA に Unicode 文字列を返すには、バリアント文字列引数を修正する必要があります。これが機能するには、C/C++ コードでバリアントへのポインターを使用するよう DLL 関数を宣言し、VBA コードで引数を ByRef varg As Variant として宣言する必要があります。古い文字列のメモリを解放し、OLE Bstr 文字列を使用して作成された新しい文字列値は DLL でのみ機能すべきです。
DLL から VBA にバイト文字列を返すには、バイト文字列 BSTR 引数をインプレースで変更する必要があります。これが機能するには、C/C++ コードで BSTR へのポインターへのポインターを使用するよう DLL 関数を宣言し、VBA コードで引数を「ByRef varg As String」として宣言する必要があります。
https://docs.microsoft.com/ja-jp/office/client-developer/excel/how-to-access-dlls-in-excel#variant-and-string-arguments
VARIANT型
VARIANT型は、構造体(VBAでは、Typeステートメントを使って定義するユーザー定義の型と思ってもらえば、とりあえずはOK。厳密に言えば違うんですけど。)で、以下の様になっています。
typedef struct tagVARIANT { union { struct { VARTYPE vt; WORD wReserved1; WORD wReserved2; WORD wReserved3; union { LONGLONG llVal; LONG lVal; BYTE bVal; SHORT iVal; FLOAT fltVal; DOUBLE dblVal; VARIANT_BOOL boolVal; SCODE scode; VARIANT_BOOL __OBSOLETE__VARIANT_BOOL; CY cyVal; DATE date; BSTR bstrVal; IUnknown *punkVal; IDispatch *pdispVal; SAFEARRAY *parray; BYTE *pbVal; SHORT *piVal; LONG *plVal; LONGLONG *pllVal; FLOAT *pfltVal; DOUBLE *pdblVal; VARIANT_BOOL *pboolVal; SCODE *pscode; CY *pcyVal; VARIANT_BOOL *__OBSOLETE__VARIANT_PBOOL; DATE *pdate; BSTR *pbstrVal; IUnknown **ppunkVal; IDispatch **ppdispVal; SAFEARRAY **pparray; VARIANT *pvarVal; PVOID byref; CHAR cVal; USHORT uiVal; ULONG ulVal; ULONGLONG ullVal; INT intVal; UINT uintVal; DECIMAL *pdecVal; CHAR *pcVal; USHORT *puiVal; ULONG *pulVal; ULONGLONG *pullVal; INT *pintVal; UINT *puintVal; struct { PVOID pvRecord; IRecordInfo *pRecInfo; } __VARIANT_NAME_4; } __VARIANT_NAME_3; } __VARIANT_NAME_2; DECIMAL decVal; } __VARIANT_NAME_1; } VARIANT;
結構たくさんの要素があるのですが、この中で、今回大切なのは、以下の3つです。
データ型 | 変数 | 備考 |
---|---|---|
VARTYPE | vt | VARIANTの変数に格納されているデータの型等の情報 |
BSTR | bstrVal | 文字列データ |
BSTR* | pbstrVal | 文字列データを指すポインタ |
気が付いた方もいるかもしれませんが、
VARIANTの中に格納される文字列の型は、BSTR型です。
vt
vtに格納される値は、VARENUMの中のいずれかが使用されます。ただし、一部は単独で使用されず、他の値とORを取るフラグとして使用されるものもあります。
VARIANTの変数に、非配列の文字列が格納されている場合、vt は、下表のいずれかの値となります。
定義 | 値 | 備考 |
---|---|---|
VT_BSTR | 0x0008 | 文字列 |
VT_BSTR | VT_BYREF | 0x4008 | 文字列(参照) |
bstrVal
vtに、VT_BSTRが設定されている時、bstrValには、文字列が格納されています。pbstrVal
vtに、VT_BSTR | VT_BYREF が設定されている時、pbstrValには、文字列へのポインタが格納されています。以下の2つの違いについては、後述します。
- VT_BSTR
- VT_BSTR | VT_BYREF
関数
DLL側でのVARIANT内部形式の判断
VARIANTとBSTRの変換を行う際に、気をつけなければいけないのが、vt の値です。
文字列が格納されている場合の vt の値は、以下の2つがある事を書きました。
これらは、DLLの関数に渡すVBAの変数の型に依存します。
vt | VBAの変数の型 | 呼出例 | 備考 |
---|---|---|---|
VT_BSTR [ 0x0008 ] | 内部処理形式が Stringの Variant の場合 | Dim v As Variant v = "" Call DllFunc(v) | vがemptyの状態で渡すと、VT_EMPTYとなる |
VT_BSTR | VT_BYREF [ 0x4008 ] | String の場合 | Dim s As String Call DllFunc(s) |
VARIANTの文字列の変換
VARIANT→BSTR
std::wstring のコンストラクタを使って初期化します。パラメータ v が、VT_BYREF の有無で処理方法が変わってきます。
vt | 文字列情報が格納されているメンバー | 左記要素に格納されている内容 |
---|---|---|
VT_BSTR | v.bstrVal | 文字列 |
VT_BSTR | VT_BYREF | v.pstrVal | 格納されている文字列を指すポインタ |
コンストラクタの第2引数には、文字列数を渡します。
文字数の取得には、SysStringLen 関数を使用します。
docs.microsoft.com
std::wstring convVstr2Wstr(const VARIANT v) { std::wstring ws; if (v.vt == VT_BSTR) { ws = std::wstring(v.bstrVal, SysStringLen(v.bstrVal)); } else if (v.vt == (VT_BSTR | VT_BYREF)) { ws = std::wstring(*v.pbstrVal, SysStringLen(*v.pbstrVal)); } return ws; }
wstring→VARIANT
VARIANT変数に文字列を設定する場合、以下の手順で行います。- vt に VT_BSTR または、VT_BSTR | VT_BYREF をセットする。
- bstrVal または、pbstrVal に文字列をセットする
VARIANT変数 を vString とする。
vString.vt == VT_BSTR の場合
SysAllocString に、C文字列を指すポインタを渡して、帰ってきた値(BSTR)を直接、vString.bstrVal に代入します。
std::wstring ws(L"設定する文字列"); vString.vt = VT_BSTR ; vString.bstrVal = SysAllocString(ws.c_str()); //以下の方法では、正しく文字列が返らない //BSTR bs = SysAllocString(ws.c_str()); //vString.bstrVal = bs; //SysFreeString(bs);
vt == VT_BSTR | VT_BYREF の場合
一旦、BSTRの変数に対して、SysAllocString を使って文字列を格納します。
上記変数をSysReAllocString に渡し、文字列データをセットします。
SysFreeString を使用して、上記BSTR変数を開放します。
std::wstring ws(L"設定する文字列");
vString.vt = VT_BSTR | VT_BYREF;
BSTR bs = SysAllocString(ws.c_str());
SysReAllocString(vString.pbstrVal, bs);
SysFreeString(bs);
コード
DLL
AccessibleFromVBA.h
AccessibleFromVBA.cpp
AccessibleFromVBA.def
stdafx.h
実行サンプル
SetString実行時
GetStringByParam、GetStringByRetVal実行結果
最後に
文字列はVARIANTで受け渡しですよ!!!
バ・リ・ア・ン・ト
次は配列当たりでしょうか?
では、また1年半後にwww
z1000s.hatenablog.com
z1000s.hatenablog.com
z1000s.hatenablog.com
z1000s.hatenablog.com
z1000s.hatenablog.com
z1000s.hatenablog.com
z1000s.hatenablog.com
プリコンパイル済みヘッダ使わなきゃよかった・・・
【VBA】9桁以上の16進数表記文字列同士のORを求める
昨日、Twitter で、
「16進数のORをとるのにもっと効率のいいやり方ある?」
というやつを見つけました。
16進数のORをとるのにもっと効率のいいやり方ある?
— ささきさん (@sasaki__san) 2019年9月16日
誰かおせーて!#VBA pic.twitter.com/qmQrwBiWti
スマホの小さい画面で見ていたので、
実は細かいところまでは見えなかった(見てなかった?)ので
「単純にLongに変換して、OR とって、Hex で16進表記に戻せばいいのでは」と
返したら、どうも4Byte超相当(9桁以上)の文字列を処理したいらしい。
32bit版では Long(16進数表記で8桁)までしか処理できないので、
8桁ずつループ処理するコードを書いてみました。
なお、Twitter で返したコードを少し変えてあります。
Public Function HexStringOr(ByVal sHexValue1 As String, ByVal sHexValue2 As String) As String Dim lLen1 As Long Dim lLen2 As Long Dim lResultLen As Long Dim lOrgLen As Long Dim sHexW1 As String Dim sHexW2 As String Dim sResult As String Dim lStartPos As Long Dim lHex1 As Long Dim lHex2 As Long Dim lHexW As Long Dim i As Long Const ZERO8 As String = "00000000" '16進文字列判定 If isHexString(sHexValue1) = False Then Exit Function ElseIf isHexString(sHexValue2) = False Then Exit Function End If lLen1 = Len(sHexValue1) lLen2 = Len(sHexValue2) If lLen1 >= lLen2 Then lOrgLen = lLen1 Else lOrgLen = lLen2 End If '8の倍数となる文字列長を求める lResultLen = ((lOrgLen + 7) \ 8) * 8 'ゼロパディング sHexW1 = String$(lResultLen - lLen1, "0") & sHexValue1 sHexW2 = String$(lResultLen - lLen2, "0") & sHexValue2 '結果格納用文字列(ダミー0埋め) sResult = String$(lResultLen, "0") For i = 1 To lResultLen \ 8 '処理先頭位置 lStartPos = 1 + (i - 1) * 8 '8文字抜き出してLongに変換 lHex1 = CLng("&H" & Mid$(sHexW1, lStartPos, 8)) lHex2 = CLng("&H" & Mid$(sHexW2, lStartPos, 8)) lHexW = lHex1 Or lHex2 '結果を格納 Mid$(sResult, lStartPos, 8) = Right$(ZERO8 & Hex(lHexW), 8) Next i '元のデータの桁に合わせてから返す HexStringOr = Right$(sResult, lOrgLen) End Function '16進文字列判定 Private Function isHexString(ByVal sValue As String) As Boolean If Len(sValue) = 0 Then Exit Function End If isHexString = Not sValue Like "*[!0-9a-fA-F]*" End Function
実行サンプル
? HexStringOr("1234567890ABCDEF11","22222222") 1234567890ABEFEF33 ? HexStringOr("444444442222222200001122","1111222200008888CCCC3333") 555566662222AAAACCCC3333 ? HexStringOr("004444444422222222000011","001111") 004444444422222222001111
「これで(速度的な意味で)効率が良くなったのか?」と聞かれれば、もちろん
「ほとんど変わらないよ!!!」
と言っちゃいます。m(_ _)m
パラメータの文字列が、数千桁とか数万桁なら、こっちの方が若干速いような気がしますけど。
最後に
今回のように、生成される文字列長が予め分かっているような場合には、
都度、文字列を結合するより、
予め、結果の文字列長のダミー文字列を用意しておいて、
それに対して、Midステートメント(Mid関数ではありません)で埋め込んでいったほうが効率的です。
左辺にMidがあるのは、見慣れないかもしれませんが、
覚えておくと便利です。
【VBA】Excelで、ひとつ上の可視セルの値を取得して貼り付けする
Excelでフィルタの適用状態に関わらず、任意のセルの(見た目上の)ひとつ上のセルの値を取得しようというもの。
元ネタはこちら
koroko.hatenablog.com
別の方法で、こちらでも。
infoment.hatenablog.com
両者とは別のアプローチで・・・
目次
抑えておくべき事
Range.Areasオブジェクト
MSのサイトより抜粋。
選択範囲内にある領域 (隣接しているセルのブロック) のコレクションです。
Areasコレクションの各メンバーは、 Range オブジェクトです。 Areasコレクションには、選択範囲内の各セルの不連続な連続した範囲のrangeオブジェクトが1つ含まれています。 選択範囲に領域が1つしか含まれていない場合、 Areasコレクションには、その選択範囲に対応するRangeオブジェクトが1つだけ含まれます。
https://docs.microsoft.com/ja-jp/office/vba/api/excel.areas
Rangeオブジェクトは、Range("A1:C3") といった指定をした場合のように、単一の矩形領域の場合だけでなく
Rrange("A1:C3,D2:F4")とか、Unionメソッドにより複数の矩形領域を纏めてひとつのRangeオブジェクトとして扱ったりする場合もあります。
それらの1個以上の矩形領域をCollectionとして纏められたものが、Range.Areasオブジェクトです。
Itemプロパティ
Areasオブジェクトに含まれているひとつの要素(Rangeオブジェクト)を返すプロパティです。
構文
Areas.Item(Index)
パラメーター
名前 | 必須/オプション | データ型 | 説明 |
---|---|---|---|
Index | 必須 | Long | オブジェクトのインデックス番号を指定する。 |
省略記法
Areas(Index)
という記述でも同様に処理できます。
Countプロパティ
Areasコレクションに含まれるオブジェクトの数を返すプロパティです。
これにより前述のItemプロパティに指定できるインデックスの上限値がわかります。
構文
Areas.Count
実際の例
領域が分かれることで、Areas.Countが増え、それぞれの要素(Rangeオブジェクト)の情報(下の例ではAddress)を取得できています。
フィルターを使用している場合
フィルター適用前の状態が下図の要なデータで見てみる。
C列のデータを選択した状態
B列をDDで抽出した状態
SpecialCells(xlCellTypeVisible)を指定しないと、非表示のセルも含まれてしまう。
SpecialCells(xlCellTypeVisible)を指定することで、可視セルのみを対象としている。
また、非表示セルが間に入ると、Areasオブジェクト内のItemプロパティで取得する領域が別々になっている。
処理
考え方
- 上記のフィルター適用時の結果から、基準となるセルの上の全てのセルのうち、表示されているセルと、基準のセルをUnionで纏める。
- 基準となるセルは、Unionで纏めたRangeの一番下なので、Areas.Item(Areas.Count) にある。
- Areas.Item(Areas.Count).Rows.Count が1ならば、基準セルのひとつ上のセル(見た目ではなく実際の1行上のセル)は非表示なので、Areas.Item(Areas.Count-1) の一番下のセルが求めるセル。
- Areas.Item(Areas.Count).Rows.Count が2以上なら、基準セルの1行上のセルは表示されているので、基準セルの1行上のセルが求めるセル。
コード
取得データ貼り付け処理
データ取得処理
コードをコピーしたい方へ
いつもの通り、コードはダブルクリックすれば選択できますので・・・
使い方
pasetFromAboveCellsValueをイミディエイトウィンドウから呼ぶなり、
適当なボタンに登録するなり、使いやすいようにして下さい。
実行サンプル
最後に
今回は、前回のやつがあったから気がつけた。
ちょっと満足。
貼り付けだけが目的であれば、プロシージャを分けずに1つにした方が
無駄なコピーが無くせるので、スッキリするかも。
単一のセルだけでなく、複数のセルに対応したのと
データの途中の行でも実行可能なので、少しは使えるか?
【VBA】ExcelのUnionで纏められたRangeの Areas.Count と Areas.Item(N)
- 初めに
- Union メソッド
- Areas.Count と Areas.Item(N)
- 複数の Range がひとつの Areas.Item( N ) に纏められるケース(具体例)
- 対象範囲が同じでも結果が変わる例
- コード(抜粋)
- 最後に
初めに
以前の記事で、Union メソッドで纏められた Range へのアクセス方法を書きました。
z1000s.hatenablog.com
その中で、
今回の場合、
赤で囲まれた範囲がArea(1)
青で囲まれた範囲がArea(2)
となり、
r.Areas(1).Address(False, False)は
B2:D4
r.Areas(2).Address(False, False)は
F3:G4
が返ります。
Unionで指定した順番に返ってくるようです。(そうでないと困ります。)
と書いたのですが、このようにならない場合があることがわかりました。
Union メソッドで、複数のRange を纏めた時、
項目 | 値 |
---|---|
Areas.Item(N) | 指定した個々の Range |
Areas.Count | 指定した Range の数 |
となるものと思い込んでいたのですが、そうならない場合があるようです。
また、Union メソッドの実行方法によっては、対象となる範囲が同じでも、Areas.Countや、Areas.Item( N ).Address が変わる場合があることがある事がわかりました。
Union メソッド
まず、改めて Union メソッドについて。
docs.microsoft.com
機能
2 つ以上のセル範囲の集合を返します
構文
expression.Union (Arg1, Arg2, Arg3, Arg4, Arg5, Arg6, Arg7, Arg8, Arg9, Arg10, Arg11, Arg12, Arg13, Arg14, Arg15, Arg16, Arg17, Arg18, Arg19, Arg20, Arg21, Arg22, Arg23, Arg24, Arg25, Arg26, Arg27, Arg28, Arg29, Arg30)
パラメータ
名前 | 必須/オプション | データ型 | 説明 |
---|---|---|---|
Arg1 | 必須 | Range | |
Arg2 | 必須 | Range | |
Arg3~Arg30 | 省略可 | Range | 範囲を指定する |
戻り値
Range
使用例
基本的な使い方は、多分以下の2通りと思われます。
対象となる Range の数が固定の場合は、このような書き方ができます。
Set r = Union(r1, r2, r3, r4)
一方で、対象となる Range が可変の場合や、ループ処理により纏めたいような場合には、こちらの方法が使いやすいと思います。
Set r = Union(r1, r2) Set r = Union(r, r3) Set r = Union(r, r4)
他にも、このような書き方も出来ますが、多分ほとんど使われることはないかと思います。
Set r = Union(Union(Union(r1, r2), r3), r4)
3番目の書き方は、最初の書き方を冗長にしただけのように見えますが、実はそうではありません。
(この件については、後述)
Areas.Count と Areas.Item(N)
実際に返ってくる値
項目 | 値 |
---|---|
Areas.Item(N) | パラメータで指定した1個以上の Range の纏まり |
Areas.Count | 1以上 指定した Range の数以下 |
Areas.Item(N)
コレクションから単一のオブジェクトを返します。
https://docs.microsoft.com/ja-jp/office/vba/api/excel.areas.item
MSのサイトには、下記のような記述があります。
コレクションから単一のRangeオブジェクトを取得するのにには、 Areas (index) を使用します。_引数 index_には、area インデックス番号を指定します。 インデックス番号は、領域が選択された順序に対応します。
https://docs.microsoft.com/ja-jp/office/vba/api/excel.areas
基本的には、この通りになるようなのですが、例外があります。
Union メソッドに指定する Range が、以下のような場合、Areas.Item(N) は、複数の Range を纏めた範囲になります。つまり、パラメータで指定された複数の Range が、ひとつの Areas.Item(N) に纏まってしまう場合があります。
「任意の複数の Range の集まりが矩形となる」場合に、
ひとつの Areas.Item(N) として纏まるようです。
その場合、領域が選択された順序と一致しない場合があります。
Areas.Count
コレクションに含まれるオブジェクトの数を表す長整数型 (Long) の値を返します。
https://docs.microsoft.com/ja-jp/office/vba/api/excel.areas.count
ここで注意すべきは、
”コレクションに含まれるオブジェクトの数”
であって、
”パラメータで渡した( Range )オブジェクトの数”
ではないということです。
前述の通り、パラメータで指定した複数の Range が、ひとつの Areas.Item に纏められる場合、Areas.Count は、それに伴い パラメータの Range 数より少なくなることになります。
Range("A1") から Range("A30") までの30個の Range を Uniono で纏めると
Set r = Union( Range("A1"), Range("A2"), ・・・, Range("A30") )
纏められた Range r は、
Areas.Count | 1 |
Areas.Item(1).Address | A1:A30 |
Areas.Address | A1:A30 |
となります。
複数の Range がひとつの Areas.Item( N ) に纏められるケース(具体例)
Union( r1, r2, r3 )
の記述で、纏めてみた結果が下の図です。
下図のA列にアドレス単独で表示されているものは、Union メソッドに渡した Range であり、Union メソッドには、上から順に渡しています。
なお、下図の中の Areas.Address: の後ろに表示されている [ ] で囲まれたそれぞれの部分が Areas.Item( N ) .Address です。
隣接する2個以上の Range が矩形となる場合
Unionに指定するRangeの順番の影響は見られません。
2個以上の Range の一部が重なるが、全体が矩形となる場合
重なっている部分を含め、全体がひとつのAreas.Item( N ) となります。
Range A に、Range B が含まれる場合
サイズが、基準となる(大きい方の)Range以下の Range は無視されています。
対象範囲が同じでも結果が変わる例
記述の違い(実行方法の違い)によるもの
前述の通り、Union メソッドの書き方は、思いついただけで3パターンあります。
同じ Range を、同じ順番でメソッドに渡しても、返ってくる結果が異なる場合を見つけました。
Range r1、r2、r3 を以下の範囲とします。
変数 | Range |
---|---|
r1 | Range("D4:E5") |
r2 | Range("D2:E3") |
r3 | Range("B2:C3") |
これらの範囲を使用して、以下の式が返す結果を比較してみます。
No. | 式 |
---|---|
1 | Set r = Union(r1, r2, r3) |
2 | Set r = Union(r1, r2) Set r = Union(r, r3) |
3 | Set r = Union(Union(r1, r2), r3) |
No.1 のみが、他と結果が異なっているのがわかります。
No.1 が、r2 と r3 が纏まっているのに対し、
No.2とNo.3は、r1 と r2 が纏まっています。
No. | 式 | Areas .Count | Areas .Item(1) .Address | Areas .Item(2) .Address |
---|---|---|---|---|
1 | Set r = Union(r1, r2, r3) | 2 | D4:E5 | B2:E3 |
2 | Set r = Union(r1, r2)Set r = Union(r, r3) | 2 | D2:E5 | B2:C3 |
3 | Set r = Union(Union(r1, r2), r3) | 2 | D2:E5 | B2:C3 |
No.2、No.3では、順次Unionメソッドで Range を纏めているため、その都度 Areas.Item( N ) が更新されていくため、このような結果になると考えられます。
Union メソッドに渡すパラメータの順番による違い
変数 | Range |
---|---|
r1 | Range("B2:C3") |
r2 | Range("D2:E3") |
r3 | Range("B4:C5") |
として、
Union( Arg1, Arg2, Arg3 ) と呼び出してみました。
No. | Arg1 | Arg2 | Arg3 |
---|---|---|---|
1 | r1 | r2 | r3 |
2 | r3 | r2 | r1 |
No.1 では、縦方向に纏まっていますが、
No. 2 では、横方向に纏まっています。
コード(抜粋)
コードが欲しい方は、コードをダブルクリックすると、少しだけいいことがあるかもしれません
データ設定、描画処理呼び出し
描画処理
その他
最後に
処理方法によって結果が変わる場合があるのは分かった。
キーワードは、矩形
しかし、Union( r1, r2, ・・・, rn ) の場合の規則性が分からん。
Areas.Item( N ).Address が変わって困るような処理は今の所ないから、
今回はここまでで勘弁してやろう・・・ orz
m(_ _)m
【VBA】偶数判定、奇数判定
主に偶数判定のための私的メモ。
判定 | 式 | 結果 | |
---|---|---|---|
偶数の場合 | 奇数の場合 | ||
偶数判定 | ( N Eqv 0 ) And 1 | 0 | 1 |
( N And 1 ) = 0 | True | False | |
奇数判定 | N And 1 | 0 | 1 |
( N And 1 ) = 1 | True | False |
Eqv(XOr の逆パターン)
値1 | 値2 | 値1 Eqv 値2 |
---|---|---|
False | False | True |
False | True | False |
True | False | False |
True | True | True |
Bit版
値1 | 値2 | 値1 Eqv 値2 |
---|---|---|
0 | 0 | 1 |
0 | 1 | 0 |
1 | 0 | 0 |
1 | 1 | 1 |
整数版
値1 | 値2 | 値1 Eqv 値2 |
---|---|---|
0 | 0 | -1 (&HFFFF) |
0 | 1 | -2 (&HFFFE) |
0 | 2 | -3 (&HFFFD) |
1 | 0 | -2 (&HFFFE) |
2 | 0 | -3 (&HFFFD) |
&HFFFF | 0 | 0 |
&HFFFF | 1 | 1 |
&HFFFF | 2 | 2 |
&HFFFF | 3 | 3 |
&HFFFE | 0 | 0 |
&HFFFE | 1 | 0 |
&HFFFE | 2 | 3 |
&HFFFE | 3 | 2 |
【VBA】同じデータのセルを結合(Union未使用版)
下記のサイトで、同じデータのセルを結合するということで、Unionを使ってRangeをまとめて最後にMergeという処理を行っていました。
これはこれで良いのですが、非連続データならともかく、連続データである場合には、
個人的には、「何度もUnionせずに、範囲(開始行と終了行)を取得してMerge」とする方が好みなので、作ってみました。
Do Until の2重ループにして、以下のようなソースに。
(縦方向のマージ処理のみです)
外側のループで、基準となる行を進めていって、
内側のループで、基準となる値が終わる行を取得しています。
Public Sub doMerge() Dim r As Range Set r = Sheet1.Range("A1").CurrentRegion Set r = r.Resize(r.Rows.Count - 1).Offset(1) Call MergeSameValueCellsV(r) Debug.Print "Done." End Sub Private Sub MergeSameValueCellsV(ByRef rTarget As Range) Const TARGGET_COL As String = "A" Dim ws As Worksheet Dim lCurrentRow As Long Dim lNextRow As Long Dim lEndRow As Long Dim lMergeBeginRow As Long Dim lMergeEndRow As Long Dim vBaseValue As Variant If rTarget Is Nothing Then Exit Sub ElseIf rTarget.Rows.Count = 1 Then Exit Sub End If Application.DisplayAlerts = False lCurrentRow = rTarget.Row lEndRow = rTarget.Row + rTarget.Rows.Count - 1 Set ws = rTarget.Parent Do Until lCurrentRow > lEndRow lMergeBeginRow = lCurrentRow lMergeEndRow = lMergeBeginRow vBaseValue = ws.Range(TARGGET_COL & CStr(lCurrentRow)).Value lNextRow = lMergeBeginRow + 1 Do Until vBaseValue <> ws.Range("A" & CStr(lNextRow)).Value lMergeEndRow = lNextRow lNextRow = lNextRow + 1 Loop If lMergeEndRow > lMergeBeginRow Then 'マージ開始行と終了行が違っていたら、マージ処理 ws.Range(TARGGET_COL & CStr(lMergeBeginRow) & ":" & TARGGET_COL & CStr(lMergeEndRow)).Merge '次の処理は、マージした次の行から lCurrentRow = lCurrentRow + lMergeEndRow - lMergeBeginRow + 1 Else 'マージしないので次の行 lCurrentRow = lCurrentRow + 1 End If Loop Application.DisplayAlerts = True End Sub
今日は(も?)、人のネタを元にする日・・・