2017年10月11日 星期三

[Raspberry PI][Flask] 土炮MJPG Stream

WIKI
Motion JPEGM-JPEGMJPEG,Motion Joint Photographic Experts Group,FourCC:MJPG)是一種影像壓縮格式,其中每一影格圖像都分別使用JPEG編碼。
或許是實作上相對單純好理解,據說MJPEG常用在網路攝影機(IP-Camera)等相關的應用,由於也是相對古老的實作方式,大多的瀏覽器都支援.mjpg格式;在網路上最常被提到的知名套件是MJPG-Streamer ,在輕鬆的設定播放相關參數後可以輕易的將你的攝影機影像投到網頁上.

今天練習用flask實作簡單的mjpg-streamer,算是練練python還有學習一點html與http的概念:

2017年9月25日 星期一

[Blogger][Javascript] 在撰寫區標記Google Prettify區段

Google prettify簡單又好用,只是在Blogger內貼程式碼時,還要手動轉到HTML頁籤在落落長的HTML元素裡找到正確排版位置、透過HTML Escape跳脫字元、自己標記<pre class="prettyprint">...</pre>...有點麻煩

2017年9月21日 星期四

[VS Code][C/C++] 基本開發環境設定


這次輪到在VS Code上開發C/C++程式,被宇宙最強IDE Visual Studio寵壞的關係,一開始搞不清楚到底在設定什麼東西,最後終於搞懂幾件關於VS Code的機理:

2017年9月20日 星期三

[VS Code][Bash Shell Script] Debug環境設定


Shell Script也是可以Debug的,下斷點、看變數內容...都可以,VS Code配來寫Shell Script也是很下飯,記錄一下環境設定

2017年9月18日 星期一

[LINUX] 跨網域連接NFS伺服器 - Part2 - 透過ssh tunnel連接NFS

透過Part1,example.ddns.net:22已經開通,通過這個連接點再配合Virtual Server連接到內網伺服器進行ssh登入,可以開始在異地登入ssh替伺服器做設定

2017年9月17日 星期日

[LINUX] 垮網域連接NFS伺服器 - Part1 - 設定DDNS與Virtual Server


目前有某台CentOS主機放在老家作為個人小伺服,硬碟裡面收藏了無數家珍(嘿嘿),只是就算開了NFS服務(Network File Systme)也只能在老家的區域網路內使用,回到租屋處該怎麼透過網際網路連回去呢?

2017年8月2日 星期三

[.NET] 流(Stream)的改變輸出入導向


衍生(Dervied)/聚合(Composited)流(Stream)的類別,具有流的輸出入能力,透過這個基礎介面,我們可以用來銜接不同類型的輸出入裝置,不需要實作任何轉換細節,對呼叫端而言只管將字串資料輸出入,可以把流看成某種程度的抽象化硬體

2017年7月30日 星期日

[.NET][Python] 集合處理筆記

Wiki
MapReduce is a programming model and an associated implementation for processing and generating big data sets with a paralleldistributed algorithm on a cluster.
集合之間的轉換,與元素操作是資料處理的日常,第一次認識MapReduce概念其實是從Python來,但發現.NET也有等價的處理(從Stackoverflow發現,搜尋"Python Map Equipvalent in c#"):

2017年7月26日 星期三

[Blogger]文內插入Latex(Mathjax)

紀錄一下如何在Blogger中插入Latex,用到的Javascript來自mathjax,他的說明提供了很簡要快速的步驟:

2017年7月22日 星期六

[.NET] 流(Stream)的理解心得


Wiki說得好:
「流」是一連串從I/O設備讀寫的字符(Byte)

流(Stream)是.NET中非常基礎的型態,存取方式是很單純的位元組依序寫入/讀出(Byte by Byte Writing/Reading),會有一個內藏Counter紀錄住目前存取位置(Position,有點類似座標),存取時都是從目前位置開始存取.

若將整個讀取完畢的Stream攤平開來,也是可以想像成時域信號圖,橫軸是位置,只是要確定好座標原點(Origin)在何處

跟位元組陣列有著類似的概念,只是位元組陣列可以隨機存取:給定INDEX存取,座標原點就是第一個元素,只需一個存取動作:給定INDEX後返回該位置的資料;若要做類似隨機存取操作,Stream必須先透過Seek改變目前位置,才能開始存取,因此需要兩個存取動作:改變目前位置、存取資料

Stream存的個別元素是位元組,也就是0-255之間的一個數值,可以說是資訊系統內超級底層的資料型態之一,也就是說Stream是一種極為基礎的資料儲存結構;任何的高階資料型態(物件、字串)都能轉換成位元組形式去表達(某種映射):

  • 物件-位元組轉換(物件與位元組之間的映射)
    • 序列化與反序列化(Serialization/Deserialization)
    • 而反序列化/序列化則是互為反函式(Inverse)

  • 字元-位元組轉換(字元與位元組之間的映射)
    • 按字碼表Encoding/Decoding
    • 線性映射?
Stream的資料表達方式容易直接轉化為現實上的物理資料傳輸,舉凡磁碟資料寫入(File)、網路資料傳輸、通訊埠資料傳輸,都是出口Stream(Out-Stream)內按位元組取出,轉化成位元(1/0)脈衝信號後在物理層一點一滴傳達;接著在目標系統物理層中收集這些脈衝信號還原成位元組,按位元組一個一個塞進目標系統的入口Stream(In-Stream)中

後續應用就視情況看是要透過反序列化還是Decoding,轉換成更高階的資料表達型態

.NET的類別規劃上,可以在相關的硬體抽象物件找到Stream的身影,好比說以下衍生自Stream的型別:
既然都是Stream,肯定是有共同性質的:)後續研究學習

參考:這系列對於Stream介紹蠻不錯

2017年7月20日 星期四

[無聊的心得] Overhead

Overhead,名詞
https://en.wikipedia.org/wiki/Overhead_(computing)

中文叫做經常開支,為了達成某種目的所必須付出的與目標無關的代價

講到Overhead我就想到酬載(Payload),用貨車載貨來聯想:想像一台20噸重的卡車載了一顆20克的蘋果, 此時20克的蘋果就是酬載(Payload),如果送到目的地可以賺10塊錢,而推動20噸車體到目的地所需的油料消耗是3000塊,屬於經常開支(Overhead),恩...好一門美麗的賠錢生意。

思緒回到資訊世界,標頭會帶來Overhead,以XML格式資料為例,角括號裡面的是屬性(Property)名稱,而屬性間的包覆關係是階層格式描述,無論是角括號本身、頭尾的包覆這些都是經常開支(Overhead),因為其實你只在乎<></>之間的值(Value)資訊

看看以下酬載少於經常開支:
 
<themotherfuckerextremelongpropertyname>
1
</themotherfuckerextremelongpropertyname>

那麼傳輸時所花費的時間、硬體損耗都是花在經常開支而不是酬載(花在傳輸PropertyName)大部分資源都花在傳輸Header,沒意義的Overhead導致效率不彰,就要開始動腦筋改善這問題嚕

比方說每次傳輸時格式都是固定,我能不能不要傳Header,以隱含的方式表達資訊?還是縮短PropertyName?好像都不錯

[.NET] 執行序同步慣用Pattern心得 - I


MSDN說:
需要使用者互動的軟體必須儘快回應使用者的活動,以便提供豐富的使用者經驗。不過,同時它也必須執行需要的計算以儘快提供使用者資料。
在個人目前的工作領域,使用者體驗並非放在第一(甚至是最後一名),大部分時間都花在Coding工作處理常式(Routines),主要實現概念為狀態機(State-Machine)與多工處理(Multi-Task),在Main Thread執行

常式內的多工工作是純粹的商業邏輯,少有牽涉I/O存取,處理頻率穩定近乎週期性,但來源資訊難免要透過外部裝置取得,如網路介面、PCI裝置、使用者輸入...等,幾乎是慢速、頻率較低的資料源,為了避免影響Main Thread的多工輪詢,此時就可考慮用其他Thread分擔低速工作。

整理三個與狀態機搭配的常用分緒招,共通點是在Main Thread輪詢某資料狀態,該輪詢函式必須不能阻塞(Blocking)住Main Thread,否則會影響到其他多工作業

(沒實測過這些方法的具體效能,比較在乎應用上的設計模式(Pattern)是否容易嵌入既有架構)
  • Task
    • 發動:
      • 將要執行的Method裝載到Task物件中後,呼喚Start()開始執行緒作業
      • 也可以利用工廠模式,呼叫共用方法Task.Run()開始執行緒作業,同步狀態透過傳回的Task實例來操作
    • 同步:
      • Worker Thread輪詢Task.IsComplete至狀態為True
    • 心得:
      • Task封裝了Thread與若干執行緒同步元件(如Mutex),因此包含了執行緒操作與同步操作
      • 據說是在執行緒池內作業(待確認
  • ThreadPool
    • 發動:
    • 同步:
      • 無法得知方法最終被放到哪個具體執行緒,也沒封裝同步操作方法,所以必須在Method裡自行設計同步物件來辨識是否方法執行完成
      • 個人慣用Concurrent執行緒安全元件,如ConcurrentQueueConcurrentDictionary傳遞資訊到Worker Thread
      • Worker Thread輪詢Concurrent元件
    • 心得:
      • 最簡單易用赤裸的異步編成模式,不含任何同步方法的純粹執行緒池操作最?似乎懂了些什麼了?
  • MulticastDelegate
    • 發動:
      • 將要執行的Method裝載到Delegate中
      • 呼叫該Delegate的BeginInvoke(),必須帶入回呼方法(Callback)
      • 必須呼叫EndInvoke()作為確認該執行緒執行完成的辨識?
    • 同步:
      • 輪詢呼叫該Delegate時傳回的IAsyncResult.IsCompleted
      • 或是在Main Thread呼叫EndInvoke()(但是可能阻塞)
    • 心得:
      • 與Task一樣,封裝了執行緒操作與同步操作,只是設計模式上透過IAsyncResult或EndInvoke達成同步
      • 據說是在執行緒池內作業(待確認
class Program
    {


        static void Main(string[] args)
        {
            int state = 0;

            Action __delegate = null; 
            Task __task = null;
            IAsyncResult handle = null;
            ConcurrentQueue exchange = new ConcurrentQueue();

            int outValue = 0;
            //the main state machine
            Program.method();
            while (state != 500)
            {
                switch (state)
                {
                    case 0:
                        // use Task 
                        __task = new Task(new Action(Program.method));
                        __task.Start();
                        state +=10;
                        break;
                    case 10:
                        // wait until task done
                        if (__task.IsCompleted)
                            state +=10;
                        break;
                    case 20:
                        // use thread pool
                        ThreadPool.QueueUserWorkItem((object foo)=>{
                            Program.method();
                            exchange.Enqueue(0); // enqueue some meaningfll result
                        },null);
                        state+=10;
                        break;
                    case 30:
                        //wait until method had been scheduled and executed
                        if (exchange.TryDequeue(out outValue))
                            state +=10;
                        break;
                    case 40:
                        // use delegate/IAsyncResult 
                        __delegate =  new Action(method);
                        handle = __delegate.BeginInvoke((IAsyncResult ar)=> 
                            {
                                method();
                                __delegate.EndInvoke(ar); // you had to call this to release ar
                            },null);
                        break;
                    case 50:
                        // wait until method had been executed
                        if (handle.IsCompleted)
                            state = 500;
                        break;
                    default:
                        break;
                }
                Thread.Sleep(0);// yield to other thread to run
            }//while
        }

        static void method()
        {
            //reflect the current thread
            Console.WriteLine(System.Threading.Thread.CurrentThread.ManagedThreadId.ToString());
        }

    }

後記1:MSDN的異步編程模式概述,對於非同步作業可有概略且完整的認識,其中APM(
Asynchronous Programming Model,就是Delegate/IAyncResult)已經漸漸式微變成不太推薦的Pattern

後記2:在.NET Core(MAC)上實測Delegate.BeginInvoke是會有例外的,原因在此,大致上是被開發小組視為Deprecated方法,未來可能會被幹掉

2017年7月17日 星期一

[.NET] REGEX 利用 Group 捕捉(Capture)重複字串(Repetitive Pattern)

Regular Expression(正規表達式),是一種抽象語言,用來表達字串的結構,比方[A-Za-z]+能表達所有的英文單字,應用上常用來驗證輸入,或是捕捉(Capture)具有特徵的字串片段,利用Regular Expression可以大幅簡化字串處理的繁瑣作業

這次的問題如下,假設某種字串格式的具體內容是以前綴(prefix)後綴(post-fix)框住,怎麼利用正規表示式驗證並捕捉(Capture)重複的片段?

好比以下類似座標的紀錄檔:
Model:M1
Coordinates:
X1.62 Y1.01
X9.53 Y2.3
X4.94 Y2.1
X8.55 Y3.15
X2.56 Y1.25

X5.57 Y2.72

首先分析一下文本的結構:
  • 標頭如同前兩行,形式與內容固定
  • 重複資料部分按行表達
  • 單筆座標以X<浮點數> Y<浮點數>表示
    • 座標值是我們最在意的部分
    • 希望可以一組一組捕捉分離,才好批次後處理解析數值
模式字串(Pattern)的設計想法如下:
  • 浮點數的表達不用傷腦筋,網路上很容易查到
  • 座標值的部分用()群組住單組X,Y座標(也是一種Symbol),後面以+表達此組Symbol會重複1到N次
  • 單組座標內又再次以()群組住浮點數字串表達
  • 考慮強健性,安插些泛空白字元在各個單元交接處
於是我們交出了以下的模式字串 :

"Model:[\\w]+[\\s]+Coordinates:[\\s]+(X([-+]?[0-9]*\\.?[0-9]+)[\\s]*Y([-+]?[0-9]*\\.?[0-9]+)[\\s]*)+"

其中"[-+]?[0-9]*\.?[0-9]+"就是浮點數的表達

這樣丟下去REGEX跑字串,模式字串中的()可以透過Match.Group(Index)存取,在這裡的模式字串我們共用到三個(),按照()在模式字串裡的順序,Group依序是:

  • Index-0:原始字串
  • Index-1:單組座標的群組
  • Index-2:X座標浮點數字串
  • Index-3:Y座標浮點數字串



String __input = 
"Model:M1 Coordinates: X1.62 Y1.01 X9.53 Y2.3 X4.94 Y2.1 X8.55 Y3.15 X2.56 Y1.25 X5.57 Y2.72";
String __patternUnnamed = 
"Model:[\\\\w]+[\\\\s]+Coordinates:[\\\\s]+(X([-+]?[0-9]*\\\\.?[0-9]+)[\\\\s]*Y([-+]?[0-9]*\\\\.?[0-9]+)[\\\\s]*)+";
List __coords = new List();
Match __matchUnnamed = Regex.Match(__input,__patternUnnamed);
//-----------------------------------
//  Unnamed         
//-----------------------------------
__coords.Clear();
for (int i = 0; i < __matchUnnamed.Groups[1].Captures.Count;i++)
{
// use X,Y group restore each coordinates
float[] eachCoord = new float[]{0,0};
eachCoord[0] = float.Parse(__matchUnnamed.Groups[2].Captures[i].Value);
eachCoord[1] = float.Parse(__matchUnnamed.Groups[3].Captures[i].Value);
__coords.Add(eachCoord);
}
//check all coordinates
Console.WriteLine(\"Unnamed\");
foreach (float[] item in __coords)
{
Console.WriteLine(String.Format(\"{0},{1}\",
item[0],
item[1]));
}
若模式字串裡()太多,或是覺得用INDEX標定群組可讀性太差,此時可以套入具名群組(Named Group)的概念,把()所圈住的Symbol具名化:

Model:[\\w]+[\\s]+Coordinates:[\\s]+(?<coord>X(?<X>[-+]?[0-9]*\\.?[0-9]+)[\\s]*Y(?<Y>[-+]?[0-9]*\\.?[0-9]+)[\\s]*)+

具名化後,便可以用Match.Group(Name)方式存取群組了

String __patternNamed = 
"Model:[\\\\w]+[\\\\s]+Coordinates:[\\\\s]+(?X(?[-+]?[0-9]*\\\\.?[0-9]+)[\\\\s]*Y(?[-+]?[0-9]*\\\\.?[0-9]+)[\\\\s]*)+";
Match __matchNamed = Regex.Match(__input,__patternNamed);
//-----------------------------------
//  Named
//-----------------------------------
//now we get the \"coord\" group
__coords.Clear();
for (int i = 0; i < __matchNamed.Groups[\"coord\"].Captures.Count; i++)
{
// use X,Y group restore each coordinates
float[] eachCoord = new float[]{0,0};
eachCoord[0] = float.Parse(__matchNamed.Groups[\"X\"].Captures[i].Value);
eachCoord[1] = float.Parse(__matchNamed.Groups[\"Y\"].Captures[i].Value);
__coords.Add(eachCoord);
}


後記1:.NET的正規表達式相容於Perl5,也與這裡相容,可以先快速的先驗表達式
後記2:據說.NET的Regular Expression模組是難得可以幫你Capture住群組值(Value)的函式庫

程式碼Repo

2017年7月16日 星期日

[.NET] 記憶體洩漏(Memory Leak)可能原因 - XML Serializer


承接此篇

.NET最爽的就是背後有GC(Garbage Collection)罩著,想配置物件就配置物件(NEW),用完拍拍屁股就走了,等下就會有服務生來收碗盤了,多爽!

可是如果你點到的菜是XML Serializer,可能就沒辦法那麼幸運了,GC是不收這道菜的,亂點到最後...整個餐廳桌上都會是你之前吃剩的XML Serializer

GC無法自動回收XML Serializer,(原因Dynamically Generated Assemblies段落),過於任性大量配置Serializer的話,記憶體可是會爆的

既然如此只能讓自己勤勞點,MSDN推薦的作法是,快取(Cache)住使用過的XML Serializer,每次欲利用XML Serializer時,統一向管理函式索取,若快取庫內沒有,則重新配置一個,若有,則取出既有的參考傳回,如此一來再利用時便不用重新配置,也就避免了潛在的記憶體洩漏問題了

參考以下程式碼:

  • 函式原型:
    • 引數 - 要序列化的物件型別(若物件是衍生自某基底型別,則透過陣列傳入具體衍生型別)
    • 傳回值 - XML Serializer實例
    • 視野(Scope): public static,視為工具函式(Utility)



 
 class utilities
    {
        /// <summary>
        /// Used to store key-serializer
        /// </summary>
        /// <returns></returns>
        private static Hashtable __dictionary = new Hashtable(); 
        /// <summary>
        /// Generate key according to contents of type list
        /// </summary>
        /// <param name="typeList"></param>
        /// <returns></returns>
        protected static int generateKey(List<Type> typeList)
        {
            //hash code summarize
           return typeList.Sum(__type => __type.GetHashCode());
        }
        /// <summary>
        /// Always fetch serializer from this function
        /// (Never allocate by yourself)
        /// </summary>
        /// <param name="mainType"></param>
        /// <param name="extraTypes"></param>
        /// <returns></returns>
        public static XmlSerializer getSerializer(Type mainType,Type[] extraTypes){
            
            //merge the list
            List<Type> __typeList = new List<Type>();

            __typeList.Add(mainType);
            __typeList.AddRange(extraTypes);

            var key = generateKey(__typeList);

            if (__dictionary.ContainsKey(key))
            {
                // this kind of serializer had been cached/used before , 
                // get the cached serializer
                return (XmlSerializer)__dictionary[key];
                }
            else
            {
                // never been used , need to create a new one and push into cache
                XmlSerializer __serializer = new XmlSerializer(mainType,extraTypes);
                __dictionary.Add(key,__serializer);
                return __serializer;
             }
        }//getserializer
    }//utilities

參考程式碼:https://github.com/smandyscom/cached_xmlSerializer

2017年7月8日 星期六

[VS Code x C#] VS Code加入參考


最近用起了VS Code,只是想單純地做一些C#程式範例(Snippet),起初照了美女Demo
(她真的很甜)安裝設定,簡要如下:


  1. 安裝VS Code(只是一個Open Architecture文字編輯器
  2. 安裝.NET Core(就是.NET Framework 核心肌群
  3. 在VS Code下安裝種種必須Plugin
  4. 下指令初始化專案資料結構
    1. $dotnet new 
  5. 開寫C#

可是呢,當我嘗試想用一些命名空間組件時就開始鬼打牆,好比說System.Collections.Hashtable,明明已經在最前面寫Using System.Collections
可VS Code不會讓你Build過,原因是 xxx Type or name could not be found

原因很簡單,你沒替專案加入參考
如同在Visual Studio下,要引用其他組件除了要在程式碼最開頭帶入Namespace(Using xxxxx),也必須在專案下加入參考(歐~想起來了)

以下紀錄一下如何替VS Code C# Project加入參考

首先打開下面的 終端機(Terminal)以以下指令下載Package,好像是可以想成下載函式庫
#dotnet add package <AssemblyName>

為什麼是System.Collections.NonGeneric?參考的Assembly(.dll/動態函式庫)怎麼查詢?
可看API Reference,裡面的Assembly就是組件名稱,以Hashtable為例,必須要加入System.Collections.NonGeneric,所以下
#dotnet add package System.Collections.NonGeneric

系統會跑出一些訊息表示正在Download,最終會處理完成,如果來了一些Error,比較大可能性是"打錯字",好比System.Collections打成System.Collection(少一個s)

下完以後,再下
#dotnet restore
會自動幫你在.csproj內引入dependencies

你的.csproj會多出dependencies片段:

 
<project sdk="Microsoft.NET.Sdk">
  <propertygroup>
    <outputtype>Exe</outputtype>
    <targetframework>netcoreapp1.1</targetframework>
  </propertygroup>
  <itemgroup>
    <packagereference include="System.Collections" version="4.3.0">
    <packagereference include="System.Collections.NonGeneric" version="4.3.0">
    <packagereference include="System.Runtime.Extensions" version="4.3.0">
    <packagereference include="System.Xml.XmlSerializer" version="4.3.0">
  </packagereference></packagereference></packagereference></packagereference></itemgroup>
</project>

這些是原先新專案結構初始化後所沒有的片段
加入組件到此就沒問題了

咦,若是要加入其他參考專案呢?
可以參考這個指令,dot add reference

後記:網路上會查到很多 手動改project.json以加入dependencies的建議,看起來是比較過去的作法,目前版本的.NET Core SDK似乎要嘗試以.csproj做統整(與Visual Studio一致),捨棄project.json作為專案結構的描述,也許是如此一來專案比較好在VS Studio與VS Code兩個平台上操作而不會有轉換的困擾

2017年6月30日 星期五

[Windows Form] Property Grid 使用心得 - Part III(EditorAttribute)


(承接此篇)

EditorAttibute

在該欄位點下"編輯"後,系統會找尋該欄位的是否有EditorAttribute,若有,則根據你指定的Editor型別配置(Allocate)一個新實例(Instance),並呼叫該實例的EditValue函式,最後將EditValue傳回的值Set回該Property

實作方式是宣告一Class,繼承UITypeEditor,並複寫(override)以下函式:

  • GetEditStyle
    • 傳回值型別為UITypeEditorEditStyle,傳回不同值可改變欄位編輯入口樣式
      • Model:該欄位會提供(...)讓使用者點擊,通常要另外提供表單(Form)讓使用者操作
      • DropDown:顧名思義是提供下拉式選單
      • None:沒得編輯
  • EditValue
    • 就是Editor最主要的函式,函式執行完畢後傳回值即為編輯完成的值
    • 慣用起手式:
      • 另外配置(Allocate)一個表單(Form)
      • 帶入(Controls.Add)自己的UserControl,i.e TextBox.Text
      • 顯示該表單,通常使用ShowDialog,使EditValue被Block在此行
      • 在使用者關閉表單後,ShowDialog返回
      • 從剛剛的UserControl取得使用者輸入的值,i.e TextBox.Text
      • 將輸入值傳回(Return)
 
class stringUIEditor :
        UITypeEditor
    {
        public override UITypeEditorEditStyle GetEditStyle(System.ComponentModel.ITypeDescriptorContext context)
        {
            return UITypeEditorEditStyle.Modal;
        }

        public override object EditValue(System.ComponentModel.ITypeDescriptorContext context, IServiceProvider provider, object value)
        {
            Form __form = new Form();       // allocating a form , used to show-up editable facility 
            TextBox __tb = new TextBox();
            __form.Controls.Add(__tb);
            __form.AutoSize=true;
            __form.ShowDialog(); // show-up the form 
            return __tb.Text; //return the text value the user just input.
        }
    }


註:EditValue的輸入引數有許多妙用,可以用來判斷呼叫對象的特性,若要打造比較萬能的Editor就得要深入了解

從(...)呼喚的UIEditor
參考:
程式碼參考

[Windows Form] Property Grid 使用心得 - Part II(TypeConvertor)

再稍微整理一下Property Grid的一些概念:
  1. 顯示的欄位名稱及內容是依SelectedObject內宣告的Properties展開
  2. 顯示樣式則由各Property所擁有的屬性(Attribute)所定義
    1. 值(Value)以外的顯示樣式由以下屬性定義
      1. Display - 控制顯示名稱
      2. Category - 控制群組分類
      3. Description - 控制下方描述
      4. Browsable - 控制是否隱藏
    2. 值(Value)的樣式控制有以下幾方面
      1. Readonly - 控制值可否給使用者輸入
      2. 顯示樣式的控制,由TypeConvertor屬性決定
      3. 編輯器的控制,由EditorAttribute屬性決定
PropertyGrid各顯示樣式控制對應屬性



TypeConvertor
概念上就像是"顯示樣式轉換器",把該欄位的值丟進去對應的Convertor,根據你自訂的邏輯處理後將傳回的值顯示,實作方式為,宣告一Class,繼承TypeConvertor,並複寫
  • ConvertTo
    • 將Object轉為顯示值,通常是String
  • ConvertFrom
    • 將顯示值轉回Object
對於Enum的顯示樣式轉換則需要自訂EnumConvertor,其實EnumConvertor也是繼承自TypeConvertor,尚不清楚此層繼承多了什麼細節,實作方式亦為宣告一Class,並繼承EnumConvertor,複寫ConvertTo/ConvertFrom


 
 class optionEnumConvertor :
        EnumConverter
    {
        /// <summary>
        /// Convert From String To Enum Type
        /// </summary>
        /// <param name="context" />
        /// <param name="culture" />
        /// <param name="value" />
        /// <returns></returns>
        public override object ConvertFrom(System.ComponentModel.ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value)
        {
            switch (value.ToString())
            {
                case "選項1":
                    return MyEnum.OPTION1;
                case "選項2":
                    return MyEnum.OPTION2;
                default:
                    return null;
            }
        }
        /// <summary>
        /// Convert From Enum Type To String Type
        /// </summary>
        /// <param name="context" />
        /// <param name="culture" />
        /// <param name="value" />
        /// <param name="destinationType" />
        /// <returns></returns>
        public override object ConvertTo(System.ComponentModel.ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value, Type destinationType)
        {
            switch ((MyEnum)value)
            {
                case MyEnum.OPTION1:
                    return "選項1";
                case MyEnum.OPTION2:
                    return "選項2";
                default:
                    return null;
            }
        }

       public optionEnumConvertor(Type type):base(type)
       {
       }
    }
最後就是在對應的Property上加註TypeConvertor屬性,PropertyGrid就可以根據其指定的TypeConvertor進行樣式轉換了
 
 [DisplayName("想要選什麼?")] // much more human-readable?
        [TypeConverter(typeof(optionEnumConvertor))]
        public MyEnum Property3 { get; set; }

  

註: Enum比較特別的是,可以加註在屬性上,也可以加註在宣告上
 
  [TypeConverter(typeof(optionEnumConvertor))]
    enum MyEnum
    {
        OPTION1,
        OPTION2,
    }


Enum轉String


疑:Code-prettify如何顯示</summary>...不懂
自問自答:XML註解在HTML內當然是要Escaping呀傻傻,把程式貼到這邊Escaping完再貼入<pre>Block中

參考:
程式碼參考