意の中のカワズ(35歳の壁 別館)

35歳の壁の別館ブログです。コード中心になるようにしたいので、技術雑記はできるだけ本館に書きます。

VBA全般:「画像付のコンボボックス表示」

ガントチャートを作る作業をちまちまやってるわけですが、
昨日くらいからどうでもよいところに躓いてます。
まぁ、気にしなきゃよいのですがちょっと画像処理系(?)弱いなぁと
思うのでがんばってます。

昨日は、

・オートシェイプをフォームに表示したい
・オートシェイプのサイズ変更を検知したい

の2つで躓きました。
結論、2つ目は、Widows近辺からフックしないとだめみたい。
イベントが飛ばないんですわ。

そんでもって、今日は、掲題のコンボボックスに画像を表示する。
という初歩的なことでこけました。

どういうものかというとこういうやつ。


わかりますでしょうか。
一番上のボックスがそれです。

え?こんなの当たり前??
いやぁ・・やったことなくて。

で、どうやって実現しているか。


色々デフォルト機能+APIで行こうと思ったのですが、
結局、DCを取得してくるのぐらいしかなさそうだったので
ActiveX方式にしました。

まぁ、要するにコントロールの参照追加です。
コントロールの追加に際して、とても重要なページを見つけました。

Visual Basic 6.0 に添付されている ActiveX コントロール一覧
 http://support.microsoft.com/kb/412577/ja

要は、ActiveXコントロール(VBE に標準参照していないヤツ)の
正式名称を探すためのサイト。
まぁ、ちゃんとしたのがあるのでしょうがVB6用です。

正式名称は、変わりませんがXPなどの標準で使うやつは、
OCX名が違うのでその点ご了承くださいませ。


以下、やることの概説です。

1) ActiveX 参照追加
2) コントロールの貼り付け
3) ImageList の設定
4) ImageComboboxの設定 コーディング

        • -

■ 1) ActiveX 参照追加

  今回、追加するのは
   ・ImageCombobox
   ・ImageList
  の2つです。
  ImageList を参照できるヤツといえば、他にも
   ・ListView
  などがあります。

  

  参照設定方法は・・わかりますね。
  上図のツールボックスを右クリックして、[その他コントロール] で
  追加してください。
  正式名称は、既出のURLを参考に。


■ 2) コントロールの貼り付け

  それぞれフォームに貼り付けるだけです。
  ここで、ImageCombobox と ImageList の関係を説明します。

  ImageList は、複数の画像を登録しておくためのコントロールで、
  タイマー同様、それ自体は表示とかはされないやつです。
  ImageComboboxは、通常のコンボと違って、イメージをコンボ内に
  内包させることのできるコントロール
  そして、そのイメージをどこから取得させるかというと、
  ImageList を指定して、そのリストから表示します。


  ImageList には、画像毎に
   ・インデックス
   ・キー
   ・タグ
  が設定できます。

  これをImageコンボで、何行目にどのインデックスのアイテムを表示。
  と、指示してやるわけです。

  これは、プロパティ画面からもできますし、プログラムからも
  設定できます。
  今回は、プログラムから設定しました。


■ 3)ImageList の設定

  前項で説明したとおり、ImageListには複数の画像をインデックス番号などで
  管理しながら登録できます。

  これもプロパティから設定できます。

  

  まさにプロパティページってのがありますので、それをクリックすると
  設定画面が起動します。

  プロパティページでは、
  [イメージ]タブ から [ピクチャの挿入] ってのを押すたびに
  画像を登録できます。
  キー、タグはなくてもいけるのかな?
  私は
   キー = a
   タグ = 1
  として、設定(後はインクリメント)しました。


■ 4)ImageCombobox の設定 コーディング

  イメージコンボボックスでも、プロパティ画面があります。
  その画面の[全般]タブ内でイメージリストの設定リストボックスで
  3) で作成したImageListの名前を指定すれば紐付けは完了なのですが、
  私はうまく行きませんでしたのでコーディングで処理します。


  

Private Sub UserForm_Initialize()
' コンボボックスの場合
Me.ImageCombo1.ImageList = Me.ImageList1
Me.ImageCombo1.ComboItems.Add 1, "a", "実線", 1, 1, 1
Me.ImageCombo1.ComboItems.Add 2, "b", "点線(丸)", 2, 2, 1

' リストビューの場合
Me.ListView1.SmallIcons = Me.ImageList1
Me.ListView1.Icons = Me.ImageList1
Me.ListView1.ListItems.Add 1, "a", "aaaaa", 1, 1

End Sub


■ 完成図

尚、後述で申し訳ないですがこのImageComboBox は挙動がおかしいことが多く、
Excelが異常終了させられやすいそうなので気をつけてください。


■ 補足

今回は、ListViewをプロパティ画面から設定しました。
後日、暇があれば動的に線画像を取込んでくるような方法
(例えば、他の設定で変更された線情報で描画しなおした画像作成し取込ませる。)
なんてのを考えてますが、やり方は画像作成し、取込むで完結しているので
興味ある日とは作ってみてはいかがでしょうか。

実は、完成図の上の方の枠内ではそういったプレビューが表示されるように
なっていますが、そこは画像作成で取込まずクリップボード経由で
取込んでいます。

VBA Excel:「ガントチャートをグラフで作る」

元ネタはこちらのサイトです。

参考:お手製 簡易ガントチャートをグラフで作ってみる。
http://edutainment-fun.com/excel/example/ganttchart.html

ここで記載されていたほかの方法は試したことがあるのですが、
これは面白そうなので試してみたらスゴイ苦労した・・orz

いえ、たぶんこれをゼロから説明するのが本当は大変なんでしょう。
TODOをWBS的なものにまで落とせるものをちょっと作ろうとしまして
勉強がてらVBA にて実現してみました。

というか、その手順のマクロ自動記録ですが、一部処理を書いています。

ということでいきなり全ソース。
わかりやすいように何してるかコメント書きました。
尚、デフォルト値の設定でもなんでも吐き出すのがマクロ記録なので
余計な設定も入っていますが、面倒なんであまり抜いてません。
(少し抜いたけど、ほんのちょっと。)


Option Explicit

Sub Macro1()


Charts.Add

' --- チャートの作成
ActiveChart.ChartType = xlBarStacked ' 積み上げ式
ActiveChart.SetSourceData Source:=Sheets("概要").Range("K21") ' K21を一時的にデータソースに設定

' データ系列作成
ActiveChart.SeriesCollection.NewSeries ' データ系列1追加
ActiveChart.SeriesCollection.NewSeries ' データ系列2追加

' 入力データ及び名称データセル指定
ActiveChart.SeriesCollection(1).XValues = "=概要!R13C12:R15C12" ' 要素名
ActiveChart.SeriesCollection(1).Values = "=概要!R13C17:R15C17" ' 系列1データ
ActiveChart.SeriesCollection(1).Name = "=概要!R12C17" ' 系列1名称
ActiveChart.SeriesCollection(2).Values = "=概要!R13C20:R15C20" ' 系列2データ
ActiveChart.SeriesCollection(2).Name = "=概要!R12C20" ' 系列2名称

' 概要シートに移動
ActiveChart.Location Where:=xlLocationAsObject, Name:="概要"

' グラフ軸の設定
With ActiveChart
.HasAxis(xlCategory, xlPrimary) = False ' 要素名非表示
.HasAxis(xlValue, xlPrimary) = True ' 軸の値表示指定(ここでは年月日)
End With
ActiveChart.Axes(xlCategory, xlPrimary).CategoryType = xlAutomatic ' 軸の目盛り設定(自動に。)

' グラフの横軸設定(つまり要素用)
With ActiveChart.Axes(xlCategory)
.HasMajorGridlines = True ' 主軸を表示
.HasMinorGridlines = False ' 補助線は非表示

End With

' グラフの縦軸設定(つまり横に伸びる値用)
With ActiveChart.Axes(xlValue)
.HasMajorGridlines = True
.HasMinorGridlines = False
End With
ActiveChart.HasLegend = False ' グラフの凡例非表示

' 縦軸(つまり値用)の設定
ActiveChart.Axes(xlValue).MajorGridlines.Select
With ActiveChart.Axes(xlValue)
.MinimumScale = 40809 ' ここが重要
.MaximumScale = 40817 'ここが重要
.MinorUnitIsAuto = True ' 補助幅自動設定
.MajorUnitIsAuto = True ' 主軸幅自動設定
.Crosses = xlAutomatic ' 交差ポイント設定
.ReversePlotOrder = False ' ここは重要ではないが、反対にならないようにする。
.ScaleType = xlLinear
.DisplayUnit = xlNone ' 軸のラベルを表示するかどうか。
.TickLabelPosition = xlHigh ' これ重要。上に軸目盛り値を持ってくる
End With


' 系列の1つ目の設定
ActiveChart.SeriesCollection(1).Select
With Selection.Border
.Weight = xlThin
.LineStyle = xlNone ' 線非表示
End With
Selection.Shadow = False
Selection.InvertIfNegative = False ' 要素を反転するか。反転してはいけない。
Selection.Interior.ColorIndex = xlNone ' 塗りつぶしをなしにする。

' 系列2に対しての処理
ActiveChart.SeriesCollection(2).Select
With Selection.Border
.Weight = xlThin
.LineStyle = xlAutomatic
End With
Selection.Shadow = False
Selection.InvertIfNegative = False
Selection.Fill.OneColorGradient Style:=msoGradientHorizontal, Variant:=3, _
Degree:=0.939208056763561 ' 色を明るいほうへ少し変更しつつ2色でグラデ
With Selection
.Fill.Visible = True
.Fill.ForeColor.SchemeColor = 18
End With


' グラフチャート全体の設定
ActiveChart.ChartArea.Select
With Selection.Border
.Weight = 2
.LineStyle = 0 ' 枠線を非表示
End With
Selection.Interior.ColorIndex = xlNone ' 塗りつぶしもなしにする

ActiveSheet.ChartObjects(Replace(ActiveChart.Name, "概要 ", "")).Activate
ActiveSheet.Shapes(Replace(ActiveChart.Name, "概要 ", "")).Height = Range("K13").Height * 5 ' プロットエリアの高さも行の高さx(行数(要素数)+2)にする。

' プロットエリアの設定
ActiveChart.PlotArea.Select
With Selection.Border
.Weight = xlThin
.LineStyle = xlAutomatic
End With
Selection.Height = Range("K13").Height * 5 ' プロットエリアの高さも行の高さx(行数(要素数)+2)にする。
Selection.Interior.ColorIndex = xlNone


ActiveWindow.Visible = False
Windows("TODO化してみせる_まくろグラフ.xls").Activate
Range("X37").Select
End Sub


簡単にファイル構成を説明すると、

最後のほうの行の

Windows("TODO化してみせる_まくろグラフ.xls").Activate

にあるTODO化してみせる_まくろグラフ.xls てのが、マクロのファイル名。
シート構成は、概要というシートが1枚だけあります。

マクロ自体はmodule1(つまりマクロ記録そのまま)で、それのみです。

上記URLを参考にしていただくとわかりますが、
手順としては、先に下図のような入力欄を用意するところから始まります。

ここで必要なのは、
・開始日
工数 ... 中身は (終了日-開始日)+ 1
      関数ならDATEDIF(終了日,開始日,"D")+1 です。
・最短開始日、最長終了日それぞれのシリアル値

の4つです。

作業内容は、上記URLを参考にしてもらいつつ、
グラフに慣れていない人には、わかりにくいところを
補完した説明を図にしました。(手抜きww)

手順1

要は、積み立て横棒グラフを使うことでの応用なので
そこから始ります。

手順2

項目1に開始日のリスト
項目2に工数のリスト
をそれぞれ指定して、要素名とかしていしてみたり・・。
しかし、普通に作成しては想像とはかけ離れたものに。


手順3

そこで縦軸に設定を施し

手順4

軸の値を下部から上部へ移動します。

手順5

横軸で項目順序を入れ替えたりして


完成したグラフがこれ

ここまでをマクロで一気にやります。
途中で、グラフを何度も生成したとき用にグラフ名をせこく生成しています。

当然、シート名やセル位置、ファイル名を自分が作成したファイルに
あわせないと動きませんがこんな感じ。

で、作っているときから気づいていましたが
これでは予定線と実績線が書けません。

なので、このアイディアでガント化するのはやめるのですが、
まぁ勉強がてらというか、こういう発想もありなのか!

という参考になればと・・。

いえ、まずはこういうアイディアを考えてくださった
上記URLの管理人さんに感謝です。m(_ _)m

Excel関数:「セル内の特定文字のカウント」

ふと作ってみたくなったのだが、頭が硬直してわからんでしたので、
検索してみたらマイクロソフトが公開していた。


=SUM(LEN(A2:A7)-LEN(SUBSTITUTE(A2:A7,"p","")))

これは、範囲だけれど


=LEN(A2)-LEN(SUBSTITUTE(A2,"p",""))

とすれば、当然、セル範囲内のカウントが取れる。
動作としては、


全体の文字数 - pを除いた文字数 = pそのものの文字数

となるので、それを範囲指定時には合算するというもの。
便利だ。

尚、VBAなら


instr("A2","p")

なわけだが、これをExcel関数でやろうとすると面倒な計算に
なってしまうことにびっくりした。


参考:
http://support.microsoft.com/kb/213889/ja

標準化:「クエリの散在をどう防ぐか」

言語は何でもよいのですが、クエリの散在をどう防ぐかという仕組みやルールが
必要になります。

FW を使うとFWで適用されないようなかなり簡易なレベルのクエリや逆に複雑な
レベルのクエリをどう散在させず管理するか。

いずれにしても発行出入口をひとつにする。
というルールが必要。

そして、決してソース内に他PGソースと同様に併記しないというルールが必要になる。
散在することのデメリットは、いうまでもなくメンテが大変なこと。
散在させないことのデメリットは、書きにくさと可読性にある。

特にトルクに近いもので作るクエリ生成エンジンは、

・エンジン用生成クエリ

・勝手に書いたクエリ
が混在する可能性がある。

で、ちょっと今そこ悩んでます。
ExcelVBA用FW内で構文解析または構文作成をするクエリエンジンを
つけているのですが、例えば


SELECT A.COLUMN FROM TABLE_A AS A WHERE A.ID='AAA'

なんて、読んでもわかりやすいものをわざわざFWの


dim strSQL as string

strSQL = makeSQL(Entity,ColNames,Values,TYPE_SRCH,strWhereDatas,StrWareColNames)



' Entity テーブル名
 ' colNames 取得カラム名
 ' strWhereDatas 条件データ群
 ' strWhareColNames 条件カラム群

なぁんて呼び出すのもバカらしいと感じることはある。
これが大規模開発となると当然、最初の直クエリを書き出すヤツがでてくる。

これはクエリ書くヤツだけが悪いのじゃなくて、そういう標準化ルーチンを作ったやつも悪い。


とはいえ、成功していると思われるクエリ群はID=クエリの列挙ファイルにて
まとめてしまうという方法もかなり嫌がられているのも確か。

小規模ならよいが、スゴイ数なわけで開けど開けどクエリみたいなファイルが
IDEに並ぶことになる。

散在の問題は、かなり肝を据えて掛からないと失敗するか不満の温床になると
感じています。

(そんで、今自分がクエリ散在をさせようとしている・・・ぉいw)

追記:ちなみに散在をさせようとしてしまっている要因は、IN句です。
   普通の検索ならよいが遅くてもINを使いたいときがある。
   これを共通化させる場合、IN用に処理分ける必要がある。
   ちょっと共通化して動いている部分を直すのが面倒なのです。
   困ってしまいますね・・。

書籍案内:「Visual Basic 6.0 Desktop」

資格試験というのが嫌いです。
なので、資格取得のために試験勉強を中心にエンジニアの技術ナレッジを
構築している人を見るとかなり嫌気がさします。

が、体系的に技術を抑えたいときに役立つのは、試験本だと最近思ったので
購入してみました。
古い本ですが、恐ろしく良書でビビリました。
最近VB6関連で書いてるのは、ここからのインスピネタです。

元々OracleJavaを久々に思い出してみようかなと思って、
面倒なので体系的にわかるものは無いか・・と考えてブックオフへと
向かったのですが、売っていたのがこれだけ。
が、思いのほか楽しい本でした。
サンプルもかなり簡潔ですし、これ・・現役時代に読んでたら何か違ったんじゃ・・
と思えるくらい良かったです。

VB6 なんて・・と、思うかもしれないですが、VBA で考えて読んでますし
VBA で考えれば、実際は2003 くらいまではその技術で書いてしまいます。
が、97 あたりとかはVB5で書くわけですね。

例えば、
Function test() as string()
 
End Function

は、VB5 では通じない。
よって、VBA97 でも通じない。
まぁ、そんな話はどうでも良くて、VB6。
懐かしいなぁと思いながらも意外と忘れている気づきが沢山あって
書かずにはいられない・・。
ノンビリ読んでいますので、昨日 変数の章が読み終わったところ。(おそっ)

サクサク読めるところがいいですね。
なので、今後もこの辺からのインスピネタが続きます。
どうぞよろしく。

Visual Basic 6.0 Desktop(試験番号:70‐176)―マイクロソフト認定技術資格試験学習書 (MCSD教科書シリーズ)

Visual Basic 6.0 Desktop(試験番号:70‐176)―マイクロソフト認定技術資格試験学習書 (MCSD教科書シリーズ)

VB6:「規約:可読性の問題 宣言部のあり方」

VBで現役PGをしていた頃の話ですが、嫌いなコーディングを見かけることがありました。

その代表例が、


dim a
dim b,c as String


b=c
a=b
d=b


If d = b then
dim z


z = b
End If

変なサンプルになってしまいましたが、かなりイラッとキマせんか?

1)まず、a 及び b の型がStringであることはわかりきっているのに
  型宣言しません。つまりVariant です。
  内部型の変換で暗黙変換を悪用した感じ。
  書ける=書いて良い ではないのが、複数人での開発だと思う。
  また、この問題には別のルールで

  dim 変数1、変数2、変数3 as 型

  とかくと、あたかもその行の変数すべてが最後の型のように読めてしまう錯覚を
  初心者が覚えているのが問題。(実験する時間がないのだろうか・・。)
  他の言語(Cなど)のときどういう動きをしたかを忘れている自分が言える話では
  ないのだけれど、使うときには言語別に注意や確認のうえで作業すべきだと思う。

2)突如出現するd 変数。
  まぁ、この辺はだいぶ改善されましたが。

3)突如宣言される変数 Z 。
  明らかに次のコーディングがしたいがために書いていると思われる。

このうち1,2 はVB ならではの特有問題。
問題なのは、3。


近年になって久々に見かけたのですが、そのプロジェクトはJava技術者によるC#開発でした。

正直、びびった。
え?っていうより、本気ですか?
とも思ったが、こちらの方が可読性が上がるという理由。

メモリリーク云々の問題もあり、統一された箇所に書くのがよいか、
メモリ消費量の問題で必要時にコンパイルさせるのがよいか。
( New でない限り結果は同じだが。)

とも違う理由であったこともそうだが、可読性が上がるということにかなり疑問があった。
Web上のサンプルでもたまに見かける。

フロー制御初心者がやってしまうIF内宣言に見えるが違うらしい。
が、一緒だろ・・。

宣言をまとめることに執着すると書きにくい。
というのはあると思うが、可読性が下がることは無いだろうと感じる。

型が何なのすぐにわからない。とも違う。

その方たちは、かなりのJava猛者であったはずだが、この書き方を通していた。
が、私は嫌いだ。

好き嫌いで標準化はできないが、私が規約を作るなら暗黙規約のひとつに


宣言部は、まとめろ。

と、たぶん書くだろう。

VB6:「サンプル:指定文字列内に検索文字数がいくつあるか」

え?
こんな関数が用意されていないのか・・?

はて?
まぁいいや、とりあえずオブジェクトを生成して作るとかメンドクサイというか、それは情けないのでさくっと書いてみた。
驚きだったので載せておきます。

VB6 というより、VB系のネタ全般で存在しない関数なのかな?
ま、細かい想定してないので今やりたいことはできる模様。


Sub testGetInstrCount()
Dim intCnt As Integer
intCnt = getInstrCount("AAA--BB-CC--", "AA")
msgBox CStr(intCnt)
End Sub


' 指定文字列中の検索文字が何個含まれるかを返す
' strSrc:検索対象文字列
' strSrch:検索文字
' 戻り値:検索文字列内の個数
Public Function getInstrCount(strSrc As String, strSrch As String) As Integer

Dim intCnt As Integer
Dim intDataCnt As Integer
Dim intCntBuf As Integer

For intCnt = 1 To Len(strSrc) + 1
intCntBuf = InStr(intCnt, strSrc, strSrch, vbBinaryCompare)
If intCntBuf >= 1 Then
intDataCnt = intDataCnt + 1
intCnt = intCntBuf
End If
Next
getInstrCount = intDataCnt + (Len(strSrch) - 1)

End Function