星期二, 6月 28, 2022

GodexRT 730X - 碳帶異常

在上次處理標籤機異常 - GodexRT 730X - 滾軸卡紙,從無法列印到可正常輸出,但有發現輸出品質似乎沒有很好,沒想到兩個禮拜後就又被告知異常啦

異常原因有兩重點
  • 第一點:列印品質異常,線條偏淡
  • 第二點:列印輸出過程中碳帶會斷
線條偏淡,用 Test 字樣來呈現,左側是異常輸出

GodexRT 730X - 碳帶異常-1

碳帶會斷,這次被通報的主因,因為碳帶斷掉後列印就會停止,使用者必須一直處理碳帶

GodexRT 730X - 碳帶異常-3

找不到原因情況下,只能請廠商跑一趟來了解,一打開就發現是碳帶異常,公司內有兩種碳帶,分別為一般和耐磨碳帶,不同碳帶得搭配不同紙張,以該例來說銅板紙必須搭配一般碳帶,但現場更換碳帶時竟然是拿耐磨碳帶來使用,才導致異常發生

兩種碳帶來個大合照,公司內也就這兩種,一般碳帶是消光、耐磨碳帶則是亮光,希望以後不要再發生啦

GodexRT 730X - 碳帶異常-2

星期二, 6月 21, 2022

[C#] 螢幕截圖含滑鼠指標

系統發生 Exception 時除了根據需求紀錄 Exception 外,在 WinForm 領域內常見會把螢幕截圖保留下來方便事後 debug,該篇紀錄螢幕截圖,還包含截圖當下的滑鼠指標並把滑鼠指標放大

範例應用重點內容

螢幕截圖範例

該範例預設應用程式為最大化,有三種截圖方式,分別為
  • 根據主螢幕
  • 根據應用程式所在螢幕
  • 多螢幕截圖
個人環境是筆電外接一個螢幕,有兩個螢幕可以進行螢幕截圖測試喔
using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.Linq;
using System.Windows.Forms;

namespace CaptureScreenSample
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();

            // 預設應用程式是最大化的
            WindowState = FormWindowState.Maximized;
        }

        #region 螢幕截圖

        private void btnPrimaryScreenCapture_Click(object sender, EventArgs e)
        {
            if (IsAppInPrimaryScreen() == false)
            {
                MessageBox.Show("應用程式不在主螢幕上,無法使用該功能進行截圖", this.Text, MessageBoxButtons.OK, MessageBoxIcon.Error);
                return;
            }

            Rectangle screenRectangle = Screen.PrimaryScreen.Bounds;
            Point cursorPoint = Cursor.Position;
            string screenCatureFullName = @"D:\ScreenCapture\PrimaryScreenCapture.png";
            ScreenCaptureWorkFlow(screenRectangle, cursorPoint, screenCatureFullName);
        }

        private void btnAppScreenCapture_Click(object sender, EventArgs e)
        {
            Rectangle screenRectangle = GetAppInScreen().Bounds;
            // 要以應用程式 Form 位置為計算基準
            Point cursorPoint = PointToClient(Cursor.Position);
            string screenCatureFullName = @"D:\ScreenCapture\AppScreenCapture.png";
            ScreenCaptureWorkFlow(screenRectangle, cursorPoint, screenCatureFullName);
        }

        private void btnAllScreenCature_Click(object sender, EventArgs e)
        {
            Rectangle allScreenRectangle = GetAllScreenRectangle();
            Point cursorPoint = Cursor.Position;
            string screenCatureFullName = @"D:\ScreenCapture\AllScreenCapture.png";
            ScreenCaptureWorkFlow(allScreenRectangle, cursorPoint, screenCatureFullName);
        }

        private void ScreenCaptureWorkFlow(Rectangle screenRectangle, Point cursorPoint, string screenCatureFullName)
        {
            try
            {
                Image image = CaptureScreenIncludeCursor(screenRectangle, cursorPoint);
                PutImage(image, screenCatureFullName);
                image.Dispose();
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message, this.Text, MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }

        private bool IsAppInPrimaryScreen()
        {
            return GetAppInScreen().Primary;
        }

        private Screen GetAppInScreen()
        {
            return Screen.FromControl(this);
        }

        private Rectangle GetAllScreenRectangle()
        {
            return Screen.AllScreens.Aggregate(
                new Rectangle(),
                (current, screen) => Rectangle.Union(current, screen.Bounds));
        }

        private Image CaptureScreenIncludeCursor(Rectangle screenRectangle, Point cursorPoint)
        {
            // 產生畫布
            Size screenSize = new Size(screenRectangle.Width, screenRectangle.Height);
            Bitmap image = new Bitmap(screenSize.Width, screenSize.Height);

            using (Graphics g = Graphics.FromImage(image))
            {
                // 螢幕截圖
                Point upperLeftSource = screenRectangle.Location;
                Point upperLeftDestination = Point.Empty; // Point.Empty 即為 Point(0, 0)
                g.CopyFromScreen(upperLeftSource, upperLeftDestination, screenSize);

                // 繪製滑鼠
                Rectangle cursorRectangle = GetCursorRectangle(cursorPoint);
                if (cursorRectangle != Rectangle.Empty)
                    Cursor.DrawStretched(g, cursorRectangle);
            }

            return image;
        }

        private Rectangle GetCursorRectangle(Point cursorPoint)
        {
            if (Cursor == Cursors.Hand ||
                Cursor.Current != Cursors.Default)
                return Rectangle.Empty;

            // 把滑鼠放大兩倍
            int rate = 2;
            int cursorWidth = Cursor.Size.Width * rate;
            int cursorHeight = Cursor.Size.Height * rate;
            Size cursorSize = new Size(cursorWidth, cursorHeight);

            return new Rectangle(cursorPoint, cursorSize);
        }

        private void PutImage(Image image, string imageFullName)
        {
            image.Save(imageFullName, ImageFormat.Png);
        }
        #endregion
    }
}

螢幕截圖成果

原本想說要把下面工具列裁掉,後來發現剛好可以用在是別主螢幕還是延伸螢幕,不過為了閱讀方便,還是有把多餘空間裁掉

主螢幕截圖,可以看到那兩倍大滑鼠指標在那

[C#] 螢幕截圖含滑鼠指標-1

延伸螢幕截圖,滑鼠指標也正確繪製在 Button 上

[C#] 螢幕截圖含滑鼠指標-2

延伸螢幕上使用主螢幕截圖,就被防呆下來,記錄如何識別主螢幕語法

[C#] 螢幕截圖含滑鼠指標-3

多螢幕截圖

[C#] 螢幕截圖含滑鼠指標-4


星期一, 6月 20, 2022

[C#] 不拋出 Exception 處理

整理公司內部 NLog 時發現某個工具 Class 內某段 Code,竟然使用 try catch 包起來不拋出 Excpeton,在 catch 內直接用 NLog Context 來接收 Exception,工具 Class 和 NLog 緊緊相依,把該段 Code 修改為變成透過事件傳出去,NLog 在事件內接收 Exception

範例程式

之前在 FB 上也看見類似案例,是使用者自訂控件內不拋出 Exception,但是要有機制通知外部程式,所以該範例會有兩個部分,分別為
  • 使用者自訂控件:自訂事件來處理
  • 自訂 Class :直接使用內建 UnhandledException 事件
就練習寫 Event 就是

使用者自訂控件
using System;
using System.Windows.Forms;

namespace UCExcpetion
{
    public partial class NOExcetionButton : UserControl
    {
        public NOExcetionButton()
        {
            InitializeComponent();
        }

        // Step3-1:自訂 EventHandler Event 寫法
        public event ExceptionEventHandler ExceptionOccur;
        // Step3-2:使用 Action Event 寫法
        // public event Action<object , ExceptionEventArgs> ExceptionOccur;

        // Step4:呼叫事件
        protected virtual void OnExceptionOccur(ExceptionEventArgs e)
        {
            if (ExceptionOccur != null)
                ExceptionOccur(this, e);
        }

        private void btnException_Click(object sender, EventArgs e)
        {
            try
            {
                throw new Exception("自訂控件內部發生 Exception");
            }
            catch (Exception ex)
            {
                // 透過事件向外通知
                ExceptionEventArgs eventArgs = new ExceptionEventArgs() { excpetion = ex };
                OnExceptionOccur(eventArgs);
            }
        }
    }

    // Step2:自訂 EventHandler 或是直接使用 Action 
    public delegate void ExceptionEventHandler(object sender, ExceptionEventArgs e);

    // Step1:自訂 EventArgs
    public class ExceptionEventArgs : EventArgs
    {
        public Exception excpetion { get; set; }
    }
}
自訂 Class 功能
using System;

namespace UCExcpetion
{
    public class ToolClass
    {
        // 使用 .NET 內建事件
        public event UnhandledExceptionEventHandler UnhandledException;

        public void ExceptionCreate()
        {
            try
            {
                throw new Exception("Toolclass 內部產生 Exception");
            }
            catch (Exception ex)
            {
                UnhandledExceptionEventArgs eventArgs = new UnhandledExceptionEventArgs(ex, false);
                // 透過 Invoke 直接呼叫 Event
                UnhandledException?.Invoke(this, eventArgs);
            }
        }

    }
}
呼叫上述兩者
using System;
using System.Windows.Forms;

namespace UCExcpetion
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();

            btnUCException.ExceptionOccur += BtnUCException_ExceptionOccur;
        }

        private void BtnUCException_ExceptionOccur(object sender, ExceptionEventArgs e)
        {
            MessageBox.Show(e.excpetion.Message, this.Text, MessageBoxButtons.OK, MessageBoxIcon.Error);
        }

        private void btnToolClassExcpetion_Click(object sender, EventArgs e)
        {
            ToolClass t = new ToolClass();
            // 不去訂閱 UnhandledException Event 的話,ExceptionCreate() 產生的 Exception 是完全無感的 
            t.UnhandledException += (innerSender, args) =>
            {
                Exception ex = args.ExceptionObject as Exception;
                MessageBox.Show(ex.Message, this.Text, MessageBoxButtons.OK, MessageBoxIcon.Error);
            };
            t.ExceptionCreate();
        }
    }
}

畫面截圖

[C#] 不拋出 Exception 處理

星期日, 6月 19, 2022

[C#] ThreadExceptionDialog

在自家 WinForm 系統內的 Global Error Handle
  • Application.ThreadException
  • AppDomain.CurrentDomain.UnhandledException
內看見使用 ThreadExceptionDialog,用該型態來呈現 Exception 錯誤訊息還蠻工程師的,哈

[C#] ThreadExceptionDialog

星期六, 6月 18, 2022

[C#] NLog - 偵錯

[C#] NLog - DatabaseTarget 測試呼叫 Store Procedure 時,Exception 資料一直沒有 insert 進 DB,一查發現原來 NLog 為了不影響主程式運作,預設是不會拋出任何 Exception

要讓 NLog 可以拋出 Exception 必須設定 ThrowExceptions 屬性、如要輸出錯誤訊息至檔案可以透過 InternalLogger 來做到

InternalLogger.LogFile

InternalLogger.LogFile 是 NLog Exception 的檔案路徑,但並不是全部的 Layout Renderers 都支援,看 官方文章 - Internal Logging 介紹,從 NLog4.6 開始才開始逐漸可以使用

模擬 NLog 內發生錯誤
using NLog;
using NLog.Common;
using NLog.Config;
using NLog.Layouts;
using NLog.Targets;
using System;
using System.Data;

namespace NLogSample
{
    internal class Program
    {
        private static readonly Logger _logger = LogManager.GetCurrentClassLogger();

        static void Main(string[] args)
        {
            ExceptionThrowConfig();

            try
            {
                ExceptionMethod();
            }
            catch (Exception ex)
            {
                _logger.Error(ex);
            }
        }

        private static void ExceptionMethod()
        {
            int numerator = 1;
            int denominator = 0;
            Console.WriteLine(numerator / denominator);
        }

        private static void ExceptionThrowConfig()
        {
            InternalLogger.LogFile = @"d:\Logs\InternalLog.txt";
            InternalLogger.LogLevel = LogLevel.Error;
            LogManager.ThrowExceptions = true;

            DatabaseTarget target = new DatabaseTarget();

            target.ConnectionString = "Data Source=.;Initial Catalog=AdventureWorks2019;Integrated Security=True";

            // 錯誤的呼叫 Store Procedure 方式
            target.CommandType = CommandType.StoredProcedure;
            target.CommandText = "exec uspLogInsert @LogTime , @Message";

            target.Parameters.Add(new DatabaseParameterInfo() { Name = "@LogTime", Layout = "${date}" });
            target.Parameters.Add(new DatabaseParameterInfo("@Message", new SimpleLayout("${exception}")));

            SimpleConfigurator.ConfigureForTargetLogging(target, LogLevel.Trace);
        }
    }
}

關鍵錯誤訊息,這樣呼叫 Store Procedure 是錯誤的
2022-06-15 22:51:55.7917 Error DatabaseTarget([unnamed]): Error when writing to database. Exception: System.Data.SqlClient.SqlException (0x80131904): 找不到預存程序 'exec uspLogInsert @LogTime , @Message'。
完整錯誤訊息
2022-06-15 22:51:55.7917 Error DatabaseTarget([unnamed]): Error when writing to database. Exception: System.Data.SqlClient.SqlException (0x80131904): 找不到預存程序 'exec uspLogInsert @LogTime , @Message'。
   於 System.Data.SqlClient.SqlConnection.OnError(SqlException exception, Boolean breakConnection, Action`1 wrapCloseInAction)
   於 System.Data.SqlClient.SqlInternalConnection.OnError(SqlException exception, Boolean breakConnection, Action`1 wrapCloseInAction)
   於 System.Data.SqlClient.TdsParser.ThrowExceptionAndWarning(TdsParserStateObject stateObj, Boolean callerHasConnectionLock, Boolean asyncClose)
   於 System.Data.SqlClient.TdsParser.TryRun(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataReader dataStream, BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject stateObj, Boolean& dataReady)
   於 System.Data.SqlClient.SqlCommand.FinishExecuteReader(SqlDataReader ds, RunBehavior runBehavior, String resetOptionsString, Boolean isInternal, Boolean forDescribeParameterEncryption, Boolean shouldCacheForAlwaysEncrypted)
   於 System.Data.SqlClient.SqlCommand.RunExecuteReaderTds(CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream, Boolean async, Int32 timeout, Task& task, Boolean asyncWrite, Boolean inRetry, SqlDataReader ds, Boolean describeParameterEncryptionRequest)
   於 System.Data.SqlClient.SqlCommand.RunExecuteReader(CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream, String method, TaskCompletionSource`1 completion, Int32 timeout, Task& task, Boolean& usedCache, Boolean asyncWrite, Boolean inRetry)
   於 System.Data.SqlClient.SqlCommand.InternalExecuteNonQuery(TaskCompletionSource`1 completion, String methodName, Boolean sendToPipe, Int32 timeout, Boolean& usedCache, Boolean asyncWrite, Boolean inRetry)
   於 System.Data.SqlClient.SqlCommand.ExecuteNonQuery()
   於 NLog.Targets.DatabaseTarget.ExecuteDbCommandWithParameters(LogEventInfo logEvent, IDbConnection dbConnection, IDbTransaction dbTransaction)
   於 NLog.Targets.DatabaseTarget.WriteLogEventSuppressTransactionScope(LogEventInfo logEvent, String connectionString)
ClientConnectionId:404d03a2-984b-4b6e-81bf-ad66fb425678
Error Number:2812,State:62,Class:16