概要
仕事の関係でC#を使えるようになっておきたいと思いました。基本的に使って覚えるしかないと思うので、たくさん作っていきたいと思います。個人的なメモです。
C#の可能性
C#という言語は、C++++が元であり、C++の発展だとどこかに書いてありました。私個人はPythonをメインに使っています。javaでウィンドウアプリケーションを作ろうとするとSwingの利用になると思います。若干作るのが面倒だった記憶があります。PythonならTkinterですが、処理が遅かったりします。C#のほうが作りやすそうな予感がします。一方でNET Frameworkを使って開発するので、ドライバのような、実行環境に密接に連携した機能は実装できそうです。Unity,Androidアプリへの活用もできそうです。C++と比べてC#のほうが実行速度は遅いみたいです。
自分のレベル
クラスやカプセル化は理解できてます。というより、大層な名前がついているだけの機能なので難しくないです。ポリモーフィズムは異なるクラスのオブジェクトで同じ関数が存在するみたいな、プログラムを記述しやすくなる工夫のようなものであると理解をしてます。余り使い慣れてないですが。一方でC++は全く手つかずです。競技プログラミングとか3日続かなかった・・・楽しそうではあるけど
勉強の流れ
- とりあえず、ネットの文法理解のサイトを読む
- Windowsアプリケーションを何個か作ってみる
- C#しか使えない、WindowsAPIに特化したアプリケーションを作る
- 音量をキーボードと対応させる
- JoyToKeyのような、コントローラからキーボード入力変換ソフト(執筆時、ここまではやった)
- WPFアプリケーション
- Formアプリケーションより、アプリと機能が分離しているらしい(どうべんきょうしようかな)
この記事を書き終わってのC#に対する感覚
Visual Studio自体が非常に書きやすく楽。頭数文字打ったらほとんどTabを押すだけでいい(未定義の変数名とか入れると全く関係ないものに変換してしまったりするのはちょっとだるいけど)。Visual Studioが優秀でビルドする前からどこが悪いかすぐわかるのもいい。フォームにものを追加してプロパティ変えて、コードいじって行くだけで完成する。Pythonぐらいライブラリも充実しているかもしれない。今回音量調整アプリを作ってみて、非常に簡単にかけた。Pythonで音量調整アプリを作ろうとすると、余り情報がない。C#だとたくさん出てくる。Windowsのシステムに直結したソフトを作るにはC#は最適である!いままで、C#をC,C++ぐらい難解でめんどくさくて、型エラーをたくさん吐いて、苦労して作っているイメージがあったのでイメージが覆った。C#はどちらかというとPython、javaに近い言語だ。PythonのTkinterでソフトを作るより楽である。というか、PythonでWindowsアプリを作るのは余り勧められないのかもしれない。C#をちょっと使えるレベルなら、2日もあれば十分なので、ぜひ遊んでみよう。
文法
とりあえず、Visual Studioを入れて
を読みます。
名前空間
namespace name{} //classのグループを定義、同一namespace内でクラスを使用する場合は考えなくていい
using namespace //importの代わり
this.field //thisが使える
セッター、ゲッターの書き方
namespace hello_world
{
class Name //クラス名の頭文字は大文字
{
private string name1 = "def";
public string Name1 //setter,getterの定義、プロパティと呼ばれる
{
set { name1 = value; }
get { return name1; }
}
}
class Program
{
static void Main(string[] args)
{
Name n1;
n1 = new Name();
Console.WriteLine(n1.Name1); //インスタンスのメンバにアクセスする場合、頭文字は大文字にすれば、プロパティが呼び出される
}
}
}
set,get
という関数を定義する必要はない。代わりにプロパティを定義しておく。プロパティはメンバ名の頭文字を大文字にすれば呼び出せる。プロパティとは
public string Name1 //setter,getterの定義、プロパティと呼ばれる
{
set { name1 = value; }
get { return name1; }
}
メンバ変数の頭文字を大文字にした処理。クラス内に定義する。これ無しで、private,inernal,publicなどの設定で直接アクセスする方法もある。
- プロパティでまとめてやることでアクセスの管理ができる(setter,getter自体のメリット)
- setter,getterの名前がシステム的に決定されるため悩みが減る(setter,getterの仕様違いなどの問題は減る)
private set;
get;
で外部から書き込めなくできる。アクセスしようとするとエラーを吐く。この書き方は自動実装プロパティと呼ばれる。
set;get;
と
set { name1 = value; }
get { return name1; }
は同じ結果になる。
get;
setを削除すると、代入しようとしたときにエラーを吐く。
メンバへの代入は
instance.Member = data //メンバーの頭文字を大文字にするだけでいい
つまり、instance.get_member(),instance.set_member(int)
みたいなことはする必要はないということです。メンバ変数の頭文字を大文字にして、代入・読み込みでOKです。禁止にしたければ`set,get`内の処理を変える。処理自体が発生してはいけないときはset,get
を消せばコンパイルエラーで現れる場所がわかります。
プロパティは書き込み・読み込み制御(エラーを出してとまる)
private,publicはそもそものアクセス制御(エラーを出してとまる)
private変数にプロパティを付けても、クラス外からは書き込み・読み込みはエラーとなる。
やろうとして、できないと両方エラーを吐いてとまる
コンストラクタ
コンストラタはjavaと同じで、クラス名と同じ関数を定義します。Python書いたあとだとdef init(self):を書きそうになりますが注意です。
継承
class class_name : inheritance_class{}
コロンでくっつけます
クラスの使い方は大分変化したので、注意が必要そうです
他言語との違いとメモ
static void Main(string[] args){} //Main処理
Console.WriteLine(string) //print
i++; //インクリメント使える
Random rnd = new Random();
int num = rnd.Next(1,7); //1 <= num < 7 の乱数生成
int[] array = new int[3]; //C言語式配列宣言
int[] array = {1,2,3,4,5,6}; //初期化型宣言
array.Length; //大文字使用
foreach(int i in n){} //python型 for(int i in n)ではエラー、java タイプfor(int i:array){}は使えない
int[,] a = new int[3,4]; //多次元 a[1,4]みたいに呼び出す []は連続させない
Windowアプリケーションを作りながら練習
ゴールから始めるC#
という本で練習します。
フォームアプリケーション:引っかかったポイント
変数名エラー
IDE1006 名前付けルール違反: これらの単語は、大文字で始まらなければなりません: textBox1_Leave ControlCheck
C#ではローカル変数以外は先頭文字は大文字にするようです。更に調べると
に依れば、
- プライベートなメソッドは小文字で始める
- パブリックなメソッドは大文字で始める
のようなルールもあるみたいです。よくわかんね
とりあえず、
TextBox1
に名前を変更すれば動きました。
正直、全部キャメルケースにしたいです。クラス名は先頭大文字ぐらいか
名前だけでパブリックとかいろんなことがわかると便利か?
むずかしいなぁ
フォームアプリケーションでの要素名はとりあえず大文字にしないとエラーが出るということで、大文字にしときます。
とりあえず配置すると、小文字で定義されているんだよなぁ
ラジオボタン
10進数
decimal i = 0.1M;
10進数として記憶します。
文字列をintに変換
int.Parce(string)
double.Parce(string)
int(string)は通りません
この本やること多いな
エラー2
このシリアル化マネージャーにはアクティブなシリアル化セッションが既に存在するため、新しいセッションを作成できません。
プロジェクトを開き直したら、解決した。
参照渡し
変数を参照渡しできる
private void function(int a){}
function(ref k);
kは参照渡しされる。
return複数はできない
代わりに、呼び出しに参照渡しで変数を渡しておく。
このときはout キーワードを付けて渡す。
文字列を数値に変換
int = int.Parse(string)
エラー対策
int answer;
if(int.TryParse(string,out answer) == true){}
else{}
参照渡しintに変換した結果を得ます。正しく、変換できない場合はfalseが返ってくるため、else内が実行されます。
コンソールアプリで数値を取得する:Console.ReadLine()
Read()の返り値はint型のアスキーコードです。一文字ずつ帰ってきます。
Console.ReadLine()の返り値はstring型。\nまでの文字列を拾ってこれます。
int ConsoleRead(string string_h)
{
int answer;
if (int.TryParse(string_h, out answer) == true)
{
//Console.WriteLine(answer);
return answer;
}
else
{
//Console.WriteLine("parce error");
return -1;
}
}
int j = ConsoleRead(Console.ReadLine());
これで、正負にも対応します。数値以外を入れるとエラーになって-1が帰ってきます。
メッセージボックス
DialogResult result = MessageBox.Show("削除していいですか","確認",MessageBoxButtons.YesNo,MessageBoxIcon.Information);
if(result == DialogResult.Yes)
{
//any process
}
MessageBox.Show(message,title,button,icon)
button | 種類 |
---|---|
YesNo | はい、いいえ |
OK | はい |
OKCancel | はい、キャンセル |
RetryCancel | 再試行、キャンセル |
YesNoCancel | はい、いいえ、キャンセル |
AbortRetryIgnore | 中止、再試行、無視 |
icon | 種類 |
---|---|
Asterisk | インフォメーションマーク |
Exclamation | 警告マーク |
Error | バツマーク |
Question | はてなマーク |
タイマー系
フォームデザイナーでTimerがある。
DateTime
DateTime now = DateTime.Now;
label1.Text = now.Year.ToString(); //2023
label1.Text = now.Month.ToString(); //2
label1.Text = now.Day.ToString(); //25
label1.Text = now.ToString(); //2023/02/25 7:00:25
label1.Text = now.ToShortDateString(); //2023/02/25
label1.Text = now.ToLongDateString(); //2023年2月25日
label1.Text = now.ToLongTimeString(); //7:02:03
label1.Text = now.ToShortTimeString(); //7:02
ラベル文字サイズ
AutoSizeの項がTrueなら、自動調節されます
label1.Font = new Font(Font.FontFamily, 36);
フォントの種類と、サイズを変更できます。
label1.Font = new Font(new FontFamily("MS ゴシック"),15F);
ゴシック、フォントサイズ選択
別のフォームを起動
Form2 form2 = new Form2();
form2.ShowDialog();
form2.Dispose();
別のフォームを起動します。終了したら、フォームをのリソースを開放します。
ただし、起動元のフォームは別のフォームが起動中は操作不可になります。
Form2 form2 = new Form2();
form2.Show();
なら、親フォームは操作可能です。
this.DialogResult = DialogResult.OK;
みたいに設定しておいて、親フォームで条件によって処理を変えるという方法もあります。
if (form2.ShowDialog() == DialogResult.OK)
{
//any process
}
クラスの追加
タブ「プロジェクト」=>「クラスの追加」
プログラムファイルを追加するのではなく、タブの機能として追加する。なんだか新鮮
form内でのキーショートカット
private void Form1_KeyDown(object sender, KeyEventArgs e)
{
label1.Text = e.KeyCode.ToString();
if(e.KeyCode == Keys.A)
{
label2.Text = "A";
}
}
listBox、textBoxのような、選択状態優先されるような物がある場合、Form側にKeyDownを付けても反応しません。
選択状態になるオブジェクトにKeyDownを付けましょう。
base
派生元のクラスのメソッドを使うためのオブジェクト
this
,base
は同種
コンストラクタ
C#でも継承を行うと継承先のオブジェクトを生成した際、親クラスのコンストラクタが先に実行されます。
コンストラクタも明示的に記述できます。引数の数が変わってもこの書き方で作れます。
class A
{
public A(int a)
{
//any process
}
}
class B :A
{
public B(int a , int b):base(a)
{
//any process
}
}
base(a)で明示的に、親クラスのコンストラクタを呼び出してます。
override
overrideするメソッドはvirtual,abstructがついている必要がある。
親メソッドを実行したい場合はbase.method()
でできる。
Windows Formに画像を挿入
PictureBoxを使用。フォームデザインのツールボックス内にある。
配列
int[] hoge = {1,2,3,5,6};
Array.Sort(hoge);
Array.Sort(hoge,3,4); //hoge[3]~4個分ソート,範囲外まで指定するとエラー
Array.Clear(hoge,0,hoge.Length);//全消去
Array.Reverse(hoge); //逆順
Array.Copy(hoge,hoge2); //hoge2にコピー、サイズが一致してないとエラー
エラーが出やすいから、ちょっとめんどいね
アクセシビリティの一貫性エラー
privateのものを他のところでアクセスしようとする出るだけでない。
privateのものを使って、publicの配列としてもエラーになる。
privateのものを参照渡しで引数にする関数をpublicにしてもエラーになる。
private:クラス内、partialで分けてたら範囲外
protected:同じクラス内
internal:プロジェクト内
public:どこからでも
null非許容
警告ででる
CS8618 null 非許容の フィールド 'Cards' には、コンストラクターの終了時に null 以外の値が入っていなければなりません。
フィールド を Null 許容として宣言することをご検討ください。
コンストラクタでメンバ変数をすべて初期化すれば問題ないが、何もしてないメンバ変数があるとnullになる可能性があると警告する。
正直めんどいし、対処しきれてない。
ボタンを押したあとフォーカスが移動するのを止める
Button.SetStyle(ControlStyles.Selectable, false);
でフォーカスされない
Control.AddRange(Button);
フォームに部品を追加するという意味。
インターフェース
public interface name{}
インターフェースを使うと多重継承可能になる。
抽象クラスはabstract
を使う。
override
を強要するはず。
abstract
とvirtual
の違い
練習したほうがいい
静的フィールド
クラス全体で共有する変数。
全体で供給してるので、あるインスタンスが変更すると全員のやつが変化する.
public static int Test{ private get; set; }
プロパティで制御することも可能
class Mark
{
public static int Test{get; set; }
static Mark()
{
Test = 100;
}
public Mark(string name)
{
Name = name;
}
}
静的コンストラクタはstatic
で作れる。普通のコンストラクタと両方作れる。
Mark.Test
のようにクラス名でアクセスする。インスタンスではないためである。
ファイル入出力
後で
メモ
resxファイル=XMLファイル?
csファイル=c#ソースファイル
double = ドウブルって読んでる人います?ダブルらしいですよ。私は今後もドウブルって呼ぶつもりですけど。
WindowsAPIをC#で利用する
音量変更アプリ
まぁまぁ情報がありますね。
これらを組み合わせて、音量が他の要素によって変わったとしても、即座にもとに戻すアプリを作ります。
開発動機としては、現在使っているホリコンをPCにつなぐと特定の操作で音量が変化してしまうためです。
音量を規定の音量にする
まず、NAudioを導入する。
つぎに、
を真似て音量を変化させる。
でけた。Releaseでビルドしたら、binファイル内に実行形式ファイルが生成された。いや~簡単に作れていいねぇええええ
using NAudio.Wave;
using System;
using System.Collections.Generic;
using System.Windows;
using NAudio.CoreAudioApi;
namespace Volume_control
{
public partial class Form1 : Form
{
VolumeController vc;
private bool control_enable = false;
private int setting_volume = 30;
public Form1()
{
InitializeComponent();
vc = new VolumeController();
label1.Text = "";
control_enable = false;
numericUpDown1.Value = (int)(vc.GetVolume() * 100);
setting_volume = (int)numericUpDown1.Value;
button1.Text = "音量 " + setting_volume.ToString();
}
private void button1_Click(object sender, EventArgs e)
{
if (!control_enable)
{
control_enable = true;
button1.Text = "音量固定化中";
}
else
{
control_enable = false;
button1.Text = "動作停止中";
}
}
private void timer1_Tick(object sender, EventArgs e)
{
if (control_enable)
{
float volume = vc.GetVolume();
int volume_int = (int)(100 * volume);
if (Math.Abs(volume_int - setting_volume) > 1)
{
vc.SetVolume(setting_volume);
}
label1.Text = "Volume " + (volume_int).ToString();
}
}
private void numericUpDown1_ValueChanged(object sender, EventArgs e)
{
setting_volume = (int)numericUpDown1.Value;
}
}
public class VolumeController
{
private MMDevice device;
private MMDeviceEnumerator DevEnum = new MMDeviceEnumerator();
public VolumeController()
{
device = DevEnum.GetDefaultAudioEndpoint(DataFlow.Render, Role.Multimedia);
}
public void SetVolume(int value)
{
//音量を変更
device.AudioEndpointVolume.MasterVolumeLevelScalar = ((float)value / 100.0f);
}
public float GetVolume()
{
return device.AudioEndpointVolume.MasterVolumeLevelScalar;
}
}
}
Releaseビルドのアイコンは、フォームのプロパティの「Icon」で変更できる。
exeファイル自体のアイコンは変更されていないので、ファイルを右クリックして、プロパティからアイコンを変更する。他のスマートな方法があったら教えて欲しい。
COM接続一覧表示
デバイスマネージャーで見てもいいけど、アプリ化すぐ見つけられて若干楽
参考
を真似てつくりました。
右ウィンドウの「ソリューションエクスプローラ」→右クリック→「NuGetパッケージの管理」
「System.Management」で検索→インストール
「SerialPort」で検索→インストール
using System;
using System.IO.Ports;
using System.Threading;
using System.Management;
using System.Text.RegularExpressions;
namespace Com_display
{
public partial class Com_checker : Form
{
string[] com_num_text = new string[100];
public Com_checker()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
timer1.Enabled = true;
}
public void update()
{
string[] ports = GetDeviceNames(ref com_num_text);
listBox1.Items.Clear();
listBox2.Items.Clear();
int index = 0;
foreach (string port in ports)
{
listBox1.Items.Add(port);
listBox2.Items.Add(com_num_text[index]);
index++;
}
if (listBox1.Items.Count > 0) listBox1.SelectedIndex = 0;
}
public void update_serial()
{
listBox1.Items.Clear ();
string[] ports = SerialPort.GetPortNames();
foreach (string port in ports)
{
listBox1.Items.Add(port);
}
if (listBox1.Items.Count > 0) listBox1.SelectedIndex = 0;
}
public static string[] GetDeviceNames(ref string[] com_num_text_array)
{
var deviceNameList = new System.Collections.ArrayList();
var check = new Regex("(COM[1-9][0-9]?[0-9]?)");
ManagementClass mcPnPEntity = new ManagementClass("Win32_PnPEntity");
ManagementObjectCollection manageObjCol = mcPnPEntity.GetInstances();
//全てのPnPデバイスを探索しシリアル通信が行われるデバイスを随時追加する
foreach (ManagementObject manageObj in manageObjCol)
{
//Nameプロパティを取得
var namePropertyValue = manageObj.GetPropertyValue("Name");
if (namePropertyValue == null)
{
continue;
}
//Nameプロパティ文字列の一部が"(COM1)~(COM999)"と一致するときリストに追加"
string name = namePropertyValue.ToString();
if (check.IsMatch(name))
{
deviceNameList.Add(name);
}
}
//戻り値作成
if (deviceNameList.Count > 0)
{
string[] deviceNames = new string[deviceNameList.Count];
int index = 0;
foreach (var name in deviceNameList)
{
var com_num = Regex.Match(name.ToString(), @"(COM[1-9][0-9]?[0-9]?)");
com_num_text_array[index] = com_num.ToString();
deviceNames[index++] = name.ToString();
}
return deviceNames;
}
else
{
return null;
}
}
private void timer1_Tick(object sender, EventArgs e)
{
update();
}
}
}
コントローラを連射コントローラにする
トリガーボタンを押している間、マウスを連打させる
using SharpDX;
using SharpDX.DirectInput;
using System.Runtime.InteropServices;
リボン「プロジェクト」ー>「NuGetパッケージの管理」より上の3つをインストール
コントローラから入力を取得する
を参考にした。非常にわかりやすいいい記事。いい記事がトップに来ない悲しい時代が来ている(ブーメラン)
イベントみたいなのはないみたいで、Timerで監視することにしている。
ボタンとプログラム変数jState[]
の対応は自分で調べる必要がある。
下記のプログラム中のコメントアウト部分を変えれば調べやすいはず。
マウスの連打
WINDOWS_APIをちょくに利用してる感があっていい。
原始的な分なにやってるかわからないけど・・・
合体
上の2つを合体!
うまくコントローラのボタンでマウスを連打させることができた。
このアプリは非アクティブ状態でも動いているので、常時コントローラの状態を監視していることになる。よって、非アクティブ状態でも連射コントローラとしての機能を失わない!
うまくできて満足!一本満足!はっ!
using SharpDX;
using SharpDX.DirectInput;
using System.Runtime.InteropServices;
namespace Rensya
{
public partial class Form1 : Form
{
// DirectInput
DirectInput dinput = new DirectInput();
// ゲームパッド
Joystick joystick = null;
// 使用するゲームパッドのID
Guid joystickGuid = Guid.Empty;
public Form1()
{
InitializeComponent();
button1.Text = "Start to Click";
// ゲームパッドを探す
if (joystickGuid == Guid.Empty)
{
foreach (DeviceInstance device in
dinput.GetDevices(DeviceType.Gamepad, DeviceEnumerationFlags.AllDevices))
{
joystickGuid = device.InstanceGuid;
break;
}
}
// ジョイスティックを探す
if (joystickGuid == Guid.Empty)
{
foreach (DeviceInstance device in
dinput.GetDevices(DeviceType.Joystick, DeviceEnumerationFlags.AllDevices))
{
joystickGuid = device.InstanceGuid;
break;
}
}
// 見つかった場合
if (joystickGuid != Guid.Empty)
{
// ゲームパッドの取得
joystick?.Dispose();
joystick = new Joystick(dinput, joystickGuid);
// ゲームパッドが取得できた場合
if (joystick != null)
{
// バッファサイズを指定
joystick.Properties.BufferSize = 128;
// ジョイスティックの軸の最小値と最大値を -1000~+1000に設定
foreach (DeviceObjectInstance deviceObject in joystick.GetObjects())
{
switch (deviceObject.ObjectId.Flags)
{
// 絶対軸または相対軸
case DeviceObjectTypeFlags.Axis:
case DeviceObjectTypeFlags.AbsoluteAxis:
case DeviceObjectTypeFlags.RelativeAxis:
var ir = joystick.GetObjectPropertiesById(deviceObject.ObjectId);
if (ir != null)
{
try
{
ir.Range = new InputRange(-1000, 1000);
}
catch (Exception) { }
}
break;
}
}
}
}
}
private void Form1_Load(object sender, EventArgs e)
{
}
private void button1_Click(object sender, EventArgs e)
{
Measure.Enabled = !Measure.Enabled;
if(Measure.Enabled)
{
button1.Text = "Rapid Fireeeeeee!";
}
else
{
button1.Text = "Click to Start";
}
}
private void Measure_Tick(object sender, EventArgs e)
{
// ゲームパッドの入力を取得
try
{
joystick.Acquire();
joystick.Poll();
}
catch
{
// ゲームパッドが抜けた
joystick?.Dispose();
joystick = null;
return;
}
// ゲームパッドのデータ取得
var jState = joystick.GetCurrentState();
// 取得できない場合
if (jState == null) { return; }
// コントローラのボタン番号検索
/*
for (int i = 0; i < 100; i++)
{
if (jState.Buttons[i])
label1.Text = i.ToString();
}
*/
if (jState.Buttons[5])
{
label1.Text = "Fire";
RapidFire.Enabled = true;
}
else
{
label1.Text = "Waiting";
RapidFire.Enabled = false;
}
}
[DllImport("USER32.dll", CallingConvention = CallingConvention.StdCall)]
static extern void mouse_event(int dwFlags, int dx, int dy, int cButtons, int dwExtraInfo);
private const int MOUSEEVENTF_LEFTDOWN = 0x2;
private const int MOUSEEVENTF_LEFTUP = 0x4;
private void RapidFire_Tick(object sender, EventArgs e)
{
//左ボタンクリック
mouse_event(MOUSEEVENTF_LEFTDOWN, 0, 0, 0, 0); // マウスの左ボタンダウンイベントを発生させる
System.Threading.Thread.Sleep(20);
mouse_event(MOUSEEVENTF_LEFTUP, 0, 0, 0, 0); // マウスの左ボタンアップイベントを発生させる
}
}
}
Bluetoothコントローラにも使えるか、今度調べてみよう。
(追記)PS4コントローラをBluetooth接続しても、jState[]
には反応がなかった
PS4コントローラでは有線で繋いでも反応がなかった。今回の方法はPS4コントローラには使えないかもしれない。
時間記録アプリ
基本機能
- 現在時刻をスタート時刻として
- 内容をボックスに入れて
- 開始ボタンで経過時間が進む
- 終了を押すと、活動時間と内容をエクセルに記録する。
- エクセルはバックアップを定期的に取りたい
- 開始を押し忘れた場合は、開始時刻を手入力できるようにする。
追加で欲しい機能
- 今日の活動時間と内容
- グラフ化
- 月間まとめレポートの作成
- 寝たボタン
以上の機能をプログラムで作ってみよう
作成しながらの方針変更
- データは起動毎にテキストファイルを生成し、そこに記録をつける
- テキストファイルはレシートのようなもの、このテキストファイルを後でまとめて集計して、まとめたり月間レポートを作成する
どんな月間レポートを作りたいか
- 寝る時間の推移
- 睡眠量
- 何に時間を使ったかわかりやすくしたい
- タスクにはそこまで種類はないはず
- 一覧で保存しておいて、入力補完したい
- レポートにするときも、そのほうが処理ミスが減る
後で、Pythonでレポートを作るプログラム作ろう。
データが溜まってきてからでいいかな
Formの開始位置
フォームのLocation
プロパティ
上左端を0,0とする。(右+、下+)のプロパティ
入力補完
オートコンプリートインスタンスをくっつけて、オートコンプリートインスタンスに補完する内容をaddする感じ
テキストボックスのプロパティでもいじれる
AutoCompleteMode
がSuggest
はテキストボックスの下に推定を出す。
Append
は入力しながら推定されるものを追加する。
SuggestAppend
は2つを同時に行う。
Tab
キーがセルの移動に縛られているのでAppend
のほうが使いやすい気がします。
テキストボックス複数行は対応してないみたいです。
if (autocomplist.Contains(string))
{
~~
}
でそもそも、存在するか確認する事が可能
Tabの順序はTabIndexの数値でOK
TabStop
をfalse
にすれば、タブ遷移しないようにできる
悪くないね!ちなみに、日本語も問題なく使える。
関係ないけど、マットレスが固くて寝苦しかったのでベットトップ買ったら夕方まで爆睡してしまった。良質な睡眠が取れそうで嬉しい。
テキストボックスにTabで遷移すると内容を消去する
private void textTask_Enter(object sender, EventArgs e)
{
textTask.Text = "";
}
Enter
イベントが対応してます!
Focusとか名前が付いてるかと思いきやのやつ
このソフトを作った事自体への影響
2日目:いま、コレをやる時間みたいのがはっきりした
やってないことに罪悪感が湧くようになった。
でも、仕事じゃないんだから・・・感はある。
レポートを生成できるぐらいデータをまず貯めよう。
作ってから気づいたけど、スマホに全く同じようなアプリあるな
まぁPCで管理したかったからいいけど。
食事記録アプリ
PCとスマホアプリ両方作って、記録をつけたい
目的
最近は明らかに、食べ過ぎである。
自身の健康のためにも記録を残したい。
既存のアプリも当然ある。
既存のアプリのほうが高機能で、簡単に使える可能性が高い
スイッチコントローラ・無線PS4コントローラの入力イベント取得とキー変更
制作時に更新
任意のキーでスクリーンショットを取って画像を保存する
- キー入力イベント:以前やったやつ流用
- スクリーンショット
- 画像保存
- 保存先ファイル選択
スクリーンショット・画像関連
ファイル選択
スクショ範囲選択
画面の上に全画面半透明フォームを作成、マウスのドラッグ範囲を取得する。
ドラッグ範囲
Press:座標取得
Release:座標取得ー>フォーム終了
新しいフォームの生成
フォーム半透明
this.Opacity = 0.3
ドラッグの範囲表示
全画面
プロパティのWindowState Maximized
ドラッグ範囲を四角で描画する
このサイトが一番使えますねぇ~
Graphics g = this.CreateGraphics();
Pen pen = new Pen(Color.RebeccaPurple, 4);
g.DrawRectangle(pen, screen_area[0,0], screen_area[0,1],
Cursor.Position.X - this.screen_area[0, 0], Cursor.Position.Y- this.screen_area[0, 1]);
g.Clear(this.TransparencyKey);
描画を初期化する方法が分からなかったので、背景で塗りつぶしてます。
保存した瞬間一瞬色が変わる
マウスイベント
イベントプロパティから追加
Cursor.Position.X, Cursor.Position.Y
で座標取得
フォルダに階層式で入れる
フォルダの存在確認
あるはずなのに、ないと出てしまう
System.IO.File.Exists(dir_name)
になっていた
System.IO.Directory.Exists(dir_name)
ミス・・・20分ロス
リソースが開放されずに溜まる
不明
20G超えると急に開放された。
ガーベジコレクタが動いていない?
完成品
githubに上げたいが・・・上げ方がよくわからないので今後
4hかなり集中した。仕事で難解でクソ重いソフト使っているせいでVisualStudioが神に感じた。
サクサク作れるの楽しぃいいいいいいい
いいソフトを使うことこそが真に生産性を高めるのだねぇ。
ショートカットキーについて
(追記:2024/4/8)コードの増加と対処方法
フォームのボタンが増えていくとコードが肥大化してしまいます。上の画像のプログラムは900行もあります。
こうなると、保守がしにくくなってきます。
上のソフトの場合
- USB接続
- USBデータ表示
- 履歴表示
- 各種データ保存
の4つの機能が1つのForm1.cs
にまとまってしまっていることが原因です。
オブジェクト指向では、コードをわかりやすくするためにはForm1クラス
のみにまとめるのではなく、機能ごとにクラス分けし、クラス間のメンバー・メソッド・プロパティを作成し、クラス間関係性をまとめることが必要です。
UML