テクニック

「馬吉」は数人のプロのプログラマーが作成したプログラムだと思います。
私みたいな素人が作成するプログラムは、自分が理解さえ出来れば事が足りる訳ですから、鰻の寝床のようなプログラムになっています。
特に違いが目につくのはプログラムを一つ一つ動作毎に細分化している点です。
「馬吉」の場合はキーワードを1つ得るのもクラス化されておりました。

「馬吉」の改造で手間がかかるのは、実は細分化されている点です。
本来は処理を細分する事によって改造が易しくなるはずなのですが、逆に手間が掛かる事になっております。
なぜなのかと考えて見ましたが、開発の設計書が無い事だと気がつきました。
プログラムの開発時には、担当者には処理の流れやクラスの説明、変数の使い方などの書かれている設計書が渡されていたはずです。
JRA-VANは折角プログラムを公開してくれるのですから、開発設計書も一緒に公開して欲しかったと思います。
プログラムの公開が、JRA-VANのData-Labを利用した競馬予想プログラムの促進のためだと思いますから尚更です。
推測ですが、JRA-VAN はそのつもりだったでしょうが、プログラムを開発した人達にとっては迷惑な事だったのでしょう。
プログラム開発会社の蓄積してきたテクニックやノウハウを公開する事には何のメリットもないからです。
メリットどころか、プログラム開発会社は教育機関ではありませんので、自分の会社のレベルをさらけ出すだけでなく、プログラムテクニックを盗まれてライバルを増やすだけになりかねません。

私のような者にとっては、プロの作成したプログラムには教えられる点が多くて勉強になります。
個人がプログラムのテクニックを盗んでもライバルになる事はありませんから、「馬吉」のプログラムの中で様々な有用なコードを学び取りましょう。
そして自分の作成するプログラムのレベルを上げて行きたいと思います。
目に付いたテクニックの中から数点を選んでみたいと思います。

●clsGridData

競馬プログラムに限らずプログラムの中でフレックスグリッド(Frexgreid)を使用する機会は多いものです。
このプログラムで使われているクラスモジュールの clsGridData は利用価値のあるモジュールだと思います。
このクラスの全容は以下の通りです。

' ' グリッドデータクラス ' Option Explicit '---+----1----+----2----+----3----+----4----+----5----+----6----+----7----+----8 ' 外部関数(イベント) '---+----1----+----2----+----3----+----4----+----5----+----6----+----7----+----8 Public Event ToolTipChange(ToolTipText As String) '---+----1----+----2----+----3----+----4----+----5----+----6----+----7----+----8 ' 内部変数 '---+----1----+----2----+----3----+----4----+----5----+----6----+----7----+----8 Private mItems() As clsGridItem '' グリッドアイテム Private mCols As Long '' カラム数 Private mRows As Long '' ロウ数 '---+----1----+----2----+----3----+----4----+----5----+----6----+----7----+----8 ' プロパティ '---+----1----+----2----+----3----+----4----+----5----+----6----+----7----+----8 ' ' 機能: カラム数プロパティ ' ' 備考: 変更と同時にグリッドアイテムの要素数を調整する ' Public Property Let Cols(RHS As Long) Dim i As Long Dim oldCols As Long oldCols = mCols mCols = RHS ReDim Preserve mItems(0 To (mCols * mRows) - 1) If oldCols < mCols Then For i = oldCols * mRows To (mRows * mCols) - 1 Set mItems(i) = New clsGridItem Next i End If End Property ' ' 機能: カラム数取得プロパティ ' ' 備考: なし ' Public Property Get Cols() As Long Cols = mCols End Property ' ' 機能: ロウ数プロパティ ' ' 備考: 変更と同時にグリッドアイテムの要素数を調整する ' Public Property Let Rows(RHS As Long) Dim i As Long Dim oldRows As Long oldRows = mRows mRows = RHS ReDim Preserve mItems(0 To (mCols * mRows) - 1) If oldRows < mRows Then For i = oldRows * mCols To (mRows * mCols) - 1 Set mItems(i) = New clsGridItem Next i End If End Property ' ' 機能: ロウ数取得プロパティ ' ' 備考: 変更と同時にグリッドアイテムの要素数を調整する ' Public Property Get Rows() As Long Rows = mRows End Property ' ' 機能: グリッドアイテムを2次元的に参照する ' ' 備考: アイテムにアクセスする為 ' Public Property Get ItemMatrix(row As Long, col As Long) As clsGridItem 'On Error GoTo EH Set ItemMatrix = mItems(row * mCols + col) Exit Property EH: gApp.ErrLog End Property ' ' 機能: グリッドアイテムを1次元的に参照する ' ' 備考: アイテムにアクセスする為 ' Public Property Get ItemArray(Index As Long) As clsGridItem Set ItemArray = mItems(Index) End Property ' ' 機能: グリッドアイテムを1次元的に参照する ' ' 備考: アイテムにアクセスする為 ' Public Property Set ItemArray(Index As Long, ByRef RHS As clsGridItem) Set mItems(Index) = RHS End Property '---+----1----+----2----+----3----+----4----+----5----+----6----+----7----+----8 ' 外部関数 '---+----1----+----2----+----3----+----4----+----5----+----6----+----7----+----8 ' ' 機能: グリッドアイテムを複製する ' ' 備考: なし ' Public Function Clone() As clsGridData Dim newGD As clsGridData Dim i As Long Set newGD = New clsGridData With newGD .Cols = Me.Cols .Rows = Me.Rows For i = 0 To UBound(mItems) Set .ItemArray(i) = mItems(i).Clone Next i End With Set Clone = newGD End Function ' ' 機能: グリッドアイテムをカラム方向に連続して設定するユーティリティー ' ' 備考: 引き数 row - ロウ番号 ' col - カラム番号 ' Text - 表示文字列 ' ToolTip - ツールチップ文字列 ' AlignChar - 配置指定子 ' Link - リンク記号 ' Key - リンクキー ' BGColor - バックカラー ' FRColor - フォアカラー '     2段のカラムヘッダの登録に便利 ' Public Sub SetItemMatrix(row As Long, ByRef col As Long _ , Optional Text As String = "" _ , Optional ToolTip As String = "" _ , Optional AlignChar As String = "<-" _ , Optional Link As String = "" _ , Optional Key As String = "" _ , Optional BGColor As Long = &H0 _ , Optional FRColor As Long = &H0 _ , Optional Strikethru As Boolean = False _ , Optional SortString As String = "" _ ) 'On Error GoTo ErrorHandler With mItems(row * mCols + col) .Text = Text .ToolTip = ToolTip Select Case AlignChar Case "<^": .Alignment = flexAlignLeftTop Case "<-": .Alignment = flexAlignLeftCenter Case "<_": .Alignment = flexAlignLeftBottom Case "^^": .Alignment = flexAlignCenterTop Case "^-": .Alignment = flexAlignCenterCenter Case "^_": .Alignment = flexAlignCenterBottom Case ">^": .Alignment = flexAlignRightTop Case ">-": .Alignment = flexAlignRightCenter Case ">_": .Alignment = flexAlignRightBottom Case Else Err.Raise 0, , "AlignCharが間違い" End Select .Link = Link .Key = Key .BGColor = BGColor .FRColor = FRColor .Strikethru = Strikethru .SortString = SortString End With col = col + 1 Exit Sub ErrorHandler: gApp.ErrLog Resume Next End Sub ' ' 機能: 2次元的に指定したセルがキーをもっていれば真 ' ' 備考: キーをもっていれば真、もっていなければ偽 ' Public Function HasAKey(row As Long, col As Long) As Boolean HasAKey = ((mItems(row * mCols + col).Link <> "") And (mItems(row * mCols + col).Key <> "")) End Function ' ' 機能: 終了処理 ' ' 備考: なし ' Public Sub Free() End Sub '---+----1----+----2----+----3----+----4----+----5----+----6----+----7----+----8 ' 内部関数 '---+----1----+----2----+----3----+----4----+----5----+----6----+----7----+----8 ' ' 機能: 初期化処理 ' ' 備考: なし ' Private Sub Class_Initialize() ' 循環参照チェック用、オブジェクトカウンタ gDebugCounter_clsGridData = gDebugCounter_clsGridData + 1 gApp.InitLog Me, gDebugCounter_clsGridData mCols = 1 mRows = 1 ReDim mItems(0) Set mItems(0) = New clsGridItem ReDim mblnSortFlag(0) End Sub ' ' 機能: 終了処理 ' ' 備考: なし ' Private Sub Class_Terminate() ' 循環参照チェック用、オブジェクトカウンタ gDebugCounter_clsGridData = gDebugCounter_clsGridData - 1 gApp.TermLog Me, gDebugCounter_clsGridData Erase mItems End Sub

この中で感心したのは、SetItemMatrix の部分です。
やっているのはマトリックスにデータを表示するだけの事なのですが、オプションコードをつけるだけで、色を変えたりデータにリンクを張ったり出来るのです。
表示位置を左寄せにしたり右寄せに指定する事もできます。
これは今まで作成した自分のプログラムにも応用させて貰おうと思います。
オプションコードに詳しく設定内容を書いてありますので、更なる説明は省略させていただきます。
呼び出すコードの書き方は慣れるまで大変でしょうが、覚えてしまえば便利に使えそうです。

●clsDataFind

データ検索(clsDataFind)の中で行っている、あいまい検索のSQLコードの作り方も参考になります。
特に馬の名前は難しいのが多く、ウをヴだとかゥだとか書く場合も多くて正確に入力しないと検索できないのでは使い勝手が悪くなってしまいます。
当然プログラムはあいまい検索を使用するのですが、「馬吉」ではさらに一捻りしております。
百聞は一見にしかずですから、例としてウオッカを検索した場合のコードを記載します。

Find SQL: SELECT * FROM UMA WHERE [Bamei] LIKE 'ウオッカ%' OR [BameiEng] LIKE 'ウオッカ%' ORDER BY [Bamei]
Find RegExp: ^[ぅうゥウゥウ]\s*[ぉおォオォオ]\s*[っつッツッツ]\s*か|^[ぅうゥウゥウ]\s*[ぉおォオォオ]\s*[っつッツッツ]\s*カ|^[ぅうゥウゥウ]\s*[ぉおォオォオ]\s*[っつッツッツ]\s*カ
ログファイルに記載された内容をコピーしたものですが、ひらがなの検索でも、大文字でも小文字でも検索してくれるようです。
実際に’うおっか’と入力したら’ウオッカ’が検索されました。
あらかじめ入力されそうな文字をプログラム内に登録しておくだけで、正確な馬名と異なった場合でも正しく検索してくれるのであれば便利です。
試しに著名なソフトのターゲットで’うおっか’と検索しても’ウオッカ’は出てきませんでした。(だからどうこうと言うのでは無いですが。)
「馬吉」の場合は正規表現(RegExp)を使ってこの機能を実現させているようです。

騎手の検索でも横山と入力するだけよりは、よこやまでも検索出来る方が便利になります。
日本語は読み方が同じでも漢字は異なる場合も多いです。
これは使えそうなテクニックではないでしょうか。
【注記】
参考に、VBAで正規表現を使う をリンクしておきます。

●ツールバーに表示

ユーザーコントロールを新規に作成して独自の画面を作成した場合などに、ツールバーにもアイコンを登録したい場合があります。
「馬吉」では、この登録が簡単に出来ます。
frmBrowserにChangeToolBarと言うコードがありますから、そこに登録したいユーザーコントロール名を記入します。
コントロール名は、例えばctlVSample と言う画面を新規に作成したのであれば、Sample を入れます。
「馬吉」では、表示画面の名前は必ず頭にctlVを付けるようになっていますので、それ以降がコントロール名になるのです。
ツールバーの表示領域も3か所あって1か所目は基本の表示領域、2か所目と3か所目が独自の表示領域になっています。
ChangeToolBar では、独自の表示領域を表示するのかどうかだけの設定を行います。
アイコン画像は、frmBrowser のロード時に読み込んでおります。
新規にアイコンを登録する場合は、リソースエディタで行います。
実際に表示する内容は、作成したユーザーコントロール内に記入します。
尚、ここではタイトルバンドの表示内容の設定も行っています。

●clsStringConverter

clsStringConverter にあるChakukaisu2 も参考になりそうなコードです。
1着から5着までの回数と着外の回数を記載した後に、レース数、勝率、連対率、3着内率の表示をFor Nextループを0〜9まで回すと一挙に目的とするデータを記載する事ができます。
コードは以下の通りです。

' ' 機能: 着回数から各種表示文字列を返す ' ' 備考: 返り値 option - 0-5:着回数 6:合計 7:勝率 8:連対率 9:3着内率 ' Public Function Chakukaisu2(ARG() As String, vOption As Long) As String Dim i As Integer Dim intDataLen As Integer Dim sngGet(0 To 5) As Single Dim sngSum As Single Dim strOut As String If Len(ARG(0)) = 3 Then intDataLen = 3 ElseIf Len(ARG(0)) = 6 Then intDataLen = 6 ElseIf Len(ARG(0)) = 0 Then Chakukaisu2 = "" End If If Len(ARG(0)) Then For i = 0 To 5 sngGet(i) = CSng(ARG(i)) Next i sngSum = sngGet(0) + sngGet(1) + sngGet(2) + sngGet(3) + sngGet(4) + sngGet(5) sngSum = IIf(sngSum = 0, -1, sngSum) Select Case vOption Case 6 Chakukaisu2 = CStr(IIf(sngSum = -1, 0, sngSum)) Case 7 Chakukaisu2 = Format$(sngGet(0) / sngSum, "0.000") Case 8 Chakukaisu2 = Format$((sngGet(0) + sngGet(1)) / sngSum, "0.000") Case 9 Chakukaisu2 = Format$((sngGet(0) + sngGet(1) + sngGet(2)) / sngSum, "0.000") Case Else Chakukaisu2 = CStr(sngGet(vOption)) End Select End If End Function 呼び出し方の例 For i = 0 To 9 .SetItemMatrix lngRP + 1, lngCP, mSC.Chakukaisu2(mBuf_BR.HonRuikei(0).Chakukaisu, i) Next i

私のデータベースの記録フォーマットではエラーで動きませんでしたので気がつきました。
文字列の型変換を行っていたり、難解な部分が多くあって処理の流れが中々掴めません。
表示方法を単純化するために強引に処理しているような気もしますが参考になりそうです。

●フレックスグリッドへの記入

「馬吉」はフレックスグリッドを多用しております。
各々のフレックスグリッドはインデックスを持って制御されています。
最初の頃は、どのフレックスグリッドに記入するのかの指定(インデックスの設定)をどこで行っているのか判りませんでした。
実は、インデックスの設定はデータ内容を設定する前に行っているのではなくて、データの書き込みが終了した時点で行っているようです。
ユーザーコントロールの取得完了通知イベントで行っています。
例えば、出馬表の基本データが全て書き終えた時点では、基本データ取得完了通知イベントが呼び出されます。
コードは以下のようになっています。

' ' 機能: 基本データ取得完了通知イベント ' ' 備考: なし ' Private Sub mData_FetchedKihon(GridData As clsGridData) 'On Error GoTo ErrorHandler Call flexTab(0).InsertGrid(GridData) With flexTab(0).Grid .FixedCols = 0 End With Call flexTab(0).AutoSize(0, flexTab(0).Grid.Cols - 1) paneTab(0).Mode = 2 mstTab.TabEnabled(0) = True Exit Sub ErrorHandler: Call WriteErrLog(Err.Number, "ctlVRA_mData_FetchedKihon") End Sub

コードは移植しているために一部変更箇所がありますが、内容は同じです。
Call flexTab(0).InsertGrid(GridData)がどのフレックスグリッドへ記入するかの指定になります。
paneTab(0).Mode = 2 はデータ読み込み中の状態を解除された事を表します。
そして、mstTab.TabEnabled(0) = True をする事によってタブが使用できる状態になります。

そこで、単純にフレックスグリッドを新規に登作成してCall flexTab(0).InsertGrid(GridData)のインデックス指定を変更して動作させてみました。
書き込み動作はすんなりと終了しましたが、表示を確認しようとすると見事にエラーが発生しました。
インデックスが有効範囲にありません。とエラーが出ると思います。
それならばと、今度は本来のものと新規に作成したものと併記してみました。
リサイズは行われておりませんが、見事に2つ同じものが作成されています。
兎に角、「馬吉」は処理がごちゃごちゃしています。やれやれ改造するにはたいへんだ〜。

●競走結果の表示

テクニックと呼ぶ程の事ではないかも知れませんが、「馬吉」のレースの競走結果の表示方法について記載します。
レースの競走結果は、1着からビリまでの馬を順番に表示するだけなので、簡単に考えたれますが、実はそれほど簡単ではありません。
レースが確定した時に、JRA-VANからは、馬毎レース情報の中に確定順位の内容も送られてきます。
その内容を1から順番に並べていけば良さそうなのですが、そうではありません。

まず、入線順位と確定順位を区別する必要があります。
入線順位とは1着から順番にゴールした順位ですが、走行妨害があると1着に入線した馬が5着に降格とか、最悪の場合は失格にもなります。
レースの結果を表示する場合は、入選順位を優先して表示させるのか確定順位を優先して表示させるのかを決めなければなりません。

入線順位を優先させた場合は、着差の表示はそのままで問題ありませんが、配当の馬番が異なりますので違和感があります。
確定順位を優先させた場合には、その逆で配当はそのままで良いですが、走破タイムとか着差がめちゃくちゃになってしまいます。
競馬予想ソフトでも、どちらを優先させるのかはまちまちです。
いずれにしても、入線順位と確定順位が異なる場合ははっきりと判るようにしておかなければなりません。

次に問題になるのは、途中で落馬した場合などです。
この場合は出走頭数にその馬は含まれますが、落馬して再騎乗でもしない限り入線する事はありませんので、入線順位や確定順位は0のままです。
落馬したからと言って、登録を削除するわけにはいきませんので、順位がつかなくても入選順位や確定順位の欄には記入しなければなりません。
悪い事に順位が0だと通常のソートでは1位よりも先に表示されますので、これはまずいでしょう。

競走除外も結構多くあります。
前日からの出走取り消しなら、最初から除外がわかっておりますが、レース前に馬が暴れたりした場合や放馬したりした場合は競走除外になる場合が多いです。
この場合も入線順位や確定順位は0ですから注意が必要です。

それと同着の場合があります。
この場合は入選順位、確定順位共に同じ数字ですから、どちらかを先にして記載する必要があるでしょう。
同着の場合は2頭、3頭が同着する場合も珍しくありませんので、その対策も必要です。

このように、一見単純に思える結果表示でも多くの部分に配慮しなければなりません。
競馬予想プログラムを作成したことのある人ならば、頭を悩ます部分の一つでもあります。
早速馬吉のレース結果の表示プログラムの部分を見てみましょう。

' ' 機能: 成績グリッドを作る ' ' 備考: なし ' Private Sub DirectMakeData_Seiseki() On Error GoTo Errorhandler Const rowOffset As Long = 1 '' データ行オフセット Dim lngCP As Long '' カラムポインタ Dim i As Long '' ループカウンタ Dim j As Long '' ループカウンタ Dim lngFlag As Long Dim gd1 As clsGridData Dim gd2 As clsGridData Dim gd3 As clsGridData Dim UA As ADODB.Recordset Dim UB As ADODB.Recordset Dim UM As ADODB.Recordset Dim AV As ADODB.Recordset Dim setTmpForCellStr As String '' セル文字列作業用一時変数 Dim KS As ADODB.Recordset '騎手マスタ Set gd1 = New clsGridData Set gd2 = New clsGridData Set gd3 = New clsGridData Set UA = mRS_UMA_RACE_A Set UB = mRS_UMA_RACE_B Set UM = mRS_UMA Set AV = mRS_TORIKESI_JYOGAI Set KS = mRS_KISHU With gd1 .Rows = 28 + rowOffset .Cols = 22 ' 1行目 カラムヘッダ登録 lngCP = 0 .SetItemMatrix 0, lngCP, "隠", "ソート用", ">-" .SetItemMatrix 0, lngCP, "入", "入線順位", ">-" .SetItemMatrix 0, lngCP, "着", "確定着順", ">-" .SetItemMatrix 0, lngCP, "異常", "異常区分", ">-" .SetItemMatrix 0, lngCP, "枠", "枠番", ">-" .SetItemMatrix 0, lngCP, "番", "馬番", ">-" .SetItemMatrix 0, lngCP, "B", "ブリンカー" .SetItemMatrix 0, lngCP, "馬名", "馬名", "^-" .SetItemMatrix 0, lngCP, "性齢", "性齢" .SetItemMatrix 0, lngCP, "負担", "負担重量", ">-" .SetItemMatrix 0, lngCP, "馬体重", "馬体重", ">-" .SetItemMatrix 0, lngCP, "増減", "馬体重増減", ">-" .SetItemMatrix 0, lngCP, "騎手", "騎手名" .SetItemMatrix 0, lngCP, "タイム", "走破タイム", ">-" .SetItemMatrix 0, lngCP, "着差", "前馬との着差", ">-" .SetItemMatrix 0, lngCP, "1C", "1コーナーでの順位", ">-" .SetItemMatrix 0, lngCP, "2C", "2コーナーでの順位", ">-" .SetItemMatrix 0, lngCP, "3C", "3コーナーでの順位", ">-" .SetItemMatrix 0, lngCP, "4C", "4コーナーでの順位", ">-" If mBuf_RA.TrackCD >= "51" And mBuf_RA.TrackCD <= "59" Then '障害の時は上1Fと表示 .SetItemMatrix 0, lngCP, "上1F", "上1ハロンタイム", ">-" Else '障害以外の場合は後3Fと表示 .SetItemMatrix 0, lngCP, "後3F", "後3ハロンタイム", ">-" End If .SetItemMatrix 0, lngCP, "人", "単勝人気順", ">-" .SetItemMatrix 0, lngCP, "単オッズ", "単勝オッズ", ">-" End With ' グリッドデータ挿入 UA.MoveFirst i = 0 Do While Not UA.EOF SafeSeek UB, Array("B_Year", "B_MonthDay", "B_JyoCD", "B_RaceNum", "B_Umaban", "B_KettoNum"), _ Array(UA("Year").value, UA("MonthDay").value, UA("JyoCD").value, UA("RaceNum").value, UA("Umaban").value, UA("KettoNum").value) SafeSeek UM, Array("KettoNum"), Array(UA("KettoNum").value) SafeSeek AV, Array("Year", "MonthDay", "JyoCD", "Kaiji", "Nichiji", "RaceNum", "Umaban"), _ Array(UA("Year").value, UA("MonthDay").value, UA("JyoCD").value, UA("Kaiji").value, UA("Nichiji").value, UA("RaceNum").value, UA("Umaban").value) SafeSeek KS, Array("KisyuCode"), _ Array(UB("KisyuCode").value) DoEvents If mblnCancelFetching Then Exit Sub End If ' 3着確定、5着確定のデータの場合、確定外データは表示しない If Not ( _ (mBuf_RA.head.DataKubun = "3" Or mBuf_RA.head.DataKubun = "4") And _ UB("KakuteiJyuni") = "00" _ ) Then lngCP = 0 With gd1 ' ソート用隠しカラムの設定 setTmpForCellStr = "" ' 一時的文字列変数を初期化 If UB("KakuteiJyuni") <> "00" Then setTmpForCellStr = UB("KakuteiJyuni") Else setTmpForCellStr = "99" End If ' 異常区分同士はソート用順位を使用 setTmpForCellStr = setTmpForCellStr & mSC.IJKB_Sort(UB("IjyoCD")) ' 同着同士の場合、着差が同着の方を最下位 If UB("ChakusaCD") = "D " Then setTmpForCellStr = setTmpForCellStr & "1" Else setTmpForCellStr = setTmpForCellStr & "0" End If setTmpForCellStr = setTmpForCellStr & UA("Umaban") .SetItemMatrix i + rowOffset, lngCP, setTmpForCellStr If UB("NyusenJyuni") <> "00" Then .SetItemMatrix i + rowOffset, lngCP, mSC.ValStr(UB("NyusenJyuni")), , ">^" Else .SetItemMatrix i + rowOffset, lngCP, "99", , ">^", , , &HFFFFFF, &HFFFFFF End If If UB("KakuteiJyuni") <> "00" Then .SetItemMatrix i + rowOffset, lngCP, mSC.ValStr(UB("KakuteiJyuni")), , ">^", , , gApp.GetChakujyunColor(val(UB("KakuteiJyuni"))), SortString:=UB("KakuteiJyuni") Else .SetItemMatrix i + rowOffset, lngCP, "99", , ">^", , , &HFFFFFF, &HFFFFFF, SortString:="99" End If .SetItemMatrix i + rowOffset, lngCP, mCC.IJKB2(UB("IjyoCD")), , ">^", SortString:=mSC.IJKB_Sort(UB("IjyoCD")) .SetItemMatrix i + rowOffset, lngCP, mSC.FitSpaceNum(UA("Wakuban"), 2), , ">^", , , gApp.GetWakubanColor(val(UA("Wakuban"))), Contrast(gApp.GetWakubanColor(val(UA("Wakuban")))) .SetItemMatrix i + rowOffset, lngCP, mSC.FitSpaceNum(UA("Umaban"), 2), , ">^", SortString:=UA("Umaban") .SetItemMatrix i + rowOffset, lngCP, IIf(UB("Blinker") = "0", "", "B") 'ブリンカー Call BameiAV(AV, UA, UB, UM, gd1, i + rowOffset, lngCP) .SetItemMatrix i + rowOffset, lngCP, mCC.SEIB4(UA("SexCD")) & mSC.ValStr(UA("Barei")) '性齢 If val(UB("FutanBefore")) = 0 Then .SetItemMatrix i + rowOffset, lngCP, IIf(val(UB("Futan")) = 0, "", Format$(UB("Futan") / 10, "#0.0")), , ">-" Else .SetItemMatrix i + rowOffset, lngCP, IIf(val(UB("Futan")) = 0, "", Format$(UB("Futan") / 10, "#0.0")), IIf(val(UB("FutanBefore")) = 0, "", Format$(UB("FutanBefore") / 10, "#0.0")) & "→" & IIf(val(UB("Futan")) = 0, "", Format$(UB("Futan") / 10, "#0.0")), ">-", , , , RGB(255, 0, 0) End If If val(UB("BaTaijyu")) < 2 Then .SetItemMatrix i + rowOffset, lngCP, " ", , ">-" ElseIf UB("BaTaijyu") = "999" Then .SetItemMatrix i + rowOffset, lngCP, " --- ", , ">-" Else .SetItemMatrix i + rowOffset, lngCP, val(UB("BaTaijyu")), , ">-" End If setTmpForCellStr = "" Select Case UB("ZogenSa") Case "999", " " setTmpForCellStr = "" Case "000" setTmpForCellStr = "±0" Case Else setTmpForCellStr = UB("ZogenFugo") & mSC.ValStr(UB("ZogenSa")) End Select .SetItemMatrix i + rowOffset, lngCP, setTmpForCellStr, , ">-" If Trim$(UB("KisyuRyakusyoBefore")) = "" Then .SetItemMatrix i + rowOffset, lngCP, Trim$(UB("KisyuRyakusyo")), , , "KS", IfExist(KS, "KisyuCode") Else .SetItemMatrix i + rowOffset, lngCP, Trim$(UB("KisyuRyakusyo")), Trim$(UB("KisyuRyakusyoBefore")) & "→" & Trim$(UB("KisyuRyakusyo")), , "KS", IfExist(KS, "KisyuCode"), , &HFF End If .SetItemMatrix i + rowOffset, lngCP, IIf(UB("Time") <> "0000", Format$(UB("Time"), "@:@@.@"), ""), , ">^" '着差コードのp ppの並び順 setTmpForCellStr = "" ' 一時的文字列変数を初期化 If UB("ChakusaCDP") <> " " Then If UB("ChakusaCDPP") <> " " Then setTmpForCellStr = mCC.CHKS1(UB("ChakusaCDPP")) & "+" End If setTmpForCellStr = setTmpForCellStr & mCC.CHKS1(UB("ChakusaCDP")) & "+" End If setTmpForCellStr = setTmpForCellStr & mCC.CHKS1(UB("ChakusaCD")) ' 「確定一着の同着以外の着差表示をブランクに」する処理 ' 確定順位が1着で、かつ、同着ではない時に、着差欄は挿入しない。 If UB("KakuteiJyuni") = "01" And UB("ChakusaCD") <> "D " Then .SetItemMatrix i + rowOffset, lngCP, "" Else .SetItemMatrix i + rowOffset, lngCP, setTmpForCellStr, , ">^" End If .SetItemMatrix i + rowOffset, lngCP, JyuniNCProc(UB("Jyuni1c")), , ">^" .SetItemMatrix i + rowOffset, lngCP, JyuniNCProc(UB("Jyuni2c")), , ">^" .SetItemMatrix i + rowOffset, lngCP, JyuniNCProc(UB("Jyuni3c")), , ">^" .SetItemMatrix i + rowOffset, lngCP, JyuniNCProc(UB("Jyuni4c")), , ">^" If UB("HaronTimeL3") <> "999" And UB("HaronTimeL3") <> " " Then .SetItemMatrix i + rowOffset, lngCP, mSC.SSSs(UB("HaronTimeL3")), , ">^" Else .SetItemMatrix i + rowOffset, lngCP, "" End If .SetItemMatrix i + rowOffset, lngCP, IIf(val(UB("Ninki")) = 0, "", val(UB("Ninki"))), , ">^" If (UB("Odds") = "0000") Then .SetItemMatrix i + rowOffset, lngCP, "" ElseIf (IsNumeric(UB("Odds"))) Then '単勝オッズ .SetItemMatrix i + rowOffset, lngCP, Format$(UB("Odds") / 10, "#0.0"), , ">-", , , , ColorHML(Format$(UB("odds") / 10, "#0.0")) Else .SetItemMatrix i + rowOffset, lngCP, UB("Odds"), , ">-", , , , RGB(0, 0, 0) End If End With i = i + 1 If gd1.Rows <= i + rowOffset Then gd1.Rows = gd1.Rows + 10 End If End If UA.MoveNext Loop gd1.Rows = i + rowOffset Call DirectMakeData_Harai(gd2) Call DirectMakeData_Lap(gd3) RaiseEvent FetchedSeiseki(gd1, gd2, gd3, lngFlag) Exit Sub Errorhandler: gApp.ErrLog Resume Next End Sub

始めて見た人は、プログラムの理解は不可能だと思いますが、ソート用隠しカラムの設定などを行っていて苦労(?)している事がはっきりと見て取れます。
それに、レース当日には3着までの結果が判明したデータや、5着まで判明したデータの提供もありますから、それにも対処している事が判ります。
私のソフトでは、途中までの結果データを表示させるのは表示が面倒なので、全結果が確定した時に初めて表示するようにしておりますが、この点は見習わなければいけないようです。
単純に3着とか5着までの表示だけだと違和感があってプログラムのバグのように見えますので、途中結果だとはっきりと判るようにしなければいけないでしょう。

私の場合は、データベースからデータを取り込む時にソートを行っていたのですが、「馬吉」はマトリックスグリッド内で行っているようです。
エクセルの場合もエクセル自身に強力なソート機能がありますから、レースデータの並べ替え程度の事は一瞬で終了します。
マトリックスグリッド内で行っているからと言って、処理が遅くなる事はないでしょう。

●馬毎レースデータの同期

「馬吉」では、馬毎レースデータ(UMA_RACE)を前半部分と後半部分の2つのファイルに分けております。
前半は subUMA_RACA_A.mdb 、後半が subUMA_RACE_B.mdb のファイル名になっています。
分けた理由は、アクセスの容量の壁に対する対策のためと思われます。
データを読み込む場合には、データベースファイルが別々ですから、同期をとって必ず同じデータを読み込まなければなりません。
この仕組みはどうなっているんでしょうか。

やり方は色々あると思いますが、一般的には特定のコードを両方のファイルに記載して、どちらから読み込んでもそのコードから別のファイルを読み込む方法です。
「馬吉」の場合はフィルターを掛けて相手を探していました。
フィルターの内容は、Year,MonthDay,JyoCD,RaceNum,Umaban,KettoNum でした。
年月日(year,MonthDay)は当然ですが、競馬場別(JyoCD)レース番号(RaceNum)馬番号(Umaban)は特定の1頭の馬が違う競馬場で、しかも異なったレースで、馬番別に重なって出走する事などは考えられませんので、どれか一つで良さそうなものです。
血統番号でもフィルターを掛けていますから、年月日と血統番号番号だけで1つに絞れると思います。
JRAが血統番号を2重登録でもしてない限り大丈夫でしょう。(1頭の馬が血統番号を2つ持っていることがあるとは聞いたような気がしますが。)
同じ馬が、同じ日に別のレースに出走する事は絶対にないのかと言われると絶対にないでしょうが、物理的には可能ですから、そこまで心配する人がいるならレース番号を加えても良いでしょう。

安全のためにフィルターを数多く掛けているとも思えませんので、私が競馬をやった事のない人がこのプログラムを作ったのではないかと思う根拠がこんな所にあります。
フィルターを1つや2つ多く掛けたからと言って、極端にアクセス時間が増えるものでも無いでしょうから、こんな細かい事はどうでも良いでしょう。
なぜ、私がこれを取り上げたかと言うと、JRAーVAN からのデータの取り込みに余りにも多くの時間(10〜20時間)が掛かるために、「馬吉」のデータベース(Access)から SQLite 直接変換した場合にどの程度時間が短縮出来るかを調べるために、データ変換プログラムを作ったためです。
その時に、馬毎レースデータが「馬吉」には2つありましたので、最初は2回に分けて取り込んでいました。
しかし、あまりにもかったるいので1回でまとめるべきだと考えて、どうやって同期を取ろうかと考えて「馬吉」のやりかたを調べる事になったのです。
つまらないと言えば、つまらない内容の話だったかも知れません。

●ポップアップメニューの表示

「馬吉」では、リンクのある項目の上でマウスを右クリックした時に、新しいウィンドウで開く、進む、戻るなどのポップアップメニューが表示されます。
この内容がどこに記載されているのかですが、ご存じの通りメニューの中に記載されています。
メニューエディターを起動すれば内容を確認することが出来ます。
多くの種類を持たせたり、右に並べて表示させたりのテクニックの詳しい解説をされているインターネットのサイトがあります。

●列挙体について

「馬吉」では、標準モジュールの basEnum で列挙体を宣言しています。
私は通常のプログラムで使用することはありませんでしたが、解説は以下の通りです。
列挙体は値を定義するための便利な入れ物として使いますが、その正体は特殊なクラスです。
列挙体が便利なのはプログラム中に入力候補が自動的に表示されるところです。
列挙体を定義するにはEnum 〜 End Enum(読み方:Enum = エナム または エニューマレーション)を使用してます。たとえば、血液型を表す列挙体を宣言する場合は次のようになります。
Public Enum ABBloodType
A
B
AB
O
End Enum

要は、変数を整数化で簡単に設定できると解釈したのですが、当たらずとも遠からずだと思います。
設定の最上部が0で始まって、下段に行くに従って+1されるようです。
上の例では、Aが0でBが1ABが3でOが4と言う具合です。
途中でB=3と言うような書き方も出来て、その場合はABが4になると言う使い方も出来るようです。
「馬吉」の場合を見ると例えば以下のようになっています。

' 機能: JV-Link 取得モード ' ' 備考: なし ' Public Enum ukJVLMode ukjUsual ukjThisWeek End Enum

この場合は、ukjUsual=0 で、ukjThisWeek=1 と言う事になります。
プログラム内ではどのように使われているのかと見ると、以下のようになっています。

' ' 機能: JV-Link取得モード 通常<->今週 を取り出す ' ' 備考: なし ' Public Property Get R_JVLMode() As ukJVLMode Dim tmp As ukJVLMode Select Case ReadINI("DatabaseInfo", "Mode") Case "Usual" tmp = ukjUsual Case "ThisWeek" tmp = ukjThisWeek Case Else tmp = ukjUsual Call WriteINI("DatabaseInfo", "Mode", "Usual") End Select R_JVLMode = tmp End Property

「馬吉」もオリジナルでは、レジストリにJV-Link のデータが記載されていますので、若干コードが異なりますが、基本的な内容は同じです。
R_JVLMode を読み込んだ場合には、Usual なら0が ThisWeek なら1が返されると思います。
(SQLite 移植版では、ThisWeek モードは存在しませんので、このコードは削除しました。)
「馬吉」の公開版では、宣言はされていても実際にはほとんど使われていないようです。

●ラベルの位置設定について

「馬吉」ではタイトルを表示したりするためにフレームを使用しています。
上からメニューバー、ツールバー、タイトルバーが並んでいます。
ここまでは、フォーム(frmBrowser)が管理しています。

その下に、通常ではタイトルバー(fraTop)、ヘッダー(fraHeader)が並んでおります。
タイトルバーやヘッダーの中には、多くのラベルが存在していますが、このラベルの位置設定はどこで行っているのでしょうか。
調べて見ると、各々のユーザーコントロールの Update サブルーチンで行っておりました。(一部はリサイズ内で行っています。)
このサブルーチンは、key が設定された時に呼ばれます。

ラベルのキャプションは、各々のクラスデータ(clsData***)で設定されます。
フレームの位置の設定は、ユーザーコントロールのリサイズ(UserControl_Resize)で行われています。
リサイズでは、共通設定のコントロールに関してはクラスモジュールの clsViewerBase が行っています。

ラベルの配置で特徴的なのは、表示位置を相対的にしている事です。
例えば2つのラベルが左右に並んでいたとすると、右のラベルは左のラベルの長さ(Width)によって表示位置が異なるのです。
これは特定の項目の位置が毎回ずれる事を意味しており、画面を高速に切り替えたりする場合は見にくいものになるでしょう。
又、フレームの Hight に関してはプログラムでは設定をしておりません。
変だとは思いますが、なぜなのか謎です。
多分、人間が作成したものではないでしょう。

●マウスポインターの制御について

「馬吉」はマウスの使い方が優れていると思います。
ウィンドウのマウスの座標位置を常に監視していて、マウスの動作(ダウン、クリック)によって様々な動作を行わせております。
これを利用すれば、マウスの位置によってポップアップメニューを表示させたりする事も簡単です。
私は基本画面内で、マウスの位置によって例えば馬名の上なら競走馬の写真や馬のコメント、騎手の上なら騎手の成績やコメントを表示させる事にしました。
この方式が優れているのは、マウスカーソルが乗っかっている場合だけ表示を行うので、画面が見易い事です。
例えば、ターゲットでは、ダブルクリックしなければ競走馬の個別の情報は出てきませんし、もとの画面に戻るには画面を一旦閉じなければなりません。
馬コメントも画面の下側に表示されますが1行だけで見にくいですし、全部の内容を見るには別ウィンドウを開かなければ見る事が出来ません。 ちょっと確認する程度で良い内容を、画面を開いたり閉じたりするのは面倒な事ではないでしょうか。

「馬吉」でマウスカーソルが乗っかっている場合に表示内容をオン、オフするにはラベルの場合とフレックスグリッドの場合とでは異なります。
フレックスグリッドの場合は、ユーザーコントロール(ctlWrappedGrid)のSetCellClickable で行っています。
この部分は、マウスのカーソルを指の形(馬吉オリジナル)にするのか矢印(デフォルト)にするのかの制御を行っているのですが、この部分に処理したい内容を記載するだけですから簡単です。
ラベルの場合は、ユーザーコントロール(ctlClickLabel)の lblWraped_MouseMove で行っています。
こちらも動作の追加は簡単です。
結構面倒なのは、例えば基本画面にその場合に例えば馬の写真や馬コメントを表示させる方法です。
表示するスペースは下部に十分あるのですが、「馬吉」のオリジナルな状態では、フレックスグリッドの表示の余り部分で塞がれておりますので、フレックスグリッド内に表示するのか、ピクチャーボックスを追加して表示させる必要があります。
私の場合は、ピクチャーボックスを追加しましたので、フレックスグリッドの長さ(Height)の調整を行ったり結構大変でした。
手間が掛かって面倒だと言うだけで、プログラム自体は難しくありませんので、根気さえあれば誰にでも出来ます。

●レース当日のオッズの表示

「馬吉」を移植した場合は、レース当日のオッズの表示は出来ません。
それは、レース当日は速報データでオッズが提供されるので表示ができないのは当然なのですが、「馬吉」ではレース当日のオッズはどのようにして表示しているのでしょうか。
調べてみると、ツールバーに速報データ取得のアイコンがあって、それをクリックするとレース当日のオッズを見る事ができるようです。
では、そのアイコンをクリックする事で、どのような処理を行っているのでしょうか。
調べてみると、フォーム(frmDBPrompt)が速報データの取得関係の一切を行っているようです。
オプションを設定する事によって、どの内容の速報データを取得するのかの設定ができます。
速報データで取得設定の内容は以下の通りです。
レース結果取得
馬体重取得
データマイニング取得
開催情報取得
オッズ取得
票数取得
インターネットを通してJRA-VAN と接続を行いますので、接続が出来なかった場合の処理や取得失敗の場合の処理も記載されています。
読み込みの設定が速報データの取得にしているだけで、処理は通常のJRA-VANからのデータ取得方法と変わらないようです。

●ピクチャーボックスを使用したプログレスバーの表示

「馬吉」ではプログレスバーは、一般的なプログレスバーを使用していますが、私の「エクセル競馬予想 馬ちゃん」ではピクチャーボックスを使用してプログレスバーを作成している。
このプログラムは、だいぶ以前にどっからか探してきたものだったが、出典が判らなかったので今回サイトで探してみました。
同じ内容のものはありませんでしたが、似たような処理ですので備忘録として2点掲載しておきます。
最初の記入は質問内容です。

インストーラーやVBのコンパイル時など、プログレスバーで青いバーが内部の文字列に重なった時、重なった部分の文字列が白に、重なってない部分が黒になってますが、これを実現するには、どうしたら良いのでしょうか(ピクチャーボックスで画像転送すれば出来ますが、APIでありますか)?

PictureBox を2つ使い、一方の幅を変化させる事で簡単に実現できてしまうので、API は無いでしょう。 Dim iPer As Integer Private Sub Command1_Click() Timer1.Enabled = True End Sub Private Sub Timer1_Timer() iPer = iPer + 2 If 100 < iPer Then Timer1.Enabled = False iPer = 0 Else Picture1.Cls Picture2.Cls Picture1.Print Space(10) + CStr(iPer) + "%" Picture2.Print Space(10) + CStr(iPer) + "%" Picture2.Width = Picture1.Width * iPer / 100 End If End Sub 2つの PictureBox は、一方は白地に黒、他方は黒地に白の感じにします。 画像を貼っておいても良い ですね。


例えばピクチャーのペイントイベントに With Picture1 .CurrentX = (.ScaleWidth - .TextWidth("あいうえお")) / 2 .CurrentY = (.ScaleHeight - .TextHeight("あいうえお")) / 2 .ForeColor = vbBlack Picture1.Print "あいうえお" .DrawMode = 14 Picture1.Line (0, 0)-(Picture1.ScaleWidth / 2, Picture1.ScaleHeight), _ vbActiveTitleBar, BF End With とかもありだと思いますけど、へんかな?

私の場合は2番目の方式に近い方法で行っております。
昔OSをインストールする時に、このようなプログレスバーでしたので、懐かしんで私も使用しています。
単なる回顧主義的な考えからで、使用に深い意味はありません。

●買い目金額の入力方法

今回の「馬吉」の改造版にはIPATの投票機能を入れております。
「馬吉」を色々と改造している間に、このプログラムは改良の仕方によっては結構使い物になる(使い勝手の良いプログラムの意味)のではないかと考えたからです。
私は今まで「馬吉」を利用した事はありませんが、現在でも数多くの方が「馬吉」を利用されており、常に人気ソフトの上位に位置しております。
私が「馬吉」を利用していない理由は色々ありますが、その中の1つにIPATの投票機能がないことが挙げられます。
有力馬を探して馬券を買おうと思った時に、IPATに投票するのに別ソフトをいちいち立ち上げるのでは、私は興ざめしてしまいます。

IPATへの接続方法については、ここでは述べませんがサイトを小まめに調べれば色々な方法の解説が載っております。
要は、人間が画面に入力する事をプログラム上で行うだけです。
画面の表示内容はHTMLに記載されておりますので、テキストボックスに文字を入力したりボタンをクリックする事もプログラムで可能です。
実際にはレース会場の選択や馬券の種類の設定、購入数などかなりの部分を設定しなければなりませんので、とても面倒です。
しかも、実際にテストできるのが土日だけですので尚更です。
私も最初の頃は、IPAD体験サイト http://www.jra.go.jp/IPAT_TAIKEN/ へ接続して、動作させるために試行錯誤しました。
実際のIPATとは異なっている部分も多いのですが、それでもテスト程度は十分に出来ます。
ここでも、プログラム作成に必要なのは才能ではなくて根気だなと改めて認識しました。

話がそれてしまいましたが、馬券の購入金額の設定は私が普段使用しているターゲットではフォーム上で買い目を設定してから。次に金額入力をクリックすると同じフォーム上にテキストボックスが現れてそこで金額を入力する方式になっています。
買い目の指定方法を色々選択させるためにこのようになっているのだと思いますが、私のように購入点数が最大でも20点程度ではさほどメリットがありません。
一括入力などをしなくても1個ずつ入力しても負担にならないからです。
そこで私は画面の金額入力の欄に直接入力させる事にしました。
この時の入力画面を下に表示します。

馬吉買い目画面

ご存じのようにフレックスグリッドに直接入力する事は出来ませんので、テキストボックスをフレックスグリッドの枠にぴったり重なるように配置してそのテキストボックスに入力する事で実際にはフレックスグリッドに入力しているように出来ます。
この方法は私が編み出したものではなくて、私が普段使用している「新VisualBasic入門 シニア編」にも詳しく記載しておりますし、VBの解説サイトの「花ちゃん」にもやり方が詳しく解説されております。
「馬吉」の場合は、フレックスグリッドの画面をクリックした時にイベントが発生しますので、そこの処理のプログラム内にイベントが発生した時にテキストボックスを表示して、テキストボックスからフォーカスが外れた時にテキストボックスを非表示させております。
実際のプログラムでは、フレックスグリッドへの書き込み処理の他にリターンキーを押した時に下へ移動させたり、入力した内容が数字であるかどうかの判定なども行っておりますが、この処理も造作もない事ですので内容は記載致しません。
この程度の応用が出来なければ、「馬吉」の改造は無理です。
動作させてみた感じですが、私には満足できるものでした。

●速報データの自動取り込み

「馬吉」には速報データの自動取り込み機能はありません。
なぜないのかと私が考えるのは、下手に自動取り込み機能をつけると使用者が多いソフトでは、JRAのサーバーがアクセスが多すぎてハングアップしてしまう事を心配しているのではないかと言う事です。
幸か不幸か私の作成している「エクセル競馬予想 馬ちゃん」には速報データの自動取り込み機能を搭載しておりますが、使用者が少ないので余計な心配をする必要はありません。
冗談は兎も角、速報データの自動取り込み機能は大変便利なものです。
配当が確定すればすぐに判りますし、オッズも常に最新の状態が維持されております。
移植版の「馬吉」にもこの機能は当然つけました。

この機能を付けた事で不満な点は、自動取り込み機能が働いている間は、パソコンの負荷が重くなると言う事です。
このVBで作成したソフトではそれ程でもありませんが、エクセルのソフトの場合はエクセル自身にも自動取り込み機能を持たせておりますので、自動取り込み中は処理がもたつく感があります。
私のパソコンの性能が低いせいもあるかもしれませんが、それでも平均以上の処理性能はあります。
これを改善するために、今回は速報データの自動取り込み機能を見直す事にしました。

従来は取り込み間隔は1分間隔で終了したレースを除いて全てのレースのオッズや配当、天候、場体重のデータを取り込んでおりました。
天候や馬体重の情報は20分間隔にしていたのですが、取り込みに時間が掛かるのはオッズ情報です。
取り込み間隔を1分ではなくて2分程度にすればパソコンの負荷は軽減されるのですが、配当情報などは一刻も早く知りたいので、そこはいじりたくありません。
そこで考えたのは、レースの発走時間に応じて取り込む間隔を変動させる方法です。
朝の1Rの開催時に12Rのオッズデータを1分間隔で取り込むと言うのはやはり無意味に思えます。

終了したレース(発走時間の過ぎた)のデータは取り込まないのは当然ですが、レース開始時間に応じて以下のように設定しました。
レース開始まで30分以内なら従来通り1分間隔。
1時間以内なら2分間隔。
2時間以内なら4分間隔。
3時間以内なら8分間隔。
それ以上は16分間隔。
こんなに細かく設定する必要性は無いでしょうが、半分趣味の世界に入っています。
レース前10分以内は30秒間隔にしようかと思いましたが、この時間帯はオッズの変化が多すぎて無意味だろうと思って止めました。
多分、JRA-VANのデータの供給も変化に追随出来ないと思います。
これで、どんな具合になるのか、次のレース開催日が待ち遠しいです。
(プログラムは、DateDiff を使ったありふれたものですので掲載は省略します。)

【追記】
変更したプログラムで速報データを取り込んでみたら、今までは頻繁に取り込んでいたのが確認できたのですが、変更してからはインターネットへのアクセスが少なくなったようです。