CheckListBox 全選功能 ItemCheck 程式碼 select all code 防止無窮遞迴prevent infinite recurse prevent stackoverflow SelectedIndexChanged

 

全選這功能,我一開始試過另一種寫法,是用SelectedIndexChanged去做,這個意義是偵測"選擇的項目的集合"有變化時,會引發事件,

而我最後才會放的是用ItemCheck 去引發事件,這方法則是用勾選狀態有變化時會引發,

兩者以我理解有些微不同但又幾乎相同,"select選擇"以我的理解是就是按在項目上面讓他變成反白後就會引發select事件,

"check勾選"則是對勾選框做按的動作 會引發check事件,
但是好像listbox可能有內建連動機制,所以做任一動作時listbox會再自動連動另個動作(這是我的觀察不一定正確)

意思就是你是做"選擇"時,listbox會再自動幫你把勾選框勾起來"勾選"

而做"勾選"時,listbox會自動幫你把你勾的那個框旁邊的選項反白也就是連動"選擇"

 

接著說一下SelectedIndexChanged的做法及我發現的缺點(缺點滿大因此是不建議用這個事件去寫)

SelectedIndexChanged因為他的相關參數跟check的true false完全無關,因此完全不能靠事件本身參數來寫,

我們將全選功能的選項設計為第0個選項是全選,1 2 3...之後的選項是其他選項,假設CheckListBox的物件名稱是 ListBoxBox,

因此要另外在方法範圍外設一個bool變數在視窗載入的時候就先記錄下

//方法外的紀錄全選的勾選狀態的bool

 private bool _checkAll = false;

//這行要放在適當時機,紀錄最初始生成時全選選項也就是index 0的選項的勾選狀態,意義是要代表舊的全選勾選狀態,下面會需要比較

_checkAll = ListBoxBox.GetItemChecked(0);

 

接下來是程式碼本身

 

 private void ListBoxBox_SelectedIndexChanged(object sender, EventArgs e)
        {

//這個事件引發時已經取得了使用者勾選後的勾選最新狀態,因此下面是取得全選選項的勾選狀態,拿來和原本舊的的全選狀態(checkall)比較,若是是相反的,則代表這次被勾選的選項是全選選項

            if (ListBoxBox.GetItemChecked(0)==! _checkAll)
            {

//如果全選勾之後最新的狀態是true

                if (ListBoxBox.GetItemChecked(0))
                {
                    for (int i = 0; i < ListBoxBox.Items.Count; i++)
                    {//把全部選項變成勾
                        ListBoxBox.SetItemChecked(i, true);
                    }

//同時同步更新全選選項的狀態紀錄同步到最新

                    _checkAll = true;

                }
                else//如果全選勾之後最新的狀態是false
                {
                    for (int i = 0; i < ListBoxBox.Items.Count; i++)
                    {//下面同理
                        ListBoxBox.SetItemChecked(i, false);
                    }
                    _checkAll = false;
                }
                
            }

//自定義的事件(非必要)

            if (CheckedTypeChange != null)
                CheckedTypeChange(sender, e);
        }

 

目前這樣的話是還可以再加寫其他功能例如   3 當其他選項全選的狀態下,當user取消勾選其中一個其他選項時,全選選項會自動跟著取消勾選

4 若其他選項只剩一個沒被勾選,當user將最後一個未被勾選的其他選項勾選時,全選選項會跟著自動勾選

這個用一些迴圈就可以達成其中的邏輯

 

但是,SelectedIndexChanged的方法我經過測試後,發現他無法處理快速連按,雖然一開始運作正常,但只要連點全選選項快一點之後就會錯誤,應該是因為反應不夠快延遲而產生的,按了一陣子後會變成你按全選的時候反而當作取消全選,你取消全選他反而把所有選項勾選,

猜是因為勾選變化太快導致bool那邊沒辦法真正即時更新全選的舊勾選狀態,可能會造成lag一拍就變成相反的

而且反應很慢,我猜這個是C#內部程式設計的一些程序造成的,也就是你用這個事件來做全選功能的話,上面的問題我試過是無法克服的,就是會特別慢

 

走到這裡我就卡在這個地步很久,雖然上網查過但是我當時實在沒找到什麼其他方法可以做全選,

同時我目前也還算是winforn新手,當時是更新手,現在其實也沒什麼在碰winform了

 

後來過了一陣子,我網路上查來查去突然發現ItemCheck 好像有機會也可行

 

段是在我還不知道EventArgs帶有什麼資訊之前,我在世的時候覺得幾乎不可能用ItemCheck來做,

因為ItemCheck引發時,如果你用GetItemChecked去取得勾選選項的值,會發現是舊的,不是勾選動作後會有的新會得到新狀態值,

也就是你怎麼取都是勾選動作前的狀態,這樣子的話根本什麼都做不了

 

但是我其他事件用久了也大略知道EventArgs在事件中常常有意想不到的資訊和好用在其中,

如果知道怎麼用的話。

所以我後來又去網路上找EventArgs的用法,最後感到很驚奇,也因為這樣子很大部分的問題被解決了,

包括只要知道e.Index, e.NewValue等就可以寫出跟上面select的效果一樣的程式碼,

而且重點是速度超快,膽的再怎麼快也完全不會有錯誤,點太快它內部機制還是運作得很好,不會有相反的情況發生,

而且完全不需要另外方法外面設bool值紀錄比對

 

使用ItemCheck之後,我很容易地將完成了這兩個功能:

1 是 user按全選之後,其他選項自動勾選起來

2 user取消全選,其他選項全部取消勾選

因為只需要if判斷e.Index是不是0就好了,是的話就把1之後的都設成勾,就這麼簡單

 

但是這兩個功能我發現我那時的做法會引發無窮迴圈

3 當其他選項全選的狀態下,當user取消勾選其中一個其他選項時,全選選項會自動跟著取消勾選

4 若其他選項只剩一個沒被勾選,當user將最後一個未被勾選的其他選項勾選時,全選選項會跟著自動勾選

 

3,4要 判斷是不是取消勾選或勾選其他選項 很簡單, index!=0就成立 

成立就會 引發將全選做勾或取消勾 

用程式碼引發全選勾或不勾之後,會再進入會進入e.index==0的程式碼(是失敗的程式碼這篇省篇幅不放出),

執行全選或全取消勾選,

而在執行全選或全取消時,

因為會開始將全選以外的其他選項設定成勾選或是不勾選。

所以又會進入 判斷是不是取消勾選或勾選其他選項 的判斷,並且判斷為真也就是 index!=0就成立

因此又會執行  引發將全選做勾或取消勾

所以就進入無窮迴圈,最後就記憶體stackoverflow了

 

那時我怎麼嘗試都沒有找到解法,費盡心思最後還是無法突破,所以當時3 4我就放棄了,只能做到1 2

 

大概又過了半個月,我又回來處理這個問題,再次上網查詢,這次花了很多時間看一堆網頁之後(當然都是英文只是下面我把意思翻中文),

突然發現有一個頁面中有人與我有相同的問題,

然後有一個人回答," 你可以在事件方法外面設一個bool變數,在會造成無窮迴圈的地方要進入之前把bool設成false,然後在那個會造成無窮的那段程式碼的進入前判斷式加上要綜合判斷那個bool變數,這樣子就會造成false ,就可以避掉無窮,然後在原來那段結束後,再把bool恢復成true "

 

看到這段時有如醍醐灌頂,原來用這麼簡單的機關好像就有大用!

接著我好像用這個方式又更進一步了,不過這邊的細節我忘了

但這是一個很大的轉機

不過我記得雖然知道了這方法,但3,4好像因為判斷式要很複雜的緣故我當時沒能完成

 

最後大概又過了半個月,我再度回來處理這個問題,

經過了半個月寫其他東西的訓練應該是有幫助,

我突然覺得以我現在的能力應該可以再來試一次看看,

所以就試了,途中遇到了很多其他問題,一一用力思考克服,最後竟然還真的給我成功了

 

 

那麼先簡單介紹一下我所寫出的全選功能的整個概觀

--------------------------------------------------------------------

 

這是一個藉由CheckListBox的事件 ItemCheck 想達到全選的功能的程式碼

首先當然是要在你的designer裡加事件並連結方法,加事件可以用程式碼加,也可以用屬性雷電記號那邊去加,這個知道的話一定知道我在說什麼

程式碼的話是像這樣,假設CheckListBox的物件名稱是 ListBoxBox

 

那事件那邊的寫法就是

this.ListBoxBox.ItemCheck += new System.Windows.Forms.ItemCheckEventHandler(this.ListBoxBox_ItemCheck);

 

+=前面就是物件本身的事件(ItemCheck)的概念,後面就是把哪個方法加入到物件的那個事件中(這邊就是ListBoxBox_ItemCheck這個方法)

 

ListBoxBox_ItemCheck這個方法的話,要注意它的參數必須是 ( object sender, ItemCheckEventArgs e )  這兩個變數

 

接下來說明全選功能所包含的功能細項,

我們將全選功能的選項設計為第0個選項是全選,1 2 3...之後的選項是其他選項,

 

全選功能在分細項來說分為4個,

1 是 user按全選之後,其他選項自動勾選起來

2 user取消全選,其他選項全部取消勾選

3 當其他選項全選的狀態下,當user取消勾選其中一個其他選項時,全選選項會自動跟著取消勾選

4 若其他選項只剩一個沒被勾選,當user將最後一個未被勾選的其他選項勾選時,全選選項會跟著自動勾選

 

 

 

        CheckedListBox ListBoxBox = new CheckedListBox();
        private EventHandler CheckedTypeChange;
        private bool RelateFirst = true;  //是否允許進入有關全選調整的程式碼段落,若否主要用意就是要避開無窮迴圈
        private bool RelateItSelf = true;    //是否允許進入非全選選項程式碼段落,若否主要用意就是要避開無窮迴圈
        private void ListBoxBox_ItemCheck(object sender, ItemCheckEventArgs e)
        {
            if (RelateFirst)
            {
                if (e.Index == 0 && e.NewValue == CheckState.Checked)   //是勾在全選且是做勾選動作,e.NewVlue代表使用者最新做的動作==CheckState的勾選的話   反之OldValue代表勾選項動作做前的勾選值
                {
                    for (int i = 1; i < ListBoxBox.Items.Count; i++)
                        ListBoxBox.SetItemChecked(i, true);   //這邊執行的下一步之後會再次進入這個方法
                }
                else if (e.Index == 0 && e.NewValue == CheckState.Unchecked)//原理同上反過來全不選而已
                {
                    for (int i = 1; i < ListBoxBox.Items.Count; i++)
                        ListBoxBox.SetItemChecked(i, false);
                }
            }
            if (e.Index != 0 && RelateItSelf)
            {
                RelateFirst = false;//先false執行完中間再true是讓中間的動作不會再進去上面的全選造成無窮迴圈

 

                //非全選選項若有一不勾則取消全選
                if (e.NewValue == CheckState.Unchecked)
                {
                    ListBoxBox.SetItemChecked(0, false);
                    RelateItSelf = false;
                    ListBoxBox.SetItemChecked(e.Index, false);//更新到最新狀態,沒加這三行的話要到這個方法整個結束後勾選值才會更新,這樣到最底下的Event進行時讀到的就是前一個還沒改變的狀態,所以才加這三行要立即更新目前勾選情況,到最後才會是勾選後的情況  又因為前面把RelateIfSelf設成false,所以再次進入這方法時因為index!=0前面全選那段不會進去,又設成false所以這段不會進來,所以什麼段都不進去直接結束那次方法,在結束的瞬間內建結束時就會把勾選值更新為最新值就成功改變,接下來跳回上一層迴圈進續進行
                    RelateItSelf = true;
                }
 
                //非全選選項如果是勾選擇檢查是否目前狀態的除了全選之外的勾選項數等於所有項目減一
                else if (e.NewValue == CheckState.Checked)
                {
                    RelateItSelf = false;
                    ListBoxBox.SetItemChecked(e.Index, true);
                    RelateItSelf = true;
 
                    if (GetExceptFirstCheckedItemsCount() == ListBoxBox.Items.Count - 1)
                        ListBoxBox.SetItemChecked(0, true);
                }
 
 
                RelateFirst = true;
            }
 
            if (CheckedTypeChange != null)
                CheckedTypeChange(sender, e);
 
        }
 
 
//及時計算處於勾選狀態的選項總數量方法
        private int GetExceptFirstCheckedItemsCount()
        {
            int cou = 0;
            for (int i = 1; i < ListBoxBox.Items.Count; i++)
                if (ListBoxBox.GetItemChecked(i) == true)
                    cou++;
            return cou;
        }
 
 
arrow
arrow
    全站熱搜
    創作者介紹
    創作者 olivermode 的頭像
    olivermode

    olivermode的部落格

    olivermode 發表在 痞客邦 留言(0) 人氣()