Chào mừng đến với Diễn đàn lập trình - Cộng đồng lập trình.
Trang 1 của 2 12 CuốiCuối
Kết quả 1 đến 10 của 16
  1. #1
    Ngày tham gia
    Sep 2015
    Bài viết
    0

    Cách phân trang, sắp xếp một file text trên ListView

    Chào các bạn,

    Hôm nay, sau khi hoàn thành một tool dùng cho việc học tập của mình, nhờ những sự giúp đỡ của các bạn trên diễn đàn, nay mình xin trình bày một cách phân trang dữ liệu đọc từ một text-file lên ListView.

    ví dụ thực tiễn: mình có một text file với nội dung sau:

    Mã:
    chuồn 1
    con bò
    con heo
    con vịt
    ...
    Nếu bây giờ có tới 10000 dòng thì việc đọc từng dòng rồi bỏ vào ListView sẽ làm chậm chương trình của bạn một cách đáng kể; và hiển thị 1000 ListViewItem lên ListView cũng không phải là cách chuyên nghiệp.

    Phần 1: PHÂN TRANG

    Trước tiên, chúng ta sẽ dùng đối tượng StreamReader cho mục đích đọc file text.


    Mã:
    ...string allLine;                    using (System.IO.StreamReader reader = new System.IO.StreamReader(pathToRead)){    allLine = reader.ReadToEnd();}...
    nếu sử dụng using statement trong trường hợp này thì bạn không cần phải viết:


    Mã:
    reader.Close(); // mẹo (feature) trong C#
    Ý tưởng phân trang của Fox là đọc tất cả các dòng rồi chứa trong một ArrayList cha, sau đó bắt đầu phân ArrayList này thành những phần (ArrayList con). Dùng đối tượng List<ArrayList> gom 6 thằng con này vô.

    ví dụ: số dòng trong file text = 55 dòng -> chúng ta sẽ có 6 ArrayList con


    Mã:
    ...System.Collections.ArrayList gettingContent = this.ReadTextFileToArrayList(allLine, Convert.ToChar((int)System.Windows.Forms.Keys.Enter));                                    System.Collections.Generic.List<System.Collections.ArrayList> pageCollection = new System.Collections.Generic.List<System.Collections.ArrayList>();                    int index = 0;                    while (index <= gettingContent.Count){    System.Collections.ArrayList eachPage = gettingContent.GetRange(index, this.DoLimit(gettingContent.Count, index, 10));                            pageCollection.Add(eachPage); // Gom ArrayList con vô...                            index += 10;}...
    Đoạn code dùng để tách nội dung một file text thành các dòng riêng biệt


    Mã:
    /// <summary>/// /// Splits all line(s) in a text file, then puts to an <c>System.Collections.ArrayList</c>./// /// </summary>/// /// <param name="textfileContent">all line(s) having in a text file.</param>/// /// <param name="lineToken">token to split to each line.</param>/// /// <returns>collection of all line(s).</returns>private System.Collections.ArrayList ReadTextFileToArrayList(string textfileContent, char lineToken){    int newpos = -1, curpos = 0;                System.Collections.ArrayList container = new System.Collections.ArrayList();                while (textfileContent.IndexOf(lineToken, (newpos + 1) > textfileContent.Length ? textfileContent.Length : (newpos + 1)) >= 0)    {        newpos = textfileContent.IndexOf(lineToken, (newpos + 1));                        container.Add(textfileContent.Substring(curpos, (newpos - curpos)));                        curpos = newpos += 2;    }    return container;}
    Giả sử bạn có 50 dòng thì việc phân thành 5 trang mỗi trang 10 dòng quá đơn giản. Ngược lại với 55 dòng thì bạn phải dùng đoạn code sau để lấy 5 dòng còn dư và số trang bây giờ sẽ là 6.


    Mã:
    /// <summary>/// /// Gets wanted number of element(s)./// /// </summary>/// /// <param name="total">total element(s).</param>/// /// <param name="offset">next index of needed element.</param>/// /// <param name="linePerPage">default number of element(s) to get</param>/// /// <returns>wanted number of element(s).</returns>private int DoLimit(int total, int offset, int linePerPage){    return (total - offset >= linePerPage) ? linePerPage : (total - offset);}
    Bây giờ bạn đã có một đối tượng List<ArrayList> quản lý việc phân trang cho bạn rồi. Chỉ việc gọi đối tượng này và hiển thị từng trang bằng việc dùng Indexer của List<ArrayList> là ok.

    Phần 2: Sắp xếp trên ListView

    Bạn đã từng dùng WindowsExplorer khi muốn sắp xếp những file và folder (details view) bạn thường click chuột lên ColumnHeader của ListView để sắp xếp những phần tử theo thứ tự alphabet. Vậy chúng ta cũng có thể viết code cho ListView của mình được như vậy không? Rất đơn giản.

    Bước 1 trong project của bạn, hãy tạo một Class (đặt tên tùy thích) rồi bỏ đoạn code sau vào:


    Mã:
    public class ListViewColumnSorter : System.Collections.IComparer{        #region - field(s) -         private int mSortingColumnIndex;         private System.Windows.Forms.SortOrder mSortOrderEnumerator;         private System.Collections.CaseInsensitiveComparer mObjectComparer;         #endregion         #region - constructor(s) -         public ListViewColumnSorter()        {            mSortingColumnIndex = 0;             mSortOrderEnumerator = System.Windows.Forms.SortOrder.None;             mObjectComparer = new System.Collections.CaseInsensitiveComparer();        }         #endregion         #region - method(s) -         #region - inherited from IComparer interface -         int System.Collections.IComparer.Compare(object x, object y)        {            int result;             System.Windows.Forms.ListViewItem itemOne, itemTwo;             // casts the objects to be compared to ListViewItem objects.            itemOne = (System.Windows.Forms.ListViewItem)x;            itemTwo = (System.Windows.Forms.ListViewItem)y;             // compares two items...            result = mObjectComparer.Compare(itemOne.SubItems[mSortingColumnIndex].Text, itemTwo.SubItems[mSortingColumnIndex].Text);             // calculate correct return value based on object comparison...            if (mSortOrderEnumerator == System.Windows.Forms.SortOrder.Ascending) return result;             else if (mSortOrderEnumerator == System.Windows.Forms.SortOrder.Descending) return (-result);             else // equal...                return 0;        }         #endregion         #endregion         #region - property(ies) -         public int SortingColumnIndex        {            get { return mSortingColumnIndex; }             set { mSortingColumnIndex = value; }        }         public System.Windows.Forms.SortOrder SortingOrder        {            get { return mSortOrderEnumerator; }             set { mSortOrderEnumerator = value; }        }         #endregion    }
    Bước 2: trong Form chứa ListView bạn khai báo như sau:


    Mã:
    // Biến toàn cụcprivate ListViewColumnSorter mSorter; // Constructor của Formpublic MainForm(){    //    // The InitializeComponent() call is required for Windows Forms designer support.    //    InitializeComponent();                mSorter = new ListViewColumnSorter();                listviewDetails.ListViewItemSorter = mSorter;} // Đăng ký sự kiện ColumnClick cho ListViewvoid ListviewDetailsColumnClick(object sender, ColumnClickEventArgs e){    // if current clicking column is already sorted...    if (e.Column == mSorter.SortingColumnIndex)        {               if (mSorter.SortingOrder == SortOrder.Ascending)                    mSorter.SortingOrder = SortOrder.Descending;                else mSorter.SortingOrder = SortOrder.Ascending;        }        else // sets column number that is to be sorted; default to ascending.        {               mSorter.SortingColumnIndex = e.Column;               mSorter.SortingOrder = SortOrder.Ascending;        }        listviewDetails.Sort();}
    Thế là xong [IMG]images/smilies/biggrin.png[/IMG]

    mình có gửi kèm 2 file ZIP (một là demo, một là source).

    Chúc các bạn thực hiện thành công [IMG]images/smilies/wave.gif[/IMG]

    Chêer.

  2. #2
    Ngày tham gia
    Sep 2015
    Bài viết
    0
    String có hàm split dùng để cắt chuỗi rồi, không cần phải viết lại đâu. [IMG]images/smilies/biggrin.png[/IMG]
    Thay vì đọc 1 lần vào biến string, bạn nên đọc từng dòng = hàm readline rồi add dần vào ArrayList thì tốt hơn.

    Nếu mà dữ liệu lớn, thì khó lòng mà đọc hết vào memory. Bạn nên cải tiến thêm là không cần phải phân trang = cách nhấn nút tới lui, mà thay vào đó sẽ xử lý add/remove các dòng vào listview khi kéo scrollbar lên xuống. (có thể tạo riêng 1 scrollbar chẳng hạn)

  3. #3
    Ngày tham gia
    Sep 2015
    Bài viết
    0
    Bài viết khá hay. Cảm ơn bạn

  4. #4
    Cám ơn ý kiến của các bạn,

    +Đ.Khánh
    Mã:
    String có hàm split dùng để cắt chuỗi rồi, không cần phải viết lại đâu.
    Thay vì đọc 1 lần vào biến string, bạn nên đọc từng dòng = hàm readline rồi add dần vào ArrayList thì tốt hơn.
    > Hàm Split() mình viết (nhờ tham khảo) là có chủ ý của nó [IMG]images/smilies/biggrin.png[/IMG].
    > Cũng nhờ tham khảo mình biết rằng dùng ReadLine() rùi bỏ từ từ sẽ làm chậm, nếu bỏ từng mảng vào sẽ nhanh hơn.

    Mã:
    Nếu mà dữ liệu lớn, thì khó lòng mà đọc hết vào memory. Bạn nên cải tiến thêm là không cần phải phân trang = cách nhấn nút tới lui, mà thay vào đó sẽ xử lý add/remove các dòng vào listview khi kéo scrollbar lên xuống. (có thể tạo riêng 1 scrollbar chẳng hạn)
    > Do tút mình làm chỉ để quản lý những dữ liệu cỡ nhỏ thôi nên không quan tâm nhiều vào Memory [IMG]images/smilies/biggrin.png[/IMG].
    > Do dữ liệu nhỏ, việc phân chia các trang chứa tối đa 10 đến 20 dòng là đủ để view rồi [IMG]images/smilies/smile.png[/IMG] ; nhưng mình sẽ tiềm hiểu cách của bạn, bít đâu sẽ có lúc dùng tới [IMG]images/smilies/biggrin.png[/IMG]

  5. #5
    To: O'Wicked Fox
    Thanks bạn đã hướng dẫn cách chia list.

    Mình mới học đôi chút về C# và muốn làm vài thứ gần giống như bạn đã hướng dẫn:

    -Load 1 file txt (thường có dung lượng khoảng 1-5MB) với charset UTF-8 hoặc GB18030 (file txt tiếng trung)

    - Chia nhỏ file txt đó ra thành các phần nhỏ (nếu được thì số dòng có thể điền vào khi chương trình chạy)

    - Lưu các đoạn txt đã chia thành các file txt mới ở một thư mục định sẵn

    Bạn có thể hướng dẫn mình cách làm hoặc tìm tài liệu liên quan được không?

    Cám ơn bạn trước.

  6. #6
    Ngày tham gia
    Sep 2015
    Bài viết
    0
    Trích dẫn Gửi bởi Slytherin
    -Load 1 file txt (thường có dung lượng khoảng 1-5MB) với charset UTF-8 hoặc GB18030 (file txt tiếng trung)
    Dung lượng 1-5Mb thì không ảnh hưởng đến việc Load; nếu máy tính của bạn có cài font tiếng Trung thì chỉ cần chỉnh Font tiếng Trung thích hợp trên ListView của bạn.

    Trích dẫn Gửi bởi Slytherin
    - Chia nhỏ file txt đó ra thành các phần nhỏ (nếu được thì số dòng có thể điền vào khi chương trình chạy)
    Trong hướng dẫn của mình thì bạn sẽ có một List<ArrayList> quản lý những "mẫu" nhỏ trong file text rồi. Mặc định mỗi mẫu chứa <= 10 dòng.

    Trích dẫn Gửi bởi Slytherin
    - Lưu các đoạn txt đã chia thành các file txt mới ở một thư mục định sẵn
    Với List<ArrayList> trên nên viết một method sau:


    Mã:
    public bool WriteEachPage(string path) // path này chỉ nên dừng tại một folder thôi ví dụ ...\bin\Debug{    bool result = false;     int fileNum = 1;                string pagename = "";                try    {        foreach (System.Collections.ArrayList page in List<ArrayList>)        {            pagename = "page" + fileNum.ToString() +".txt"; // sinh tên file ví dụ ...\bin\Debug\page1.txt                                using (System.IO.StreamWriter writer = new System.IO.StreamWriter(path + "\\" + pagename))            {                foreach (object line in page) // duyệt từng dòng rồi ghi trên file.                    writer.WriteLine(line.ToString());                        }            fileNum++; // tăng biến đếm để tạo một file mới... tiếp tục lặp.        }                        result = true;    }    catch(Exception)    {        throw;    }                return result;}
    Mình đã test trên file Text của mình thành công (có 45 trang sinh ra 45 file [IMG]images/smilies/biggrin.png[/IMG]) Chúc bạn thành công.

  7. #7
    Ngày tham gia
    Sep 2015
    Bài viết
    0
    @To 3B: Cám ơn ý kiến của bạn rất nhiều, đúng là nếu bỏ 10.000 item vào thì cũng chưa đến nổi là chậm. Ở đây, mình nói chậm là do máy tính của mình nó "yếu" quá rồi nên mới chậm. Trong code mình có dùng AddRange() mà bạn [IMG]images/smilies/biggrin.png[/IMG]

    @To Slytherin: Đoạn code của mình chỉ là minh họa cho ý tưởng của bạn thôi, bạn đừng quá dựa hẳn vào bài code demo của mình như vậy. Hãy từ code của mình mà tìm đường đi cho bạn [IMG]images/smilies/smile.png[/IMG]


    Nếu thay "List<ArrayList>" bằng "System.Collections.Generic.List<System.Collec tion s.ArrayList>" thì nó vẫn báo lỗi.
    Đây là đoạn code mình làm thực tế trên code của mình. List<ArrayList> mình ghi là để cho bạn biết là nên dùng đối tượng nào để làm việc cùng thôi. Và List<ArrayList> ở đây là biến toàn cục mPageCollection.


    Mã:
    public bool WriteEachPage(string path)        {            bool result = false;            int fileNum = 1;                        string pagename = "";                        try            {                foreach (System.Collections.ArrayList page in mPageCollection)                {                    pagename = "page" + fileNum.ToString() +".txt";                                        using (System.IO.StreamWriter writer = new System.IO.StreamWriter(path + "\\" + pagename))                    {                        foreach (object line in page)                        {                            writer.WriteLine(line.ToString());                        }                    }                                        fileNum++;                }                                result = true;            }            catch(Exception)            {                throw;            }                        return result;        }
    đã có hàm public này rồi thì qua Form chứa ListView bạn thêm dòng code sau
    vào constructor của MainForm


    Mã:
    ...bool writePage = mPager.WriteEachPage(Application.StartupPath); // test cho việc ghi ra file các trang đã chia....

    À thêm nữa vì fle tx của mình tiếng trung nhưng không được save vơi encoding chuẩn, muốn hiện đúng nội dung thì phải mở với Chartset encoding là UTF-8 hoặc GB18030. Mình thử thay 1 file txt đó vào file demo (vì chuơng trình không nhận tên khác) thì nó hiện không đúng font chữ.
    Nó không nhận một file nào khác ngoài file demo.txt là do trong constructor của MainForm mình chỉ bắt nó đọc chính file này. Nếu muốn thay thế file để đọc bạn có 2 cách:

    1. là delete file demo.txt của mình đi và paste file text của bạn vào đổi tên nó thành demo.txt (Can thiệp bên ngoài)
    2. Bạn vào constructor của MainForm và sử code như sau


    Mã:
    mPager = new PagingTextFile(Application.StartupPath + "\\demo.txt", ref mResult); // thay demo.txt = tên file text tiếng Trung của bạn.

    Vậy nếu mình muốn ép cho chuơng trình mở file với charset là UTF-8 thì làm cách nào?
    Mặc định khi đọc file text thì StreamReader nó mặc định Encoding là UTF-8 rồi bạn [IMG]images/smilies/smile.png[/IMG]


    Nếu làm một form cho chuơng trình mở 1 file nào đó rồi lấy tên file thay vào demo thì thay ở đâu?
    Bạn tìm hiểu về 2 đối tượng trong System.Windows.Form sau

    Mã:
    FolderBrowserDialog // duyệt folder chứa tất cả các file cần mở.
    OpenFileDialog // duyệt các file cần mở.

    Khi mình thay số dòng 10 thành 50 chẳng hạn thì chuơng trình không hiện đủ 50 dòng mà chỉ hiện 10 dòng dù mình đã chỉnh lại kích thước trong mainform còn đọc file MainForm.Designer.cs thì chả hiểu gì.
    Có lẽ bạn mới học nên chưa hiểu những code phát sinh trong quá trình design form. Do MainForm mình là demo đã khóa không cho resize với mục đích chỉ cho view 1 trang 10 dòng thôi. Tuy nhiên nếu bạn nói thay 10 thành 50 thì việc Form ở đây không bị ảnh hưởng là do bạn sửa còn thiếu. Cách khắc phục là
    Vào constructor của PagingTextFile sửa tất cả số 10 thành 50 (có đến 2 chỗ lận). Khi sửa xong và chạy thì ListView nó sẽ tự có 1 ScrollBar cho bạn kéo lên xuống để view.

    Do bạn mới học nên Fox nghĩ những gì Fox nói có lẽ bạn chưa nắm hết [IMG]images/smilies/smile.png[/IMG] nhưng bạn cứ cố gắng tìm hiểu nhé, không hiểu chỗ nào Fox sẽ giúp bạn (nếu có thể [IMG]images/smilies/biggrin.png[/IMG]).

  8. #8
    Ngày tham gia
    Sep 2015
    Bài viết
    0
    @To Fox: mình vẫn còn đang trong giai đoạn tìm hiểu ý nghĩa của các đoạn code nên khi giải quyết 1 vấn đề phát sinh thêm căn bản là khó.

    Trước mình học thêm về HTML ở trường thì nó trực quan, viết bao nhiêu dòng code trình duyệt sẽ cố hiển thị bấy nhiêu nội dung. Còn viết code cho một chương trình nào đó thì phải thành một cấu trúc hoàn chỉnh nó mới có thể chạy mặc dù chưa chắc nội dung xuất ra đúng ý.

    Để mình tìm hiểu thêm về OpenFileDialog tiếp. Cám ơn bạn đã chỉ dẫn.

  9. #9
    Ngày tham gia
    Sep 2015
    Bài viết
    0
    phân trang là một một kỹ thuật rất hay. Nhưng nếu như bạn nói là ghi khoảng 1000 item vào list view mà là chậm thì 3B có thể nói rằng không hẳn như vậy.
    Đúng là với listview cách fill dữ liệu khá chậm nhưng bù lại code cho listview rất linh động, và listview giao diện rất đẹp nên rất nhiều người ưa dùng listview.
    Thế nhưng cũng vì cách fill dữ liệu chậm mà nhiều người bỏ ko dùng listview (trong đó có 3B [IMG]images/smilies/biggrin.png[/IMG]). Tuy nhiên, theo mình biết nếu fill khoảng dưới 10.000 item vào listview thì tốc độ cũng ko chậm lắm. Mặt khác nếu bạn dùng phương pháp addrange thì tốc độ giảm rất nhiều. :P
    ============
    Nhưng bài viết của bạn quả rất bổ ích, thank.
    Mình cũng từng tìm hiểu một cách phân trang trên datagridview, có thời gian mình sẽ up trong topic "một vài solution với Datagridview" cũng trong chuyên mục hướng dẫn này. [IMG]images/smilies/biggrin.png[/IMG]
    Bạn vào xem vào cho ý kiến nha

  10. #10
    Ngày tham gia
    Sep 2015
    Bài viết
    0
    Mã:
    The Type of namespace name "ArrayList" couldnot be found (are you missing a using directive or an assembly reference ?)
    
    The name "List" doesnot exit in the current context
    Khi mình thêm method vào sau private method(s) ở file PagingTextFile.cs thì nó báo như vậy.

    Nếu thay "List<ArrayList>" bằng "System.Collections.Generic.List<System.Collec tion s.ArrayList>" thì nó vẫn báo lỗi.

    Phải thêm vào file nào hay chỗ nào thì mới đung?

    Mình mới học C# qua ebook nên đọc qua thì có hiểu đôi chỗ nhưng cơ bản không hiểu lắm về cả cấu trúc file, cách thêm bớt..

    À thêm nữa vì fle tx của mình tiếng trung nhưng không được save vơi encoding chuẩn, muốn hiện đúng nội dung thì phải mở với Chartset encoding là UTF-8 hoặc GB18030. Mình thử thay 1 file txt đó vào file demo (vì chuơng trình không nhận tên khác) thì nó hiện không đúng font chữ.

    Vậy nếu mình muốn ép cho chuơng trình mở file với charset là UTF-8 thì làm cách nào?

    Nếu làm một form cho chuơng trình mở 1 file nào đó rồi lấy tên file thay vào demo thì thay ở đâu?

    Khi mình thay số dòng 10 thành 50 chẳng hạn thì chuơng trình không hiện đủ 50 dòng mà chỉ hiện 10 dòng dù mình đã chỉnh lại kích thước trong mainform còn đọc file MainForm.Designer.cs thì chả hiểu gì.

    Thanks bạn.

 

 
Trang 1 của 2 12 CuốiCuối

Quyền viết bài

  • Bạn Không thể gửi Chủ đề mới
  • Bạn Không thể Gửi trả lời
  • Bạn Không thể Gửi file đính kèm
  • Bạn Không thể Sửa bài viết của mình
  •