プログラミングの最近のブログ記事

今、触っているのが Visual Studio 2005(以降、VS 2005)の VB.NET なんで、最新の .NET ではどうなのかわからないが・・・

本来、縦 1000px で表示する画面を、縦が 700pxほどしかないディスプレイで表示したところ、ディスプレイ内に表示できるように(?)自動的に画面の縦が 700pxにリサイズされてしまった。
(ちなみに FormBorderStyle プロパティが FixedSingle に設定されているのでユーザが自分でリサイズすることはできない)

現在の環境は、ディスプレイ1がノートパソコンのディスプレイで、本来 1,920 x 1080 の解像度のディスプレイを 150% 表示としているため、縦は 700px となっている(加齢のため、字が小さくてよく見えないもんで(^^;)。もう一台、「複数のディスプレイ」を「表示画面を拡張する」の設定でディスプレイ2が接続されており、こちらは 1,680 x 1,050 の解像度で 100% 表示としている。

本来、.NET アプリの画面サイズが 1,280 x 1,000 なので、ディスプレイ2の方で表示してデバッグなどしていたのだが、ディスプレイ2の電源を入れずに起動してしまい、自動的にディスプレイ1で表示されてしまったのだ。

で、その後、縦 700pxの状態から戻らなくなってしまった。

VS 2005 のデザイン画面で Size プロパティを確認しても、ちゃんと 1,280 x 1,000 になっている。しかし、ディスプレイ2で表示しても 1,280 x x 700 で表示されてしまう。
試しに Size プロパティで縦を 900pxとかに変えてみても表示は変わらない。実際の画面表示サイズは 1,280 x 700 のままなのである。どうも、Size プロパティの値を正しく認識していない???

これは、Visual Studio を立ち上げ直してみても、PC を再起動してみても変わらなかった・・・

20210324_vs2005.jpg

結局、原因は(片方の)ディスプレイを 150% 表示していたためのようである。

ディスプレイ設定画面で「拡大縮小とレイアウト」の「テキスト、アプリ、その他の項目のサイズを変更する」設定を 100% にしたところ、Size プロパティの値が 1,280 x 1,000 から実際の表示サイズである 1,280 x 700 に変わっていたので、これを 1,280 x 1,000 に変更したところ表示は正常に戻った。

もちろん、Visual Studio が 2005 という古いものだというのはあるだろう。その時代には Windows の設定で画面の倍率変更ってできなかったのかも?だから VS 2005 はそのあたりの値が正常に取得できず誤動作してしまうのか???

まあ、それでもあえて言おう。

「Microsoft よ・・・。なんだかな~、なんだかな~」
Microsoft Report のメモ。

前の行の内容と同じだったら空白表示・・・というケース。

20210322_report1.jpg

この場合の「式」の例。(例えば「顧客番号」のフィールド)

=IIF(RowNumber(Nothing) > 1, (IIF((Previous(Fields!client_cd.Value) = Fields!client_cd.Value), "", Fields!client_cd.Value)), Fields!client_cd.Value)

    • 1行目は「前の行(Previous)」がないので、そのまま値を表示
    • 2行目以降は、「前の行(Previous)」の client_cd と現在の行の内容が同じなら空値(空白)表示

ちなみに、Fields!client_cd.Value は表示される値ではなく、その列にセットされる値そのものである。
そのため、例えば前の行が空白か?というような判断は不要。常に Previous(Fields!client_cd.Value) と Fields!client_cd.Value の値を比較するだけでよい。
もう Excel VBA ネイティブな人たちの間では、何年も前に話題になったであろう今更な話(笑)。

お客さんに送ってもらった Excel シートで「コンパイルエラー:このプロジェクトのコードjは、64 ビット システムで使用するために更新する必要があります。Declare ステートメントの確認及び更新を行い、次に Declare ステートメントに PtrSafe 属性を設定してください。」のエラーメッセージが表示される。

セルの編集をすると実行される VBA マクロがあり、それがエラーになっているようだ。

引っかかっているのは、メッセージにあるように 

Private Declare Function SHBrowseForFolder Lib "SHELL32" (lpbi As BROWSEINFO) As Long
Private Declare Function SHGetPathFromIDListLong Lib "SHELL32" Alias "SHGetPathFromIDList" (ByVal pIDL&, ByVal lpStr$) As Long
Private Declare Function GetForegroundWindow Lib "USER32" () As Long

この部分。

メッセージに書かれているように、PtrSafe オプションを追加してやるだけでいい。

Private Declare PtrSafe Function SHBrowseForFolder Lib "SHELL32" (lpbi As BROWSEINFO) As Long
Private Declare PtrSafe Function SHGetPathFromIDListLong Lib "SHELL32" Alias "SHGetPathFromIDList" (ByVal pIDL&, ByVal lpStr$) As Long
Private Declare PtrSafe Function GetForegroundWindow Lib "USER32" () As Long

これでエラーは出なくなる。

Declare ステートメントで Dynamic Link Library(DLLファイル)内の外部プロシージャへの参照を宣言するのだが、64bit版の VBA では必ず PtrSafe を付けないといけないらしい。「この外部プロシージャが 64bit版 Office で実行しても安全よ」ということを表明するだけのオプションだと聞いたことがあるが、本当なのか、どうなのか?(笑)
まあ、64bit版で動かして安全なのかどうなのかは・・・知らんけど(^^;;;
指定せんと動かんからな。あとで、「あんたが『安全』だと表明して PtrSafe オプション設定したんでしょ!」とか言われたら困るけどな(^^;

ちなみに、この Excel シートを 64bit版でも 32bit版でも参照するのであれば、

#If VBA7 And Win64 Then

    Private Declare PtrSafe Function SHBrowseForFolder Lib "SHELL32" (lpbi As BROWSEINFO) As Long
    Private Declare PtrSafe Function SHGetPathFromIDListLong Lib "SHELL32" Alias "SHGetPathFromIDList" (ByVal pIDL&, ByVal lpStr$) As Long
    Private Declare PtrSafe Function GetForegroundWindow Lib "USER32" () As Long

#Else

    Private Declare Function SHBrowseForFolder Lib "SHELL32" (lpbi As BROWSEINFO) As Long
    Private Declare Function SHGetPathFromIDListLong Lib "SHELL32" Alias "SHGetPathFromIDList" (ByVal pIDL&, ByVal lpStr$) As Long
    Private Declare Function GetForegroundWindow Lib "USER32" () As Long

#End If 

このように条件付きコンパイルをしてやる必要がある。

今回は俺のPC+Office(64bit環境)でしか使わないので、PtrSafe をべた書きで追加しただけということ。
VB.NET なんだから Shift_JIS については頑張れ・・・という話。

(他人の)プログラムのテストをしているのだが、固定長のデータ(Shift_JIS の日本語混じり)の処理でちょっとはまった。

Using tfp = fs.OpenTextFieldParser(path)

で読み込んだテキストファイルのレコード(1行)毎に、

Dim ByteArray As Byte() = Encoding.GetEncoding("Shift_JIS").GetBytes(Str)

みたいにバイト単位に分解して、バイト数を指定してデータを抜き出すんだけど、なんか、範囲外へのアクセスが行われた関係の例外が発生する。

調べると、227バイトのはずのレコードが 225バイトになっている。うまくエンコードできてないんやなあ・・・

こりゃ、読み込んだ時点で Shift_JIS だと判断されてないで・・・と思い、OpenTextFieldParser で文字コードを指定しようと思ったんだけどできんのやね。それならバイナリモードみたいなん、ないの?と思ったけどよくわからん。

結局、

File.ReadAllLines(path, Encoding.GetEncoding("shift_jis"))

で読み込めば問題なく処理された。

やれやれ。Microsoft なんだから、しっかり Shift_JIS は処理せえよ(笑)

ちなみに、こういう話になると、自称 UNIX 技術者が「Shift_JIS なんか捨てなよ」で話を終わらせようとする場面をよく見る。
思考停止も甚だしい。この世には Shift_JIS のファイルも、Excel 方眼紙も、まだまだ存在しているんだ。「Excel なんか捨てなよ」って簡単に言うやつは単なる「思考停止のふにゃちん技術者」。「捨てなよ」だけではなんの解決にもならない。

「もう、こういうのはやめましょうね」は良い。しかし、過去のデータは簡単には捨てられない。そこで「捨てちゃいなよ」しか言わないやつは糞の役にも立たないので黙っとけってことなのだ(笑)。お前だ、お前。まず、「捨てちゃいなよ」「やめちゃいなよ」の前に現状の解決策を示せ。
Web アプリの開発をやっていると、生のメールソースの解析をすることが時々あるんだけど、そのときよく使う Web サービス。


と、


ね。

アプリ内で MIMEエンコードしたあとの文字列をチェックする必要があるときとかにちょっと利用する。
いや、自分でもツール作ってるんだけど、CentOS サーバのシェル上でコマンド起動なんで(Perlスクリプトなんでな(笑))、がっつり開発をしているときはいいんだけど、ちょっとお客さんから「このメールがうまくアプリで読み込めないんだけどなんで?」とか質問されたときに、ちょっと中身を調べるのに Web サービスが便利だ。

例えば、

=?UTF-8?B?Y2FrZXPnt6jpm4bpg6g=?= <support@cakes.mu>

を MIMEデコードすれば、

cakes編集部 <support@cakes.mu>

になる。

「URL、MIME、BASE64エンコードツール」はエンコード(暗号化)とデコード(復号化)ができるので日頃重宝しているのだが、どうもバグがあって、エンコードした文字列に + 記号が入っているとデコードしてくれない(^^;;;(2020/11/23現在)
バグの連絡先もわからないので放置しているが、多分 + 記号のエスケープ処理が抜けてるだけだと思うので、連絡先を知っている人は教えてあげて。

で、そういうときは「読めないメールヘッダを変換!MIMEデコードツール」を使う。

まあ、他にもエンコード/デコードサービスは色々あるので、試してみると(バグを発見したりして(笑))楽しいかも。
例えば、子供向けに、一問一問別ファイルになったなぞなぞ音声ファイルのうちから 3問だけランダムに抜き出して、ひとつの音声ファイルにしてダウンロードさせるとか、まあ、そういう感じのデモ要件があるので、プログラムを作ってみた。

まあ、ffmpeg 使えば一発なんだけどね。

プログラム的には、結合する 3つのファイルをランダムにもとめて、そのファイル名を i オプションと一緒に連結していくだけ。(ひとつのファイルごとに i オプションは必要なので注意)

下のように編集したオプションで ffmpeg を実行するだけだ。
(出力ファイル名を毎回同じにするなら、上書き許可の y オプションもお忘れなく。プログラム内から呼ぶのであれば、ffmpeg は大量のログを標準出力に出すので、loglevel オプションでの抑制もお忘れなく)

/usr/bin/ffmpeg -y  -i /data/11.mp3 -i /deta/7.mp3 -i /data/5.mp3  -loglevel quiet -filter_complex "concat=n=3:v=0:a=1" /data/nazonazo.mp3

赤いところがプログラムの編集が必要なところ。
n=3 は入力ファイルの数で、最後の /data/nazonazo.mp3 が出力ファイル名だ。

連結対象のファイルの個数が変わらないのなら n=3 のところは変更不要だし、毎回同じファイルに書くのなら /data/nazonazo.mp3 も編集不要。

結合したファイルを聞いてみたが、つなぎ目に変なノイズが入ることもなく、なかなかいい感じであった。
まあ、DateTime 型のオブジェクトには AddMonths メソッドがあるので、

Dim hoge_today As DateTime = "2020/03/23"
Dim hoge_addmon As DateTime = hoge_today.AddMonths(1)

とすれば、hoge_addmon には 2020/04/23 がセットされる。

一応、自動で月の最終日の判断はするので、

Dim hoge_today As DateTime = "2020/03/31"
Dim hoge_addmon As DateTime = hoge_today.AddMonths(1)

であれば、hoge_addmon には 2020/04/30 がセットされる。
決して、2020/05/01 であったり、ましてや 2020/04/31 になってしまうわけではない。

なんか、ネット上の情報を見ていると、「.NET って、ちゃんと最終日の判断してるやん!!」「3/31の一ヶ月後が 4/30 になってて感動!」みたいな記事が多い。
いや、それは当たり前なんちゃう?(^^; そんな感動することか!?と思うのだが、VB.NET なんかは、何もプログラミングのことを知らずに入社したプログラマが最初に使う言語ということも多いだろうから、こういう感動をする人も多いのかなあ・・・とか思ったり。

ただこの「自動で最終日をセットしてくれて素敵」は、基準となる年月日が「31日まである月」のみの話である。

Dim hoge_today As DateTime = "2020/02/29"
Dim hoge_addmon As DateTime = hoge_today.AddMonths(1)

の場合、hoge_addmon には 2020/03/29 がセットされる。最終日でもなんでもない。

Dim hoge_today As DateTime = "2020/02/29"
Dim hoge_addmon As DateTime = hoge_today.AddMonths(-1)

なら、2020/01/29 がセットされる。やはり 1月の最終日ではない。

結局、翌月の最終日を求めようと思えば、単純に AddMonths を使うのではなく、

Dim old_mm As String = hoge_today.ToString("MM")
If hoge_today.AddDays(1).ToString("MM") <> old_mm Then
    saisyu_flg = 1
End If

みたいに最終日かどうかをチェックして、最終日だったときは、

Dim days As Integer = DateTime.DaysInMonth(hoge_today.AddMonths(1).Year, theDay.AddMonths(1).Month)
Dim hoge_addmon As DateTime = New DateTime(hoge_today.AddMonths(1).Year, hoge_today.AddMonths(1).Month, days)

みたいにしないとダメ?

すんまへん。VB.NET とかそんなに詳しいわけじゃないんで、もっとこんな簡単な方法が・・・みたいなものがあればご教示ください。>識者の方
そうか(笑)

VB.NET で DBNull を String 変数に代入するときは、ToString() でよかったのか(^^;;;
(あ、もともと DB からも文字型として読み込んだデータの話ね。もちろん数値型のデータは ToString してたよ)

例えば、dt As DataTable に DB から取得した値が入っているとして、それを a As String な変数に代入する際、例外発生を回避するために、

Dim a As String = if (IsDBNull(dt(cnt)("namae")), String.Empty, dt(cnt)("namae"))

とかやりよったわぁ。

Dim  a As String =  dt(cnt)("namae").ToString()

でよかったのね(笑)

ToString メソッドは、DBNull だったら空文字に変換してくれるのか。なぜか、Nothing を渡すものだと思ってた。

ということで、わざわざ If 文で置換してやらなくても、とりあえず(元の変数が String 型だろうが関係なく)ToString() して代入というのがコーディングも簡単でいいね。

早く教えてくれなきゃ(笑)
VisualBasic は文字列補間(変数補間、変数置換)の機能がないので、ある文字リテラルの中に別の変数の値を埋め込もうとすると、

Dim day  As String = "13"
Dim text As String = "死の数字は" & day & "です"

のように、&(+ でもいいのか)で変数を連結してやる必要があったのだが、今のバージョンだと、

Import System.Text
Dim day  As String = "13"
Dim text As String = $"死の数字は{day}です"

のように、文字列内に変数を埋め込むことができる。
文字列を囲むダブルクオーテーションの前に、$(ダラーマーク)を置くことで変数を挿入できるようになる。(System.Text のインポートを忘れずに(笑))

他の言語(C, Perl, PHP, Python...)だと文字列補間はできて当たり前なので、どうにも VisualBasic が使いづらいと考える理由のひとつだったのだが、いつの間にか解決してたのね。

ちなみに Perl なら、

my $day  = "13";
my $text = "死の数字は$dayです";

である。

VB.NET で $ をつけ忘れたら文字列補間は使えないが、これは Perl で文字列をシングルクオーテーションで囲んでしまった場合と同じだ。

[VB.NET]
a = $"これは{b}です"
[Perl]
$a = "これは$bです";

上の二つは同じ意味(文字列補間可能)だし、

[VB.NET]
a = "これは{b}です"
[Perl]
$a = 'これは$bです';

は同じ意味だ。文字列補間はできない。

下の例だと、VB.NET では「これは{b}です」とそのまま出力されるし、Perlでも「これは$bです」とそのまま出力される。

「VB.NET 使いの人」から見たら、「何をいまさら」なんだろうけど、俺のように色々な開発言語の現場を流れ歩き、ひとつの言語を突き詰めて勉強する機会がないと、例えば & で連結するなど(不格好だが)他の手段を知っていればそれを使うし、なかなか知識のアップデートができないのよね。これもそのひとつ。

コーディング基準・規約などできちんと「可読性向上のため、補間文字列を積極的に使用する」などと明記されていれば気づくけど。
以前からこのブログにもたまに書いていたが、LibreOffice Calc でサーバが吐き出したテキストファイルを読み込み集計、最後にその結果を元に印刷処理を行うマクロを Python で書いていたのだが、OpenOffice.org 系オフィススイーツの定番マクロ言語である Basic で書き直すことにした。

理由は色々あるので、別のエントリーにまとめようかな。

とりあえず、今日の日付をセルにセットするだけの簡単な処理を Python と Basic で書いてみたので貼っておく。どちらもまったく同じ動きをする。

def get_today(*args):

    doc = XSCRIPTCONTEXT.getDocument()

    # シートを選択
    sheet = doc.getSheets().getByName('Main')

    # 今日の日付の取得
    dt_now = (datetime.datetime.now()).strftime('%Y/%m/%d')

    # すでにセルに値が入っている時は更新するか確認する
    kaisai_nengetsu = sheet.getCellRangeByName('C3')

    if kaisai_nengetsu.String:
        msg_box = Bridge()
        res = msg_box.run_querydialog(title='開催年月日セット', message='すでに値がセットされています。\n上書きでセットしますか?')

        # res=2 はい res=3 いいえ
        if res != 2:    # 「はい」でなければ終了
            return

    kaisai_nengetsu.String = dt_now

次に Basic で書かれたマクロ。

Option Compatible

Sub get_now_date

Dim objDoc As Object
Dim objSheet As Object
Dim objCell As Object

objDoc = StarDesktop.CurrentComponent
objSheet = objDoc.Sheets.getByName("Main")

' 既にセルに値が入っていたら、上書きするかを聞く       
objCell = objSheet.getCellRangeByName("C3")

If objCell.String <> "" Then
If MsgBox ("すでに値がセットされています。" & vbCrLf & "上書きでセットしますか?", 292, "開催年月日セット") = 7 Then
        ' いいえのボタンが押された場合
return
End If
End If 

' 現在時刻の取得
objCell.String = Format(Now(), "yyyy/mm/dd")

End Sub

以上。まあ、これだけ簡単な処理では特にどっちの言語で書いてもなんの問題もない。
見比べてもあまりおもしろいもんじゃないな(笑)

<追記>
コメントで「文字列リテラル中の \n は効かない」ということを教えていただきましたので、Option Compatible を指定して VBA compatibility モードをオンにし、vbCrLf 定数を連結する形に修正しました。16進文字"\x0D\x0A"を直打ちでもよさげですが、ついでに VBA compatibility モードを試してみたかったので。

このアーカイブについて

このページには、過去に書かれたブログ記事のうちプログラミングカテゴリに属しているものが含まれています。

前のカテゴリはパソコンです。

最近のコンテンツはインデックスページで見られます。過去に書かれたものはアーカイブのページで見られます。


月別 アーカイブ

電気ウナギ的○○ mobile ver.

携帯版「電気ウナギ的○○」はこちら