#if SELFTEST
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.Windows.Forms;
namespace TIASshot {
public partial class Form1 {
/// <summary>
/// ヘッドレス(非表示)セルフテスト用コンストラクタの目印型(SELFTEST 限定).
/// </summary>
internal sealed class SelfTestHeadless {
internal static readonly SelfTestHeadless Instance = new SelfTestHeadless();
private SelfTestHeadless() { }
}
// ヘッドレスモードか(ShowError をモーダル表示せず記録に切り替える)
private bool _selfTestHeadless = false;
// ヘッドレスモードで ShowError に渡されたメッセージ(最後の 1 件)
private string _selfTestLastError = null;
// ヘッドレスモードで ShowError / ShowMessage に渡されたメッセージの履歴(古い順._selfTestLogLock で保護)
private readonly List<string> _selfTestErrors = new List<string>();
private readonly List<string> _selfTestMessages = new List<string>();
private readonly object _selfTestLogLock = new object();
/// <summary>
/// ヘッドレス(非表示)セルフテスト用コンストラクタ(SELFTEST 限定).
/// InitializeComponent は IC Imaging Control(カメラ SDK)のコントロールを生成するため呼ばず,
/// プレビュー・校正・撮影の経路(ShowImage / MainRefreshTimer_Tick / ShowMessage / EnableShots /
/// GetDataName 等)が参照するコントロールだけを生成する.
/// 本モニタの再描画タイマーの設定は Form1_Load と同じ値で行う(Form1_Load はカメラ接続を含むため呼ばない).
/// </summary>
internal Form1(SelfTestHeadless marker) {
_selfTestHeadless = true;
picPreview = new PictureBox();
picDisplay = new PictureBox { Size = new Size(449, 610), SizeMode = PictureBoxSizeMode.Zoom };
txtMessage = new TextBox();
txtDataName = new TextBox { Text = "selftest" };
txtNumMultiShots = new TextBox { Text = "1" };
txtMultiShotsInterval = new TextBox { Text = "0" };
txtDeviceName = new TextBox();
txtSerialNo = new TextBox();
txtSaveFolder = new TextBox();
txtConvElemNum = new TextBox();
btnShotOne = new Button();
btnShotMulti = new Button();
btnLightSW = new Button();
btnOpenDataFolder = new Button();
Controls.AddRange(new Control[] {
picPreview, picDisplay, txtMessage, txtDataName, txtNumMultiShots, txtMultiShotsInterval,
txtDeviceName, txtSerialNo, txtSaveFolder, txtConvElemNum,
btnShotOne, btnShotMulti, btnLightSW, btnOpenDataFolder,
});
ShowInTaskbar = false;
Text = $"{APP_NAME} [SELFTEST]";
_mainRefreshTimer.Interval = MainRefreshIntervalMs;
_mainRefreshTimer.Tick += MainRefreshTimer_Tick;
}
/// <summary>
/// ウィンドウハンドルを生成し(表示はしない),本モニタの再描画タイマーを開始する.
/// ハンドルが無いと InvokeRequired が常に false となり,ワーカースレッドからの
/// ShowMessage / EnableShots が UI スレッドへマーシャリングされない(実運用と異なる経路になる)ため必ず呼ぶ.
/// </summary>
/// <param name="previewMonitor">副モニタ(null 可)</param>
internal void SelfTestStart(PreviewMonitor previewMonitor) {
CreateHandle();
_previewMonitor = previewMonitor;
EnableShots(false);
_mainRefreshTimer.Start();
}
/// <summary>本モニタ(picDisplay)に現在表示中の画像</summary>
internal Image SelfTestDisplayedImage => picDisplay.Image;
/// <summary>ステータス表示(txtMessage)の現在値</summary>
internal string SelfTestMessage => txtMessage.Text;
/// <summary>ヘッドレスモードで ShowError に渡された最後のメッセージ(無ければ null)</summary>
internal string SelfTestLastError => _selfTestLastError;
/// <summary>撮影ボタンが有効か</summary>
internal bool SelfTestShotsEnabled => btnShotOne.Enabled && btnShotMulti.Enabled;
/// <summary>ヘッドレスモードで ShowError に渡されたメッセージの履歴(古い順のコピー)</summary>
internal IReadOnlyList<string> SelfTestErrors {
get { lock (_selfTestLogLock) return _selfTestErrors.ToArray(); }
}
/// <summary>
/// ヘッドレスモードで ShowMessage に渡されたメッセージの履歴(古い順のコピー).
/// ステータス表示は上書きされるため,「保存失敗時に『全ての画像保存完了』を一度も表示しない」のような
/// 途中経過の検証に使う.
/// </summary>
internal IReadOnlyList<string> SelfTestMessages {
get { lock (_selfTestLogLock) return _selfTestMessages.ToArray(); }
}
/// <summary>ShowError・ShowMessage の記録(最後のエラー・履歴)を消去する</summary>
internal void SelfTestClearLog() {
lock (_selfTestLogLock) {
_selfTestLastError = null;
_selfTestErrors.Clear();
_selfTestMessages.Clear();
}
}
/// <summary>データ名(txtDataName)を設定する(GetDataName の戻り値=保存フォルダ名の一部)</summary>
internal void SelfTestSetDataName(string dataName) {
txtDataName.Text = dataName;
}
/// <summary>
/// 連続撮影の枚数・間隔の入力欄(txtNumMultiShots・txtMultiShotsInterval)を設定する.
/// 不正値の注入ができるよう文字列で受け取る(GetNumMultiShots / GetMultiShotsInterval の解釈に従う).
/// </summary>
internal void SelfTestSetMultiShots(string numShots, string intervalMs) {
txtNumMultiShots.Text = numShots;
txtMultiShotsInterval.Text = intervalMs;
}
// 閉じるときの後始末(ShutdownOnClose)が通過した段階の記録(古い順._selfTestLogLock で保護)
private readonly List<string> _selfTestCloseSteps = new List<string>();
/// <summary>
/// 閉じるときの後始末の対象にするカメラを設定する(Form1_Load の代わり.ヘッドレス Form1 は _camera を持たないため).
/// 閉じてよいかの判定(保存中か)と,後始末でのカメラ切断に使われる.
/// </summary>
internal void SelfTestAttachCamera(CameraBase camera) {
_camera = camera;
}
/// <summary>
/// ユーザーが閉じようとしたときの処理(Form1_FormClosing)を呼ぶ(UI スレッドで呼ぶこと).
/// ヘッドレス Form1 では FormClosing イベントが接続されていない(InitializeComponent を呼ばない)ため,この口から呼ぶ.
/// 閉じると決まった場合は後始末(閉じ始めのフラグ → タイマー停止 → カメラ切断 → 光源)まで行う.
/// フォームの破棄(Dispose)は行わない(ハーネスの後始末に任せる).
/// </summary>
/// <returns>閉じると決まったら true,取り消されたら(保存中)false</returns>
internal bool SelfTestRequestClose() {
var e = new FormClosingEventArgs(CloseReason.UserClosing, false);
Form1_FormClosing(this, e);
return !e.Cancel;
}
/// <summary>閉じ始めたか(以降 ShowMessage / EnableShots は何もしない)</summary>
internal bool SelfTestIsClosing => _isClosing;
/// <summary>本モニタの再描画タイマーが動作中か(閉じるときの後始末で停止される)</summary>
internal bool SelfTestRefreshTimerRunning => _mainRefreshTimer.Enabled;
/// <summary>
/// 閉じるときの後始末が通過した段階(古い順のコピー."closing" → "timer" → "camera" → "light").
/// カメラ切断が例外になった場合は "camera" が抜ける.
/// </summary>
internal IReadOnlyList<string> SelfTestCloseSteps {
get { lock (_selfTestLogLock) return _selfTestCloseSteps.ToArray(); }
}
partial void OnCloseStep(string step) {
lock (_selfTestLogLock) {
_selfTestCloseSteps.Add(step);
}
}
/// <summary>ShowError のヘッドレス版(モーダルダイアログを出すと無人実行が止まるため記録のみ行う)</summary>
private void SelfTestRecordError(string msg) {
Debug.WriteLine($"[SELFTEST] ShowError: {msg}");
lock (_selfTestLogLock) {
_selfTestLastError = msg;
_selfTestErrors.Add(msg);
}
}
/// <summary>ShowMessage の記録(ヘッドレスモードのみ.表示そのものは通常どおり行う)</summary>
private void SelfTestRecordMessage(string msg) {
if (!_selfTestHeadless) return;
lock (_selfTestLogLock) {
_selfTestMessages.Add(msg);
}
}
/// <summary>
/// タイマー停止と表示用 Bitmap の解放(FormClosing の代わり.Dispose の前に UI スレッドで呼ぶ).
/// </summary>
internal void SelfTestShutdown() {
_mainRefreshTimer.Stop();
_mainRefreshTimer.Dispose();
var shown = picDisplay.Image;
picDisplay.Image = null;
shown?.Dispose();
Bitmap pending;
lock (_dispBufLock) {
pending = _dispBuf;
_dispBuf = null;
_isUpdateBuf = false;
}
pending?.Dispose();
}
}
}
#endif