2018年2月23日 星期五

動態控制 ContextMenuStrip 不出現

通常在按下滑鼠右鍵要出現選單時,都會用到 ContextMenuStrip 這個控制項,使用方式如下:
1.從工具箱點兩下 ContextMenuStrip,產生 contextMenuStrip1
2.點擊該 contextMenuStrip1,可以編輯清單內容
3.選擇 contextMenuStrip1 要出現在哪個控制項,在該控制項的 ContextMenuStrip 屬性上選擇 contextMenuStrip1 

但是當要動態控制 contextMenuStrip1 要不要出現時,
無法寫成
// 假設 contextMenuStrip1 要出現在 treeView1 的右鍵時
treeView1.ContextMenuStrip = "";
只能在 contextMenuStrip1.Opening 事件上動手腳
boolean bContextMenuStripVisible = false; // 指定是否顯示 ContextMenuStrip
private void contextMenuStrip1_Opening(object sender, CancelEventArgs e)
{
    if (bContextMenuStripVisible == false)
    {               
          e.Cancel = true;
    }
}

2018年2月22日 星期四

TableLayoutPanel 內的控制項的 Dock 屬性無法完全作用

TableLayoutPanel 是可以讓控制項排列整理的一個控制項(有點繞舌) 因專案需要,在 TableLayoutPanel 內塞了幾種控制項(Label、TextBox、ListBox)

然後把TableLayoutPanel 內的控制項的 Dock 屬性設定為 Fill,塞滿格子

有沒有發現
TextBox 的 Dock 設為 Fill 後,沒完全 Work?
原來是因為 TextBox 的 MultiLine 是 False 的關係,因為只能有一行字,當然無法撐開控制項的高度,所以修改的方式有兩種,視需求選擇使用
1. 改變字型大小
2. 把 MultiLine 設為 True

ListBox 的 Dock 設為 Fill 後,沒完全 Work?
原來 ListBox 有一個屬性 IntegralHeight(表示清單是否可以只包含完整的項目),意思應該是指 ListBox 的高度不足以顯示完整的 Item 時,只會展開到完整顯示的 Item 的高度,所以只要將 IntegralHeight 設為 False,就可以達到 Dock=Fill 的完整效果了

如果要將控制項完全貼合 TableLayoutPanel,則調整內部的各控制項的 Margin 屬性即可

2018年2月9日 星期五

NChar 與 Char 的差別

簡言之

NChar 是以位元組(2個位元)的方式儲存資料(NVarChar 亦同)
Char 是以位元的方式儲存資料(VarChar 亦同)

說明:
位元組:可以儲存半型字 或 全型字
位元:僅可以儲存半型字

有點雙人床與單人床的概念
雙人床:可以睡 1 個人或是 2 個人
單人床:僅可以睡 1 個人

所以當你要存的資料
只包含 英文、數字,即半型字,可以用 Char 即可
只包含 中文,即全型字,也可以用 Char 但記得長度要開 2 倍,或是直接用 NChar,長度就不用開到 2 倍

舉例:
以下語法會出現 字串或二進位資料會被截斷 的訊息
因為資料"五"是一個全型字,而欄位Char(1)僅能存一個半型字
declare @MyTable Table (ColName Char(1))
insert into @MyTable Values ('五')
改用 NChar
-- 改用 NChar
declare @MyTable Table (ColName NChar(1))
insert into @MyTable Values ('五')
或是改成 2 倍長度
--改成 2 倍長度 Char(2)
declare @MyTable Table (ColName Char(2))
insert into @MyTable Values ('五')
就OK了

若是 中文、英文、數字夾雜,那就看個人喜愛了



另外提供兩個SQL函數(Len、DataLength),可以幫助釐清上面的差別
將語法改成如下:
將欄位長度改成 3
-- 在 Char 的情況下
declare @MyTable Table (ColName Char(3))
insert into @MyTable Values ('五')
select *, Len(ColName), DATALENGTH(ColName) from @MyTable
-- 結果:
-- Len(ColName) = 1,即資料的字元數
-- DATALENGTH(ColName) = 3,即欄位的位元數
-- 在 NChar 的情況下
declare @MyTable Table (ColName NChar(3))
insert into @MyTable Values ('五')
select *, Len(ColName), DATALENGTH(ColName) from @MyTable
-- 結果:
-- Len(ColName) = 1,即資料的字元數
-- DATALENGTH(ColName) = 6,即欄位的位元數

再來,將資料內容修改一下(儲存一個全型字和一個半型字)
-- 在 Char 的情況下
declare @MyTable Table (ColName Char(3))
insert into @MyTable Values ('五5')
select *, Len(ColName), DATALENGTH(ColName) from @MyTable
-- 結果:
-- Len(ColName) = 2,即資料的字元數
-- DATALENGTH(ColName) = 3,即欄位的位元數
-- 在 NChar 的情況下
declare @MyTable Table (ColName NChar(3))
insert into @MyTable Values ('五5')
select *, Len(ColName), DATALENGTH(ColName) from @MyTable
-- 結果:
-- Len(ColName) = 2,即資料的字元數
-- DATALENGTH(ColName) = 6,即欄位的位元數

2018年1月19日 星期五

還原TreeView 的展開狀態

例:
 
如果在 A 下的 C 下加入 D,如果只是在 TreeView 上改變顯示,那麼要遍歷整個 Tree,找出所有的 C (B 下有一個 C)之後,改變畫面,雖然可以這樣做,但程式碼上似乎複雜了點
所以改變思路,在 C 下加入 D 之後,重新綁定 TreeView 的資料。

但是重新綁定 TreeView 之後,TreeView 會收合到剩下第一層,使用者就要重新展開之後,再往下加入其他子項目,這點其實很麻煩。

所以在重新綁定前先記錄下目前 TreeView 的展開狀態,綁定之後再還原展開狀態,程式碼加下

設定兩個全域變數
private Dictionary<string, bool> NodesStatus = new Dictionary<string, bool>();  // 記錄展開狀態
private string SelectNodeFullPath = ""; // 記錄選擇到的節點

記錄展開狀態
/// <summary>
/// 記錄展開狀態
/// </summary>
/// <param name="nodes"></param>
private void GetTreeNodesStatus(TreeNodeCollection nodes)
{
 foreach (TreeNode node in nodes)
 {
  if (node.IsExpanded)
  {
   NodesStatus[node.FullPath] = true;
  }
  else
  {
   NodesStatus.Remove(node.FullPath);
  }

  if (node.IsSelected)
  {
   SelectNodeFullPath = node.FullPath;
  }
  GetTreeNodesStatus(node.Nodes);
 }
}

還原展開狀態
/// <summary>
/// 還原展開狀態
/// </summary>
/// <param name="nodes"></param>
private void SetTreeNodesStatus(TreeNodeCollection nodes)
{
 foreach (TreeNode node in nodes)
 {
  if (NodesStatus.ContainsKey(node.FullPath))
  {
   node.Expand();
  }

  if (node.FullPath == SelectNodeFullPath)
  {
   treeView1.SelectedNode = node;
  }

  SetTreeNodesStatus(node.Nodes);
 }
}

使用方式
// 對treeView1做了某些操作之後(例如加入子選項)
GetTreeNodesStatus(treeView1.Nodes);
BindTreeViewData(); // 綁定TreeView
SetTreeNodesStatus(treeView1.Nodes);

2018年1月17日 星期三

C# Window Form - 動態指定Form尺寸

如果想要動態控制 Form 的尺寸,做法會先指定以某個控制項(或原點)做為基準,指定控制項的位置後,確定最右下角的那個控制項之後,才能決定 Form 的尺寸。

比方右下角有一個 Button(button1),指定的 Form 的語法會是
this.Height = button1.Top + button1.Height;
this.Width = button1.Left + button1.Width;
但這樣加完的結果是看不到,因為 Form 的最上方有一條標題列,要再加上這標題列的高度才行,所以改成
this.Height = button1.Top + button1.Height + iControlSpacer + (this.Height-this.ClientSize.Height);
this.Width = button1.Left + button1.Width + iControlSpacer + (this.Width - this.ClientSize.Width);
其中 this.Height-this.ClientSize.Height 是標題列高度的計算方式,
iControlSpacer  是控制項之間的間隔

2017年9月30日 星期六

Google Cloud Platform 探索經驗 (5) - 將 Google Storage 中的檔案當網站

雖然 Google 有提供部落格 Blogger 可以使用,網路上也有教學將個人部落格改成公司網站使用,但畢竟是部落格的使用方式,在設計上難免會困難度較大。

所以 Google Cloud Platform 有另一個工具 - Storage 可以將寫好的網頁或檔案放在裡面(個人覺得類似於 Google 文件),可以公開連結給需要的人,就變成像是網站的感覺。

接下來就將個人的經驗寫下來供參考

【網站檔案設定】
步驟一:當然要先有 Google Cloud Platform (以下簡稱 GCP)的權限
步驟二:到 GCP 的控制台,展開左邊 ≡ 符號,然後點選 Storage

步驟三:建立 BUKET (此 BUKET 的名稱要和你的網域名稱相同)
步驟四:點選剛才建立 BUKET ,然後將 首頁錯誤頁 的網頁上傳

步驟五:在 BUKET 清單頁找到剛才建立的 BUKET 右邊會有一個 (三個黑點)的符號,點下去之後選擇 "編輯網站設定",將首頁和錯誤頁設好

步驟六:此時點選 BUKET 下的 index.html 的 公開連結,就可以看到網站的首頁了,但是此時網址還是你 Google Storage 的樣子,所以接下來要到你的網域申請的網站上設定DNS對應到的網址。


【網域申請與設定】-這邊是以免費網域申請(https://my.freenom.com/)為範例

步驟一:進入官網後,找到 SERVICE 下的 Register a New Domain,將想要的網域名稱打進去之後,按下 Check Availability,確認看看是否可用和所需費用,選擇清單中想要的那一個,按下 Get it now,然後按下上面的 checkout 後,選擇要用的區間(既然有 12 個月免費可以選,哪有不選的道理),選擇之後按下 Continue,在左下角填入你的 Email Address(這會用來確認並啟用),並按下 Verify My Email Address,然後到你的信箱去點確認連結。

步驟二:點下確認信中的連結後,會要你填寫一些資料(好像全部都是必填),然後勾選I have read and agree to the Terms & Conditions,然後按下 Complete Order,完成申請。

步驟三:完成申請後,要將 Google Storage 和 DNS 做連結,所以找到官網上的 Services 下的 My Domains,選擇剛才申請的 Domain 的 Manage Domain,再點選 Manage Freenom DNS 來設定關聯。


步驟四:先跳到 Google 的 Cloud Storage 文件(Hosting a Static Website)來看一下如何做確認,這邊說明要到你的DNS申請的地方做的設定,但設定前需要先做一下確認,所以先跳到Google 網站網理員中心,網頁告訴你驗證的方法。


步驟五:Google 網站網理員中心 ,按下新增資源,輸入要驗證的網域後按下繼續
,選擇 其他方法 > 網域名稱供應商 > 其他,選擇下方的 新增 CNAME 紀錄
這是要填入步驟三中的設定,供確認用

步驟六:我們將【網域申請與設定】步驟一申請的那個網域填入上列相對資訊,按下 Save Change
然後再回到步驟五的地方按下下方的"驗證",(驗證可能不會馬上生效,需要幾分鐘時間後,才會生效),如果驗證過了,表示你是該網域的擁有者。就會在你的資源清單中看到剛才的資源了


步驟七:開始瀏覽器,在網址列打上你設定的網域,就可以看到和 Google Storage 中一樣的網頁內容了 (如同【網站檔案設定】中的步驟六)


2017年7月25日 星期二

GridView 自訂分頁

一般 GridView 可以交給自動分頁產生出分頁效果,只需要將 GridView 的屬性 AllowPaging 設為 true,再搭配取得資料的語法,即可完成。
GridView 會依據資料筆數 和 GridView.PageCount 自動計算出頁數。
protected void Page_Load(object sender, EventArgs e)
{
    if (!IsPostBack)
    {
        GridView1.DataSource = GetData();
        GridView1.DataBind();
    }
}

private DataTable GetData()
{
    // 讀取資料
}

// 翻頁時改變 PageIndex 後重新綁定 GridView 的資料
protected void GridView1_PageIndexChanging(object sender, GridViewPageEventArgs e)
{
    GridView gridview = (GridView)sender;
    GridView.PageIndex = e.NewPageIndex;
    GridView.DataSource = GetData();
    GridView.DataBind();
}
因為GridView其實是將全部的資料都載入後,只顯示某一頁的資訊出來,所以當讀取的資料太多時就會造成載入的間很久。


此時可以使用自訂分頁的方式,自訂分頁除了要將 GridView 的屬性 AllowPaging 設為 true 之外,還要將 GridView 的屬性 AllowCustomPaging 設為 true,然後再在程式碼中指定 GridView.VirtualItemCount (虛擬 GridView 總共有幾筆資料),然後 GridView 會依據 VirtualItemCount 和 GridView.PageCount 自動計算出頁數,具體做法大致如下:
protected void Page_Load(object sender, EventArgs e)
{
    if (!IsPostBack)
    {
        // GridView.VirtualItemCount:虛擬 GridView 總共有幾筆資料
        GridView1.VirtualItemCount = GetAllDataCount();
        GridView1.DataSource = GetSpecPageData(0);
        GridView1.DataBind();
    }
}
private int GetAllDataCount()
{
    // 讀取全部資料的筆數
}

/// 只讀取指定頁的資料
private DataTable GetSpecPageData(int PageIndex)
{
    // 比方每頁要顯示 15 筆
    // 讀取第一頁要顯示的資料,那就只讀取 TOP 15
    // 或是若要讀取第三頁 31 到第 45 筆,則可以用 OFFSET 30 ROWS FETCH NEXT 15 ROWS ONLY
    // 意思是是位移 30 筆之後,再往下取 15 筆資料
   請參考MIS2000 Lab
}

翻頁時改變 PageIndex 後重新綁定 GridView 的資料
protected void GridView1_PageIndexChanging(object sender, GridViewPageEventArgs e)
{
    GridView gridview = (GridView)sender;
    GridView.PageIndex = e.NewPageIndex;
    GridView.DataSource = GetSpecPageData(e.NewPageIndex);
    GridView.DataBind();
}
此時遇到的題目是 GridView 的每頁筆數不一致(比方某個欄位要固定只顯示幾種資料),所以除了上述的寫法之外,還要加上一點點修正,如下:

加上一個全域變數,用以儲存每頁的筆數資訊
Dictionary<int, int> dictCountPerPage;
protected void Page_Load(object sender, EventArgs e)
{
    if (!IsPostBack)
    {
        dictPerPageCount = GetPerPageCount();
        GridView1.PageCount = dictPerPageCount[0];  // 指定第一頁的筆數
        // GridView.VirtualItemCount:計算虛擬 GridView 總共有幾筆資料(因為每頁筆數不一,所以以要顯示的該頁的筆數當基準,用以計算出虛擬總筆數,好產生頁數(碼)         GridView1.VirtualItemCount = GridView1.PageCount * dictPerPageCount.Count;         GridView1.DataSource = GetSpecPageData(0);         GridView1.DataBind();     } } /// 依需求記錄每頁要顯示的資料筆數 /// Key: 頁碼, Value: 每頁到第幾筆資料 private Dictionary<int, int> GetPerPageCount() {     // 讀取全部每頁的資料的筆數     // 例如:     // Dictionary<int, int> dict = new Dictionary<int, int>();     // dict.Add(0, 10); // 第 1 頁: 0~10 筆     // dict.Add(1, 16); // 第 2 頁: 11~16 筆    // dict.Add(2, 18); // 第 3 頁: 17~18 筆     // dict.Add(3, 22); // 第 4 頁: 19~22 筆     // Session.Add("PerPageCount", dictPerPageCount); 將分頁資訊記錄在 Session     // return dict; }
/// 只讀取指定頁的資料 private DataTable GetSpecPageData(int PageIndex) {     // 比方每頁要顯示 15 筆     // 讀取第一頁要顯示的資料,那就只讀取 TOP 15     // 或是若要讀取第三頁 31 到第 45 筆,則可以用 OFFSET 30 ROWS FETCH NEXT 15 ROWS ONLY     // 意思是是位移 30 筆之後,再往下取 15 筆資料     請參考MIS2000 Lab     // SQL 語法:    StringBuilder SQL = new StringBuilder("...");     SQL.Append(" ORDER BY 1, 2, 3, 4");     // 取回第幾筆~第幾筆資料     if (CurrentPage == 0)         SQL.AppendFormat(" OFFSET {0} ROWS FETCH NEXT {1} ROWS ONLY", 0, dicRowCountPerPage[CurrentPage]);     else         SQL.AppendFormat(" OFFSET {0} ROWS FETCH NEXT {1} ROWS ONLY", dictCountPerPage[PageIndex- 1], dictCountPerPage[PageIndex] - dictCountPerPage[PageIndex- 1]);     // 每次都重新指定 PageCount 和 VirtualItemCount     if (PageIndex== 0)         GridView1.PageSize = dictCountPerPage[PageIndex];     else         GridView1.PageSize = dictCountPerPage[PageIndex] - dictCountPerPage[PageIndex - 1];     GridView1.VirtualItemCount = GridView1.PageSize * dictCountPerPage.Count; } /// 翻頁時改變 PageIndex 後重新綁定 GridView 的資料 protected void GridView1_PageIndexChanging(object sender, GridViewPageEventArgs e) {     GridView gridview = (GridView)sender;     GridView.PageIndex = e.NewPageIndex;     if (dictCountPerPage == null)         dictCountPerPage = (Dictionary<int, int>)Session["PerPageCount"];     GridView.DataSource = GetSpecPageData(e.NewPageIndex);     GridView.DataBind(); }