2016年1月15日 星期五

DataGridView 控制項(3)

如何在設定 DataSource 屬性時,不要觸發 SelectionChanged 事件

DataGridView 設定 DataSource 屬性時,會引發 SelectionChanged 事件,而且會有二次。 一次是原本的選取列失效了,另一次是 Binding 後,會自動選取第一列,所以總共會發生次 SelectionChanged 事件。 若要停止這個事件,可以在 Binding 前先移除事件的訂閱或者使用一個 flag 讓程式識別。

gv.SelectionChanged -= this.gv_SelectionChanged;
    gv.DataSource = list;
    gv.SelectionChanged += this.gv_SelectionChanged;

如何以程式選取指定的 Row

通常我們會點選 DataGridView 來操作變更選取資料列,如何想要透過程式碼來完成這個操作,可以參考以下做法。

highlight 選取列

想要 highlight 特定的資料列只要將該資料列的 Selected 設成 true 即可。

Grid.Rows[i].Selected = True

highlight 選取列,並且變更 CurrentRow 屬性

上面程式碼只會變更 highlight 的資料列,並不會變更 CurrentRow 屬性,除非你指定 CurrentCell 屬性。

Grid.CurrentCell = Grid.Rows[i].Cells[j];

所以完整模擬 DataGridView 取得 focus ,並選取一資料列,必須這麼做

  Grid.CurrentCell = Grid.Rows[i].Cells[j]Grid.Rows[i].Selected = True

不過底下這一點很重

通常我們藉由 UI 上 DataGridView 的操作,可以在 SelectionChanged 事件裡透過 DataGridView.CurrentRow 屬性取得游標所在的資料列。 但是,若是透過程式碼變更 CurrentCell 屬性來移動游標,雖然也會引發 SelectionChanged 事件,但是它是發生在 CurrentCellChanged 事件之前。 所以,若你在 SelectionChanged 事件中讀取 DataGridView.CurrentRow 屬性,取得的是未變更前的資料。

資料來源:https://msdn.microsoft.com/zh-tw/library/system.windows.forms.datagridview.currentcell(v=vs.100).aspx

如何不要有 Selection 產生的 highlight

當滑鼠點到 DataGridView 時,它就會產生一個 highlight ,可能是一整個列或者是一個儲存格。 有時為了畫面美觀,要如何不顯示這個 highlight 呢。最簡單的方法就是把 DataGridView 的 readonly 屬性設成 true ,不過這麼做使用者將無法執行 copy 等操作。 下面方法將 highlight 的背景色設定成 DataGridView 的背色是不錯的技巧,可以參考使用。

gv.DefaultCellStyle.SelectionBackColor = gv.DefaultCellStyle.BackColor;
gv.DefaultCellStyle.SelectionForeColor = gv.DefaultCellStyle.ForeColor;

DataGridView.DataSource 屬性

DataGridView.DataSource屬性,可用來取得或設定 DataGridView 的資料來源。它支援以下資料繫結模型:

只要實作這四種介面的實體,都可以當做 DataGridView 的資料來源,它們之間有一項很重要的差異就是前二種資料類型是一次性繫結,後二種資料類型是雙向繫結,也就是資料來源若發生變更時,UI上的資料也會自動變更,反之亦然。

List<Catalog> list = null;

list = dbContext.Catalogs.Where(c => c.TweOtc == 1).ToList();
dataGridView1.DataSource = list;

// 若資料來源發生變更,必須重新繫結,資料才會顯示到 DataGridView
list.Add(new Catalog());
dataGridView1.DataSource = null;
dataGridView1.DataSource = list;
BindingList<Catalog> bsCatalog = null; 

var query = dbContext.Catalogs.Where(c => c.TweOtc == 1);
bsCatalog = new BindingListt<Catalog>(query.ToList());
dataGridView1.DataSource = null;
dataGridView1.DataSource = bsCatalog;

// 若資料來源發生變更,資料會自動顯示到 DataGridView ,不用再重新繫結
bsCatalog.Add(new Catalog());

Binding EF entities to a DataGridView

如果要將 EF 的實體繫結到 DataGridView ,你可以像上面的例子,直接將 LINQ 查詢指定給 DataSource 屬性。 之後若在 DataGridView 上進行變更,只要叫用 context.SaveChanges() 方法即可存回資料庫。

private void bnGetData_Click(object sender, EventArgs e)
{
    var query = from c in dbContext.Catalogs
                where c.TweOtc == 1
                select c;

    dataGridView1.DataSource = null;
    dataGridView1.DataSource = query.ToList();
}

private void bnSave_Click(object sender, EventArgs e)
{
    dbContext.SaveChanges();
}

但是,在下面例子中,因為不想要顯示所有欄位,所以我們透過 new 關鍵字,回傳我們需要的欄位名稱,這時你會發現繫結後的 DataGridView 會是唯讀的。 這是因為 new 會建立一個匿名型別的物件,當 DataGridVidw 的資料來源是匿名型別串列時,其屬性會自動設定唯讀無法變更。

var query = from c in dbContext.Catalogs
                where c.TweOtc == 1
                select new { c.CatalogID, c.CatalogName };

    dataGridView1.DataSource = null;
    dataGridView1.DataSource = query.ToList();

要處理這個問題,可以用以下幾種方式解決:

隱藏不要的欄位

你可以將 DataGridView 的 Column 屬性設為唯讀或者隱藏不要的欄位。

dataGridView1.Columns[0].Visible = false;
在 EF 中
使用自訂類別對應

將 LINQ 篩選出來的結果,轉成我們自訂的類別,只要是具名型別,繫結到 DataGridView 就沒有唯讀限制了。

private class MyCatalog
{
    public string CatalogID { get; set; }
    public string CatalogName { get; set; }
}

List<MyCatalog> myCatalogList = null;
private void bnGetData_Click(object sender, EventArgs e)
{
    var query = from c in dbContext.Catalogs
                where c.TweOtc == 1
                select new MyCatalog { CatalogID = c.CatalogID, CatalogName=c.CatalogName };
    myCatalogList = query.ToList();

    dataGridView1.DataSource = null;
    dataGridView1.DataSource = myCatalogList;
}

private void bnSave_Click(object sender, EventArgs e)
{
    // 這個方法,必須自行判斷 DataGridView 中的內容,並自行變更 context 中的相對應資料。

    foreach (var myCatalog in myCatalogList)
    {
        var catalog = dbContext.Catalogs.Where(c => c.CatalogID == myCatalog.CatalogID).FirstOrDefault();
        if (catalog != null)
        {
            catalog.CatalogName = myCatalog.CatalogName;
        }
    }

    dbContext.SaveChanges();
}

使用 DataSource 的 DataGridView 如何手動增加一列

像下面這段 Code ,當 DataGridView 的 DataSource 繫結到某個資料來源時,如果你想手動增加一個資料列,那是不允許的。

List<Catalog> listCatalogs = new List<Catalog>();
dataGridView1.DataSource = listCatalogs;

//無效,必須重新再 binding 一次可行
listCatalogs.Add(new Catalog());

//無效,會產生 error ,因為 DataGridView 已繫結,無法手動加入新的資料列。
dataGridView1.Rows.Add();

通常你必須重新繫結, DataGridView 才會產生變畫。

listCatalogs.Add(new Catalog());
dataGridView1.DataSource = null;
dataGridView1.DataSource = listCatalogs;

不過你可以改用 BindingSource 繫結,然後對 BindingSource 操作,來變更 DataGridView 。

BindingSource bs = new BindingSource();

dataGridView1.DataSource = bs;

bs.DataSource = listCatalogs;

bs.Add(new Catalog());  //新資料列會自動產生。

使用雙向繫結

List 只可以做到單向繫結,也就是若你對 List 清單異動,這個結果會反應到 UI 上。 但是反過來,若你在 UI 上新增一筆資料,該結果並不會反應到 List 清單。

沒有留言:

張貼留言