謎のエラーが発生した・・・

‘System.InvalidOperationException’ の初回例外が System.Data.dll で発生しました。
‘System.Data.OleDb.OleDbException’の初回例外が system.data.dllで発生しました。

プログラムをリファクタリングしていたら起動時にコンソールへ上記のエラーが表示されるようになった。思い当たる節がないんだが・・・。sqlのinsert文を手直ししたけれど、起動時に引っかかる箇所じゃないし、DBのテーブルにフィールドを一個追加したことはしたけれど、起動時に見るテーブルとは無関係だし・・・。謎。

バックアップを作業前に念のためとっていたため、どこが引っかかるか再現させてみようとしたら、今度は現れない・・・。謎。

(その後)
データベースのテーブルのフィールド名を追加したり、名前を変えたりしたら、
‘System.Data.OleDb.OleDbException’の初回例外が system.data.dllで発生しました。
というエラーがまた出た。一度visual studioを終了して、プロジェクトを開きなおしたら出なくなった。またしても謎。推測だけれど、デバッグでDB接続関連のエラーが一度出たらその後もずっとで続けるのだろうか。それとも私のvisualstudioが壊れてるか。

メモ2

構文の備忘録。下記のコードは拙作のシンプルな家計簿(仮)で使っているコードです。あくまでサンプルです。

if文

if文のコードサンプル。下記のコードは時間によって挨拶文を変化させる分岐。

        '現在の時間を取得
        Dim nowHour As integer = DateTime.Now.Hour

        '現在時刻が5時から11時の間ならば、
        If (nowHour > 4) And (nowHour < 12) Then

                'ユーザ名+おはよう
                Me.hello_label.Text = title_user & "さん おはよう!"

        '現在時刻が12時から17時の間ならば、
        ElseIf (nowHour >= 12) And (nowHour < 18) Then

                'ユーザ名+こんにちは
                Me.hello_label.Text = title_user & "さん こんにちは!"

        '上記以外の場合は、
        Else

                'ユーザ名+こんばんは
                Me.hello_label.Text = title_user & "さん こんばんは!"

        End If

for文

for文のコードサンプル。下記のコードはデータグリッド内の数値がプラスかマイナスかを一行ずつ判断例。

        'データグリッドの行数を取得
        ds_record = main_form.dg_search.Rows.Count

        'データグリッドの行数分ループ
        For i = 0 To (ds_record - 1)

            'データグリッド内の金額を取得。
            cb_item = Convert.ToDecimal(main_form.dg_search.Rows(i).Cells(4).Value)

            '金額が0以下(マイナス)ならば、
            If cb_item < 0 Then

                '金額のセルのカラーを赤色に設定
                main_form.dg_search.Rows(i).Cells(4).Style.ForeColor = Color.Red

            '金額が0以下以外ならば、
            Else

                '金額のセルのカラーを緑色に設定
                main_form.dg_search.Rows(i).Cells(4).Style.ForeColor = Color.Green

            End If

        '次の行へ
        Next i

select case 文

日付の値によって元号を分岐させるselect~case文のサンプル


        Select Case jpCal.GetEra(targetDate)

            Case 1
                gengo = "明治"

            Case 2
                gengo = "大正"

            Case 3
                gengo = "昭和"

            Case Else
                gengo = "平成"

        End Select

menustripのショートカットキーが効かない

また嵌ってしまったので備忘録。vb.netのmenustripコントロールに配置されたアイテムにshortcutkeysプロパティでキーを割り当てても、ショートカットが機能しない現象について。shortcutkeysプロパティでは色々と複雑なキーを割り当てることができるようになっているけれど、menustripの項目に設置しても何の反応もない・・・。あのファイル(F)や編集(E)や表示(V)といったお決まりの項目。ショートカットキーを設定して、設定したショートカットキーを押したときにはmenustripの項目がプルダウンするというか、メソッドとしてはshow()の動作をしてもらいたいのだけれど、効かない。なんでだ?としばらく考え込んでみたところ、menustripの項目にショートカットを割り当てるには&(アンパサンド)使わなければならない模様。テキストプロパティに、例えばファイルのメニュー項目ならば”ファイル(&F)”という風に入力すると、altキー+Fキーでプルダウンするようになる。こんな単純なことだとは知らずに、危うくkeypressイベントでkeycharを拾ってshowメソッドを呼ぶしかないかと実行するところだった。

・・・で、なんでshortcutkeysプロパティが効かないんだろう?

進捗状況を表示するプログレスバー

プログレスバーの使い方

プログレスバーは、アプリケーションが何かの処理中であることをユーザに知らせてあげるという役割を持っている。時間がかかる処理などの場合にその進行状況をプログレスバーのアニメーションで明示することで、ユーザに安心感を与えることができる・・・かもしれない。

自分なりにプログレスバーの使い方を調べてみたところ、どうやらプログレスバーはプロパティの初期値と最大値、増加率を設定し、増加するタイミングにメソッドを呼び出すことで進捗率を表現する模様。具体的なプロパティ名は下記の通り。

        'プログレスバーの初期値
        Me.progressBar.Value = 0

        'プログレスバーの最大値
        Me.progressBar.Maximum = 100

        'プログレスバーの増加率
        Me.progressBar.Step = 25

        'プログレスバーの値を増加させるメソッド
        (このメソッドを呼ぶごとに上の増加率がvalueに追加される)
        Me.progressBar.PerformStep()

使い方によっては上手いやりかたがありそう。例えばデータベースに関わるプログラムを作る場合などで、プログレスバーの最大値をdatasetのレコード件数にして、プログレスバーの増加率を1にし、ループ処理中にPerformStep()を組み込むとか・・・・思いつきでやったことはないけれども。

サンプルコード

プログレスバーを使ったサンプルコードを下記に記してみる。使い方としては大体こんなイメージだろうと思う。

        Me.ProgressBar1.Maximum = recordIds.Length
        Me.ProgressBar1.Step = 1
        Me.ProgressBar1.Value = 0
        Me.ProgressBar1.Visible = True

        For i = 0 To (recordIds.Length - 1)
                sqlStr = "UPDATE targetTable SET targetCol = #" & targetVal & "# WHERE targetId = " & recordIds(i)
                obj.execSql(sqlStr)
                Me.ProgressBar1.PerformStep()
        Next i

        Me.ProgressBar1.Visible = False

メモ

現在改良中の家計簿ソフトのデータモデルを変更するかどうかについてのメモ。今のところはmicrosoftのaccessのjetという名前のデータベースでデータを管理している。作る前まではデータを保存する方法といえばこれ一択だった。技術と知識があまりないためだけれども。で、多少慣れてきたのでxml形式でのデータ保存に変更しようかどうか考え中。

microsoft accessを使うことのメリット

  • windowsアプリケーションを作る上では親和性が高い
  • スタンドアロン形式のmdbファイルを使うことができるので外部サーバなどを用意しなくてもよい
  • テーブルやクエリなどの管理が開発者にとっては容易
  • データを一元的に管理できるため楽

microsoft accessを使うことのデメリット

  • ファイルが破損した場合全てのデータが失われる可能性がある
  • ファイルサイズが大きくなった場合に処理速度が低下する可能性
  • オフィスのバージョンアップ毎に機能の変更をチェックしなければならない(?)

今のところ、accessのデメリットというものはあまり感じてない。まあ、accessに限ったことではないし、そうそう壊れるものでもないだろう・・・という楽観的な考えで、バックアップ機能を実装することで最低限これで良しとかいいかげんに考えているからなわけで。それで、accessを使うことをやめてxml形式にした場合にどうなるか考えてみた。

xmlファイルをデータ保存に使うことのメリット

  • microsoft excel 2003以降で開くことができる
  • メモ帳でも開くことができる
  • 別のプログラムで加工して利用するというようなこともできる(かも)
  • mdbに比べてファイルサイズを小さくすることができる(かも)
  • windows以外のコンピュータでも開くことができる

xmlファイルをデータ保存に使うことのデメリット

  • sqlを発行するデータベースに比べてデータの検索や追加などの処理速度が遅くなる可能性がある
  • コード量が割り増ししそうで疲れそう(開発者にとって)
  • データの秘匿・機密性というものがない(私が知らないだけかも・・・)
  • パソコン初心者にはとっつきにくい。

結論

あまり、xmlを使うことのメリットらしいものは思いつかなかった。そもそも初心者向けの家計簿ソフトでxml形式のデータがどうしても欲しいなんていうことはないだろうしなぁ。処理速度が速くなってファイルサイズが小さくなるならxmlファイル形式にすぐにでも変更したいところだけど、そうとも言えないどころかパフォーマンスは悪化しそうだ。excelで開けるといっても年に1回するかどうかもわからない機能のためにこだわるわけにもいかんし。家計簿ソフトのxmlファイル形式への変更は、中止。やっぱり今のままaccessのmdbで運用して、xmlファイル形式に出力できる機能を追加するというのがベストの予感。

datasetの内容をxmlファイルに出力する

datasetの内容をxmlに出力するのにはたった一行、writexmlという関数を使うだけでできあがる模様。あいたたた。datagridにバインドされたデータを一生懸命xml化するためにコードを考えていたのが馬鹿らしくなった。datasetの内容をxmlに変換できるというのは知らないとわからないけれど、xmlデータをdatagridにバインドできるのを知ってたのに逆を想像できなかったのが鈍すぎる。

datasetをxmlファイルに出力するサンプルコード(スマートな方法)

    'datasetを作成するための素材
    Dim cn As New OleDb.OleDbConnection("データベース接続文字列")
    Dim da As OleDb.OleDbDataAdapter
    Dim ds As DataSet

    'datasetにsqlの結果のテーブルをセット
    ds = New DataSet("家計簿")
    da = New OleDb.OleDbDataAdapter("SELECT * FROM 収支", cn)
    da.Fill(ds, "家計簿")

    'datasetにセットされたテーブルをxmlファイル出力
    ds.WriteXml("./data/account.xml")

    'datasetの解放
    da.Dispose()
    ds.Dispose()

datasetをxmlファイルに出力するサンプルコード(悪い見本)

    'datasetを作成するための素材
    Dim cn As New OleDb.OleDbConnection("データベース接続文字列")
    Dim da As OleDb.OleDbDataAdapter
    Dim ds As DataSet

    ds = New DataSet("家計簿")
    da = New OleDb.OleDbDataAdapter(SELECT * FROM 収支, cn)
    da.Fill(ds, "家計簿")

    'xmlドキュメントの宣言
    Dim xmlDoc As New System.Xml.XmlDocument

    'XML宣言
    Dim xmlDecl As System.Xml.XmlDeclaration = xmlDoc.CreateXmlDeclaration("1.0", "UTF-8", Nothing)

    '宣言を子要素として追加
    xmlDoc.AppendChild(xmlDecl)

    'ルート要素の作成
    Dim rootEle As System.Xml.XmlElement = xmlDoc.CreateElement("家計簿データ")

    'データグリッドのレコード件数をセット
    Dim records As Integer
    Dim i As Integer
    records = ds.Tables(0).Rows.Count
    i = ds.Tables(0).Rows.Count

    '家計簿のレコードの各要素の宣言
    Dim recordElements(i) As XmlElement
    Dim idEle(i) As XmlElement
    Dim dateEle(i) As XmlElement
    Dim catEle(i) As XmlElement
    Dim detEle(i) As XmlElement
    Dim accEle(i) As XmlElement
    Dim moneyEle(i) As XmlElement
    Dim comEle(i) As XmlElement

    'datasetのテーブルからレコードの各値を取り出してノードを作成。レコード件数分ループ。
    For i = 0 To (records - 1)

        recordElements(i) = xmlDoc.CreateElement("レコード")

        dateEle(i) = xmlDoc.CreateElement("入力日")
        dateEle(i).InnerText = ds.Tables(0).Rows(i).Item(0).ToString

        catEle(i) = xmlDoc.CreateElement("費目")
        catEle(i).InnerText = ds.Tables(0).Rows(i).Item(1).ToString

        detEle(i) = xmlDoc.CreateElement("内訳")
        detEle(i).InnerText = ds.Tables(0).Rows(i).Item(2).ToString

        accEle(i) = xmlDoc.CreateElement("口座名称")
        accEle(i).InnerText = ds.Tables(0).Rows(i).Item(3).ToString

        moneyEle(i) = xmlDoc.CreateElement("収支")
        moneyEle(i).InnerText = ds.Tables(0).Rows(i).Item(4).ToString

        comEle(i) = xmlDoc.CreateElement("備考")
        comEle(i).InnerText = ds.Tables(0).Rows(i).Item(5).ToString

        idEle(i) = xmlDoc.CreateElement("id")
        idEle(i).InnerText = ds.Tables(0).Rows(i).Item(6).ToString

        recordElements(i).AppendChild(dateEle(i))
        recordElements(i).AppendChild(catEle(i))
        recordElements(i).AppendChild(detEle(i))
        recordElements(i).AppendChild(accEle(i))
        recordElements(i).AppendChild(moneyEle(i))
        recordElements(i).AppendChild(comEle(i))
        recordElements(i).AppendChild(idEle(i))

        rootEle.AppendChild(recordElements(i))

    Next i

    '作成したルート要素をxmlDocの子要素として追加
    xmlDoc.AppendChild(rootEle)

    '作成したDOMドキュメントをファイルに保存
    xmlDoc.Save("./data/account.xml")

    'datasetの解放
    da.Dispose()
    ds.Dispose()

・・・こんなしんどいことをやろうとしてました(;´д` )

datagridにバインドしたデータが120秒経つと消える・・・

訳の分からない現象が現れた。accessのmdbへselect文を発行して取得したテーブルをdatagridコントロールにバインドするようにプログラムした。そしてデバッグで表示を確かめようとしたところ、一定時間経つとdatagridに一覧表示されていたレコードがフッと消えてしまう。一応時間を計ってみたところ120秒、2分ちょうどで消える・・・。なんだこれは。datagridのプロパティをいじりすぎてぶっ壊してしまったんだろうか。

とりあえず新規にdatagridコントロールを作り直して、みたところ訳の分からない現象は出なくなった。プログラミングのミスではないところのトラブルが対処法がないだけに一番困る。

※原因が判明した。monthcarendarコントロールのdatechangeイベントが悪さをしていた模様。私のプログラムミスでした。datagridコントロール作り直してもよくみたら直ってなかったヽ(´ー`)ノ

datagridの選択した行のセルの値を取得する

備忘。.netframeworks2.0のコントロールであるdatagridコントロールで、バインドされたデータ一覧から選択された行のセル値を取得する方法。datagridのSelectedRowsプロパティを使うとセルの値を取得することができるようになる。SelectedRowsという風に複数形になっているところに気づかなくてしばらく嵌ってしまった。行を複数選択された場合のことも想定されているようなのでインデックスを指定しなければセルの値は取得できない。SelectedRows(0)という風に。

サンプルコード

Dim selectedValue As String
selectedValue = Me.datagrid1.SelectedRows(0).Cells(0).Value

今月の年月の値を取得する

sqlのwhere句に使うための検索条件を作るときに迷ってしまったので覚え書き。まあ、出来てしまえばなんてことは無かったけれど、フォーマットとか関数とか調べても年月のみを取得するメソッドが見当たらなかったので自分で加工するしかなく、datetimeオブジェクトのtoday関数から日付を取得して、それをtostringして、余計な部分をremoveすることで年月のできあがり。下記にサンプルコード。
(昔は文字列の加工やループとかいうものがアレルギーだったけれど、今はもう何の抵抗もなくなっている自分に驚き。慣れってすごい・・・)

サンプルコード

        '今日の日付を取得
        Dim dtYearMonth As DateTime = DateTime.Today

        '取得した今日の日付を文字列型に変換
        Dim str As String = dtYearMonth.ToString

        '年月を取得するために余計な部分を削除
        str = str.Remove(7,11)

        '出来上がりヾ(´ー` )ノ
        MsgBox(str)

サンプルコード2

こういう方法もありかな・・・

        '今日の日付を取得
        Dim today_dates As Date = DateTime.Today

        '今日の日付から年の部分を取り出し。そして文字列型へ変換。
        Dim yearValue As String = today_dates.Year.ToString

        '今日の日付から月の部分を取り出し。format関数で形式を二桁に加工。
        Dim monthValue As String = String.Format("{0:00}", today_dates.Month)

        '年と月の値を合体。
        Dim yearMonthValue As String = yearValue & monthValue

        '出来上がりヾ(´ー` )ノ
        MsgBox(yearMonthValue)