Visual Basic 6の後、2010年前後に一時期 Visual Basic 2008/2010をかじった事が有って、何か?懐かしい感じがする。
当時重~く感じたIDEもSSDのお陰も有ってか快適。馴染むまで暫くやってみよう!!
別フォームのインスタンスをクラス宣言の直下で定義しておくことで、そのフォームを立ち上げずとも、有るべきコントロールを利用することが出来る。
つまり、そのインスタンスに属するテキストボックスにテキストを保存したり、string str = formConfig.Name.Text; のようにして保存しているデータを読み出すことが出来る。
複数のテキストボックスが Typedefのように使えることになる。
MakeMyList()のように関数内でリストの宣言・作成をして、return MyList で他の関数に渡すのが推奨されているが、
グローバルを多用せず、コードの可読性を損なわなければ理解しやすい。
C#で作ったDLLを動的に切り替えて、選択した衛星毎に処理を変える
/* DLLファイルのパスを作成 */
string dllFilePath = "./dll/" + LblNoradID.Text + ".dll"; // カレントディレクトリー/dll/53106.dll
/* dllが供給されているかチェック */
if (File.Exists(dllFilePath))
{
/* DLLをロードする */
Assembly assembly = Assembly.LoadFrom(dllFilePath);
/* 利用するクラスの完全修飾名(名前空間を含む"namespace.class")を指定 */
string className = "_53106._53106";
/* クラスのTypeを取得する */
Type classType = assembly.GetType(className);
/* クラスのインスタンスを生成する */
object classInstance = Activator.CreateInstance(classType);
/* メソッドの名前を指定して呼び出す */
MethodInfo method = classType.GetMethod("ReceiveDataFormat");
method.Invoke(classInstance, null);
}
else
{ /* ファイルが無い旨を知らせる */
MessageBox.Show("Not exists " + dllFilePath + " for " + CmbSatName.Text);
}
アプリケーション運用ログの作成
using Serilog;
public static void CreateLogFile()
{
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Information() // Information以上に重要なもののみ
.MinimumLevel.Override("Microsoft", Serilog.Events.LogEventLevel.Warning) // MicrosoftからのWarning以上も
.Enrich.FromLogContext()
.WriteTo.File(
@".\\syslog\\TLMForwarder-.log",
outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level}] {Message}{NewLine}{Exception}",
rollingInterval: RollingInterval.Day,
retainedFileCountLimit: 10
)
.CreateLogger(); // 上記設定でLoggerインスタンスを作成
try
{
Log.Information("Starting Application and Logger...");
}
catch (Exception ex)
{
Log.Fatal(ex, "The Application has closed.");
}
// 終了時には必ず Log.CloseAndFlush(); を実行する (quitボタンなど)
}
/*-------------------------------------------------------------------------------------*/
using Serilog;
praivate void SomeThing()
{
// 何らかの処理関数
Log.Information("処理に関する情報・結果など");
Log.Warning("注意など");
Log.Error("エラーメッセージ");
Log.Debug("デバッグに必要なコメントなど");
}
JSONファイルで設定の別ファイル化や複数のログファイルに対応できる。
その他のファイル作成とネーミング
private readonly FrmSettings frmSettings = new();
string directory = frmSettings.TxtDirectory.Text;
try
{
char lastChar = directory[^1];
if (lastChar != '\\') // D:\sample\data のように最後が \ でない
{
directory += "\\"; // D:\sample\data\ にする
}
}
catch (IndexOutOfRangeException)
{
MessageBox.Show("Settingsでフォルダを指定してください。");
return;
}
/* フォルダが存在しない時は作成する */
if (!Directory.Exists(directory))
{
Directory.CreateDirectory(directory);
}
/* ファイル名を作成する */
string timeStamp = DateTime.UtcNow.ToString("s").Replace(":", ""); // UTCを使用している
string fileName = "ファイル名" + "_" + timeStamp + "_" + ".txt"; // filename_2023-09-22T003032.txt
string filePath = Path.Combine(directory, fileName); // D:\sample\data\filename_2023-09-22T003032.txt
/* ファイルパスを使ってファイルを作成 */
File.Create(filePath).Close(); // この時フォルダが存在しない場合フォルダも作成する。
なお、ToString("s").Replace(":", "") の意味は "s" で 2023-09-22T00:30:32 となり ':' が挿入される。
これはファイル名として使えない文字でなので .Replace(":", "") で ':' を取り除いている。
フォルダ内の特定ファイルを削除
/* 特定フォルダ内の空のファイルが有れ削除する */
string[] files = Directory.GetFiles(directory);
foreach (string file in files)
{
FileInfo fileInfo = new(file);
if (fileInfo.Length == 0) // サイズゼロの空ファイル
{
File.Delete(file);
}
}
/* フォルダ内にサブフォルダがある場合 */
string[] files = Directory.GetFiles(directory, "*", SearchOption.AllDirectories);
Formのテキストデータをコントロールの順にテキストファイルに書き込む
public void SaveDatatoFile()
{
/* ファイルにデータを書き込む */
using (StreamWriter writer = new(filePath))
{
foreach (Control control in Controls.OfType<TextBox>().Reverse())
{
if (control is TextBox textBox)
{
string key = textBox.Name[3..]; // テキストボックス名の頭文字Txtを省き keyとし
string value = textBox.Text; // テキストの内容をvalueとして
string line = $"{key}={value}"; // イコールで繋いだものを
writer.WriteLine(line); // 一行ずつ書き込む
}
}
}
}
問題は、foreach (Control control in Controls) でOKなのだが、最初に作った(手書き)ファイルの項目順と逆になる場合が有る。
その時は、Controls.OfType<TextBox>().Reverse() とすると全く逆順に出来る。(<TextBox>は代替文字ではなく、そのまま記述)
受信データを受信した長さに縮める
C#では下記のように変数の長さを指定する必要が有るため、指定した長さのデータとして余分なデータエリアを持ち回る事になる。
そこで受信するたびに、受信した長さに切り詰める。
/* 受信用バッファを定義 */
byte[] buffer = new byte[1024];
// 受信が無くても1秒に一度ループさせる
client.ReceiveTimeout = 1000; // clientは Socketで定義し、Connectで接続済みとする
// 受信ループ(clientStop変数がTrueになったらループを出る)
while (!clientStop)
{
try
{
// ソケットからデータを受信、実長さも取得
int bytes = client.Receive(buffer);
// 実長さに沿った一時バッファを生成
byte[] tmpBuff = new byte[bytes];
// 受信データ(1024バイト)を実長さにカット
Array.Copy(buffer, tmpBuff, bytes);
// メインスレッドにデータ(実長さ)を渡す
}
catch
{
// タイムアウトでループしても空回しする
continue;
}
}
タイムアウトを設けているのは、受信の無い時、ループが回らずクローズ操作(clientStop = true;)をしても、
クライアント及びサブスレッドが閉じないため1秒ごとにループさせる。その時は空データが発生しないよう catch で単にループさせる。
主な時刻表示パターン
string timestamp = DateTime.Now.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ss.fffZ");
yyyy: 年(4桁)
MM: 月(2桁)
dd: 日(2桁)
T 接続文字(spaceでも可)
HH: 時間(2桁)
mm: 分(2桁)
ss: 秒(2桁)
fff: ミリ秒(3桁)
Z: タイムゾーン(ZはUTCを表します)
timestamp 変数には "2023-10-13T15:05:00.187Z" のような形式のタイムスタンプ文字列が格納される。(テレメトリーはこの表記)
非同期関数の返り値と変数のスコープに関する(._.)φ
private static something()
{
string data = someData.Trim();
string url = "https://database.server.com"
// データを非同期送信関数に渡し、返り値を受け取る
string responce = SendToServer(data, url).Result; // 返り値を受けるには呼び出し関数に.Resultを付ける
}
private static async Task SendToServer(string someData, string webUrl)
{
string data = someData.Trim();
string url = webUrl.Trim();
// データをエンコードする
StringContent content = new(data, Encoding.UTF8, "application/json");
// UDPソケットを作成
using HttpClient client = new();
// 送信
client.Timeout = TimeSpan.FromSeconds(2); // 応答の待ち時間を指定する(2秒)
HttpResponseMessage res = await client.PostAsync(url, payload);
// 返り値用に変数を定義 // if文の内側で代入された値を、外側でリターンするには
string responce = string.Empty; // リターンと同じレベル(if文の外側)で定義しておく
// レスポンスを文字列にする
if (res != null)
{
responce = await res.Content.ReadAsStringAsync(); // 返り値をセット(if分内側)
}
else
{
responce = $"Error: BadRequest"; // 返り値をセット(if分内側)
}
// 結果を呼び出し元へ返す
return responce; // リターンする
}
異なるスレッド間のデータ書き込み(TxtBox)
private void UpdateTextBox(string text)
{
if (TxtSample.InvokeRequired)
{
TxtSample.Invoke(new Action(UpdateTextBox), text);
}
else
{
TxtSample.Text = text;
}
}
private void SomeBackgroundThreadFunction()
{
string response = //何らかのバックグラウンド処理を行う
// UIスレッド上でテキストボックスを更新する
UpdateTextBox(response);
}
private void BtnStartProcessing_Click(object sender, EventArgs e)
{
// バックグラウンドスレッドを開始する
Thread backgroundThread = new Thread(SomeBackgroundThreadFunction);
backgroundThread.Start();
}
TxtBoxは一般的に初期スレッド(UIスレッド)で起ち上げられるフォームに存在します。
その初期スレッドから別スレッド(backgroundThread)を起ち上げ、何らかの処理をした結果を、初期スレッド上の TxtBox.Textに書き込もうとするとエラーが発生します。
それを回避して安全に書き込む事が出来る様にする方法です。
異なるスレッド間のデータ書き込み(DataGridView)
private void UpdateGrid(string data)
{
if (grdGrid.InvokeRequired)
{
grdGrid.Invoke(new Action(UpdateGrid), data, counter);
}
else
{
// DataGridView のセル内容を更新する
if (data != null && counter >= 0)
{
GrdDigipeater.Rows.Insert(
counter,
data[0].Trim(),
data[1].Trim(),
data[2].Trim(),
data[3].Trim()
);
}
// 縦スクロールバーが有る場合、表示可能な最終行まで下げる
GrdDigipeater.FirstDisplayedScrollingRowIndex
= GrdDigipeater.Rows.GetLastRow(DataGridViewElementStates.Visible);
}
}
private void SomeBackgroundThreadFunction()
{
counter++; // データ作成回数などをカウントする-->グリッドの行番号になるものとする
string[] data = // 受信データ等を配列にする
// UIスレッド上でDataGridViewのセルを更新する
UpdateGrid(data, counter);
}
private void BtnStartProcessing_Click(object sender, EventArgs e)
{
// バックグラウンドスレッドを開始する
Thread backgroundThread = new Thread(SomeBackgroundThreadFunction);
backgroundThread.Start();
}
DataGridViewである条件で行に色付け
GrdDigipeater.Rows.Insert(
row,
data[0].Trim(),
data[1].Trim(),
data[2].Trim(),
data[3].Trim()
);
// data[0]が特定の内容の時行全体を色分けする
DataGridViewCellStyle stringColor = new();
if (data[0].Trim() == [比較データ}
{
// 背景を黒にし、文字をオレンジにする
stringColor.ForeColor = Color.Orange;
stringColor.BackColor = Color.Black;
GrdDigipeater.Rows[row].DefaultCellStyle = stringColor;
}
else
{
// 条件が合わない時はデフォルトに戻す
GrdDigipeater.Rows[row].DefaultCellStyle = null;
}
データを表示させてから、色指定するところがみそ。
Back
Alt+HOME
この 作品 は
クリエイティブ・コモンズ 表示 - 非営利 - 改変禁止 4.0 国際 ライセンス
の下に提供されています。
English
Powered by