2012年12月29日 星期六

c# 如何實作FindTextDialog

(下載範例)

從整篇文字裡面查找某一個指定字串是文字編輯器的基本功能,在Windows系統裡面FindTextDialog常常也被視為基本的通用對話盒而由系統或SDK所提供。但在.NET裡的Toolbox裡面沒有提供這樣的元件,底下以最容易的方式實作了一個FindTextDlg所必備的最基本功能。包含:

  • 基本的從文本搜尋指定字串(Find Text)
  • 往上或往下查找(Foreward or Backward)
  • 查找符合大小寫的字串(Match Case)
  • 查找符合完整字串(Match Whole Word)
首先第一步,建立一個用來作為FindTextDlg的Form,這是按照如上所列出可以滿足一般的需求功能而建立的一個Form,如下所示。


FindTextDlg顯示的方式是以Modaless的形式顯示出來的,所以可以在FindTextDlg出現時同時又切換Focus到其它地方繼續其它操作。依照慣例,當使用者按下Ctrl+F時,FindTextDlg會顯示出來。或者當使用者按下F3時,會繼續從當前位置繼續往下(或往上)杳找指定字串。假如當使用者按下F3時FindTextDlg是Hide的狀態的話,那它的執行動作就相當於按下Ctrl+F。

許多編輯器支援可以開啟多個文件作編輯,這時候FindTextDlg也可以只需要一個Instance。考慮到這點,FindTextDlg可以實作作Singleton型式。當然,是否如此實作需視實際實求。


private void richTextBox1_KeyDown(object sender, KeyEventArgs e)
{
  if (e.Control && e.KeyCode == Keys.F)
    FindTextDlg.ShowFindTextDlg();
  else if (e.KeyCode == Keys.F3)
    FindTextDlg.Search();
}

如上,當在文字編輯器裡按下Ctrl+F或F3時,分別呼叫FindTextDlg的ShowFindTextDlg或Search。


static FindTextDlg Instance = null;

static public void ShowFindTextDlg()
{
  if (null == Instance)
    Instance = new FindTextDlg();

  Instance.Show();
  Instance.cbFindStr.SelectAll();
  Instance.cbFindStr.Focus();
}

static public void Search()
{
  if (null == Instance)
  {
    ShowFindTextDlg();
    return;
  }
}

接下來的重點就是如何實作Search的功能。

基本的查找功能,我們可以由String.IndexOf(string value)得到。如果加上要由當得或指定文件位置開始查找則可以由String.IndexOf(string value, int startIndex)得到。如果要再加上是否檢查大小寫則可以由String.IndexOf(string value, StringComparison comparisonType)得到。至於要往前查找,則只需要使用對應的String.LastIndexOf版本即可。

其它細節可參考範例程式

2012年8月3日 星期五

如何將巨集轉為字串

在c/c++的程式裡面用#define定義的東西,要怎麼變成字串呢?

c/c++裡面有個字串化的運算子#(stringizing operator),可以把巨集的參數轉換成字串常數。如下的簡單範例所示,定義了幾個簡單的巨集,利用#運算子就能把任何東西轉換成字串。

#define TOSTR2(s) #s
#define TOSTR2W(s) L#s
#define TOSTR(tok) TOSTR2(tok)
#define TOSTRW(tok) TOSTR2W(tok)

#define min(a,b) (a) < (b) ? (a) : (b)

int main()
{
    int i = min(min(4,2), 3);

    char *s1 = TOSTR2(min(min(4,2), 3));
    wchar_t *ws1 = TOSTR2W(min(min(4,2), 3));

    char *s2 = TOSTR(min(min(4,2), 3));
    wchar_t *ws2 = TOSTRW(min(min(4,2), 3));

   return 0;
}

在程式的設定裡,打開/P參數可以將巨集展開後的結果另外輸出成檔案,我們可以由這個檔案來觀察一些有趣的結果。


底下是輸出結果的片段。


int main(void)
{
    int i = ((4) < (2) ? (4) : (2)) < (3) ? ((4) < (2) ? (4) : (2)) : (3);

    char *s1 = "min(min(4,2), 3)";
    wchar_t *ws1 = L"min(min(4,2), 3)";

    char *s2 = "((4) < (2) ? (4) : (2)) < (3) ? ((4) < (2) ? (4) : (2)) : (3)";
    wchar_t *ws2 = L"((4) < (2) ? (4) : (2)) < (3) ? ((4) < (2) ? (4) : (2)) : (3)";

    return 0;
}

可以看到s1使用TOSTR2作展開,因為TOSTR2使用了#運算子,所以只把代入的參數轉為字串,此例中為min(min(4,2), 3)。如果還要將min(min(4,2), 3)作最終的完全展開內容轉為字串的話,則需要再加一層巨集,使用TOSTR。TOSTR將min(min(4,2), 3)代入後,由preprocessor展開後再當作參數再代TOSTR2,這時就能得到完全展開的字串結果。

要轉換成unicode字串則使用xxxW版本的巨集作展開。

2012年7月14日 星期六

當機除錯法

小朋友:...
老王:...
小朋友:...
老王:兄弟怎麼了?
小朋友:老大,最近我在作的XXX在YYY的時候一直會ZZZ,不知道怎麼搞的,我已經看Code看了幾天了,一直找不到問題。
老王:為什麼不動手去找Bug呢?看Code太花時間了,也太難看出問題來。
小朋友:我也想,但實在是沒辨法啊... T_T
老王:不行Trace?
小朋友:不行... T_T
老王:不行丟訊息?
小朋友:不行... T_T
老王:Draw Something也不行?
小朋友:T_T
老王:丟Port也不行?
小朋友:嗚嗚嗚... T_T
老王:讓它叫一叫也不行?
小朋友:嗚嗚嗚... T_T
老王:....
小朋友:老大,還有什麼招式可以教教我嗎?
老王:好吧,這個時候就只好用我最後的絕招了!這是從我最尊敬的林老師那裡所繼承得到的絕招。據我所知,這個絕招原本只有三個人懂,一個是林老師,一個是我,另一個已經不在了,現在我傳授給你。
小朋友:(肅然起敬)
老王:聽好了!我最尊敬的林老師告訴過我說:你要當!就讓它當!
小朋友:...??
老王:好了,以上就是禁斷的絕技當機除錯法的要訣。現在跟著我再複頌一遍。
老王 and 小朋友:你要當!就讓它當!


老王:這是在任何方法都無效的情況之下,最後的Debug手段了,它的另一個別名叫作人工Debugger。這是因為我們要用人工的方法,一步一步的讓我們的程式在我們自己指定的地點造成當機,用這個手段來檢驗追踪程式執行的流程。這是個萬不得已的笨方法,很慢很花時間,但也只能用這個辨法了。
小朋友:我大概有點懂了。
老王:想辨法去找到可疑的問題點,再配合當機法慢慢夾擠,找到問題所在。總之,這個辨法的應用也還要再根據遭遇到的問題,配合應用。我最尊敬的林老師曾經告戒過我說:找Bug,無論如何就是要不擇手段。你懂了嗎?
小朋友:嗯嗯,Yes Sir!我再去試試看。

(三個星期後....)

小朋友:老大!老大!哈哈哈,這下子終於讓我找到問題了!!!
老王:嗯,很好。怎麼找到的啊?
小朋友:我把電腦重灌,再重灌一次VC就可以Trace了。 ^_^
老王:......

2012年6月30日 星期六

C++ INI 類別的設計

使用INI格式,底下有幾個優點。
  1. 格式簡單易懂 、容易使用
  2. 編輯器隨處可得,編輯維護相對容易
  3. 實作一個INI Parser不難
在程式裡,無論是作為程式設定儲存格式,或是作為小型的資料庫格式,使用INI資料格式對於以上需求,綽綽有餘。Good Game Editor的資料格式就是現成的實例。

;

INI的格式內容非常結構化,一個INI是由Section組成,每一個Section裡面可以包含Item,每一個Item是由一對Key和Value組成。

  • 一個Section由字元'['及']'所構成,括號所包圍起來的字串為Section的名稱。如[Player]。
  • Item由Key及Value成對以字元'='隔開組成。如Name=Waync Cheng。
  • 所有在字元';'後面到行尾的文字都視為註解而被忽略。

底下是一個簡單的範例。
[Player]
Name=Waync Cheng
Windows提供了GetPrivateProfileString系列的API可以讀寫INI的資料內容,使用Windows API寫入INI檔案的方式如下。
char StrName[] = "Waync Cheng" WritePrivateProfileString("Player","Name",StrName,"c:\\test.ini");
從INI檔案讀出的方法如下。
char StrName[MAX_LEN];
GetPrivateProfileString("Player","Name","DefaultName", StrName, MAX_LEN, "c:\\test.ini");
;

Windows API使用起來稍微不方便,同時也不具跨平台,底下使用C++語言設計一個新的INI讀寫類別,主要的設計考量是更方便的讀寫INI。

C++語言提供了operator overloading的機制,正好可以被我們利用來作為一個介面,讓我們可以更直覺的讀寫INI的內容。

設sec是個C++的INI Section Object,我們可以下面的方法對它作操作。
sec["Item1"] = 12345;
sec["Item2"] = "string";
sec["item3"] = 123.456;

int item1 = sec["Item1"];
string item2 = sec["Item2"];
double item3 = sec["Item3"];
這是充分利用了C++ operator overloading的好處,讓我們程式的寫作更直覺明瞭。同樣的設ini是一個INI的檔案物件。底下的示範,結合上面對Section和Item的操作。
ini["Sec"]["Item1"] = 12345;
ini["Sec"]["Item2"] = "string";
ini["Sec"]["item3"] = 123.456;

int item1 = ini["Sec"]["Item1"];
string item2 = ini["Sec"]["Item2"];
double item3 = ini["Sec"]["Item3"];
以上的示範是對於INI裡Section的Item的讀寫操作,除此之外還可以定義增減Section及Item的操作,對Stream讀寫INI內容等等。這邊就到此為此,不再繼續多說。以上的概念知道後,可以很容易的設計及實作出一個簡單又好用的INI類別。有興趣的人可以動手試試。

2012年5月3日 星期四

Good Game Player for PSP

使用DevkitPSP編譯最新的程式
修了幾個小問題
先只port一種圖形格式
現在可以顯示png貼圖

下面是用我爛爛的野火機很克難的錄下來的一小段影片
品質不好請多見諒..


沒錯
背景那個鬼鬼祟祟的人就是我

2012年5月1日 星期二

永恆的OLG


十年前
幾個伙伴以不到一年的時間
從無到有熱情奔放的作出了這個永恆online


2012年4月18日 星期三

新增貪食蛇範例

花了幾小時編寫了一個經典的貪吃蛇小遊戲,程式長度約180行。 (下載)


(下載)

2012年4月7日 星期六

TextBox如何顯示行號

用C#來開發工具有很高的生產力,不過有些功能還是需要自己實作或是要找其它資源作整合。例如TextBox元件就沒有像許多文字編輯器都有的顯示文件行號的功能,這個功能需要自己想辨法。要作到顯示行號,有幾種方法,而下面要介紹的是一個較簡單的實現方法。

基本概念是這樣:

  1. 在TextBox左邊加一個Panel元件,在Panel上根據目前TextBox的位置顯示行號在其上
  2. 每當TextBox內容變更(OnTextChanged)或垂直捲軸發生滾動(OnVScroll)時重畫Panel

想法很簡單,實作也同樣不難。

在Form放上一個Panel元件,屬性設置如下:

  • (Name) = panel1
  • BorderStyle = None
  • Dock = Left

接著放上一個RichTextBox元件,,屬性設置如下:

  • (Name) = textBox1
  • BorderStyle = None
  • Dock = Fill
  • WordWrap = False

完成後如下圖所示。


接下來是加上事件的處理。

在TextBox上加上TextChanged和VScroll二個事件處理函式。

private void textBox1_TextChanged(object sender, EventArgs e)
{
  panel1.Invalidate();
}

private void textBox1_VScroll(object sender, EventArgs e)
{
  panel1.Invalidate();
}

當文字內容變更或發生捲動時,重畫在Panel上的行號,反應目前的狀態。

接下來加上Panel的Paint事件處理,在這裡面把行號顯示出來。這部份的處理主要分幾個部份。

  1. 計算可見範圍的行號,這樣作是避免作多虛功,只畫需要畫的部份
  2. 建立一個Off Screen Graphics,先畫在這個緩衝區上,然後再一次貼上畫面,這樣作是為了避免畫面更新如果不夠快的話會造成閃爍

底下是部份程式碼。

private void panel1_Paint(object sender, PaintEventArgs e)
{
  //
  // Calc number range.
  //

  int FirstLineIndex = textBox1.GetCharIndexFromPosition(Point.Empty);
  int FirstLine = textBox1.GetLineFromCharIndex(FirstLineIndex);
  int LastLineIndex = textBox1.GetCharIndexFromPosition(new Point(textBox1.Width, textBox1.Height));
  int LastLine = textBox1.GetLineFromCharIndex(LastLineIndex);
  int w = Math.Max(FirstLine.ToString().Length, LastLine.ToString().Length);

  //
  // Create off-screen image.
  //

  Bitmap img = new Bitmap(panel1.Width, panel1.Height);
  Graphics g = Graphics.FromImage(img); // Off-screen image.

  ...

  //
  // Draw line numbers.
  //

  for (int i = FirstLine; i <= LastLine; i++, Pos.Y += H)
  {
    ...
  }

  //
  // Blit.
  //

  e.Graphics.DrawImage(img, 0, 0);
}

下圖是程式結果。


(下載範例)

2012年3月30日 星期五

jaja vm

作了幾個重要的修改:

  1. 對heap和stack相關的幾條指令作了正規化,更符合stack machine的設計。
  2. 新增了一條指令discardx,function return時在沒有使用配製local時可以更簡單的free stack空間。
  3. 修正了heap指令的一個大問題,這樣才有辨法正確實作出string模組的功能,如strcpy等。
  4. 原本的sample作了修正外,新增了string和stdio模組,實作了幾個簡單的function。
  5. 實作簡易stdio.printf時,debugger新增自動dump stack狀態,這樣一來debug就更容易了。此外,在debug過程中也發現了幾個debugger的bug,順便一起修正。
  6. assembly parser也作了一些小修正,現在字串可以使用像是\n的escape字元,ID token也支援原先漏掉的底線_。
  7. jaja vm on java vm順便作了porting,不過還有點小bug,大致上執行起來沒問題。



有二點考慮改進的地方:

  1. function return時對於stack裡需要free的空間的處理。
  2. macro的支援。
這二點可有可無,可以跳過。接下來是支援high level language compiler,目前考慮的語言是c,但也可能是lua。將以高階語言寫成的程式編譯成jaja指令集,然後在jaja vm上執行。如果使用lua,目標是將good作出來的程式轉換成可以在jaja機器上直接執行。如果是這樣,指令集需要重新編碼,這部份需要再研究。


2012年3月2日 星期五

C/C++ sizeof的陷井

猜猜看下面的程式輸出結果如何?

int a = 0;
cout << sizeof(a = 10) << endl;
cout << a << endl;

答案是4及0,答錯了吧。

其實很簡單,假如你知道sizeof是在編譯時期(compile time)就決定值的話,就可以知道正確答案是多少。

;

再來看看下面這個程式輸出是什麼。

struct A {
};

printf("%d\n", sizeof(struct A));

答案可能是0,也可能是1。怎麼說?

如果程式是C語言編譯器編譯,那麼輸出會是0。如果程式是C++編譯器編譯,那麼輸出是1。

Standard C++ language definition定義如下:
A class with an empty sequence of members and base class objects is an empty class. Complete objects and member subobjects of an empty class type shall have nonzero size.

根據C++的定義,一個空類別的大小不能為0,至少會是1。這是C和C++定義不同的地方。

2012年2月28日 星期二

粒子程式編輯器及動作編輯器小修正

> 粒子程式編輯器(StgeScriptEditor)在重新編譯script前,詢問是否存檔。


> 動作編輯器(SpriteEditor)的選取項目框改為紅色。

2012年2月22日 星期三

太空戰士13-2時鐘任務輔助器

利用空閒時間作了一個太空戰士13-2時鐘任務輔助器,可以用來產生時鐘任務的解答。連結



使用javascript實作,當作一個練習題。解法很簡單,很暴力。簡單講,就是硬幹。用亂數的方法,亂到答案出來為止。不過因為怕會有亂不出答案的情況發生,所以實際上最多給它亂個1萬次就停止,因為在亂填資料時,可能會出現無解。

得到答案之後,再簡單的用圖解的方式把解答顯示出來,幫助使用。

Hello World! Hello Jaja!

export hello
export msg

jump hello

msg:
db "Hello Hello!", '\n', 0

hello:
push msg
call system.outstr
ret

這是使用jaja指令集實作的一個HelloWorld程式,jaja指令集乍看下和x86指令集很相像,但其實並不相同。jaja是一個軟體虛擬機器,是一個Stack Machine類型的虛擬機器,也就是指令操作的對象都是置於堆疊中。

原始碼檔組譯過後,如果沒有任何錯誤發生則會輸出一個mod檔案。每一個原始碼檔,可以組譯成一個對應的mod檔。透過dump工具顯示組譯過後的mod檔內容,如下。

module 'test/hello.mod', version: 0.01
...names table
0 hello
1 msg
2 system
3 outstr
...exports table
0 hello 16
1 msg 3
...imports table
0 system.outstr
...code section
code size: 23 byte(s)

可以觀察到,mod檔是section導向的。可以看到import/export表,這和系統呼叫及symbol匯出相關。import/export表都是以字串型式儲存,所以還可看到一個名稱字串表。最後是一個程式碼區段,每一個程式碼區段的大小最大為64k。


使用dbg工具,可以以單步的方式去追踪程式的執行,以及作基本的除錯。在命令提示符號下輸入g指令直接執行程式,觀看結果。畫面輸出

Hello World!

在Hello程式裡面,export了二個symbol,hello及msg。hello指向一個函式的開始,而msg指向一串資料的開始。下面的範例import中,使用了這二個在hello程式裡export的symbol。

call hello.hello

push hello.msg
call system.outstr

ret

如同要輸出字串時,使用system模組export的outstr呼叫,要使用hello模組export的hello或msg,則使用hello.hello或hello.msg。import模組dump結果如下。

module 'test/import.mod', version: 0.01
...names table
0 hello
1 msg
2 system
3 outstr
...imports table
0 hello.hello
1 hello.msg
2 system.outstr
...code section
code size: 10 byte(s)

2012年2月3日 星期五

Good Game Editor 1.3 Beta

* 新增TGA圖形格式。
* 關卡編輯器(LevelEditor)加大物件選取框。
* 修正程式碼編輯器(SciprtEditor)鍵盤焦點(Focus)問題。
* 範例StgeTest1更名為stge1。
* 移除貼圖的KeyColor屬性。
* 移除View選單裡的Toolbar及Status Bar選項。
* 新增範例fire(火焰模擬)。
* 新增Resource.GetTextureSize API。
* 新增Graphics.DrawImage及Graphics.FillRect API。
* 新增粒子測試器(STGE particle script tester)。
* 新增OnDraw事件(OwnerDraw)。
* 新增範例stge2(粒子效果)。
* 預設ShowFps開啟。
* 新增MRU(Most Recently Used)。
* 修正一個拼字錯誤(You are using latest version)。
* 拿掉新增貼圖對話盒(Add new texture dialog),簡化成開啟檔案對話盒(Open file dialog)作檔案選取。
* 修正地圖編輯器(MapEditor)輔助線編輯器的顏色選擇鈕沒有正確重畫的問題。
* 新增範例savegame(記錄檔讀寫)。
* 修正遊戲視窗改變大小時滑鼠座標產生的誤差。
* 新增範例mouse(滑鼠狀態)。
* 地圖編輯器(MapEditor)橡皮擦工具也能以右鍵選取區塊範圍。
* 修正地圖編輯器(MapEditor)編輯狀態錯誤的問題。

下載
Related Posts Plugin for WordPress, Blogger...