Newer
Older
TIASShot / TIASshot / UI / Form1.SelfTest.cs
#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