---
tags: 視窗程式設計
---
# 單位轉換程式:進一步修改
之前的實作我們已經把單位轉換的功能初步實作出來,但它仍有一些功能沒有做出來,而且也有一些小問題,這邊我們再帶領同學一一完成。
## 清除按鍵功能
如果你已經很清楚所謂「事件綁定」的觀念,你大概就會知道清除按鍵怎麼實作。我們選擇使用「Click」事件,你可以在「屬性視窗」設定「Click」事件綁定,如下圖:

或者,直接在按鍵上面「連點兩下」也可以,就會直接到以下的程式碼片段。

如果你還記得在輸入文字框顯示結果的語法,其實只要給予每一個輸入文字框一個「空字串」,就可以直接將輸入文字框的內容清楚。請參考以下語法,就能達到清除所有輸入文字框的效果。
```csharp=
private void btnAllClear_Click(object sender, EventArgs e)
{
// 給予每一個輸入文字框一個「空字串」
txtCM.Text = "";
txtM.Text = "";
txtKM.Text = "";
txtIn.Text = "";
txtFt.Text = "";
txtYard.Text = "";
}
```
## 檢討目前寫的程式
如果同學你有好好完成上一節的「課堂作業 2-1:請將其他單位轉換的功能做出來」,你是怎麼完成的呢?老師會猜大概是這樣的作法:
:::success
1. 一一對所有的輸入文字框綁定「KeyUp」事件
2. 然後依照參考程式,複製到每一個輸入文字框的事件綁定程式中
3. 依照不同單位的轉換,修改複製過去的程式
:::
同學可以來看看[參考答案](https://hackmd.io/gDNqhQODQ_qiAtcXVe27mw?view#%E8%AA%B2%E5%A0%82%E4%BD%9C%E6%A5%AD-2-1%EF%BC%9A%E5%8F%83%E8%80%83%E7%AD%94%E6%A1%88),這是可行的方法,但是你可以仔細觀察,這樣寫程式有一些問題...
1. 你需要重複貼上好多程式碼
2. 因為每一個單位轉換都需要仔細的改程式,如果要測試,很麻煩
3. 如果有錯,還要把所有程式都找過一次,很痛苦
4. 還是有一個很討厭的問題,就是如果輸入文字,或者按倒退鍵容易出現程式錯誤
:::info
接下來,就要幫助同學一一改善程式,並且讓同學複習或強化之前所學的程式設計基礎觀念。
:::
## 避免錯誤
我們先解決第一個麻煩的問題,就是輸入文字框如果輸入文字或者刪除到空白,就會發生程式錯誤。先來看看原本程式是錯在哪邊?我們來看看之前寫好的公分輸入文字框綁定事件的程式片段。
```csharp=
private void txtCM_KeyUp(object sender, EventArgs e)
{
double douCM; //宣告一個double變數,變數名稱叫douCM
douCM = Convert.ToDouble(txtCM.Text); //從txtCM輸入文字框取得輸入的文字,並且轉換成double的資料型態
//透過string.Format格式化成小數點後共10位的數字,轉型成文字型態,在txtM顯示結果
txtM.Text = string.Format("{0:0.##########}", douCM / 100);
txtKM.Text = string.Format("{0:0.##########}", douCM / 100000);
txtIn.Text = string.Format("{0:0.##########}", douCM / 2.54);
txtFt.Text = string.Format("{0:0.##########}", douCM / 30.48);
txtYard.Text = string.Format("{0:0.##########}", douCM / 91.44);
}
```
你可以先試著測試程式,然後故意輸入文字資料在公分的輸入文字框中,就會出現類似以下的錯誤訊息:

[放大](https://i.imgur.com/23hMkmE.png)
所以你可以發現到,錯誤在哪裡?是錯在這裡:
```csharp=
double douCM;
douCM = Convert.ToDouble(txtCM.Text); //這裡有問題
```
:::success
* 第1行:建立一個double浮點數型態的douCM變數
* 第2行:使用Convert.ToDouble,將txtCM.Text的文字,轉換成double浮點數型態,並且指定給douCM變數
:::
第2行的部分就是所謂的「資料型態轉換(轉型)」,將原來是文字的內容,轉換成double浮點數,例如你輸入「50.10」,雖然看起來是數字,但在輸入文字框中仍然是「字串」,必須經過「轉型」,才能變成「浮點數」,也才能進一步做數值計算。
可是如果你輸入的本來就是「文字」,例如:abc、程式設計,這類的文字,Convert.ToDouble是沒有辦法轉換的,所以在這裡發生程式錯誤。
:::warning
很多程式的錯誤多半是轉型問題所造成
:::
## 解決方法:使用TryParse來做轉型
我們先使用「**TryParse函式**」試著把「**文字**」轉換「**double浮點數**」。
如果你忘記了轉型的概念,可以參考[這裡](https://hackmd.io/gDNqhQODQ_qiAtcXVe27mw?view#%E8%B3%87%E6%96%99%E5%9E%8B%E6%85%8B%E8%BD%89%E6%8F%9B%EF%BC%88%E8%BD%89%E5%9E%8B%EF%BC%89)。
因此「**TryParse函式**」也是做轉型的工作,不過Convert不同,**它會先「試著轉換」**,視成功與否回傳true或false,如果試著轉換成double是成功的,回傳true,不成功,就會傳false。
如果成功轉換,還會將轉換後的值另外存到「輸出變數」中,再讓之後的程式處理。
使用TryParse這個函式就像下面的語法,你需要輸入一個「文字」變數,然後他會丟出一個布林變數告知你是否有成功轉換,如果有成功轉換,會將轉換後的數字儲存在「輸出變數」裡面。
:::info
型別.TryParse(字串或字串變數, out 輸出變數)
:::
範例可以參考以下:
```csharp
// 如果你要將字串轉成int整數,就會像這樣寫
int.TryParse(strInput, out intOutput)
// 如果你要將字串轉成double浮點數,就會像這樣寫
double.TryParse(strInput, out douOutput)
```
## 流程控制:判斷式-讓程式選擇與決定
你可能之前就學過甚麼是判斷式,這邊我們複習一下。為了應付程式可能遇到的各種狀況,C#提供了「if條件判斷」陳述,它的語法如下:
```csharp=
if (條件式) {
陳述句一;
陳述句二;
}
else {
陳述句三;
陳述句四;
}
```
這段程式語法的意思我們可以改成白話文:
1. **當條件式成立時(真,true)**,則執行陳述句一、二。
2. **如果條件不成立(假,false)**,就執行陳述句三、四,我們也可以不要寫包括else以下的程式碼,亦即如果條件式不成立則整段跳過。
if或else中只有一個陳述句的時候,可以省略掉大括號「{ }」,像是以下的寫法,也是可以成立的,不過建議同學先加上去,這有助於可讀性,也有助於避免縮排上造成的程式碼閱讀誤解。
```csharp=
if (條件式)
陳述句一;
```
:::info
使用判斷式,可以讓程式依照不同條件,執行不同程式,達到不同的結果。
:::
## 利用TryParse做型別轉換,再用判斷式來決定程式走向
結合TryParse與判斷式,所以我們可以設計一個簡單流程如下 ...
``` mermaid
graph TD;
將txtCM文字框的值放入strInput變數-->strInput變數丟進double.TryParse試著轉換成double型態;
strInput變數丟進double.TryParse試著轉換成double型態-->用判斷式來決定轉換成功或失敗的程式路徑;
用判斷式來決定轉換成功或失敗的程式路徑-->TryParse回傳true代表成功轉換;
用判斷式來決定轉換成功或失敗的程式路徑-->TryParse回傳false代表轉換失敗;
TryParse回傳true代表成功轉換-->將成功轉換的值存到douOutput變數;
將成功轉換的值存到douOutput變數-->douOutput變數依序做單位轉換的計算;
TryParse回傳false代表轉換失敗-->在說明文字中顯示錯誤訊息;
在說明文字中顯示錯誤訊息-->將txtCM文字框清除;
```
所以,我們可以將程式改為以下:
```csharp=
// 全域變數
string strInput; // 字串型態的strInput變數
double douOutput; // double浮點數型態的douOutput變數
private void txtCM_KeyUp(object sender, EventArgs e)
{
strInput = txtCM.Text; // 將txtCM文字框的值放入strInput變數
// 判斷式,如果能夠以double.TryParse成功轉型,那才做數值的計算
if (double.TryParse(strInput, out douOutput) == true)
{
txtM.Text = string.Format("{0:0.##########}", douOutput / 100);
txtKM.Text = string.Format("{0:0.##########}", douOutput / 100000);
txtIn.Text = string.Format("{0:0.##########}", douOutput / 2.54);
txtFt.Text = string.Format("{0:0.##########}", douOutput / 30.48);
txtYard.Text = string.Format("{0:0.##########}", douOutput / 91.44);
}
else
{
// 如果無法轉型,則是在說明文字中顯示錯誤訊息,並且將txtCM文字框清除
txtInfo.Text = "請輸入數字";
txtCM.Text = "";
}
}
```
你可以進一步將其他輸入文字框修改,讓其他輸入文字框不會因為輸入的問題產生程式錯誤。
## 全域變數與區域變數的觀念
如果同學你眼睛夠尖,會發現到一個奇怪的東西,什麼是全域變數呢?
```csharp
// 全域變數
string strInput; // 字串型態的strInput變數
double douOutput; // double浮點數型態的douOutput變數
```
:::info
* 全域變數:程式中所有區塊都可存取的變數,和它相對的則是區域變數。
* 區域變數:定義在一個程式區塊中的變數,這個變數只有在這個程式區塊中可以被存取。
:::
有點難理解沒關係,這時就要回到之前我們曾經給你看過的「基本C#視窗程式架構」,請看下圖。

還記得嗎?老師跟你解釋說,我們可以把程式大概分成好幾個區塊,以上圖為例,只要是同一個顏色的,都是同一個區塊。老師也說過,不同區塊可以再包在另外一個區塊裡面。
那你可以再觀察一下,我們程式範例中的全域變數是放在什麼地方?就是綠色區塊之中。你再仔細的觀察,全域變數strInput與douOutput,也會在淺藍區塊中被使用。

:::success
因此,全域變數是一個可以被大家共用的變數。
:::
那為什麼要用這個全域變數?以我們所設計的全域變數來說:
:::success
1. strInput:是字串型態的變數,用途在暫存使用者在任何一個輸入文字框中所填寫的文字資料
2. douOutput:是浮點數型態的變數,用途為儲存「經過轉型後」的數值
:::
因為我們之後所有的輸入文字框綁定事件,都需要取得使用者輸入的值,然後都要進入TryParse來轉換,**如果每一個輸入文字框都要特定宣告一個變數來存,真的太麻煩了,不如做一個共用的**,這樣就不需要一直重複宣告,直接將修改的值指定給這些全域變數就可以了!
因此,如果你有一個變數希望程式每一個地方都可以用,就需要將這個變數「提升到上一層區塊」。例如藍色區塊的變數如果希望別的藍色區塊也可以用,要「提升」到綠色區塊。如果又希望所有藍色與綠色區塊都可以用,那就要「提升」到紅色區塊!
## 結語
這個章節我們透過解決單位轉換程式的錯誤,讓同學進一步理解除錯的概念,再來則是轉型的應用與判斷式的複習。
並且藉由程式區塊來,說明了「全域變數」與「區域變數」的概念。
不過正確來說,全域變數與區域變數的定義應該如下更為精準。
:::info
* 全域變數:**在函數外部宣告的變數為全域變數**,和它相對的則是區域變數。
* 區域變數:**在函數內部宣告的變數為區域變數**,這個變數只有在這個函式中可以被存取。
:::
所以接下來的章節,我們要來說明甚麼是函式。