星期一, 11月 23, 2020

[C#] 控件智能標籤

根據 Walkthrough: Adding Smart Tags to a Windows Forms Component 的筆記文

以 TextBox 為例,點選控件右上角的三角符號,跳出來的 TextBox 工作,就是所謂的智能標籤

[C#] 控件智能標籤-1

要建立智能標籤,要先引用 System.Design.dll 

[C#] 控件智能標籤-2 
 

星期六, 11月 21, 2020

[C#] Enum - FlagsAttribute

Enum FlagsAttribute 使用練習筆記

官方文章內容:FlagsAttribute 和 Enum 的指導方針
  • 將列舉常數定義為2的乘冪,也就是1、2、4、8 等等。 這表示結合的列舉常數中的個別旗標不會重迭
  • 請考慮建立常用旗標組合的列舉常數。 例如,如果您有一個用於包含列舉常數和之檔案 i/o 作業的列舉 Read = 1 Write = 2 ,請考慮建立列舉常數 ReadWrite = Read OR Write ,其中結合了 Read 和 Write 旗標。 此外,在某些情況下,用來結合旗標的位 OR 運算可能會被視為一種簡單的概念
  • 如果您定義負數作為旗標列舉常數,因為有許多旗標位置可能設定為1,這可能會讓您的程式碼變得令人困惑,並鼓勵程式碼錯誤,請務必謹慎使用
  • 測試旗標是否設定在數值中的方便方式,是在數值和旗標列舉常數之間執行位 AND 運算,將數值中的所有位設定為零,而不會對應至旗標,然後測試該運算的結果是否等於旗標列舉常數
  • 用作 None 值為零之旗標列舉常數的名稱。 您無法使用 None 位 and 運算中的列舉常數來測試旗標,因為結果一律為零。 不過,您可以在數值和列舉常數之間執行邏輯(而非位比較), None 以判斷是否已設定數值中的任何位。如果您建立一個值列舉,而不是旗標列舉,則建立列舉常數仍然是值得的 None 。 原因是,通用語言執行時間預設會將用於列舉的記憶體初始化為零。 因此,如果您未定義其值為零的常數,則在建立時,列舉會包含不合法的值。如果您的應用程式有明顯的預設情況,您的應用程式需要表示,請考慮使用其值為零的列舉常數來表示預設值。 如果沒有預設案例,請考慮使用值為零的列舉常數,表示不是任何其他列舉常數所表示的大小寫
  • 請勿只定義列舉值來鏡像列舉本身的狀態。 例如,請勿定義只標示列舉結尾的列舉常數。 如果您需要判斷列舉的最後一個值,請明確檢查該值。 此外,如果範圍內的所有值都有效,您可以針對第一個和最後一個列舉的常數執行範圍檢查
  • 請勿指定保留給未來使用的列舉常數
  • 當您定義採用列舉常數作為值的方法或屬性時,請考慮驗證值。 原因是,您可以將數值轉換成列舉型別,即使該數值未在列舉中定義
C# Code
using System;

namespace EnumFlags
{
    class Program
    {
        static void Main(string[] args)
        {
            Days days = Days.Saturday;
            Console.WriteLine($"初始值:{days}");

            days = days.Add(Days.Sunday);
            Console.WriteLine($"把星期日加入:{days}");

            days = days.Remove(Days.Saturday);
            Console.WriteLine($"把星期六移除:{days}");

            Console.WriteLine($"星期六日是否同時存在:{days.IsSatAndSunBoth()}");
            Console.WriteLine($"星期六日是否存在一日:{days.IsSatOrSunExists()}");
        }
    }

    // 沒有特別宣告的話,Enum 預設是 int
    [Flags]
    public enum Days
    {
        Monday = 1,
        Tuesday = 2 ,
        Wednesday = 4,
        Thursday = 8 ,
        Friday = 16,
        Saturday = 32,
        Sunday = 64,

        All = Monday | Tuesday | Wednesday | Thursday | Friday | Saturday | Sunday
    };

    public static class ExtDays
    {
        public static Days Add(this Days source, Days flagAdd)
        {
            return source | flagAdd;
        }

        public static Days Remove(this Days source, Days flagRemove)
        {
            return source & ~flagRemove;
        }

        // 星期六日是否同時存在
        public static bool IsSatAndSunBoth(this Days value)
        {
            return value.HasFlag(Days.Saturday | Days.Sunday);
        }

        // 星期六日是否存在一日
        public static bool IsSatOrSunExists(this Days value)
        {
            return value.HasFlag(Days.Saturday) || value.HasFlag(Days.Sunday);
        }
    }
}
一開始練習時,因為是根據官方文章把 Enum 值,從 0 開始往後編,偏偏又用 [值為 0 的列舉] 來初始化,FlagAttribute 怎麼都加不上去,閱讀 [FlagsAttribute 指方針] 才發現到問題點
用作 None 值為零之旗標列舉常數的名稱。 您無法使用 None 位 and 運算中的列舉常數來測試旗標,因為結果一律為零。 不過,您可以在數值和列舉常數之間執行邏輯(而非位比較), None 以判斷是否已設定數值中的任何位。

如果您建立一個值列舉,而不是旗標列舉,則建立列舉常數仍然是值得的 None 。 原因是,通用語言執行時間預設會將用於列舉的記憶體初始化為零。 因此,如果您未定義其值為零的常數,則在建立時,列舉會包含不合法的值。

如果您的應用程式有明顯的預設情況,您的應用程式需要表示,請考慮使用其值為零的列舉常數來表示預設值。 如果沒有預設案例,請考慮使用值為零的列舉常數,表示不是任何其他列舉常數所表示的大小寫。

星期五, 11月 20, 2020

[C#] 刪除唯讀檔案

收到 Exception 訊息為 [System.UnauthorizedAccessException: 拒絕存取路徑],原以為是 File Server 上權限異常,經確認後發現,該圖檔莫名奇妙被設為 [唯讀],Orz
透過 C# 來刪除唯讀檔案,可以透過 FileInfo 或 FileAttribute 取消唯讀後再進行

FileInfo 取消唯讀
using System;
using System.IO;

namespace ReadOnlyFileDelete
{
    class Program
    {
        static void Main(string[] args)
        {
            string targetFileFullName = @"D:\Demo.txt";

            if (!File.Exists(targetFileFullName))
                throw new FileNotFoundException();

            // 利用 FileInfo
            FileInfo fi = new FileInfo(targetFileFullName);
            if (fi.IsReadOnly) fi.IsReadOnly = false;
            fi.Delete();
        }
    }
}
FileAttribute 取消唯讀
using System;
using System.IO;

namespace ReadOnlyFileDelete
{
    class Program
    {
        static void Main(string[] args)
        {
            string targetFileFullName = @"D:\Demo.txt";

            if (!File.Exists(targetFileFullName))
                throw new FileNotFoundException();

            FileAttributes attributes = File.GetAttributes(targetFileFullName);
            if (attributes.HasFlag(FileAttributes.ReadOnly))
            {
                attributes = attributes & ~FileAttributes.ReadOnly;
                File.SetAttributes(targetFileFullName, attributes);
            }            
            File.Delete(targetFileFullName);
        }
    }
}

星期二, 11月 17, 2020

[SQL] 隱含轉換與 Index Seek

一直都以為隱含轉換後,就不會有機會跑 Index Seek,只會是 Index Scan,在上一篇 [SQL] 探查剩餘 (Probe Residual) 內,因為朋友測試發現 Nested Loop 執行計畫,才注意到發生隱含轉換還是有機會跑 Index Seek 的

完整執行計畫,可以從黃框框發顯示 tblChar Clustered Index Seek

[SQL] 隱含轉換與 Index Seek-1 

看該 operator 明細就可以發現,搜尋述詞內的隱含轉換不是針對 char 欄位來進行,所以 Index Seek 還是有觸發
 
[SQL] 隱含轉換與 Index Seek-2

星期六, 11月 14, 2020

[SQL] 探查剩餘 (Probe Residual)

Turning 時發現執行計畫很詭異,因為清楚該 TSQL 商業邏輯,所以注意到 Hash Join 存在和其  [探查剩餘 (Probe Residual)] 資訊,後來發現是 JOIN ON 欄位資料型態不一致造成隱含轉換,再加上沒有 Index 造成該現象

模擬該情況,先建立 tblChar 和 tblNChar 兩個 Table
USE [AdventureWorks2017]
GO

CREATE TABLE [dbo].[tblChar]
(
	[NOs] [char](11) NOT NULL, -- 資料形態為 char
	CONSTRAINT [PK_tblChar] PRIMARY KEY CLUSTERED ([NOs] ASC)
)
GO

CREATE TABLE [dbo].[tblNChar]
(
	[ID] [int] NOT NULL,
	[Nos] [nchar](11) NULL, -- 資料形態為 nchar
	CONSTRAINT [PK_tblNChar] PRIMARY KEY CLUSTERED 
	(
		[ID] ASC
	)
) 
GO
針對兩個 Table 進行 JOIN,來呈現 Hash Join 內有 Probe Residual 情況
SELECT T1.*
FROM tblChar AS T1
	JOIN tblNChar AS T2 ON T1.NOs = T2.NOs
完整執行計畫 

[SQL] 探查剩餘 (Probe Residual)-1

重點的 Hash Join operator 和隱含轉換,只擷取重點來顯示

[SQL] 探查剩餘 (Probe Residual)-2

[SQL] 探查剩餘 (Probe Residual)-3 
解決方式就把資料形態改為一致 (char(11)) 並加上 Index 結案,謎之音:只存英數資料,為什麼要開成 unicdoe 欄位形態阿,>.<

以前對 Hash Join 認知,只存在 OLTP 系統內是要關注的重點,趁這次機會找些文章來更了解它

20201117 朋友根據文章內容試時,發現執行計畫不一樣,跑 Nested Loop,該點在自建的模擬環境內也會發生,可以透過連結提示來看到有 Hash Join 的執行計畫
SELECT T1.*
FROM tblChar AS T1
	INNER Hash JOIN tblNChar -- 連結提示,強迫 TSQL 跑 Hash Join
	AS T2 ON T1.NOs = T2.NOs

星期二, 11月 10, 2020

[C#] 設定檔 - Settings.settings

設定檔 - Settings.settings 可以用來儲存應用程式所需的參數,例如公司名稱、專案名稱、使用者、使用者喜好設定等,避免 Hard Code,增加應用彈性

在 Project Properties 內可以找到 Setting.settings 檔案
 
[C#] 應用程式設定檔 - Settings.settngs-1

點擊 Setting.settings 可以開啟 [設定] 頁面,分別輸入 Title、BGColor、LocationX、LocationY 和 BindingText 這 5 個設定值,各設定值用途
  • Title:Form.Text 使用
  • BGColor:使用者挑選顏色,並在 PictureBox 內顯示
  • LocationX 和 LocationY:紀錄 Form 最後的位置
  • BindingText:直接綁定 TextBox.Text Property

範圍有應用程式 (Application) 和使用者 (User) 兩種
  • 應用程式:唯讀,無法進行修改,設定值會存放在 App.Config 內的 <applicationSettings> 區段
  • 使用者:可讀寫,設定值會存放在 App.config 內的 <userSettings> 區段

  [C#] 應用程式設定檔 - Settings.settngs-2

設定完成後,可以在 app.Config 內看見相關設定
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <configSections>
        <sectionGroup name="userSettings" type="System.Configuration.UserSettingsGroup, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" >
            <section name="ConfigSample.Properties.Settings" type="System.Configuration.ClientSettingsSection, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" allowExeDefinition="MachineToLocalUser" requirePermission="false" />
        </sectionGroup>
        <sectionGroup name="applicationSettings" type="System.Configuration.ApplicationSettingsGroup, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" >
            <section name="ConfigSample.Properties.Settings" type="System.Configuration.ClientSettingsSection, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false" />
        </sectionGroup>
    </configSections>
    <startup> 
        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2" />
    </startup>
    <userSettings>
        <ConfigSample.Properties.Settings>
            <setting name="BGColor" serializeAs="String">
                <value />
            </setting>
            <setting name="LocationX" serializeAs="String">
                <value>0</value>
            </setting>
            <setting name="LocationY" serializeAs="String">
                <value>0</value>
            </setting>
            <setting name="BindingText" serializeAs="String">
                <value>控件綁定</value>
            </setting>
        </ConfigSample.Properties.Settings>
    </userSettings>
    <applicationSettings>
        <ConfigSample.Properties.Settings>
            <setting name="Title" serializeAs="String">
                <value>[C#] 應用程式設定檔 - Settings.settngs</value>
            </setting>
        </ConfigSample.Properties.Settings>
    </applicationSettings>
</configuration>

星期三, 11月 04, 2020

[C#] DockPanel Suite

DockPanel Suite 可以讓 WinForm 的 Form 有類似 VS 視窗,可以移動、隱藏和縮效果

透過 nuget 安裝 V3.0.6 後,可以在 VS 工具箱內看見綠框這三個選項 (DockPanel、VisualStudioToolStripExtender 和 VS2005Theme),另外 DockPanel Suite 還有其他 Theme 可以安裝,只安裝 VS2015 Theme (橘框)

[C#] DockPanel Suite-1

要 Dock 的 Form 必須繼承 DockContent (以本例來說是 frmDock Form)
using WeifenLuo.WinFormsUI.Docking;

namespace DockSample
{
    public partial class frmDock : DockContent
    {
        public frmDock()
        {
            InitializeComponent();
        }
    }
}
把 DockPanel 拉到 Form 內 (以本例來說,是 frmMain Form) 並進行下面設定
using System.Windows.Forms;
using WeifenLuo.WinFormsUI.Docking;

namespace DockSample
{
    public partial class frmMain : Form
    {
        public frmMain()
        {
            InitializeComponent();

            // frmMain 必須開啟 IsMdiContainer
            this.IsMdiContainer = true;

            // 設定 DockPanel 置中
            dockPanel.Dock = DockStyle.Fill;

            // 不允許使用者移動 DockPanel 內視窗 (Form)
            dockPanel.AllowEndUserDocking = false;

            // 設定 DockPanel Theme
            dockPanel.Theme = new VS2015BlueTheme();

            // 根據 DockState 把 frmDock 釘在 frmMain.DockPanel 上
            frmDock frmDocument = new frmDock() { TabText = "Document" };
            frmDocument.Show(this.dockPanel, DockState.Document);

            frmDock frmDockLeft = new frmDock() { TabText = "DockLeft" }; ;
            frmDockLeft.Show(this.dockPanel, DockState.DockLeft);

            frmDock frmDockRight = new frmDock() { TabText = "DockRight" }; ;
            frmDockRight.Show(this.dockPanel, DockState.DockRight);

            frmDock frmDockBottom = new frmDock() { TabText = "DockBottom" }; ;
            frmDockBottom.Show(this.dockPanel, DockState.DockBottom);

            frmDock frmDockLeftAutoHide = new frmDock() { TabText = "DockLeftAutoHide" }; ;
            frmDockLeftAutoHide.Show(this.dockPanel, DockState.DockLeftAutoHide);

            frmDock frmFloat = new frmDock() { TabText = "Float" }; ;
            frmFloat.Show(this.dockPanel, DockState.Float);
        }
    }
}
執行效果

[C#] DockPanel Suite-2