アクセスランキング C# 家計簿アプリの習作 ”Instant Expenses” 技術解説 - スポンサー広告Instant Expenses

直列つなぎ。 -とある発達障害者の記録

知識と知識を繋ぐためのblog。 広汎性発達と診断されました。ぜんぜん役に立ってないけど。月収13万円(うち手取り11万)、家賃4万円で生活するひつじ人間。モウマンタイ。

  

スポンサーサイト

上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。

 スポンサー広告

   

C# 家計簿アプリの習作 ”Instant Expenses” 技術解説

ブログパーツ

更新履歴
  12.16 コードをhilighter化。
    ソースコード修正
    

18/2.18 このソフトのバグ修正版:https://mojakouinfotaker.blog.fc2.com/blog-entry-956.html
 基本はこっちつかってください。

18/3.10  ちょこっと修正しました。
  https://mojakouinfotaker.blog.fc2.com/blog-entry-1011.html

18/3.26
  参考リンクの追加  


 こんばんわ。ひつじ!人間です。
 もう製作途絶してから3年ぐらいになる家計簿アプリを頑張って完成させてやろうと、いろいろと手を加えています。ネックだったグラフ表示も何とかなりそうなので、一応公開しようかと思います。


 C# 家計簿 でググっても、まああまりたいしたモノは出ない筈なんで(俺が昔出したやつも消えてた)サンプルアプリとしても参考になるのではと思います。

  家計簿
 


 とはいうものの・・・・機能としては呆れるほどシンプルな代物なので(何しろ、開発動機が家計簿アプリとか入力がめんどくさい 人気のスマホアプリも無理だった)、誰でも作れると思います。こんなもん。
 特徴としては、入力の簡便さに特化しているため、数値をテキストボックスに入れてエンターキーを押すだけで入力が完了します。レシート上の値段を入力するのに20秒もかからない筈です。



 どのコントロールも、マウスオーバー+マウスホイールしただけでフォーカスが動くようになっています。


 例によってCodeフォルダに入ってるVisual Studioのプロジェクトをそのまんま出します。
 https://box.yahoo.co.jp/guest/viewer?sid=box-l-jjsgnkx53gwi3lt2r7nmvpbfvm-1001&uniqid=7a1465d4-8e67-41cd-9b50-22b596c80df4&viewtype=detail(旧バージョン)


 別に変なものは入れてないんだけど、githubじゃないというだけで警戒する人も居るらしいですね。
ま、どうでもいいんですが。
 exeも入ってるから(まだdebug側)、アプリとして使いたい場合は掘り出してください。
 いずれ単体で出すつもりです。




 ノイズが酷かったので音は消しました。
  
 
  



 
 
 コードの内容
 基本機能を備えた一つのアプリケーションとして機能させるには、いろんなコードを組み込まないといけないのは言うまでもありません。プログラマはめんどくさがりに向いてるとか言われてますが、とんでもないですね。むしろ、どう整理して捉えるかが要求されることになります。


 本アプリで使われているコードを挙げておきます。
 以下のコードは、基本的にコピペで誰でも組み込めるように意図してあります。

 

 
  ・ListBoxで項目読み込み
    StreamReader sr = new StreamReader(
        current + "\\sort.txt", Encoding.GetEncoding("Shift_JIS"));
            while (sr.Peek() >= 0)
            {
                // ファイルを 1 行ずつ読み込む
                string stBuffer = sr.ReadLine();
                // 読み込んだものを追加で格納する
                listBox1.Items.Add(stBuffer);



            }
            sr.Close();
 

            listBox1.SelectedIndex = 0;
           
           
}

 
ーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーー



 ・グラフ表示
       private void graph_chart_display()
        {

       

            chart1.Series.Clear();
            chart1.Series.Add("合計");
         


            chart1.Series[0].ToolTip = "#VAL{C}";
            chart1.Series[0].Label = "#VAL{C}";
  //  -http://www.atmarkit.co.jp/fdotnet/dotnettips/1016aspcharttooltip/aspcharttooltip.html   キーワードエディタでマクロが分かる。
   // -http://www.kanazawa-net.ne.jp/~pmansato/net/net_compo_mschart.htm
    //   サンプルファイルがまだ生きてる。



            chart1.Series[0].Color = CommmonColor;
          //CommmonColorは共有用変数。要らなければ削除。
           

            try
            {
                string current = Directory.GetCurrentDirectory();

                // Full path to the data source file

                string file = comboBox1.Text;
                string path = current + "\\kakeibo\\";

                // Create a connection string.
                string ConStr = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" +
                path + ";Extended Properties=\"Text;HDR=No;FMT=Delimited\"";
                OleDbConnection myConnection = new OleDbConnection(ConStr);


      //   -https://teratail.com/questions/45667
       //これはオレがした質問。SQLの扱いに結構手間取った。


     -http://blog.livedoor.jp/akf0/archives/51408939.html
   ////"Extended Properties=""text;HDR=No;FMT=Delimited;   で、F1,F2とフィールド名が自動で設定される。提示されたSQL文内がなぜF1なのか疑問だったがこれで意味が分かった。先頭行にフィールド名がないCSVの場合はHDR=No(ヘッダ情報なし)などと入れるらしい。
    // 「FMT=Delimited」は、ファイルのフォーマット形式がCSVファイルであることを示す。

              


                  string mySelectQuery = "SELECT F3,SUM(F1) FROM " +"\\" +file+ " GROUP BY F3" +  " ORDER BY F3 ASC";
   //ここで注意しないといけないのが、半角スペースを入れることだ。ORDER BY直前の半角スペースを削除すると動かないことを確認してほしい。またはbreak point で mySelectQuery内を覗くと良く分かる。
 


                //string mySelectQuery = "SELECT F2, SUM(F1) AS F1S" + "WHERE F2 LIKE '%水道代%' GROUP BY F2 ORDER BY F2 FROM" +"\\" + file;
  //項目名で絞り込む場合はwhere句にLikeを付ける。小文字でも構わないらしい?


                OleDbCommand myCommand = new OleDbCommand(mySelectQuery, myConnection);

                // Open the connection and create the reader
                myCommand.Connection.Open();
                OleDbDataReader myReader = myCommand.ExecuteReader(CommandBehavior.CloseConnection);

         
               
               

                chart1.ChartAreas[0].InnerPlotPosition.Width = 90;
                chart1.ChartAreas[0].InnerPlotPosition.Height = 90;
                chart1.ChartAreas[0].InnerPlotPosition.X = 8;
                chart1.ChartAreas[0].InnerPlotPosition.Y = 0;
                chart1.ChartAreas[0].AxisX.Title = "支出";
                chart1.ChartAreas[0].IsSameFontSizeForAllAxes = true;
            

                chart1.Series[0].Points.DataBindXY(myReader, "0", myReader, "1");
    // "0"列目(F2)が系列名、"1"列目(SUM(F1))が値
            

                myReader.Close();
                myConnection.Close();


              

            }
            catch (OleDbException OleEx)
            { MessageBox.Show(OleEx.Message);  }
        }


--------------------------------------------------------------------------------------------------

  ・コントロール上でマウスオーバーするとフォーカスが動く
  -コンストラクタ
     public Form1()
        {
            InitializeComponent();
            this.dateTimePicker1.MouseWheel
              += new MouseEventHandler(this.dateTimePicker1_MouseWheel);

            this.dataGridView1.MouseWheel += new MouseEventHandler(this.dataGridView1_MouseWheel);

            this.listBox1.MouseWheel += new MouseEventHandler(this.listBox1_MouseWheel);

        }

 

   ・DateTimePicker上でマウスオーバー+マウスホイールすると日付変更
   

//フィールド変数
        DateTime DT = new DateTime();

        private void dateTimePicker1_MouseWheel(object sender, MouseEventArgs e)
        {
            // スクロール量(方向)の表示
            DT = dateTimePicker1.Value.AddDays(e.Delta / 120);
            dateTimePicker1.Text = DT.ToShortDateString();
        }
   //概要:マウスオーバー+マウスホイールだけで日付部分のみ変更可能なサンプル。
   //ちなみに自作。
   //年や月とかも変えたいが、内部boxのフォーカス判定方法が分からない。
     自作しました。

  ・ListBoxでマウスオーバー


        private void listBox1_MouseWheel(object sender, MouseEventArgs e)
        {

            try
            {
                int wheel = e.Delta / 120;
                int Current = listBox1.SelectedIndex;
                listBox1.SetSelected(Current - wheel, true);
            }


            catch (ArgumentOutOfRangeException)
            {// MessageBox.Show(OutEx.Message); }
      //OutOfRangeException例外をまんま握り潰してるけど、出来ればイベントをキャンセルすべき
            }
        }

 ・DataGridViewで

        private void dataGridView1_MouseWheel(object sender, MouseEventArgs e)
        {
            dataGridView1.MultiSelect = false;

            try
            {
              int wheel = e.Delta / 120;

                int Current = dataGridView1.CurrentCell.RowIndex - wheel;
                dataGridView1.Rows[Current].Cells[0].Selected = true;
            }

            catch (Exception exception)
            {
                if (exception is ArgumentOutOfRangeException || exception is ArgumentNullException)
                { }
            }
        }
 //この辺は改良予定。
 -------------------------------------
 

・DataGridview内の列合計値を算出
 //シンプルながら中核となる機能
 //作りたての時は相当スパゲッティなコードを無理やり作っていた。
 //なんかdefaultでこの機能があるとか


        public void TotalCostCalc()
        {
            try
            {
                List<int> TotalCalc = new List<int>();

                for (int i = 0; i < dataGridView1.Rows.Count; i++)
                {
                    //if (i == 0) { label3.Text = (Convert.ToDecimal(dataGridView1.Rows[i].Cells[0].Value)).ToString(); }

                    if (i >= 0)
                    {
                        //if (i + 1 == dataGridView1.Rows.Count) break;

                        TotalCalc.Add(Convert.ToInt32(dataGridView1.Rows[i].Cells[0].Value));

                    }

                }
                label3.Text = TotalCalc.Sum().ToString("C");
                  //"C"は通貨で使用される書式
            }
            catch (FormatException)
            { }
        }

------------------------------------
 ・DataGridviewの末尾・先頭に追加(自作)
    private void textBox1_KeyDown(object sender, KeyEventArgs e)
        {
            textBox1.Text = re.Replace(textBox1.Text, myReplacer);
            dataGridView1.MultiSelect = false;

            if (e.KeyCode == Keys.Enter && textBox1.Text != "")
            {
                #region 末尾から追加する処理
                if (BottomAdd.Checked == true)
                {

                    //int DataAbouveRow = dataGridView1.Rows.Count;
                    int EndData = dataGridView1.Rows.Count - 1;


                    if (textBox1.Text != "" && dataGridView1.Rows[EndData].Cells[0].Value == null)
                    {



                        dataGridView1.Rows.Add(1);
                        dataGridView1.Rows[EndData].Cells[0].Value = textBox1.Text;

                        dataGridView1.Rows[EndData].Cells[1].Value = listBox1.SelectedItem;

                        dataGridView1.Rows[EndData].Cells[2].Value = dateTimePicker1.Text.Substring(5, dateTimePicker1.Text.Length - 5);
                    }



                    dataGridView1.FirstDisplayedScrollingRowIndex = dataGridView1.Rows.Count - 1;


                     dataGridView1.Rows[dataGridView1.Rows.Count - 2].Selected = true;



                }
                //常に直近の入力データをフォーカス

#region 先頭から追加する処理
else if (BottomAdd.Checked == false)
{
dataGridView1.Rows.Add(1);



for (int i = dataGridView1.RowCount - 1; i >= 1; i--)
{
for (int j = 0; j <= dataGridView1.ColumnCount - 1; j++)
{
if (i == 1)
{
dataGridView1.Rows[i].Cells[j].Value = dataGridView1.Rows[i - 1].Cells[j].Value; }

else if (i > 1) {

dataGridView1.Rows[i - 1].Cells[j].Value = dataGridView1.Rows[i - 2].Cells[j].Value; }
}

}



dataGridView1.Rows[0].Cells[0].Value = textBox1.Text;

dataGridView1.Rows[0].Cells[1].Value = listBox1.Text;

dataGridView1.Rows[0].Cells[2].Value = dateTimePicker1.Text.Substring(5, dateTimePicker1.Text.Length - 5);
dataGridView1.Rows[0].Cells[3].Value = "";

dataGridView1.FirstDisplayedScrollingRowIndex = 0;
                //先頭にフォーカス


dataGridView1.Rows[0].Selected = true;


// dataGridView1.;
}
#endregion



                textBox1.Text = "";
            }



            TotalCostCalc();

        }

ーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーー

 ・数字のみ入力可能なテキストボックス(日本語文、コピーペーストによる入力不可、全角数字は可能)※全角数字は半角に自動変換されます。
 
//Textboxに数値しか入力出来ないようにする

 
 public class NumericTextBox : TextBox
        {

            const int ES_NUMBER = 0x2000;
            protected override CreateParams CreateParams
            {
                [SecurityPermission(SecurityAction.Demand,
                    Flags = SecurityPermissionFlag.UnmanagedCode)]
    

                get
                {
                    CreateParams parms = base.CreateParams;
                    parms.Style |= ES_NUMBER;
                    return parms;
                }

            }
        }


ーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーー
エラーと対処
chart1.Series[0].Points.DataBindXY(myReader, "0", myReader, "1");


・一つだけの抽出に成功
・集計させないで抽出させるとエラー 
  -''1' という名前の列が見つかりませんでした。'


 SQLを以下のように修正すると通った
    string mySelectQuery = "SELECT F3,F1 FROM " +"\\" +file+ " WHERE F2 LIKE '%水道代%'";
SELECT F3,F1  カンマ区切りは複数指定だった模様
 つまりF3とF1を指定



DataGridviewとグラフの同時読み込みにおいて、一見どこも問題ないのにグラフの読み込みに失敗・・・というより、DataGridviewと競合してグラフ側の表示が上手くいっていない。


 parser.Close(); と明示的に閉じればいいだけだった・・・・・・・。

・「開く」を押してからキャンセルすると"クエリ句の構文エラー"と出る。既に表示されているグラフも消える。 -グラフ表示は更新されないように改善予定
  if文でif (ofd.SafeFileName != "") と分岐。

・ 備考欄が保存されない・・・・
 保存されないのではなく、編集を確定させずに「上書き保存」すると反映されないだけ。
     dataGridView1.EndEdit();とした。



ーーーーーーーーーーーーーーーーーーーーーーーーーーーー
苦労した点。
 いうまでもなくグラフ。SQLというのに馴染みがなく、しかもコード上でstringを+するなどしたため、修正に苦労した。分かってしまえばなんてことないんだが。


★SQLとOLEDBに関して学びたい方へのリンク★
レコードの絞り込みと並べ替え/ADOによるデータベース制御
集計を行う「GROUP BY」句
64bit版Windowsでの「Microsoft.Jet.OLEDB.4.0」について
ADO Extended Properties='text;HDR=NO' でヘッダー無しCSVと接続(ソースと関連するものです 魚拓済み)


 基本オレ専用のアプリだから誰も使わないだろうな。細かい作り込みは必要なんだけどめんどい。



WPFでも同じコードで走ると思います。FormコントロールをWPF側で使う方法も用意されています。ちょっと手順が要るんだけど。
関連記事
スポンサーサイト

 Instant Expenses

 家計簿

     

-   0   Comments

Leave a comment

上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。