[2019/06/20] EXCELファイルはXMLファイルの集まり(ZIPファイル)? (No.106)
[2019/06/11] プログラムの多重起動の抑止(Mutexを使った方法) (No.102)
[2019/05/09] スレッドを停止させる方法について (No.97)
[2019/05/08] 引数があるスレッドの実行について(パラメータ付きスレッド) (No.96)
-
文字列の分解は、文字列オブジェクトのメソッド Split を用いて行います。 一般的な例として日付を分解する方法を見てみます。
'文字列の分解の例(年月日を例とする) Dim strTest As String = "2019/08/01" '"/"(スラッシュ)で分解する Dim arr As String() = strTest.Split("/") '3個の配列で「arr」には入る For i As Integer = 0 To UBound(arr) Console.WriteLine("arr({0}) : {1}", i, arr(i)) Next動作結果として、デバッグ時の出力ウインドウには以下の様に表示されます。 日付文字列が「/」(スラッシュ)で分解されることが分かります。 但し、注意する点は、配列要素には指標が「0」から順に格納されることです。
上の例では、文字列配列「arr」が何個返されるか分からないので、 UBound 関数を使って最後の指標の値を指定しています。arr(0) : 2019 arr(1) : 08 arr(2) : 01
さらに、以下の例を見て下さい。
'文字列の分解の例(年月日を例とする:スラッシュは無い) strTest = "20190801" '"/"(スラッシュ)で分解する arr = strTest.Split("/") '1個の配列で「arr」には入るはず For i As Integer = 0 To UBound(arr) Console.WriteLine("arr({0}) : {1}", i, arr(i)) Next実行結果は以下の通りです。
arr(0) : 20190801
分解する文字列にはスラッシュは無いので、元の文字列が要素が1個の配列の先頭に返されます。
これは少し、問題が有りまして、例えば分解すべき文字列がテキストボックスか何かで入力する場合、 スラッシュで分解された配列が3個だと固定で考えて組んでしまうと、エラーが発生するはずです。 それが、以下の様な例です。'文字列を「TextBox1」から取るとする Dim strTest As String = Me.TextBox1.Text '"/"(スラッシュ)で分解する Dim arr As String() = strTest.Split("/") '3個の配列で「arr」には入る Dim strYMD As String = arr(0) + arr(1) + arr(2) Console.WriteLine("日付 : {0}", strYMD)「TextBox1」に「2019/08/01」等のスラッシュがあるデータが入力されればいいのですが、 「20190801」と入力すると、日付の連結のところでエラーが発生します。
このエラーを回避するには、分解された文字列配列の個数をチェックする方法があります。
または、テキストボックスでは無く必ず日付文字列が返されるであろう日付型コントロールを使うのも一つです。
Split には同じ名前で別の引数を持つメソッドがあります。 以下の例は、分解文字を配列で与えるメソッドです。 「/」(スラッシュ)「 」(スペース)「:」(コロン)を文字配列で与えることで、年月日時刻を一括で分解することが出来ます。'文字列の分解の例(年月日を例とする) Dim strTest As String = "2019/08/01 13:15:47" '"/"(スラッシュ)で分解する Dim arrSplit As Char() = {"/"c, " "c, ":"c} Dim arr As String() = strTest.Split(arrSplit) '3個の配列で「arr」には入る For i As Integer = 0 To UBound(arr) Console.WriteLine("arr({0}) : {1}", i, arr(i)) Next実行結果は以下の通りです。
arr(0) : 2019 arr(1) : 08 arr(2) : 01 arr(3) : 13 arr(4) : 15 arr(5) : 47
関連する記事
⇒文字列の連結をStringBuilderで高速に行う(StringBuilder)
⇒文字列定数(改行、タブ、バックスペース等)について
⇒文字列変換関数(StrConv)で変換が途中で切れる
⇒文字列変換関数(StrConv)の使い方
PR -
EXCELファイルの拡張子が xlsx のファイルですが、多分 EXCEL2007以降のバージョンでEXCEL2010,2013,2015などで出力される形式があります。 このファイルですが、中身はXMLファイルの集まりが圧縮されているそうなのです。 お恥ずかしい限りですが、つい最近まで知らなかったのですが、EXCELファイルをZIPファイルにリネームし圧縮を解凍すれば中身が見られます。
そこで、どうなっているのかを調べてみたので順に見ていきます。
■先ずはテスト用のEXCELファイルを作成します。簡単な内容のデータを作成します。
■フォルダの中に test.xlsx ファイルが作成されました。
■test.xlsx ファイルのコピーを作成します。
■test.xlsx のコピーファイルのファイル名を test.zip に変更します。
■test.zip ファイルを展開してやります。
以下は test.zip ファイルの上で右クリックをしたところですが、その中の「すべて展開(T)...」を選択します。 (私のPCでは各種ツールが入っていますので、変なメニューも表示されていますが)
■展開結果は以下の様になります。
展開された中のXMLファイルを見てみます。 XMLファイルの中身を見たい場合にはXML表示ソフトを使いますが、以下のフリーソフト「XMLEDITOR.NET(xml.exe)」が使いやすいと思います。 このソフトを使ってXMLファイルをのぞいてみます。
⇒XMLEDITOR .NETについて
■\test\xl\worksheets フォルダの中の sheet1.xml ファイルを「XMLEDITOR.NET(xml.exe)」で見てみます。
以上からエクセルのファイルは各種のXMLファイルで構成されていることが分かります。 尚、今回なぜエクセルの中身を解析する必要が在ったかと言いますと、PHPでのエクセル出力で、エクセルのグラフの表現がどうなっているかを調査するためでした。
VB.NETからエクセルを使う場合はエクセルのオブジェクトからそのメソッド等を利用して操作ができるので、中身までは意識しませんが、 PHPからの処理ではPHPExcleライブラリだけでは難しい部分もありますので、参考になればと思います。関連する記事
⇒Excel ファイルの操作について(Microsoft.Office.Interop.Excel COM参照)
-
プログラムの多重起動を抑止する方法(自分自身が1回しか起動されないようにする方法)について説明します。 抑止する方法はいろいろあるのですが Mutex を使った方法が比較的簡単だと思いますので、今回はその方法について記します。
先ずは Mutex についてですが、これは排他制御を行う為の仕組みで、現在それを「使用中」か「未使用」かを示すフラグの様なものです。 あるプロセス(実行プログラム)が、ある Mutex を取得しそのフラグを「使用中」にします。 再度そのプロセスを実行した場合にその Mutex は既にフラグが「使用中」であれば、起動を中止する様にします。 (簡単な「セマフォ」であるとも言えます)
.NET には Mutex クラスがありますのでそれを使うことになります。Mutex クラスについて
<コンストラクタ> System.Threading.Mutex(initiallyOwned As Boolean, name As String, ByRef createdNew As Boolean) ・initiallyOwned:名前付きシステム ミューテックスの初期所有権を付与する場合は true。 それ以外の場合は false。 ・name :Mutex の名前です。 ・createdNew :指定した名前付きシステム ミューテックスが作成された場合はブール値 true が格納されます。 指定した名前付きシステム ミューテックスが既に存在する場合は false が格納されます。
Mutexを使ったプログラムは以下の様になります。 上記の Mutex クラスを生成した結果 createdNew フラグを確認することで Mutex が生成されたかが分かります。 true でなければ実際の処理は行わない様にして処理を抜ける様にします。
Mutexを使ったプログラムの多重起動の抑止
Module MdlMain 'Main関数 Sub Main() 'ミューテックス名(一意な名前にすること) Dim mstrMutexName As String = "TestProgramName" '(仮の名前) 'ミューテックス Dim mMutex As System.Threading.Mutex = Nothing '新規作成フラグ Dim blnCreated As Boolean = False Try Try '名前付きシステム ミューテックスの初期所有権を付与 mMutex = New System.Threading.Mutex(True, mstrMutexName, blnCreated) Catch ex As Exception MessageBox.Show("多重起動はできません。Mutex Error[" & ex.Message & "]") Return End Try 'ミューテックスが作成されたか調べる If blnCreated = False Then MessageBox.Show("多重起動はできません。") Return End If '既設のMainメソッドの処理を実行(表示させたいフォームを表示) Dim frm As New frmTest Application.Run(frm) Finally If blnCreated Then 'ミューテックスを解放する mMutex.ReleaseMutex() End If mMutex.Close() End Try End Sub End Module
プログラム中の frmTest は通常のフォームを作成しただけで、フォームの動きは特にプログラミングしていません。
実行の様子を下図に示します。作成された実行ファイルを起動し、その後同じものを起動した時の様子です。
尚、今回の例では Sub Main() メソッドを開始指定(エントリポイント指定)をしてやるのですが、 「プロジェクト・プロパティ」の中の「アプリケーション」タブにある「スタートアップフォーム」でエントリポイントを指定するのですが、 このままでは Sub Main() が中に表示されません。
そこで「アプリケーションフレームワークを有効にする」チェックボックスを無効にすると 「スタートアップフォーム」は「スタートアップオブジェクト」の表示となり、 Sub Main() が出てきますので、それを指定します。
-
スレッドを以下のプログラムの様に作成し開始させると Main 関数は先に終了しますが、スレッドは約5秒後にしか終了しないため、 全体のプログラムの実行はスレッドが終了するまで続きます。
コンソールへの表示からも分かりますが「Main終了」が表示された後で「...スレッド」が5回表示されます。
これは何もおかしいことでは無く、プログラムは属する全てのスレッドが終了するまでは終われないのです。
Main 関数も1個のスレッド(メインスレッド)として実行されるのでが処理的に先に終わってしまいます。
しかし、別のスレッドとして起動された ThreadProc 関数が残っているので、このスレッドが終了するまで プログラムは終了しません。スレッドの実行プログラム
Imports System Imports System.Threading Module ThreadStop 'メイン関数(エントリーポイント) Sub Main() 'スレッド生成 Dim pThread As Thread = New Thread(AddressOf ThreadProc) Console.WriteLine("スレッド開始") 'スレッド開始 pThread.Start() Console.WriteLine("Main終了") End Sub 'スレッド関数 Sub ThreadProc() For i As Integer = 1 To 5 Threading.Thread.Sleep(1000) Console.WriteLine("...スレッド") Next End Sub End Module取敢えず、実行結果がコンソールに以下の様に表示されます。
スレッド開始 Main終了 ...スレッド ...スレッド ...スレッド ...スレッド ...スレッド
それでは Main 関数の中でスレッドの終了を待つにはどうすればいいのでしょうか。 そのために Thread クラスには Join メソッドがあります。 この Join をスレッドの実行側で呼出せば、呼び出し側の処理がブロックされます。
(Join 実行した場合に制御が戻ってこない)
先ほどの例に Join を追加した例を示します。
スレッドの実行を待つプログラム
Imports System Imports System.Threading Module ThreadStop 'メイン関数(エントリーポイント) Sub Main() 'スレッド生成 Dim pThread As Thread = New Thread(AddressOf ThreadProc) Console.WriteLine("スレッド開始") 'スレッド開始 pThread.Start() 'スレッド終了待機 pThread.Join() Console.WriteLine("Main終了") End Sub 'スレッド関数 Sub ThreadProc() For i As Integer = 1 To 5 Threading.Thread.Sleep(1000) Console.WriteLine("...スレッド") Next End Sub End Module実行結果は以下の様に「Main終了」が最後に表示されます。
スレッド開始 ...スレッド ...スレッド ...スレッド ...スレッド ...スレッド Main終了
Join の替わりにスレッドが生きているかどうかのフラグを見て以下の様にしても同じです。
スレッドの実行を待つプログラム2
Imports System Imports System.Threading Module ThreadStop 'メイン関数(エントリーポイント) Sub Main() 'スレッド生成 Dim pThread As Thread = New Thread(AddressOf ThreadProc) Console.WriteLine("スレッド開始") 'スレッド開始 pThread.Start() 'スレッド終了待機 While pThread.IsAlive = True Threading.Thread.Sleep(10) End While Console.WriteLine("Main終了") End Sub 'スレッド関数 Sub ThreadProc() For i As Integer = 1 To 5 Threading.Thread.Sleep(1000) Console.WriteLine("...スレッド") Next End Sub End Module
それでは標題にある様にスレッドを停止させる方法についてですが、 Abort メソッドを使用します。 スレッドの中で非常に時間の掛かる処理を行わせて、途中で停止させる例を以下に示します。スレッドの実行を途中で停止させるプログラム
Imports System Imports System.Threading Module ThreadStop 'メイン関数(エントリーポイント) Sub Main() 'スレッド生成 Dim pThread As Thread = New Thread(AddressOf ThreadProc) Console.WriteLine("スレッド開始") 'スレッド開始 pThread.Start() '10秒待つ Threading.Thread.Sleep(10000) 'スレッド停止 pThread.Abort() 'スレッド終了待機 pThread.Join() Console.WriteLine("Main終了") End Sub 'スレッド関数 Sub ThreadProc() For i As Integer = 1 To 1000 '時間の掛かる処理 Threading.Thread.Sleep(1000) Console.WriteLine("...スレッド") Next End Sub End Module実行結果は以下の様になり「...スレッド」が10回表示されてからスレッドが停止していることが分かります。
スレッド開始 ...スレッド ...スレッド ...スレッド ...スレッド ...スレッド ...スレッド ...スレッド ...スレッド ...スレッド ...スレッド Main終了
スレッドをいきなり終了させるのも怖いので、スレッドに対してフラグで指示を行い、 スレッドの中でフラグを見て終了させるようにしてみます。
尚、スレッドを含む処理をクラスの中に入れ込んで、 その中で開始メソッドや停止メソッドを作成します。スレッドの実行を途中で停止させるクラスの導入
Imports System Imports System.Threading Module ThreadStop 'メイン関数(エントリーポイント) Sub Main() 'スレッド用クラスの生成 Dim pclsThread As New clsThread 'スレッド開始 pclsThread.Start() Console.WriteLine("スレッド開始") '10秒待つ Threading.Thread.Sleep(10000) 'スレッド停止 pclsThread.Abort() Console.WriteLine("Main終了") End Sub 'スレッド用クラス Class clsThread 'スレッド Private mThread As Thread = Nothing 'スレッド停止フラグ Private mblnStop As Boolean = False '開始メソッド Sub Start() mThread = New Thread(AddressOf ThreadProc) 'スレッド開始 mThread.Start() End Sub '停止メソッド Sub Abort() 'スレッド停止フラグON mblnStop = True 'スレッド停止を待つ mThread.Join() End Sub 'スレッド関数 Private Sub ThreadProc() For i As Integer = 1 To 1000 If Me.mblnStop = True Then 'スレッド停止フラグONならば処理中断 Exit For End If Threading.Thread.Sleep(1000) Console.WriteLine("...スレッド") Next End Sub End Class End Module
-
スレッドの関数に引数を渡して別々の処理をさせたい場合があると思いますが、 スレッドを生成する時にアドレス指定のところに ParameterizedThreadStart デリゲートを使うことで可能です。
以下に実行プログラムの例を示します。引数があるスレッドの実行プログラム
Imports System Imports System.Threading Module ThreadParam 'メイン関数(エントリーポイント) Sub Main() '[ParameterizedThreadStart]デリゲートを使ったスレッド生成 Dim pThread As Thread = New Thread(New ParameterizedThreadStart(AddressOf ThreadParam)) '(ParameterizedThreadStart)の指定が無くてもOKですが 'Dim pThread As Thread = New Thread(AddressOf ThreadParam) 'スレッド開始 pThread.Start("スレッド...") Console.WriteLine("スレッド開始") End Sub '引数を持つスレッド関数(引数は1個のオブジェクトのみ) Sub ThreadParam(ByVal obj As Object) For i As Integer = 1 To 5 Threading.Thread.Sleep(1000) Console.WriteLine(obj.ToString()) Next End Sub End Module取敢えず、実行結果がコンソールに以下の様に表示されます。
スレッド開始 スレッド... スレッド... スレッド... スレッド... スレッド...
ParameterizedThreadStart の利用は引数が1個なので複数のデータを渡す場合には 配列等(リストオブジェクト等)にして、スレッド関数側で配列の内容について処理を規定する必要があります。
以下に引数をオブジェクトの配列として渡す場合の例を示します。引数があるスレッドの実行プログラム2
Imports System Imports System.Threading Module ThreadParam 'メイン関数(エントリーポイント) Sub Main() '[ParameterizedThreadStart]デリゲートを使ったスレッド生成 Dim pThread As Thread = New Thread(New ParameterizedThreadStart(AddressOf ThreadParam)) 'オブジェクト配列データ設定 Dim arrObj(2) As Object arrObj(0) = "1111" arrObj(1) = 12345 arrObj(2) = True 'スレッド開始 pThread.Start(arrObj) Console.WriteLine("スレッド開始") End Sub '引数を持つスレッド関数(引数は1個のオブジェクトのみ) Sub ThreadParam(ByVal obj As Object) '引数をオブジェクト配列として扱う Dim pArr As Object() = CType(obj, Object()) For i As Integer = 0 To pArr.Length - 1 Threading.Thread.Sleep(1000) Console.WriteLine(pArr(i).ToString()) Next End Sub End Module引数にして渡さなくても、スレッド実行をラップする様なクラスを導入して コンストラクタの引数でデータ型を規定することもできます。
以下に、スレッドを実行するためのクラスを導入した例を示します。スレッドの引数としてクラスでラップした実行プログラム
Imports System Imports System.Threading Module ThreadParam 'メイン関数(エントリーポイント) Sub Main() 'スレッド用クラスの生成 Dim pclsThread As New clsThread(12345, "AAA", False) 'スレッド開始 pclsThread.Start() Console.WriteLine("スレッド開始") End Sub 'スレッド用クラス Class clsThread 'クラス生成時に退避されるデータ群 Private mintData As Integer Private mstrData As String Private mblnData As Boolean 'コンストラクタ Sub New(ByVal aintData As Integer, ByVal astrData As String, ByVal ablnData As Boolean) Me.mintData = aintData Me.mstrData = astrData Me.mblnData = ablnData End Sub '内部スレッドの開始メソッド Sub Start() Dim pThread As Thread = New Thread(AddressOf ThreadProc) 'スレッド開始 pThread.Start() End Sub 'スレッド関数(この関数はプライベート) Private Sub ThreadProc() Threading.Thread.Sleep(1000) Console.WriteLine("Integer Data = " & Me.mintData) Threading.Thread.Sleep(1000) Console.WriteLine("String Data = " & Me.mstrData) Threading.Thread.Sleep(1000) Console.WriteLine("Boolean Data = " & Me.mblnData) End Sub End Class End Module実行結果は以下の様に表示されます。
スレッド開始 Integer Data = 12345 String Data = AAA Boolean Data = False
スレッドを含む処理をクラスの中に入れ込んで、 その中で開始や停止などを行った方がスレッド処理を呼出す側との処理の分離がうまくいくと思います。