Giáo trình Môi trường và công cụ lập trình 1 - Chương 1: Ôn tập các lớp cơ sở

pdf 162 trang cucquyet12 2171
Bạn đang xem 20 trang mẫu của tài liệu "Giáo trình Môi trường và công cụ lập trình 1 - Chương 1: Ôn tập các lớp cơ sở", để tải tài liệu gốc về máy bạn click vào nút DOWNLOAD ở trên

Tài liệu đính kèm:

  • pdfgiao_trinh_moi_truong_va_cong_cu_lap_trinh_1_chuong_1_on_tap.pdf

Nội dung text: Giáo trình Môi trường và công cụ lập trình 1 - Chương 1: Ôn tập các lớp cơ sở

  1. TRƯỜNG ĐẠI HỌC ĐÀ LẠT KHOA CÔNG NGHỆ THÔNG TIN NGUYỄN MINH HIỆP GIÁO TRÌNH MÔI TRƯỜNG VÀ CÔNG CỤ LẬP TRÌNH 1 Dành cho sinh viên ngành công nghệ thông tin Đà Lạt, 2010
  2. Khoa CNTT [MÔI TRƯỜNG VÀ CÔNG CỤ LẬP TRÌNH] MỤC LỤC Trang 226
  3. Khoa CNTT [MÔI TRƯỜNG VÀ CÔNG CỤ LẬP TRÌNH] Trang 227
  4. Khoa CNTT [MÔI TRƯỜNG VÀ CÔNG CỤ LẬP TRÌNH] Trang 228
  5. Khoa CNTT [MÔI TRƯỜNG VÀ CÔNG CỤ LẬP TRÌNH] Trang 229
  6. Khoa CNTT [MÔI TRƯỜNG VÀ CÔNG CỤ LẬP TRÌNH] Trang 230
  7. Khoa CNTT [MÔI TRƯỜNG VÀ CÔNG CỤ LẬP TRÌNH] Trang 231
  8. Khoa CNTT [MÔI TRƯỜNG VÀ CÔNG CỤ LẬP TRÌNH] Chương 1: Ôn tập các lớp cơ sở Tổng quan Trong chương này ta sẽ có cái nhìn rõ hơn về các lớp cơ sở ( base classes) và cách mà chúng tương tác với ngôn ngữ C# để hổ trợ cho ta trong việc viết mã.Cụ thể ta sẽ xem xét các chủ đề sau: Chuỗi và biểu thức chính quy ( regular expression) Nhóm đối tượng ,bao gồm các danh sách mảng, collections và từ điển Ta cũng xem xét System.Object, lớp mà mọi thứ đều được dẫn xuất từ nó. 1.1 System.object System.object là lớp cơ sở chung mà mọi đối tượng khác được thừa kế và ta cũng xem xét về các phương thức thành viên chính của nó. Trong chương này ta sẽ tìm hiểu các phương thức còn lại của system.object. Đầu tiên ta sẽ tìm hiểu tóm tắt của từng phương thức: Phương thức Truy xuất Mục đích string ToString() public virtual Trả về một chuỗi đại diện cho đối tượng Trả về mã băm của đối tượng được thiết int GetHashCode() public virtual kế cho phép ta tìm kiếm một cách hiệu quả các thể hiện của đối tượng trong từ điền So sánh đối tượng này với một đối tượng bool Equals(object obj) public virtual khác bool Equals(object objA, object objB) public static So sánh 2 đối tượng Trang 1
  9. Khoa CNTT [MÔI TRƯỜNG VÀ CÔNG CỤ LẬP TRÌNH] bool ReferenceEquals(object So sánh các tham chiếu đối tượng để xem public static objA, object objB) chúng có chỉ đến cùng đối tượng Trả về một đối tượng dẫn xuất từ Type GetType() public System.Type mà đưa ra chi tiết kiểu dữ liệu object MemberwiseClone() protected protected void Finalize() Hàm hủy ( Destructor) virtual 1.2 Xử lý chuỗi (System.string) Phương thức Mục đích Compare So sánh nội dung của 2 chuỗi Giống compare nhưng không kể đến ngôn ngữ bản địa hoặc văn CompareOrdinal hoá Định dạng một chuỗi chứa một giá trị khác và chỉ định cách mỗi Format giá trị nên được định dạng. IndexOf Vị trí xuất hiện đầu tiên của một chuỗi con hoặc kí tự trong chuỗi. Vị trí xuất hiện đầu tiên của bất kì một hoặc một tập kí tự trong IndexOfAny chuỗi. LastIndexOf Giống indexof, nhưng tìm lần xuất hiện cuối cùng. LastIndexOfAny Giống indexofAny, nhưng tìm lần xuất hiện cuối cùng. Trang 2
  10. Khoa CNTT [MÔI TRƯỜNG VÀ CÔNG CỤ LẬP TRÌNH] Canh phải chuỗi điền chuỗi bằng cách thêm một kí tự được chỉ PadLeft định lặp lại vào đầu chuỗi. Canh trái chuỗi điền chuỗi bằng cách thêm một kí tự được chỉ định PadRigth lặp lại vào cuối chuỗi. Thay thế kí tự hay chuỗi con trong chuỗi với một kí tự hoặc chuỗi Replace con khác. Chia chuỗi thành 2 mảng chuỗi con ,ngắt bởi sự xuất hiện của một Split kí tự nào đó. Substring Trả về chuỗi con bắt đầu ở một vị trí chỉ định trong chuỗi. ToLower Chuyển chuỗi thành chữ thuờng. ToUpper Chuyển chuỗi thành chữ in. Trim Bỏ khoảng trắng ở đầu và cuối chuỗi . 1.2.1 Định dạng Chuỗi Nếu ta muốn những lớp mà ta viết thân thiện với người sử dụng , thì chúng cần để trình bày chuỗi theo bất cứ cách nào mà người sử dụng muốn dùng. Thời gian chạy .NET định nghĩa một cách chuẩn để làm: dùng một interface hoặc IFormatable Ví dụ: double d = 13.45; int i = 45; Console.WriteLine("The double is {0,10:E} and the int contains {1}", d, i); Trang 3
  11. Khoa CNTT [MÔI TRƯỜNG VÀ CÔNG CỤ LẬP TRÌNH] Chuỗi định dạng tự nó bao gồm hầu hết văn bản được trình bày, nhưng bất cứ ở đâu có biến được định dạng, chỉ mục của nó trong danh sách thông số trong dấu ngoặc. Có thể là thông tin khác bên trong dấu ngoặc về việc định dạng của mục đó. Số kí tự được giữ bởi sự trình bày của mục có thể xuất hiện, thông tin này sẽ có dấu phảy đứng trước. Một số âm chỉ định rằng mục đó đưọc canh trái, trong khi một số dương chỉ định mục đó được canh phải. Nếu mục đó giữ nhiều kí tự hơn được yêu cầu, nó vẫn xuất hiện đầy đủ. Một chỉ định định dạng cũng có thể xuất hiện. Điều này sẽ được đặt trước bởi dấu hai chấm và chỉ định cách ta muốn mục được định dạng. Ví dụ ta muốn định dạng số như kiểu tiền tệ hoặc trình bày theo ký hiệu khoa học? Đặc tả Áp dụng đến Ý nghĩa Ví dụ C numeric types locale-specific $4834.50 (USA)£4834.50 (UK) monetary value D integer types only general integer 4834 E numeric types scientific notation 4.834E+003 F numeric types fixed point decimal 4384.50 G numeric types general number 4384.5 N numeric types usual locale specific 4,384.50 (UK/USA)4 384,50 format for numbers (continental Europe) P numeric types Percentage notation 432,000.00% X integer types only hexadecimal format 1120 (NB. If you want to display 0x1120, you'd need to write out the 0x separately) Trang 4
  12. Khoa CNTT [MÔI TRƯỜNG VÀ CÔNG CỤ LẬP TRÌNH] 1.3 Biểu thức chính quy ( Regular Expression) 1.3.1 Giới thiệu: Ngôn ngữ biểu thức chính quy là ngôn ngữ được thiết kế đặc biệt cho việc xử lí chuỗi. Chứa đựng 2 đặc tính: - Một tập mã escape cho việc xác định kiểu của các kí tự. Ta quen với việc dùng kí tự * để trình bày chuỗi con bất kì trong biểu thức DOS. Biểu thức chính quy dùng nhiều chuỗi như thế để trình bày các mục như là 'bất kì một kí tự' ,'một từ ngắt ','một kí tự tuỳ chọn', - Một hệ thống cho việc nhóm những phần chuỗi con, và trả về kết quả trong suốt thao tác tìm. Dùng biểu thức chính quy , có thể biểu diễn những thao tác ở cấp cao và phức tạp trên chuỗi. ví dụ : - Xác định tất cả các từ lặp lại trong chuỗi , chuyển ' "The computer books books" thành "The computer books" - Chuyển tất cả các từ theo title case, như là chuyển "this is a Title" thàh "This Is A Title". - Chuyển những từ dài hơn 3 kí tự thành title case , ví dụ chuyển "this is a Title" to "This is a Title" - Bảo đảm các câu được viết hoa - Phân cách những phần tử của URL Mặc dù có thể sử dụng các phương thức System.String và System.Text.StringBuilder để làm các việc trên nhưng nếu dùng biểu thức chính quy thì mã có thể được giảm xuống còn vài dòng. Ta khởi tạo một đối tượng System.Text.RegularExpressions.RegEx, truyền vào nó chuỗi được xử lí, và một biểu thức chính quy (Một chuỗi chứa đựng các lệnh trong ngôn ngữ biểu thức chính quy). Trang 5
  13. Khoa CNTT [MÔI TRƯỜNG VÀ CÔNG CỤ LẬP TRÌNH] Một chuỗi biểu thức chính quy nhìn giống một chuỗi bình thường nhưng có thêm một số chuỗi hoặc kí tự khác làm cho nó có ý nghĩa đặc biệt hơn. Ví dụ chuỗi \b chỉ định việc bắt đầu hay kết thúc một từ, vì thế nếu ta muốn chỉ định tìm kí tự th bắt đầu một từ, ta có thể tìm theo biểu thức chính quy, \bth. Nếu muốn tìm tất cả sự xuất hiện của th ở cuối từ ta viết th\b. tuy nhiên, biểu thức chính quy có thể phức tạp hơn thế, ví dụ điều kiện để lưu trữ phần kí tự mà tìm thấy bởi thao tác tìm kiếm. Một ví dụ khác giả sử như ta muốn chuyển một số diện thoại UK từ trong nước sang định dạng quốc tế. Trong UK, định dạng ví dụ như là 01233 345532 hoặc (01233 345532) mà theo quốc tế sẽ là +44 12330345532, nói cách khác số 0 đầu sẽ được thay bằng +44 và các dấu ngặc phải được bỏ. Thao tác này không quá phức tạp, nhưng cũng hơi rắc rối nếu ta dùng lớp chuỗi để làm ( nghĩa là dùng các phương thức trong lớp chuỗi). Ngôn ngữ biểu thức chính quy sẽ cho phép ta xây dựng một chuỗi ngắn mà sẽ được phiên dịch để đạt được yêu cầu trên. Ta xem đoạn văn bản này là chuỗi input. Giả sử ta muốn tìm tất cả các lần xuất hiện của ion. ta sẽ viết như sau: string Pattern = "ion"; MatchCollection Matches = Regex.Matches(Text, Pattern, RegexOptions.IgnoreCase | RegexOptions.ExplicitCapture); foreach (Match NextMatch in Matches) { Console.WriteLine(NextMatch.Index); } Trong ví dụ này ta dùng phương thức tĩnh Matches() của lớp Regex trong namespace System.Text.RegularExpressions. Phương thức này có thông số là text, pattern, và tập cờ từ cấu trúc liệt kê RegexOptions. Trong trường hợp này ta chỉ định tìm Trang 6
  14. Khoa CNTT [MÔI TRƯỜNG VÀ CÔNG CỤ LẬP TRÌNH] kiếm không phân biệt chữ hoa - thường. Và cờ ExplicitCapture, cập nhật cách mà match được thu thập. Ta sẽ thấy tại sao hàm Matches() trả về một tham chiếu đến đối tượng MatchCollection. Match là một thuật ngữ kĩ thuật cho những kết quả của việc tìm một thể hiện của pattern trong biểu thức. Được trình bày bởi lớp System.Text.RegularExpressions.Match. Do đó ta sẽ trả về một MatchCollection chứa tất cả các match, mỗi cái đưọc trình bày bởi một đối tượng Match. Trong đoạn mã trên, ta đơn giản lặp trên tập thu được và dùng thuộc tính index của lớp Match, mà trả về chỉ mục trong đoạn input nơi mà match được tìm thấy. Khi chạy nó sẽ tìm ra 4 match. Ý nghĩa Ví dụ Examples that this will match ^ Bắt đầu của chuổi nhập ^B B, nhưng chỉ nếu kí tự đầu tiên trong chuỗi $ Kết thúc của chuỗi nhập X$ X, nhưng chỉ nếu kí tự cuối cùng trong chuỗi . Bất kì kí tự nào ngoại trừ kí tự xuống i.ation isation, ization dòng(\n) * Kí tự trước có thể được lặp lại 0 hoặc nhiều ra*t rt, rat, raat, raaat, and so on lần + Kí tự trước có thể được lặp lại một hoặc ra+t rat, raat, raaat and so on, (but not nhiều lần rt) ? Kí tự trước có thể được lặp lại 0 hoặc 1 lần ra?t rt and rat only \s Bất kì kí tự khoảng trắng \sa [space]a, \ta, \na (\t and \n có ý nghĩa giống như trong C#) \S Bất kì kí tự nào không phải là khoảng trắng \SF aF, rF, cF, but not \tf \b Từ biên ion\b any word ending in ion Trang 7
  15. Khoa CNTT [MÔI TRƯỜNG VÀ CÔNG CỤ LẬP TRÌNH] \B bất kì vị trí nào không phải là từ biên \BX\B bất kì kí tự X ở giữa của một từ 1.3.2 Trình bày kết quả Trong phần này ta sẽ xét ví dụ RegularExpressionsPlayaround. Để ta thiết lập một vài biểu thức chính quy và trình bày kết quả để thấy cách mà biểu thức chính quy làm việc. Tâm điểm là phương thức WriteMatches(), mà trình bày tất cả các match từ MatchCollection theo định dạng chi tiết hơn. Trong mỗi match, nó trình bày chỉ mục nơi mà match được tìm thấy trong chuỗi nhập, chuỗi của match bao gồm match cộng thêm 19 kí tự bao quanh nó trong chuỗi nhập - 5 kí tự đứng trước và 5 kí tự đứng sau. (Nhỏ hơn 5 kí tự nếu match xuất hiện trong 5 kí tự của phần đầu và kết thúc của đoạn nhập.) Ví dụ match trên từ messaging mà xuất hiện gần cuối của chuỗi nhập được đánh dấu sẽ trình bày "and messaging of d" ( 5 kí tự trước và sau match)nhưng một match trên từ cuối data sẽ trình bày "g of data. "( chỉ một kí tự sau match). Bởi vì sao đó là cuối chuỗi. Một chuỗi dài hơn để ta thấy rõ nơi biểu thức chính quy định vị match: static void WriteMatches(string text, MatchCollection matches) { Console.WriteLine("Original text was: \n\n" + text + "\n"); Console.WriteLine("No. of matches: " + matches.Count); foreach (Match nextMatch in matches) { int Index = nextMatch.Index; string result = nextMatch.ToString(); int charsBefore = (Index < 5) ? Index : 5; int fromEnd = text.Length - Index - result.Length; int charsAfter = (fromEnd < 5) ? fromEnd : 5; int charsToDisplay = charsBefore + charsAfter + result.Length; Console.WriteLine("Index: {0}, \tString: {1}, \t{2}", Index, result, Trang 8
  16. Khoa CNTT [MÔI TRƯỜNG VÀ CÔNG CỤ LẬP TRÌNH] text.Substring(Index - charsBefore, charsToDisplay)); } } Phần lớn của quy trình trong phương thức này minh hoạ số kí tự đượctrình bày trong chuỗi con dài hơn mà nó có thể trình bày không quan tâm đến đầu hay cuối chuỗi. Lưu ý ta sử dụng một thuộc tính khác của đối tượng Match, Value, chứa chuỗi xác định trong Match.RegularExpressionsPlayaround chứa một số phương thức với tên như là Find1, Find2 mà biểu diễn việc tìm kiếm dựa trên ví dụ trong phần này. Ví dụ find2 tìm bất kì chuỗi chứa n vào lúc đầu của một từ : static void Find2() { string text = @"XML has made a major impact in almost every aspect of software development. Designed as an open, extensible, self-describing language, it has become the standard for data and document delivery on the web. The panoply of XML-related technologies continues to develop at breakneck speed, to enable validation, navigation, transformation, linking, querying, description, and messaging of data."; string pattern = @"\bn"; MatchCollection matches = Regex.Matches(text, pattern, RegexOptions.IgnoreCase); WriteMatches(text, matches); } Cùng với phương thức này là một phương thức main() mà ta có thể chỉnh sửa đề chọn một trong những phương thức Find ( ): static void Main() { Find1(); Trang 9
  17. Khoa CNTT [MÔI TRƯỜNG VÀ CÔNG CỤ LẬP TRÌNH] Console.ReadLine(); } 1.3.3 Matches, Groups, and Captures: Một đặc tính hay nữa của biểu thức chính quy là ta có thể nhóm những kí tự cùng nhau. Nó làm việc theo cùng cách như lệnh hợp trong C#. Trong Pattern biểu thức chính quy, ta có thể nhóm bất kì kí tự (bao gồm metacharacters và chuỗi escape) với nhau, và kết quả xem như là một kí tự đơn. Chỉ khác là ta dùng ngoặc đơn thay cho ngoặc vuông. Chuỗi kết quả gọi là group. Ví dụ pattern (an)+ sẽ định vị bất kì chuỗi an. Quatifier + áp dụng chỉ cho kí tự trước nó. Nhưng bởi vì ta đã nhóm chúng lại nên việc áp dụng sẽ cho cả an như là một kí tự thống nhất. Ví dụ ta dùng (an)+ trong chuỗi nhập "bananas came to Europe late in the annals of history", sẽ cho anan từ bananas, nếu chỉ viết an+ thì sẽ có ann từ annals, cũng như 2 chuỗi tách biệt an từ bananas. Biểu thức (an)+ sẽ bắt sự xuất hiện của an, anan, ananan, Trong khi biểu thức an+ sẽ bắt sự xuất hiện của an, ann, annn, ta có thể thắc mắc là (an)+ sẽ cho anan từ bananas chứ không phải là an, bởi vì theo luật thì nếu có 2 trường hợp có khả năng ( ở đây là an và anan ) thì mặc định match sẽ chứa khả năng dài nhất có thể. ví dụ khác : ta có URL có định dạng sau: :// : port là tuỳ chọn. Ví dụ của URL là giả sử ta muốn lấy protocol, address, port từ URL trên. Ta biết là có thể có khoảng trắng hoặc không có (nhưng không có dấu chấm). Ta có thể dùng biểu thức sau: \b(\S+)://(\S+)(?::(\S+))?\b Đây là cách biểu thức làm việc. Đầu tiên là phần đầu và đuôi là chỗi \b bảo đảm rằng chúng ta chỉ quan tâm đến phần kí tự mà là từ nguyên vẹn. Trong đó, nhóm đầu tiên là (\S+):// sẽ lấy một hoặc nhiều kí tự mà không đếm khoảng trắng, mà theo sau bởi :// điều này sẽ lấy http:// vào phần đầu của HTTP URL. Chuỗi con (\S+) sẽ lấy phần như là Trang 10
  18. Khoa CNTT [MÔI TRƯỜNG VÀ CÔNG CỤ LẬP TRÌNH] www.wrox.com của URL trên. Nhóm này cũng kết thúc khi nó gặp phần cuối của từ ( \b) hoặc nó gặp dấu hai chấm (:) đánh dấu phần kế tiếp. Phần kế tiếp được lấy là port. Dấu ? chỉ định nhóm này là tuỳ chọn trong match Quan trọng là số port không phải luôn được đặc tả trong URL - có lúc nó không có mặt. Ta muốn chỉ định dấu hai chấm có thể xuất hiện hoặc không, nhưng ta không muốn lưu trữ dấu hai chấm trong nhóm. Ta làm điều này bằng cách tạo 2 group lồng nhau. Cái bên trong ( \S+) sẽ lấy những thứ sau dấu hai chấm ( ví dụ ở đây là 4355) nhóm ngoài chứa đựng nhóm trong đứng trước dấu hai chấm mà được đứng trước chuỗi ?: , chuỗi này chỉ định rằng nhóm trong câu hỏi không được lưu ( ta chỉ muốn lưu 4355; không lưu :4355) đừng nhầm lẫn bởi 2 dấu hai chấm - dấu đầu tiên là của phần chuỗi ?: nói rằng ' không lưu nhóm này' , và cái thứ hai là kí tự được tìm kiếm. Nếu ta chạy pattern này trên chuỗi : Hey I've just found this amazing URI at http:// what was it - oh yes Ta sẽ lấy một match Trong match này có 3 nhóm đưọc đề cập do đó có thể mỗi nhóm có thể không lấy gì, một hoặc nhiều hơn một nhóm. Mỗi match riêng này được biết đến như là capture. Vì thế, nhóm đầu tiên , (\s+), có một capture, http. Nhóm thứ hai cũng có một capture, www.wrox.com nhưng nhóm thứ ba không có capture, bởi vì không có số port trong URL này. Lưu ý chuỗi chứa một nửa Mặc dù điều này không phù hợp với nhóm đầu tiên của ta. Nó sẽ không được lấy ra qua tìm kiếm bởi vì biểu thức tìm kiếm đầy đủ sẽ không phủ hợp vời phần kí tự này. Ta không phải biểu diễn bất kì ví dụ của C# mà dùng Groups và captures. Nhưng ta sẽ đề cập những lớp .NET RegularExpressions hổ trợ groups và captures, là những lớp Group và Capture. Cũng có những lớp GroupCollection và CaptureCollection, mà trình bày việc thu thập groups và captures. Lớp Match chứa phương thức Group(). Mà trả về một đối tượng GroupCollection. Lớp Group thi hành một phương thức Captures() mà trả về một CaptureCollection. Mối quan hệ giữa những đối tượng được thể hiện qua biểu đồ sau : Trang 11
  19. Khoa CNTT [MÔI TRƯỜNG VÀ CÔNG CỤ LẬP TRÌNH] Việc trả về một đối tượng Group mỗi lần ta muốn nhóm một số kí tự cùng với nhau có thể không phải là những gì ta muốn làm. Có một số overhead liên quan đến việc khởi tạo đối tượng, mà bị lãng phí nếu tất cả những gì ta muốn là nhóm một vài kí tự cùng nhau như là một phần pattern. Ta có thể không cho phép điều này bằng việc bắt đầu nhóm với chuỗi kí tự ?: cho mỗi nhóm riêng, khi ta làm trong ví dụ URL, hoặc cho tất cả những nhóm bằng việc chỉ định cờ RegExOptions.ExplicitCaptures trên phương thức REgEX.Matches() như ta đã làm trong các ví dụ trước. 1.4 Nhóm các đối tượng Chúng ta đã khảo sát một số lớp cơ sở của .NET có cấu trúc dữ liệu trong đó một số đối tượng được nhóm với nhau. Cấu trúc đơn giản mà ta đã học là mảng, đây là một thể hiện của lớp System.Array. Mảng có lợi điểm là ta có thể truy nhập từng phần tử thông qua chỉ mục. Tuy nhiên khuyết điểm của nó là ta phải khởi tạo kích thước của nó. Không thể thêm, chèn hoặc bỏ một phần tử sau đó. Và phải có một chỉ mục số để truy nhập vào một phần tử. Điều này không tiện lắm ví dụ như khi ta làm việc với một bản ghi nhân viên và muốn tìm bản ghi theo tên nhân viên. Trang 12
  20. Khoa CNTT [MÔI TRƯỜNG VÀ CÔNG CỤ LẬP TRÌNH] .NET có một số cấu trúc dữ liệu khác hổ trợ cho công việc này. Ngoài ra còn có một số inteface, mà các lớp có thể khai báo chúng hổ trợ tất cả chức năng của một kiểu cụ thể cấu trúc dữ liệu. Chúng ta sẽ xem xét 3 cấu trúc sau : - Array lists - Collection - Dictionary ( hay maps) Các lớp cấu trúc dữ liệu này nằm trong namespace System.Collection 1.4.1 Array lists Arraylist giống như mảng, ngoại trừ nó có khả năng phát triển. Được đại diện bởi lớp System.Collection.Arraylist. Lớp Arraylist cũng có một một vài điểm tương tự với lớp StringBuilder mà ta tìm hiểu trưóc đây. Như StringBuilder cấp phát đủ chỗ trống trong vùng nhớ để lưu trữ một số kí tự, và cho phép ta thao tác các kí tự trong chỗ trống đó, the Arraylist cấp đủ vùng nhớ để lưu trữ một số các tham chiếu đối tượng. Ta có thể thao tác trên những tham chiếu đối tượng này. Nếu ta thử thêm một đối tượng đến Arraylist hơn dung lượng cho phép của nó, thì nó sẽ tự động tăng dung lượng bằng cách cấp phát thêm vùng nhớ mới lớn đủ để giữ gấp hai lần số phần tử của dung lượng hiện thời. Ta có thể khởi tạo một danh sách bằng cách chỉ định dung lượng ta muốn. Ví dụ, ta tạo ra một danh sách Vectors: ArrayList vectors = new ArrayList(20); Nếu ta không chỉ định kích cỡ ban đầu , mặc định sẽ là 16: ArrayList vectors = new ArrayList(); // kích cỡ là 16 Ta có thể thêm phần tử bằng cách dùng phương thức Add(): vectors.Add(new Vector(2,2,2)); vectors.Add(new Vector(3,5,6)); Trang 13
  21. Khoa CNTT [MÔI TRƯỜNG VÀ CÔNG CỤ LẬP TRÌNH] Arraylist xem tất cả các phần tử của nó như là các tham chiếu đối tượng. Nghĩa là ta có thể lưu trữ bất kì đối tượng nào mà ta muốn trong một Arraylist. Nhưng khi truy nhập đến đối tượng, ta sẽ cần ép kiểu chúng trở lại kiểu dữ liệu tương đương: Vector element1 = (Vector)vectors[1]; Ví dụ này cũng chỉ ra Arraylist định nghĩa một indexer, để ta có thể truy nhập những phần tử của nó với cấu trúc như mảng. Ta cũng có thể chèn các phần tử vào array list: vectors.Insert(1, new Vector(3,2,2)); // chèn vào vị trí 1 Đây là phương thức nạp chồng có ích khi ta muốn chèn tất cả các phần tử trong một collection vào arraylist ta có thể bỏ một phần tử : vectors.RemoveAt(1); // bỏ đối tượng ở vị trí 1 Ta cũng có thể cung cấp một đối tượng tham chiếu đến một phương thức khác, Remove(). Nhưng làm điều này sẽ mất nhiều thời gian hơn vì arraylist phải quét qua toàn bộ mảng để tìm đối tượng. Lưu ý rằng việc thêm và bỏ một phần tử sẽ làm cho tất cả các phần tử theo sau phải bị thay đổi tương ứng trong bộ nhớ, thậm chí nếu cần thì có thể tái định vị toàn bộ Arraylist. Ta có thể cập nhật hoặc đọc dung lượng qua thuộc tính : vectors.Capacity = 30; Tuy nhiên việc thay đổi dung lương đó sẽ làm cho toàn bộ Arraylist được tái định vị đến một khối bộ nhớ mới với dung lượng đưọc yêu cầu. Để biết số phần tử thực sự trong arraylist ta dùng thuộc tính Count : int nVectors = vectors.Count; Một arraylist có thể thực sự hữu ích nếu ta cần xây dựng một mảng đối tuợng mà ta không biết kích cỡ của mảng sẽ là bao nhiêu. Trong trường hợp đó, ta có thể xây dựng 'mảng' trong Arraylist, sau đó sao chép Arraylist trở lại mảng khi ta hoàn thành xong nếu Trang 14
  22. Khoa CNTT [MÔI TRƯỜNG VÀ CÔNG CỤ LẬP TRÌNH] ta thực sự cần dữ liệu như là một mảng (ví dụ nếu mảng được truyền đến một phương thức xem mảng là một thông số). Mối quan hệ giữa Arraylist và Array theo một cách nào đó giống như mối quan hệ giữa StringBUilder và String không như lớp StringBuilder, không có phương thức đơn nào để làm việc chuyển đổi từ một arraylist sang array. Ta phải dùng một vòng lặp để sao chép thủ công trở lại. Tuy nhiên ta chỉ phải sao chép tham chiếu chứ không phải đối tượng: // vectors is an ArrayList instance being used to store Vector instances Vector [] vectorsArray = new Vector[vectors.Count]; for (int i=0 ; i< vectors.Count ; i++) vectorsArray[i] = (Vector)vectors [i]; 1.4.2 Collections Ý tưởng của Collection là nó trình bày một tập các đối tượng mà ta có thể truy xuất bằng việc bước qua từng phần tử. Cụ thể là một tập đối tượng mà ta có thể truy nhập sử dụng vòng lặp foreach. nói cách khác, khi viết một thứ gì đó như: foreach (string nextMessage in messageSet) { DoSomething(nextMessage); } Ta xem biến messageSet là một collection. Khả năng để dùng vòng lặp foreach là mục đích chính của collection. Tiếp theo ta tìm hiểu chi tiết collection là gì và thi hành một collection riêng bằng việc chuyển ví dụ Vector mà ta đã phát triển . Trang 15
  23. Khoa CNTT [MÔI TRƯỜNG VÀ CÔNG CỤ LẬP TRÌNH] 1.4.2.1 Collection là gì ? Một đối tượng là một collection nếu nó có thể cung cấp một tham chiếu đến một đối tượng có liên quan, được biết đến như là enumarator, mà có thể duyệt qua từng mục trong collection. đặc biệt hơn, một collection phải thi hành một interface. System.Collections.IEnumerable. IEnumerable định nghĩa chỉ một phương thức như sau: interface IEnumerable { IEnumerator GetEnumerator(); } Mục đích của GetEnumarator() là để trả về đối tuợng enumarator. Khi ta tập họp những đoạn mã trên đối tượng enumarator được mong đợi để thi hành một interface, System.Collections.IEnumerator. Ngoài ra còn có một interface khác Icollection, được dẫn xuất từ IEnumerable. Những collection phức tạp hơn sẽ thi hành interface này. Bên cạnh GetEnumerator(), nó thi hành một thuộc tính trả về trực tiếp số phần tử trong collection. Nó cũng có đặc tính hổ trợ việc sao chép collection đến một mảng và có thể cung cấp thông tin đặc tả nếu đó là một luồng an toàn.tuy nhiên trong phần này ta chỉ xem xét interface IEnumerable. IEnumarator có cấu trúc sau: interface IEnumerator { object Current { get; } bool MoveNext(); void Reset(); } IEnumarator làm việc như sau: đối tuợng thực thi nên được kết hợp với một collection cụ thể. Khi đối tượng này được khởi động lần đầu tiên, nó chưa trỏ đến bất kì một phần tử nào trong collection, và ta phải gọi MoveNext(), mà sẽ di chuyển enumarator Trang 16
  24. Khoa CNTT [MÔI TRƯỜNG VÀ CÔNG CỤ LẬP TRÌNH] để nó chuyển đến phần tử đầu tiên trong collection. Ta có thể nhận phần tử này với thuộc tính Current. Current trả về một tham chiếu đối tượng, vì thế ta sẽ ép kiểu nó về kiểu đối tượng mà ta muốn tìm trong Collection. Ta có thể làm bất cứ điều gì ta muốn với đối tượng đó sau đó di chuyển đến mục tiếp theo trong collection bằng cách gọi MoveNext() lần nữa. Ta lập lại cho đến khi hết mục trong collection- khi current trả về null. Nếu muốn ta có thể quay trở về vị trí đầu trong collection bằng cách gọi Reset(). Lưu ý rằng Reset() thực sự trả về trước khi bắt đầu collection, vì thế nếu muốn di chuyển đến phần tử đầu tiên ta phải gọi MoveNext() collection là một kiểu cơ bản của nhóm đối tượng. Bởi vì nó không cho phép ta thêm hoặc bỏ mục trong nhóm. Tất cả ta có thể làm là nhận các mục theo một thứ tự được quyết định bởi collection. Và kiểm tra chúng, thậm chí ta không thể thay thế hoặc cập nhật mục vì thuộc tính current là chỉ đọc. Hầu như cách dùng thường nhất của collection là cho ta sự thuận tiện trong cú pháp của lặp foreach. Mảng cũng là một collection, nhưng lệnh foreach làm việc tốt hơn mảng. Ta có thể xem vòng lặp foreach trong C# là cú pháp ngắn trong việc viết: { IEnumerator enumerator = MessageSet.GetEnumerator(); string nextMessage; enumerator.MoveNext(); while ( (nextMessage = enumerator.Current) != null) { DoSomething(nextMessage); // NB. We only have read access // toNextMessage enumerator.MoveNext(); } } Một khía cạnh quan trọng của collection là bộ đếm được trả về như là một đối tượng riêng biệt.lý do là để cho phép khả năng có nhiều hơn một bộ đếm có thể áp dụng đồng thời trong cùng collection. Trang 17
  25. Khoa CNTT [MÔI TRƯỜNG VÀ CÔNG CỤ LẬP TRÌNH] 1.4.2.2 Thêm collection hổ trợ cấu trúc Vector Trong lần cuối cùng ta nói về Vector, một thể hiện của Vector chứa đựng 3 phần: x,y,z. Nó có thể đuợc xem một thể hiện Vector là một mảng, để ta có thể truy nhập vào phần x bằng cách viết someVector[0], phần y bằng cách viết someVecor[1] và z là someVector[2]. Bây giờ ta sẽ mở rộng cấu trúc vector, dự án VectorAsCollection mà cũng có thể quét qua các phần của một vector bằng cách viết : foreach (double component in someVector) Console.WriteLine("Component is " + component); Nhiệm vụ đầu tiên của ta là biểu thị vector như là một collection bằng việc cho nó thực thi interface IEnumerable, ta bắt đầu bằng việc cập nhật khai báo của cấu trúc vector: struct Vector : IFormattable, IEnumerable { public double x, y, z; Bây giờ ta thi hành interface IEnumerable : public IEnumerator GetEnumerator() { return new VectorEnumerator(this); } Việc thi hành GetEnumerator() hầu như là đơn giản, nhưng nó tuỳ thuộc trên sự tồn tại của một lớp mới, VectorEnumerator, mà ta cần định nghĩa. Vì VectorEnumerator không phải là một lớp mà bất kì đoạn mã bên ngoài có thể thấy trực tiếp, ta khai báo nó là lớp private bên trong cấu trúc Vector. Việc định nghĩa nó như sau: private class VectorEnumerator : IEnumerator { Trang 18
  26. Khoa CNTT [MÔI TRƯỜNG VÀ CÔNG CỤ LẬP TRÌNH] Vector theVector; // Vector object that this enumerato refers to int location; // which element of theVector the enumerator is // currently referring to public VectorEnumerator(Vector theVector) { this.theVector = theVector; location = -1; } public bool MoveNext() { ++location; return (location > 2) ? false : true; } public object Current { get { if (location 2) throw new InvalidOperationException( "The enumerator is either before the first element or " + "after the last element of the Vector"); return theVector[(uint)location]; } } public void Reset() { location = -1; Trang 19
  27. Khoa CNTT [MÔI TRƯỜNG VÀ CÔNG CỤ LẬP TRÌNH] } } Khi được yêu cầu như một bộ đếm, VectorEnumerator thi hành interface IEnumerator. Nó cũng chứa hai trường thành viên, theVector, một tham chiếu đến Vector (collection) mà bộ đếm kết hợp, location, một số nguyên mà chỉ định nơi trong collection mà bộ đếm tham chiếu đến. Cách làm việc là xem location như là chỉ mục và thi hành enumerator để truy nhập Vector như mảng.khi truy nhập vector như mảng giá trị chỉ mục là 0,1,2 – ta mở rộng bằng cách dùng -1 như là giá trị chỉ định bộ đếm trước khi bắt đầu collection,và 3 để chỉ nó đến cuối của collection. Vì vậy, việc khởi tạo của trường nay là -1 trong hàm dựng VectorEnumerator : public VectorEnumerator(Vector theVector) { this.theVector = theVector; location = -1; } Lưu ý rằng hàm dựng cũng lấy một tham chiếu đến thể hiện của Vector mà chúng ta định đếm - điều này được cung cấp trong phương thức Vector.GetEnumerator : public IEnumerator GetEnumerator() { return new VectorEnumerator(this); } 1.4.3 Dictionaries Từ điển trình bày một cấu trúc dữ liệu rất phức tạp mà cho phép ta truy nhập vào các phần tử dựa trên một khoá nào đó, mà có thể là kiểu dữ liệu bất kì.ta hay gọi là bảng ánh xạ hay bảng băm.Từ điển được dùng khi ta muốn lưu trữ dữ liệu như mảng nhưng muốn dùng một kiểu dữ liệu nào đó thay cho kiểu dữ liệu số làm chỉ mục.nó cũng cho Trang 20
  28. Khoa CNTT [MÔI TRƯỜNG VÀ CÔNG CỤ LẬP TRÌNH] phép ta thêm hoặc bỏ các mục , hơi giống danh sách mảng tuy nhiên nó không phải dịch chuyển các mục phía sau trong bộ nhớ. Ta minh họa việc dùng từ điển trong ví dụ sau:MortimerPhonesEmployees. Trong ví dụ này công ty điện thoại có vài phần mềm xử lí chi tiết nhân viên. Ta cần một cấu trúc dữ liệu chứa dữ liệu của nhân viên. Ta giả sử rằng mỗi nhân viên trong công ty được xác định bởi ID nhân viên, là tập kí tự như B342 và được lưu trữ thành đối tượng EmployyeeID. Chi tiết của nhân viên được lưu trữ thành đối tượng EmployeeData, ví dụ chỉ chứa ID, tên, lương của nhân viên. Giả sử ta có EmployeeID: EmployeeID id = new EmployeeID("W435"); Và ta có một biến gọi là employees, mà ta có thể xem như một mảng đối tượng EmployeeData. Thực sự đó không phải là mảng - đó là từ điển và bởi vì nó là từ điển nên ta có thể lấy chi tiết của một nhân viên thông qua ID đuợc khai báo trên: EmployeeData theEmployee = employees[id]; // lưu ý rằng ID không phải kiểu số- đó là một thể hiện của EmployeeID Ta có thể dùng kiểu dữ liệu bất kì làm chỉ mục, lúc này ta gọi là khoá chứ không phải là chỉ mục nữa. Khi ta cung cấp một khoá truy nhập vào một phần tử (như ID trên), xử lí trên giá trị của khoá và trả về một số nguyên tuỳ thuộc vào khoá, và được dùng để truy nhập vào 'mảng' để lấy dữ liệu. 1.4.3.1 Từ điển trong .NET Trong .NET , từ điển cơ bản được trình bày qua lớp Hasthable, mà cách làm việc cũng giống như từ điển thực. Nghĩa là một bảng băm có thể lưu trữ bất kì cấu trúc dữ liệu nào ta muốn. Ta có thể tự định nghĩa một lớp từ điển riêng cụ thể hơn. Microsoft cung cấp một lớp cơ sở trừu tượng, DictionaryBase, cung cấp những chức năng cơ bản của từ điển mà ta có thể dẫn xuất đến lớp mà ta muốn tạo. Nếu khoá là chuỗi ta có thể dùng lớp Trang 21
  29. Khoa CNTT [MÔI TRƯỜNG VÀ CÔNG CỤ LẬP TRÌNH] System.Collections.Specialized.StringDictionary thay cho Hasthable. khi tạo một Hasthable ta có thể chỉ định kích thước khởi tạo: Hasthable employees = new Hasthable(53); Ở đây ta chọn số 53 bởi vì thuật toán bên trong được dùng cho từ điển làm việc hiệu quả hơn nếu kích thước của nó là một số nguyên tố. Thêm đối tượng vào từ điển ta dùng phương thức Add(), có 2 thông số kiểu object: thông số đầu là khoá, thứ hai là một tham chiếu đến dữ liệu. ví dụ: EmployeeID id; EmployeeData data; // khởi tạo id và dữ liệu. // giả sử employees là một thể hiện của bảng băm //mà chứa đựng các tham chiếu EmployeeData employees.Add(id, data); để nhận dữ liệu ta cung cấp khoá cho nó: EmployeeData data = employees[id]; để bỏ một mục ta cung cấp khoá và gọi : employees.Remove(id); Để đếm số mục trong từ điển ta dùng thuộc tính Count: int nEmployees = employees.Count; Việc lưu trữ trong từ điển không theo phải theo kiểu từ trên xuống, nghĩa là ta không thể tìm thấy một khối lớn dữ liệu ở phần đầu của cấu trúc và một khối rỗng ở phần cuối. biểu đồ sau minh hoạ cho việc lưu trữ trong từ điển, các phần không đánh dấu là Trang 22
  30. Khoa CNTT [MÔI TRƯỜNG VÀ CÔNG CỤ LẬP TRÌNH] rỗng: 1.4.3.2 Cách từ điển làm việc: Hasthable ( hay bất kì lớp từ điển nào khác) sử dụng vài thuật toán để thực hiện việc đặt mỗi đối tượng dựa trên khoá. Có 2 giai đoạn, và phần mã cho từng giai đoạn phải được cung cấp bởi lớp khoá. Một phần của thuật toán thực thi bởi lớp khoá gọi là băm (vì vậy có thuật ngữ bảng băm) và lớp Hasthable tìm một nơi cụ thể cho thuật toán băm. nó nhìn vào phương thức Gethashcode() trong đối tượng của ta, mà thừa kế từ System.Object() nếu ta nạp chồng GetHashCode(). Cách nó làm việc là Gethashcode() trả vế một số nguyên. Bằng cách nào đó nó dùng giá trị của khoá để sinh ra một số nguyên. Hasthable sẽ lấy số nguyên này và làm các việc xử lí khác trên nó mà liên quan đến việc tính toán toán học phức tạp, và trả về chỉ mục của mục đưọc lưu trữ tương ứng với khóa trong từ điển. Ta không đi sâu vào thuật toán này nhưng ta sẽ tìm hiểu tại sao nó liên quan đến số nguyên tố và tại sao dung lượng bảng băm nên là số nguyên tố. Có một số yêu cầu nghiêm ngặt khi ta nạp chồng GetHashCode(). Những yêu cầu này nghe có vẻ trừu tượng nhưng qua ví dụ MortimerPhonesEmployees ta sẽ thấy rằng không quá khó để viết lớp khoá thỏa mãn những đòi hỏi sau: Trang 23
  31. Khoa CNTT [MÔI TRƯỜNG VÀ CÔNG CỤ LẬP TRÌNH] - Phải nhanh ( bởi vì việc đặt và lấy các mục trong một từ điển được coi là nhanh) - Phải được đồng nhất - nếu ta cho 2 khoá cùng giá trị thì chúng phải cho cùng giá trị trong băm. - Cho những giá trị khả dĩ trong khoảng giá trị của một số kiểu int. Lí do của điều kiện cuối là: điều gì sẽ xảy ra nếu ta lấy 2 mục trong từ điển mà khi băm cả hai đều cho cùng một chỉ mục? Nếu điều này xảy ra, lớp từ điển sẽ phải bắt đầu tìm kiếm vị trí trống có giá trị gần nhất để lưu trữ mục thứ hai. Xung đột giữa các khóa cũng gia tăng khi từ điển đầy, vì thế cách tốt nhất là bảo đảm dung lượng lớn hơn số phần tử thực sự trong nó. Vì lí do này mà Hasthable tự định vị lại kích cỡ của nó để tăng dung lượng trước khi nó đầy. Tỷ lệ của bảng mà đầy gọi là load. Ta có thể thiết lập giá trị lớn nhất mà ta muốn load đến trước khi Hasthable tái định vị theo hàm dựng Hasthable khác : // dung lượng =50, Max Load = 0.5 Hasthable employees = new Hasthable(50, 0.5); Max load càng nhỏ bảng băm làm việc càng hiệu quả nhưng càng cần nhiều vùng nhớ. Khi bảng băm tái định vị để tăng dung lượng, luôn chọn một số nguyên tố làm dung lượng mới. Một điểm quan trọng khác là thuật toán băm phải đồng nhất. Nếu 2 đối tượng chứa những gì ta coi như là dữ liệu trùng, thì chúng phải cho cùng một giá trị băm, và điều này dẫn đến một giới hạn quan trọng trên cách nạp chồng phương thức Equals() và Gethashcode() của System.Object. Cách mà Hasthable quyết định 2 khoá a và b là bằng nhau là nó gọi a.equals(b). Nghĩa là ta phải chắc rằng điều sau luôn đúng: Nếu a.equals(b) là đúng thì a.gethashcode() và b.gethashcode() phải luôn trả về cùng mã băm. Nếu ta cố ý nạp chồng những phương thức này để những câu lệnh trên không đúng thì bảng băm sẽ không làm việc bình thường. Ví dụ như ta đặt một đối tượng vào bảng băm nhưng không nhận lại được nó hay nhận lại được nhưng không đúng mục. Trang 24
  32. Khoa CNTT [MÔI TRƯỜNG VÀ CÔNG CỤ LẬP TRÌNH] Trong system.object điều kiện này đúng, vì Equals() đơn giản so sánh 2 tham chiếu và gethashcode() thực sự trả về một băm dựa trên địa chỉ của đối tượng. Nghĩa là bảng băm dựa trên một khoá mà không nạp chồng những phương thức này sẽ làm việc đúng. Tuy nhiên, vấn đề với cách làm này là những khóa coi là bằng chỉ nếu chúng là cùng đối tượng. Nghĩa là khi đặt một đối tượng vào từ điển ta phải nối tham chiếu đến khóa. Ta không thể khởi tạo một khóa khác sau đó mà có cùng giá trị, vì cùng giá trị được định nghĩa theo nghĩa là cùng một thực thể. nghĩa là nếu ta không nạp chồng bản object của Equals() và Gethashcode(), lớp của ta sẽ không thuận lợi để dùng trong bảng băm. Tốt hơn nếu thi hành gethashcode() sinh ra một băm dựa trên giá trị của khoá hơn là điạ chỉ của nó trong bộ nhớ. Do đó ta sẽ cần nạp chồng gethashcode() va equals() trong bất kì lớp nào mà ta muốn nó được sử dụng như khoá System.String có những phương thức nạp chồng tương đương, Equals() được nạp chồng để cung cấp giá trị so sánh, và gethashcode() được nạp chồng để trả về một băm dựa trên giá trị của chuỗi. Vì lí do này thuận lợi để dùng chuỗi như là khoá trong từ điển. 1.4.3.3 Ví dụ MortimerPhonesEmployees Đây là chương trình thiết lập từ điển nhân viên. Chương trình khởi tạo từ điển, thêm vài nhân viên và sau đó mời người dùng gõ vào Id nhân viên. Mỗi khi gõ, chương trình dùng ID để trỏ vào tử điển và nhận chi tiết nhân ivên. quy trình lặp lại cho đến khi người dùng gõ X: MortimerPhonesEmployees Enter employee ID (format:A999, X to exit)> B001 Employee: B001: Mortimer £100,000.00 Enter employee ID (format:A999, X to exit)> W234 Employee: W234: Arabel Jones £10,000.00 Enter employee ID (format:A999, X to exit)> X Các lớp của chương trình : Trang 25
  33. Khoa CNTT [MÔI TRƯỜNG VÀ CÔNG CỤ LẬP TRÌNH] class EmployeeID { private readonly char prefix; private readonly int number; public EmployeeID(string id) { prefix = (id.ToUpper())[0]; number = int.Parse(id.Substring(1,3)); } public override string ToString() { return prefix.ToString() + string.Format("{0,3:000}", number); } public override int GetHashCode() { return ToString().GetHashCode(); } public override bool Equals(object obj) { EmployeeID rhs = obj as EmployeeID; if (rhs == null) return false; if (prefix == rhs.prefix && number == rhs.number) return true; return false; } } Trang 26
  34. Khoa CNTT [MÔI TRƯỜNG VÀ CÔNG CỤ LẬP TRÌNH] Phần định nghĩa đầu tiên của lớp lưu trữ ID.bao gồm một kí tự chữ đứng đầu theo sau là 3 kí tự số. Ta dùng kiểu char để lưu chữ đầu và int để lưu phần sau. Hàm dựng nhận một chuỗi và ngắt nó thành những trường này.phuơng thức Tostring() trả về ID là chuỗi: return prefix.ToString() + string.Format("{0,3:000}", number); Phần đặc tả định dạng (0,3:000) để phần int chứa số được điền thêm số 0 ví dụ ta sẽ có B001 không phải B1ta đến 2 phương thức nạp chồng trong từ điển : Đầu tiên là Equals() để so sánh giá trị của những thể hiện EmployeeID : public override bool Equals(object obj) { EmployeeID rhs = obj as EmployeeID; if (rhs == null) return false; if (prefix == rhs.prefix && number == rhs.number) return true; return false; } } Đầu tiên ta kiểm tra xem đối tượng trong thông số có phải là một thể hiện của EmployeeID không bằng cách thử ép kiểu nó thành đối tượng EmployeeID. Sau đó ta chỉ việc so sánh những trường giá trị của nó có chứa cùng giá trị như đối tuợng này không. Tiếp theo là Gethashcode(): public override int GetHashCode() { string str = this.ToString(); return str.GetHashCode(); } Trang 27
  35. Khoa CNTT [MÔI TRƯỜNG VÀ CÔNG CỤ LẬP TRÌNH] Phần trên ta đã xem xét các yêu cầu giới hạn mà mã băm được tính phải thỏa mãn. Tất nhiên có những cách để nghĩ ra những thuật toán băm hiệu quả và đơn giản. Tói chung, lấy một trường, nhân nó với một số nguyên tố lớn, và công những kết quả lại với nhau là một cách tốt. Nhưng ta không phải làm những điều đó vì MIcrosoft đã làm toàn bộ trong lớp String, vì thế ta có thể lợi dụng lớp này để tạo ra số dựa trên nội dung của chuỗi. Nó sẽ thoã mãn tất cả những yêu cầu của mã băm. Chỉ có một khuyết điểm khi dùng phương thức này là có vài việc thi hành đã mất kết hợp với việc chuyển đổi lớp EmployeeID thành chuỗi trong phần đầu tiên. Nếu không muốn điều này ta sẽ cần thiết kế mã băm riêng thiết kế thuật toán băm là một chủ đề phức tạp mà ta không không thể đi sâu trong cuốn sách này. Tuy nhiên ta sẽ đưa ra một cách đơn giản cho vấn đế này, mà chỉ việc nhân số dựa trên những trường thành phần của lớp với số nguyên tố khác (nhân bởi một số nguyên tố khác giúp ta ngăn ngừa sự kết hợp giá trị khác nhau của các trường từ việc cho cùng mã băm): public override int GetHashCode() // alternative implementation { return (int)prefix*13 + (int)number*53; } Ví dụ này sẽ làm việc nhanh hơn Tostring() mà ta dùng ở trên. Tuy nhiên khuyết điểm là mã băm sinh ra bởi các employeeID khác nhau thì không trải rộng trên vùng số kiểu int. Tiếp theo ta xem lớp chứa dữ liệu nhân viên: class EmployeeData { private string name; private decimal salary; private EmployeeID id; Trang 28
  36. Khoa CNTT [MÔI TRƯỜNG VÀ CÔNG CỤ LẬP TRÌNH] public EmployeeData(EmployeeID id, string name, decimal salary) { this.id = id; this.name = name; this.salary = salary; } public override string ToString() { StringBuilder sb = new StringBuilder(id.ToString(), 100); sb.Append(": "); sb.Append(string.Format("{0,-20}", name)); sb.Append(" "); sb.Append(string.Format("{0:C}", salary)); return sb.ToString(); } } Ta dùng đối tượng StringBuilder để sinh ra chuỗi đại diện cho đối tượng Employeedata. Cuối cùng ta viết đoạn mã kiểm tra lớp TestHarness: class TestHarness { Hashtable employees = new Hashtable(31); public void Run() { EmployeeID idMortimer = new EmployeeID("B001"); EmployeeData mortimer = new EmployeeData(idMortimer, "Mortimer", Trang 29
  37. Khoa CNTT [MÔI TRƯỜNG VÀ CÔNG CỤ LẬP TRÌNH] 100000.00M); EmployeeID idArabel = new EmployeeID("W234"); EmployeeData arabel= new EmployeeData(idArabel, "Arabel Jones", 10000.00M); employees.Add(idMortimer, mortimer); employees.Add(idArabel, arabel); while (true) { try { Console.Write("Enter employee ID (format:A999, X to exit)> "); string userInput = Console.ReadLine(); userInput = userInput.ToUpper(); if (userInput == "X") return; EmployeeID id = new EmployeeID(userInput); DisplayData(id); } catch (Exception e) { Console.WriteLine("Exception occurred. Did you use the correct format for the employee ID?"); Console.WriteLine(e.Message); Console.WriteLine(); } Console.WriteLine(); } Trang 30
  38. Khoa CNTT [MÔI TRƯỜNG VÀ CÔNG CỤ LẬP TRÌNH] } private void DisplayData(EmployeeID id) { object empobj = employees[id]; if (empobj != null) { EmployeeData employee = (EmployeeData)empobj; Console.WriteLine("Employee: " + employee.ToString()); } else Console.WriteLine("Employee not found: ID = " + id); } } Đầu tiên ta thiết lập dung lượng của từ điển là số nguyên tố, 31, phần chính của lớp này là phương thức run(). Đầu tiên là thêm vài nhân viên vào từ điển mortimer và arabel và thêm chi tiết của họ vào: employees.Add(idMortimer, mortimer); employees.Add(idArabel, arabel); Tiếp theo ta bước vào vòng lặp để yêu cầu người dùng nhập vào EmployeeID. Có khối try bên trong vòng lặp, bẫy những lỗi khi người dùng không gõ đúng định dạng cuả EmployeeID: string userInput = Console.ReadLine(); userInput = userInput.ToUpper(); if (userInput == "X") return; EmployeeID id = new EmployeeID(userInput); Trang 31
  39. Khoa CNTT [MÔI TRƯỜNG VÀ CÔNG CỤ LẬP TRÌNH] Nếu hàm dựng EployeeID đúng, ta trình bày kết hợp nhân viên bằng cách gọi DisplayData(). Đây là phương thức mà ta muốn truy nhập vào từ điển với cú pháp mảng. nhận dữ liệu nhân viên với ID là việc đầu tiên trong phương thức này: private void DisplayData(EmployeeID id) { object empobj = employees[id]; Nếu không có nhân viên với ID tên, thì employees[id] trả về Null, mà ta sẽ đưa ra thông báo lỗi nếu ta tìm thấy. Nếu không ta ép kiểu tham chiếu empobj thành EmployeeData. Khi ta có tham chiếu EmployeeID, ta trình bày dữ liệu của nó bằng phương thức EmployeeData.ToString(): EmployeeData employee = (EmployeeData)empobj; Console.WriteLine("Employee: " + employee.ToString()); Ta có phần cuối của mã - phương thức main() kích hoạt ví dụ trên . khởi tạo đối tượng TestHarness và chạy nó: static void Main() { TestHarness harness = new TestHarness(); harness.Run(); } Trang 32
  40. Khoa CNTT [MÔI TRƯỜNG VÀ CÔNG CỤ LẬP TRÌNH] Chương 2: Windows application Tổng quan: Trong lúc những ứng dụng kinh doanh phát triển ngày nay được thiết kế bởi World Wide Web, những client cổ điển vẫn tồn tại và sẽ luôn luôn được yêu cầu. Nó là một ứng dụng Intranet sử dụng bên trong một tổ chức hay là mẫu phần mềm cài đặt trên máy để bàn. Những chức năng mạnh và kinh nghiệm của người sử dụng như một môi trường cung cấp sẽ luôn luôn được yêu cầu cho những kiểu ứng dụng. Web form thì tuyệt vời nhưng chúng không thể so sánh kinh nghiệm của người sử dụng thông qua một client tốt. May mắn, .NET cung cấp một khả năng để tạo những client mạnh thực thi bên trong Common Language Runtime. Ứng dụng này gọi là Window form. Bất kỳ ngôn ngữ .NET nào cũng có thể sử dụng Window Form để xây dựng Windows Applications. Những ứng dụng này được truy cập đến .NET FrameWork của các namespace và đối tượng. Trong chương này ta bàn luận về cách để xây dựng các ứng dụng Windows trong .NET. Chúng ta sẽ bàn luận về một số chủ đề sau: Cách xây dựng ứng Window form sử dụng .NET FrameWork. Cách sử dụng Visual studio.NET để xây dựng ứng dụng Window form nhanh chóng. Thêm những menu hỗ trợ vào một ứng dụng bao gồm dynamic và context- sensitive menu. Các tài nguyên Utilizing custom và common dialog trong một đề án Cách sử dụng Visual inheritance để xây dựng ứng dụng Window Form. Cách sử dụng Window Form để điều khiển một ứng dụng Cách tạo và mở rộng những điều khiển cho những chức năng đặc biệt. Trang 33
  41. Khoa CNTT [MÔI TRƯỜNG VÀ CÔNG CỤ LẬP TRÌNH] Các sự kiện từ custom control 2.1 Windows Applications in .NET Thật là quan trọng để hiểu sự khác nhau giữa rich client và thin client, bởi vì nó là điểm cốt yếu để hiểu tại sao Windows applications được xem như rich clients. Một thin client không yêu cầu quá nhiều cài đặt và hình thể trước khi sử dụng những ứng dụng của nó. Rich clients đôi lúc gọi là fat clients, yêu cầu một vài mẫu cài đặt và hình thể trên máy client. Các rich client khi chạy trên client, thì chúng có thể đạt được thuận lợi đầy đủ của môi trường và năng lượng xử lý của máy client. Vấn đề này cho phép các nhà phát triển tạo một sự tác động qua lại và thân thiện với người dùng hơn. Trước .NET, các nhà phát triển có vài chọn lựa trong việc xây dựng một ứng dụng Window. Họ có thể có nhiều cách đi xuống lớp API và xây dựng một ứng dụng C hay C++ sử dụng Win32 API. Việc này là công việc rất khó và chi phối thời gian và không có nhiều cơ sở nào có thể bỏ ra nhiều thời gian để phát triển ứng dụng tại tầng lớp này. Bởi vì thế, Các môi trường mở ra để cung cấp một giao diện dễ dàng hơn cho Win32 API và cho phép các nhà phát triển có nhiều chọn lựa hơn. Microsoft Foundation Classes (MFC) là một thư viện lớp sử dụng C++ được gói gọn trong Win32 API. Visual Basic là một công cụ phát triển ứng dụng nhanh chóng, sử dụng một nguồn gốc của ngôn ngữ lập trình Basic cho phép mọi nhà phát triển tạo các ứng ụng Window tương đối nhanh chóng. Tuy nhiên, các giải pháp này đều không sử dụng .NET Framework hay CLR. .NET Framework chứa một tầng lớp năng xuất mới gói gọn trong Win32 API, và giống như MFC và Visual Basic, nó cho phép phát triển hiệu suất cao hơn và dễ dàng hơn của các ứng dụng Window. Môi trường này được gọi là Windows Forms; nó cho phép các nhà phát triển tạo một tương tác cấp cao và các ứng dụng Window lớn sử dụng bất kỳ ngôn ngữ .NET nào. Chúng ta sẽ được xem xét môi trường Windows Forms trong chương này. Bằng cách sử dụng .NET để tạo các ứng dụng Window, các nhà phát triển biết được nhiều tính năng mới và hay được cung cấp bởi .NET. Tất cả namespaces và classes Trang 34
  42. Khoa CNTT [MÔI TRƯỜNG VÀ CÔNG CỤ LẬP TRÌNH] trong .NET Framework có thể sử dụng bên trong một .NET rich client. Thêm vào đó, ứng dụng có thể được phát triển trong bất kỳ ngôn ngữ .NET nào; các thành phần khác nhau của ứng dụng có thể sử dụng các ngôn ngữ khác nhau. Hầu như tất cả chức năng chúng ta đang xem xét trong chương này không được giải thích thông qua các lớp chứa bên trong System.Windows.Forms namespace. Đây là một namespaces lớn chứa nhiều lớp và các namespace phụ vào, tất cả chúng làm cho ta dễ dàng tạo các ứng dụng Window. Biểu đồ bên dưới hiển thị vài lớp thừa hưởng từ System.Windows.Forms.Control. Đối tượng này hành động như một lớp cơ sở cho đa số lớp trong namespace này, và chứa nhiều chức năng cơ bản của giao diện hiển thị và tương tác với người dùng. Trong biểu đồ trên, nếu một lớp không được thêm vào đầu một namespace nó được chứa trong System.Windows.Forms namespace. Không hiển thị số lượng lớn lớp thừa hưởng từ Control, tất cả chúng cung cấp chức năng chuyên dụng. Chúng cung cấp truy cập đến thư viện bao quát của các control Windows Form. Biểu đồ này cung cấp một cái nhìn tổng quát về cách thừa kế và cách các lớp Form và Control ăn khớp trong sơ đồ lớn. System.Windows.Forms.Control - hành động này như lớp cơ bản cho phần lớn các lớp trong namespace. Nó chứa chức năng cơ bản của thao tác xử lý bàn phím và nhập từ chuột và xử lý tin nhắn window. Trang 35
  43. Khoa CNTT [MÔI TRƯỜNG VÀ CÔNG CỤ LẬP TRÌNH] System.Windows.Forms.ButtonBase - Lớp này hỗ trợ chức năng cơ bản của một nút mà mọi lớp thừa hưởng sử dụng trong các cách khác nhau. System.Windows.Forms.TextBoxBase - một lần nữa, lớp này là một lớp cơ sở được sử dụng để cung cấp chức năng và thuộc tính thông thuờng cho các lớp thừa hưởng. Cả hai lớp TextBox và RichTextBox sử dụng chức năng cung cấp bởi TextBoxBase. System.Windows.Forms.ScrollableControl - đây là một lớp cơ bản khác cung cấp hỗ trợ cho các lớp thừa hưởng. Lớp này quản lý sự phát sinh và hiển thị của các thanh cuộn đến người dùng để truy cập đến gốc của một hiển thị. System.Windows.Forms.ContainerControl - Lớp này quản lý chức năng yêu cầu cho một control để hành động như một sự chứa đựng những control khác. System.Windows.Forms.Panel - đây là control khác có thể chứa các control thêm vào, nhưng khác với lớp ContainerControl, nó phân loại các control một cách đơn giản. System.Windows.Forms.Form - Đây là lớp mà phân phát với việc tạo ra và hiển thị các cửa sổ. Lớp này có thể đuợc dùng để tạo bất kỳ loại cửa sổ nào: standard, toolbox, borderless, even modal dialog boxes và multi-document interfaces. System.Windows.Forms.UserControl - Đây là lớp có thể được dùng để thừa hưởng từ việc tạo một custom control đến việc được dùng trong một nơi phức tạp trong một ứng dụng hay tổ chức. 2.2 Windows Forms Hầu hết mọi ứng dụng Windows Form mở rộng chức năng của System.Windows.Forms. Chức năng cơ bản của lớp Form không thể tạo một cửa sổ có thể sống và tương tác trong môi trường Windows một cách đúng đắn. Đây là một thuận lợi như một điểm khởi đầu và bằng việc mở rộng lớp Form và thêm các control tuỳ biến và Trang 36
  44. Khoa CNTT [MÔI TRƯỜNG VÀ CÔNG CỤ LẬP TRÌNH] các bộ điều khiển sự kiện tuỳ biến, một ứng dụng rất hữu ích được tạo để có thể tương tác với người dùng và dữ liệu hiện tại thông qua một giao diện người dùng tinh vi. Chúng ta đang xem xét cách tiến trình này làm việc theo hai cách. Để hiểu tốt hơn cách mà Windows Forms hoạt động và cách nó tương tác với .NET Framework, chúng ta sẽ xây dựng một ứng dụng Window hoàn toàn mà không sử dụng Visual studio.NET. Nó sẽ cung cấp cho bạn một sự đánh giá mạnh mẽ về Visual studio.NET khi chúng ta chuyển đến xây dựng một ứng dụng Window Form sử dụng nó. VS.NET cho phép các nhà phát triển tạo ứng dụng Window Form nhanh hơn và hiệu quả hơn. 2.2.1 Windows Forms không sử dụng Visual Studio .NET Hầu hết mọi ứng dụng Window Form sẽ mở rộng lớp System.Windows.Form để tuỳ chỉnh và thêm nguyên lý kinh doanh. Vì thế, ứng dụng Windows Form đơn giản nhất sẽ trình bày bên dưới: using System; using System.Windows.Forms; namespace WindowsFormsApp { class MyForm : Form { static void Main(string[] args) { MyForm aForm = new MyForm(); Application.Run(aForm); } } Trang 37
  45. Khoa CNTT [MÔI TRƯỜNG VÀ CÔNG CỤ LẬP TRÌNH] } Để xem vấn đề này trong hành động, bạn hãy lưu đoạn mã trên với tên BasicForm.cs, sau đó biên dịch và chạy nó. Khi đó bạn sẽ thấy kết quả như sau: Khi ứng dụng trên được chạy, một cửa sổ cơ bản sẽ được mở ra. Chú ý rằng cửa sổ hành động giống như một cửa sổ chuẩn và có thể được thu nhỏ, mở to, kéo đi, hay đóng lại. Nó là một ứng dụng Window đầy đủ chức năng trong 13 dòng mã. Hãy xem đoạn mã của nó để hiểu những gì đang xảy ra trước khi ta thấy những điều thú vị hơn sau đây. class MyForm : Form Dòng này chỉ rằng lớp của chúng ta đang thừa hưởng từ lớp System.Windows.Forms.Form, có nghĩa là chúng giành được truy cập đến tất cả chức năng của lớp Form cơ bản. Tiếp đến, chú ý rằng trong phương thức Main() chúng ta tạo một thể hiện của đối tượng MyForm và chuyển nó đến phương thức Application.Run(): static void Main(string[] args) { MyForm aForm = new MyForm(); Application.Run(aForm); } Application là một lớp static trong System.Windows.Forms namespace, nó chứa các phương thức để bắt đầu và dừng các ứng dụng và các luồng. Phương thức Run() có Trang 38
  46. Khoa CNTT [MÔI TRƯỜNG VÀ CÔNG CỤ LẬP TRÌNH] thể chấp nhận vài tham số; bằng việc truyền vào một đối tượng Form chúng ta đang báo hiệu với .NET Framework bắt đầu xử lý các tin nhắn Window cho form này, và để thoát khỏi ứng dụng khi form này đóng. 2.2.1.1 Các Control Hãy thêm một control đơn giản Button vào form. Chúng ta sẽ thấy các sự kiện bao quát hơn, bây giờ chúng ta chỉ xem xét những gì nó làm để thêm một control vào một ứng dụng Window Form không dùng Visual studio.NET. Về cơ bản, mọi control trên form là một thành phần dữ liệu của lớp custom Form. Vì thế, để thêm một Button vào form, chúng ta sẽ thêm một thành phần dữ liệu Button mới vào lớp MyForm. Thêm dòng sau vào tập tin BasicForm.cs: class MyForm : Form { //Data member to hold Button control private Button BigButton; Trước khi thành phần dữ liệu này làm bất cứ điều gì hoặc hiển thị một nút trên form nó phải được khởi tạo và các thuộc tính khác nhau của Button phải được định hình. Nó nên được thực hiện trong constructor cho đối tượng MyForm. Tại thời điểm đó chúng ta sẽ cài các thuộc tính cho chính đối tượng Form, như là size và name. Chú ý rằng có nhiều thuộc tính có thể được cài và thực hiện. Vì thế trong constructor là thời điểm tốt nhất để thực hiện khởi tạo giá trị. Thêm khối mã sau vào constructor của MyForm: public MyForm() { //Set the properties for the Button BigButton = new Button(); BigButton.Location = new System.Drawing.Point(50, 50); Trang 39
  47. Khoa CNTT [MÔI TRƯỜNG VÀ CÔNG CỤ LẬP TRÌNH] BigButton.Name = "BigButton"; BigButton.Size = new System.Drawing.Size(100, 100); BigButton.Text = "Click Me!"; //Set properties of the Form itself ClientSize = new System.Drawing.Size(200, 200); Controls.Add(BigButton); Text = "My Windows Form!"; } Đoạn mã này đầu tiên khởi tạo một đối tượng Button mới và ấn định nó vào thành phần dữ liệu riêng BigButton. Nó sau đó cài các thuộc tính Location, Name, Size, và Text để với các giá trị thích hợp. Bất kỳ thuộc tính nào không cài ở đây sẽ lấy giá trị mặc định. Những dòng tiếp theo cài kích cở của form, và sau đó phương thức this.Controls.Add() được gọi để thêm control Button vào tập hợp Controls của form. Việc này được yêu cầu trước khi nút sẽ được hiển thị trên form. Tập hợp Controls sẽ chứa tất cả các control trên một form và có thể cập nhật và sửa đổi tự động trong thời gian chạy để thêm và xoá các control nếu cần. Chúng ta sẽ xem xét cách chúng thực hiện ở phần sau của chương. Nếu bạn chạy ứng dụng tại điểm này, bạn sẽ thấy một cửa sổ như sau: Trang 40
  48. Khoa CNTT [MÔI TRƯỜNG VÀ CÔNG CỤ LẬP TRÌNH] Tuy nhiên, không có gì xảy ra khi nút được click. Để thay đổi chúng ta sẽ cần thêm một sự kiện vào đoạn mã. 2.2.1.2 Các sự kiện (event): Mỗi đối tượng trong một ứng dụng Windows Form có một tập sự kiện. Nếu bạn muốn có một đoạn mã thực hiện một điều gì đó khi các sự kiện xảy ra, bạn nên thêm một bộ điều khiển sự kiện(event handler) vào lớp và kết hợp nó với đối tượng. Để Windows Forms được sử dụng đoạn mã của bạn, bạn phải truyền cho nó vị trí của phương thức bộ điều khiển sự kiện trong đoạn mã của bạn. Bạn thực hiện bằng cách tạo một thể hiện delegate thích hợp kết hợp với một phương thức trong lớp custom Form. Để thêm vài chức năng cho nút đó, ta cần thêm vài dòng mã vào lớp chúng ta. Thêm phương thức sau vào lớp Form của chúng ta. Nó sẽ hành động như bộ điều khiển sự kiện cho sự kiện Click của nút. Chú ý rằng bộ điều khiển sự kiện có thể được gọi bất kỳ đối tượng nào. Sự kiện của control tự định nghĩa tham số cho phù hợp với bộ điều khiển. static void Main(string[] args) { Trang 41
  49. Khoa CNTT [MÔI TRƯỜNG VÀ CÔNG CỤ LẬP TRÌNH] MyForm aForm = new MyForm(); Application.Run(aForm); } private void ClickHandler(object sender, System.EventArgs e) { MessageBox.Show("Clicked!","My Windows Form",MessageBoxButtons.OK); } Hầu hết các bộ điều khiển sự kiện Windows Forms có dạng phương thức này. Thông số đầu tiên chứa đối tượng khởi sự kiện. Trong trường hợp này nó sẽ là đối tượng Button từ lớp MyForm. Thông số tiếp theo chứa dữ liệu về sự kiện trong một thông số System.EventArgs hay lớp thừa hưởng. Lớp System.EventArgs không chứa dữ liệu- Nó chỉ hành động như một lớp cơ sở. Nếu một sự kiện phải truyền dữ liệu đến client thì nó phải sử dụng một lớp thừa hưởng. Sự kiện Button.Click không cần truyền bất kỳ thông tin thêm vào, vì thế nó sử dụng lớp System.EventArgs cơ sở. Cuối cùng, thêm đoạn mã sau vào constructor MyForm để sự kiện gắn bộ điều khiển sự kiện của chúng ta vào sự kiện trong lớp MyForm. public MyForm() { //Set the properties for the Button BigButton = new Button(); BigButton.Location = new System.Drawing.Point(50, 50); BigButton.Name = "BigButton"; BigButton.Size = new System.Drawing.Size(100, 100); Trang 42
  50. Khoa CNTT [MÔI TRƯỜNG VÀ CÔNG CỤ LẬP TRÌNH] BigButton.Text = "Click Me!"; BigButton.Click += new EventHandler(ClickHandler); //Set properties for the Form itself ClientSize = new System.Drawing.Size(200, 200); Controls.Add(BigButton); Text = "My Windows Form!"; } Ví dụ này trình bày cách Windows Form sử dụng delegates để wrap một phương thức của đối tượng trước khi ấn định nó vào sự kiện chúng ta muốn vận dụng. System.EventHandler delegate được sử dụng để tham khảo phương thức ClickHandler() và nó được liên kết với sự kiện Click của nút bằng cách thêm nó vào bộ điều khiển sự kiện Click. Chú ý cú pháp sử dụng - có nghĩa là các bộ điều khiển sự kiện thêm vào có thể được liên kết với một sự kiện đơn. Chúng sẽ được xử lý để chúng được thêm vào bộ điều khiển sự kiện. Biên dịch ứng dụng lại, và chạy nó. Lúc này khi click nút bạn sẽ thấy một hộp tin nhắn nhỏ. Trang 43
  51. Khoa CNTT [MÔI TRƯỜNG VÀ CÔNG CỤ LẬP TRÌNH] 2.2.2 Windows Form sử dụng Visual Studio .NET Giống như trong .NET, sử dụng Visual studio.NET tạo các ứng dụng Windows Form đơn giản hơn nhiều. Visual studio.NET giảm số lượng mã rắc rối mà các nhà phát triển phải viết, cho phép các nhà phát triển tập trung vào giải quyết các vấn đề kinh doanh. Hãy xem cách tạo một ứng dụng Window Forms đơn giản sử dụng Visual studio.NET. Chúng ta sẽ tạo một màn hình thực thể dữ liệu đơn giản cho một hệ thống quản lý thông tin cá nhân hư cấu. Loại màn hình này sẽ được gắn vào một số form của cơ sở dữ liệu sử dụng để lưu trữ dữ liệu cá nhân. Chúng ta xem xét cách để tạo một tầng giao diện người dùng trong chương này. Tạo một dự án Windows Application C# mới trong Visual studio.NET với tựa đề là SimpleDataEntry. Sau khi dự án được tạo, bạn sẽ thấy một form đơn giản trong Visual Studio.NET trong màn hình thiết kế. Màn hình thiết kế được dùng để thêm control vào form. Click phải trên tập tin Form1.cs trong Solution Explorer và chọn View Code. Nó sẽ hiển thị mã phát ra bởi form được hiển thị trong màn hình thiết kế. Nhìn qua đoạn mã này. Với việc thêm vào của một vài tiêu chuẩn, mà Visual studio.NET biên dịch như là phương thức InitalizeComponent(), đoạn mã nhìn rất giống với ứng dụng Windows Forms ban đầu. Chú ý cách dùng của Application.Run trong phương thức Main, và sự thật là lớp Form này thừa hưởng từ System.Windows.Forms.Form. Trang 44
  52. Khoa CNTT [MÔI TRƯỜNG VÀ CÔNG CỤ LẬP TRÌNH] Phương thức InitializeComponent() được dùng bởi Visual studio.NET để xây dựng Form tại thời gian chạy. Tất cả control và thuộc tính mà một nhà phát triển cài suốt thời gian thiết kế được cài tại thời gian chạy trong phương thức này. Khi có những sự thay đổi được tạo ra cho Form trong thời gian thiết kế, Visual Studio.NET sẽ cập nhật phương thức này. Quay lại màn hình thiết kế để thêm vài control vào form này để làm cho nó hữu dụng và thú vị hơn. Chú ý rằng khi bạn chọn form, cửa sổ properties cài đặt các thuộc tính khác nhau của các control trong ứng dụng Windows Forms của chúng ta. Nó là một bộ phận quan trọng của Visual studio.NET IDE, khi sử dụng nó thì dễ tìm kiếm tên của mọi thuộc tính, của mỗi control trong tài liệu .NET hơn. Có một số nút ở tại đầu của cửa sổ này. Hai thay đổi đầu tiên là cách mà các thuộc tính được hiển thị. Nhóm đầu tiên hiển thị các mục chọn trong phạm trù luận lý, như là tất cả thuộc tính với hình thức, cách cư xử, thiết kế và vân vân. Nút thứ hai sắp xếp tất cả thuộc tính theo thứ tự alphabe. Hai nút kế tiếp chốt vào giữa sự hiển thị thuộc tính hoặc các sự kiện. Chúng ta sẽ bàn luận những sự kiện và cách thêm chúng vào các control tiếp đó. Nút cuối cùng mở trang thuộc tính của dự án này: Cài các thuộc tính sau của form bằng cách sửa đổi chúng trực tiếp trong cửa sổ Properties: Trang 45
  53. Khoa CNTT [MÔI TRƯỜNG VÀ CÔNG CỤ LẬP TRÌNH] Property Value Text Data Entry Form Size 300, 220 (Name) frmMain StartPosition CenterScreen Các cài đặt này sẽ tạo một cửa sổ 300 tới 220 pixel ở giữa màn hình. Thuộc tính Name là một thuộc tính quan trọng trên tất cả các controls. Giá trị này được dùng như tên đối tượng của các biến thành viên của lớp, và được dùng để tham khảo đế control trong đoạn mã. Bây giờ thêm hai control Button vào form. Cài các thuộc tính của hai control Button như sau: Property button1 Value button2 Value (Name) btnSave btnCancel Location 125, 157 210, 157 Size 78, 25 78, 25 Text Save Cancel Ở đây chúng ta đang thay đổi các tên mặc định của Button đến một giản đồ đặt tên chuẩn hơn, và định vị chúng vào vị trí chúng ta muốn chúng trên Form1. Quay về màn hình mã để xem xét những gì Visual studio.NET đã làm suốt thời gian này. Bạn sẽ thấy phần thêm của hai biến thành viên mới trong lớp Form. Nếu bạn Trang 46
  54. Khoa CNTT [MÔI TRƯỜNG VÀ CÔNG CỤ LẬP TRÌNH] mở rộng vùng tiêu đề "Windows Form Designer generated code" bạn sẽ thấy phương thức InitializeComponent(), nơi mà tất cả control trên form được khởi tạo và định hình chính xác. Phương thức này được gọi trong constructor của form. Tiếp đó thêm ba control TextBox và ba Control Label Next vào Form. Gán các thuộc tính như sau: Property TextBox1 TextBox2 TextBox3 Label1 Label2 Label3 (Name) txtFName txtLName txtSSN label1 label2 label3 Location 97, 25 97, 61 97, 99 20, 25 20, 62 20, 99 Size 115, 20 115, 20 115, 20 70, 18 70, 18 70, 18 Text (Blank) (Blank) (Blank) First Last Name: SSN: Name: Bạn nên có một Form giống một màn hình thực thể dữ liệu về thông tin người dùng. Một end-user có thể sử dụng màn hình này để nhập tên đầu và cuối của chúng như Social Security Number. Tại lúc này Form1 sẽ giống như sau: 2.2.2.1 Các sự kiện (event): Các ứng dụng Windows là event-driven và không thêm mã, nó đáp ứng các sự kiện. Visual studio.NET tạo ra nó rất đơn giản bằng cách thêm mã đáp ứng các sự kiện phát sinh bởi ngừơi dùng và hệ thống. Trang 47
  55. Khoa CNTT [MÔI TRƯỜNG VÀ CÔNG CỤ LẬP TRÌNH] Cửa sổ Properties được cập nhật để phản ánh toàn bộ danh sách sự kiện có thể được điều khiển từ đối tượng này. Để thấy danh sách này, chọn nút thứ tư từ bên trái. Nó sẽ hiển thị một danh sách sự kiện cho đối tượng đang chọn, và chọn bất kỳ đối tượng nào nó sẽ có liên kết mã với chúng. Màn hình bên dưới chỉ dãy sự kiện khi đối Form được chọn. Việc thêm một sự kiện có thể được vận dụng một trong hai cách. Để thêm một sự kiện mặc định cho một control bằng cách click đôi lên nó trong màn hình thiết kế. Cách khác để thêm các bộ điều khiển sự kiện vào đoạn mã của bạn và các tuỳ chọn nếu bạn không thêm các sự kiện mặc định, bằng cách sử dụng cửa sổ Properties. Một lần nữa, đoạn mã pluming đúng sẽ được chèn vào lớp Form và bạn sẽ lấy các bộ điều khiển sự kiện cho sự kiện được chọn. Chú ý cửa sổ Properties, bạn phải làm như vậy nếu bạn có nhiều nút mà tất cả chúng cùng làm những việc giống nhau và yêu cầu cùng một sự xử lý. Hãy thêm vài đoạn mã trong bộ điều khiển sự kiện của hai control Button của chúng ta. Thêm bộ điều khiển sự kiện Click vào hai Buttons đang tồn tại. Thêm đoạn mã sau vào tập tin: Trang 48
  56. Khoa CNTT [MÔI TRƯỜNG VÀ CÔNG CỤ LẬP TRÌNH] private void btnSave_Click(object sender, System.EventArgs e) { SaveFile(); } private void btnCancel_Click(object sender, System.EventArgs e) { Clear(); } private void SaveFile() { //Save the values to an XML file //Could save to data source, Message Queue, etc. System.Xml.XmlDocument aDOM = new System.Xml.XmlDocument(); System.Xml.XmlAttribute aAttribute; aDOM.LoadXml(" "); //Add the First Name attribute to XML aAttribute = aDOM.CreateAttribute("FirstName"); aAttribute.Value = txtFName.Text; aDOM.DocumentElement.Attributes.Append(aAttribute); //Add the Last Name attribute to XML Trang 49
  57. Khoa CNTT [MÔI TRƯỜNG VÀ CÔNG CỤ LẬP TRÌNH] aAttribute = aDOM.CreateAttribute("LastName"); aAttribute.Value = txtLName.Text; aDOM.DocumentElement.Attributes.Append(aAttribute); //Add the SSN attribute to XML aAttribute = aDOM.CreateAttribute("SSN"); aAttribute.Value = txtSSN.Text; aDOM.DocumentElement.Attributes.Append(aAttribute); //Save file to the file system aDOM.Save("UserData.xml"); } private void Clear() { //Erase all the text txtFName.Text = ""; txtLName.Text = ""; txtSSN.Text = ""; } Ví dụ đơn giản này lưu dữ liệu được nhập bởi một tập tin XML trên hệ thống tập tin. Mọi ứng dụng sẽ sử dụng ADO.NET để lưu thông tin vào một nguồn dữ liệu back- end. Tuy nhiên, trong ví dụ này chúng ta sẽ xuất khẩu một tập tin XML nhỏ. Trang 50
  58. Khoa CNTT [MÔI TRƯỜNG VÀ CÔNG CỤ LẬP TRÌNH] Chúng ta sử dụng các phương thức private để thể hiện chức năng thực sự, vì thế chúng ta có thể sử dụng cùng chức năng từ các tuỳ chọn menu sau này. Mọi đoạn mã trong phương thức SaveFile() bao gồm việc viết ra tập tin XML chứa dữ liệu user- supplied. Sự kiện Click của nút Cancel gọi phương thức Clear() để xoá tất cả các control textbox. Chú ý rằng trong một ứng dụng hoàn chỉnh, nó có thể đóng cửa sổ này và trả về người dùng một màn hình chính. Chú ý rằng có một lỗi trong Visual studio.NET thỉnh thoảng yêu cầu một nhà phát triển thay đổi tên của lớp Form sử dụng trong phương thức Main() bằng tay. Nếu bạn có một lỗi khi biên dịch phương thức Main() và bảo đảm nó giống như đoạn mã sau. Phải bảo đảm rằng đoạn mã tạo đối tượng sử dụng tên lớp frmMain. Khi một tên lớp Form bị thay đổi thì dòng này không luôn luôn cập nhật. static void Main() { Application.Run(new frmMain()); } Nếu bạn chạy ứng dụng này tại lúc này bạn sẽ có một cửa sổ thực thể dữ liệu nhỏ có đầy đủ chức năng. Bạn có thể nhập dữ liệu, lưu nó vào một tập tin XML, và xoá tất cả giá trị. Việc đó thì đơn giản nhưng nó biểu lộ cách tạo các ứng dụng sử dụng Visual studio.NET. 2.2.2.2 Resizing Windows Một vấn đề với cửa sổ thực thể dữ liệu của chúng ta là khi nó được thay đổi kích thước thì các control bị khoá lại trong một vùng. Điều đó có vẽ buồn cười và không chuyên nghiệp với một ứng dụng cao cấp,do đó nên hỗ trợ khả năng thay đổi kích thước lại và định vị một cửa sổ trong bất kỳ hình dạng nào người dùng mong muốn. Bất kỳ nhà phát triển nào viết mã để điều khiển việc thay đổi kích thước và thay thế của các control sẽ đánh giá sự dễ dàng khi sử dụng .NET Framework và Window Forms để làm việc này. Trang 51
  59. Khoa CNTT [MÔI TRƯỜNG VÀ CÔNG CỤ LẬP TRÌNH] Với một thuộc tính đơn thì tất cả công việc này có thể được điều khiển bởi .NET Framework. Thuộc tính Anchor thể hiện năng lực kỳ diệu này, và nó là một thành viên của hầu hết tất cả các lớp trong System.Windows.Forms namespace bởi vì nó là một thuộc tính của lớp System.Windows.Forms.Control. Nhắc lại là, mọi control đều thừa hưởng từ lớp này. Thuộc tính Anchor được cài một liên kết cuả một hay nhiều cạnh của cha mẹ nó. Cài một trong những cạnh này trong thuộc tính sẽ dẫn đến control duy trì mối quan hệ vị trí giữa cạnh của nó và cạnh của cha mẹ nó khi form được thay đổi kích thước và di chuyển. Thuộc tính này rất quan trọng để thiết kế giao diện người dùng thân thiện, và nên được thí nghiệm để hiểu cách nó làm việc. Visual Studio .NET bao gồm một cửa sổ pop-up để cài thuộc tính này vào đúng mối liên kết. Cửa sổ pop-up này cho phép một nhà phát triển chọn cạnh để neo control. Cửa sổ pop-up này có thể được tìm thấy như một phần của cửa sổ Properties. Chúng ta sẽ dùng thuộc tính Anchor để tạo một giao diện người dùng hiệu quả hơn cho màn hình thực thể dữ liệu của chúng ta. Chọn các control TextBox trong môi trường thiết kế Visual studio.NET. Thay đổi thuộc tính Anchor vào Top, Left, Right sử dụng cửa sổ pop-up. Nó sẽ duy trì khoảng cách giữa top, left, và right của các cạnh của cha mẹ, bằng cách đó thay đổi kích thước control chính xác. Chọn các control Button thứ hai và thay đổi thuộc tính Anchor của nó ở Bottom, Right. Nó sẽ duy trì vị trí đóng của chúng ở bottom-right của form. Chạy ứng dụng và thay đổi kích thước cửa sổ để thấy cách các control điều chỉnh chính bản thân chúng. Một lần nữa, thuộc tính này được là quyết định hoàn toàn trong thiết kế giao diện người dùng chuyên nghiệp trong .NET, và sử dụng nó giảm số lượng của công việc yêu cầu bởi các nhà phát triển. Các nhà phát triển tự do này tập trung giải quýêt vấn đề kinh doanh thực tế để thay cho việc thay đổi kích thước cấp thấp. Trang 52
  60. Khoa CNTT [MÔI TRƯỜNG VÀ CÔNG CỤ LẬP TRÌNH] 2.2.3 Menus Các menu được dùng trong hầu hết mọi ứng dụng Window, và chúng cung cấp một cách tuyệt vời để giao tiếp người dùng với các tuỳ chọn để họ làm việc theo các chức năng có sẳn. Có hai kiểu menu khác nhau. Thông thường nhất là một menu chính(main menu), ở đầu của một cửa sổ và thường bao gồm các mục như File, Edit, và Help. Vài ứng dụng chứa các menu theo ngữ cảnh để cho phép người dùng truy cập đến thông tin về các chủ đề hay mục đặc biệt. Menu theo ngữ cảnh được ẩn cho đến khi người dùng nhấn chuột phải - sau đó menu được hiển thị tại vị trí con trỏ. Windows Forms cung cấp hỗ trợ đầy đủ cho việc thêm hai kiểu menu vào một ứng dụng. Lớp System.Windows.Forms.Menu cung cấp lớp cơ sở cho tất cả lớp menu trong hệ thống. Lớp MainMenu tượng trưng cho menu chính, và có thể liên kết với một form. Menu này chứa một tập hợp đối tượng MenuItem tượng trưng cho một tuỳ chọn menu riêng rẽ. Lớp ContextMenu thì có thể thêm các menu theo ngữ cảnh cho một ứng dụng. Lớp này cũng chứa một tập hợp đối tượng MenuItem, nhưng ContextMenu có thể xuất hiện trong bất kỳ vị trí nào trong một form, nó không chỉ tại đầu của một cửa sổ như lớp MainMenu. Chúng ta sẽ thêm một menu vào ứng dụng thực thể dữ liệu của chúng ta. Thêm một menu vào một ứng dụng Window Form thì dễ như thêm bất kỳ control chuẩn nào như là một Button hay TextBox. Chọn control MainMenu từ thanh công cụ và vẽ một hộp trên bề mặt thiết kế. Nó sẽ thêm một menu tại đầu của form. Chọn menu và gõ File để thêm mục menu đầu tiên. Bây giờ khi bạn click trên File một menu mới sẽ hiển thị bên dưới, nó có thể thêm vào như chúng ta thêm mục menu File. Bạn có thể tiếp tục gõ vào MenuItem, bằng cách đó tạo ra cấu trúc thực sự của hệ thống menu trong IDE Trang 53
  61. Khoa CNTT [MÔI TRƯỜNG VÀ CÔNG CỤ LẬP TRÌNH] Sử dụng hệ thống menu để tạo menu sau. Chú ý rằng: bằng cách nhập một ký tự gạch(–) đơn lẽ thì một dòng riêng lẽ được tạo. Nó rất hữu ích cho việc phân chia các nhóm chọn lựa trong một menu. Một phần quan trọng khác để nhớ là bằng cách mở đầu một ký tự với ký hiệu là (&) thì ký tự đó trở thành phím tắt cho mục menu này. Vì thế một người dùng có thể chọn menu bằng cách chỉ sử dụng bàn phím. Top Level Menu Item Contained Menu Items Text – &File Text – &Save Name – mnuFile Name – mnuSave Text – &Cancel Name – mnuCancel Text – "-" (Single Dash) Text – E&xit Name – mnuExit Trang 54
  62. Khoa CNTT [MÔI TRƯỜNG VÀ CÔNG CỤ LẬP TRÌNH] Top Level Menu Item Contained Menu Items Text – &Color Text – &Gray Name – mnuColor Name – mnuGray RadioCheck – true Checked – true Text – G&reen Name – mnuGreen RadioCheck – true Text – &Blue Name – mnuBlue RadioCheck – true Text – &Red Name – mnuRed RadioCheck – true Text – &Purple Name – mnuPurple RadioCheck – true Chạy ứng dụng và thấy rằng bạn có một cửa sổ với một menu đang làm việc trên đó. Tuy nhiên không có gì xảy ra khi một menu được chọn. Để thay đổi, bộ diều khiển sự kiện này phải được thêm bên dưới những mục menu riêng lẽ. Chúng ta sẽ tiếp tục với cùng ví dụ này và thêm sự kiện điều khiển để người dùng có thể sử dụng menu. Trang 55
  63. Khoa CNTT [MÔI TRƯỜNG VÀ CÔNG CỤ LẬP TRÌNH] Các MenuItems riêng rẽ là mọi control giống như các cái khác, và chúng có thể được chọn trong bề mặt thiết kế. Làm các việc này bằng cách chỉ ra các thuộc tính và sự kiện của chúng trong cửa sổ Properties. Sử dụng danh sách sự kiện để thêm bộ điều khiển sự kiện Click cho các mục chọn Save, Cancel, và Exit. Thêm đoạn mã sau trong bộ điều khiển sự kiện mới: private void mnuSave_Click(object sender, System.EventArgs e) { SaveFile(); } private void mnuCancel_Click(object sender, System.EventArgs e) { Clear(); } private void mnuExit_Click(object sender, System.EventArgs e) { Close(); } 2.2.3.1 Dynamic Menus Các menu thường được dùng để phản ánh trạng thái của ứng dụng. Khi người dùng tạo các chọn lựa và thay đổi trong ứng dụng, menu phải phản ánh các sự thay đổi này. Các mục menu có thể được thêm, xoá và chỉnh sửa để phản ánh tình trạng ứng dụng hiện tại. Một lần nữa, các MenuItem hành động như các thành phần khác và có thể được vận dụng. Trang 56
  64. Khoa CNTT [MÔI TRƯỜNG VÀ CÔNG CỤ LẬP TRÌNH] MenuItems có thể có một nút kiểm kế bên để minh hoạ tuỳ chọn hiện tại. Nó rất hữu dụng cho người dùng để họ có thể đánh giá chính tình trạng của ứng dụng của họ. Thuộc tính Checked như một biến cờ, nó có thể cài để hiện hay dấu một điểm kiểm tra kế bên mục menu. Nếu thuộc tính RadioCheck được cài bằng true thì nút kiểm sẽ xuất hiện như một chấm đơn giản. Vì thế chỉ một mục menu đơn giản được chọn tại một thời điểm với thuộc tính RadioCheck. Chúng ta đang thêm một số mã bên dưới các mục menu color để thay đổi màu nền của form. Chúng ta sẽ thực hiện bằng cách sử dụng một bộ điều khiển sự kiện cho mọi đối tượng MenuItem. Trong ứng dụng của chúng ta, thêm phương thức sau vào: private void mnuItems_Click(object sender, System.EventArgs e) { } Chúng ta thêm một bộ điều khiển sự kiện ở đây để thay cho việc cho phép Visual Studio.NET IDE làm giùm chúng ta. Chúng ta cần làm như vậy để chúng ta có thể liên kết phương thức đơn này với mọi bộ điều khiển sự kiện Click của các mục menu. Nó sẽ cho phép chúng ta điều khiển tình trạng của menu và ứng dụng từ phương thức đơn này. Trở lại với màn hình thiết kế của IDE, click trên mục menu Gray. Trong cửa sổ Properties chuyển tới màn hình sự kiện và chọn sự kiện Click. Click trên mũi tên thả xuống để hiển thị một danh sách tên phương thức có thể liên kết với sự kiện này. Đây là cách để gắn các phương thức vào các sự kiện. Chọn phương thức mnuItems_Click() từ danh sách. Lập lại thủ tục này với mọi mục trong menu Color. Sự kiện Click của mọi mục nên được liên kết với cùng phương thức. Trang 57
  65. Khoa CNTT [MÔI TRƯỜNG VÀ CÔNG CỤ LẬP TRÌNH] Bây giờ, mọi đối tượng được liên kết với cùng phương thức bộ điều khiển sự kiện, thêm đoạn mã sau để cập nhật BackColor của form và tình trạng menu. private void mnuItems_Click(object sender, System.EventArgs e) { MenuItem aObj; //Set the BackColor of the form based on the selected object if(sender == mnuGray) this.BackColor = System.Drawing.SystemColors.Control; else if(sender == mnuGreen) this.BackColor = Color.Green; else if(sender == mnuBlue) this.BackColor = Color.Blue; else if(sender == mnuRed) this.BackColor = Color.Red; else if(sender == mnuPurple) this.BackColor = Color.Purple; //Set all checkboxes to false mnuGray.Checked = false; mnuGreen.Checked = false; mnuBlue.Checked = false; mnuRed.Checked = false; mnuPurple.Checked = false; Trang 58
  66. Khoa CNTT [MÔI TRƯỜNG VÀ CÔNG CỤ LẬP TRÌNH] //Change the selected item to checked aObj = (MenuItem)sender; aObj.Checked = true; } Đoạn mã này sử dụng sự kiện là tham số sender trong một bộ điều khiển sự kiện là đối tượng để khởi sự kiện. Điều này được yêu cầu bởi vì bộ điều khiển sự kiện này được dùng bởi tất cả đối tượng MenuItem. Vì thế, bước đầu là xác định mục menu được chọn bởi người dùng. Và sau đó thay đổi BackColor của form theo màu sắc tương ứng. Bước kế tiếp là cài một nút kiểm kế bên mục menu thích hợp. Chúng ta có thể thực hiện việc này bằng cách cài đặt đơn giản thuộc tính Checked bằng true, sau đó đặt nó vào đối tượng MenuItem. Tuy nhiên, trước khi chúng ta làm việc này, chúng ta cần cài tất cả đối tượng MenuItem là unchecked. Chạy ứng dụng và chọn các tuỳ chọn màu sắc khác nhau. Bạn sẽ thấy màu nền của của sổ thay đổi, và hộp kiểm trong menu cập nhật để phản ánh màu sắc hiện tại 2.2.3.2 Menus ngữ cảnh Mọi ứng dụng Window cho phép người dùng click phải và hiện lên một menu theo ngữ cảnh. Nó có nghĩa là các sự chọn lựa menu được dựa vào đối tượng, hay ngữ cảnh , người dùng đã chọn. Các menu ngữ cảnh cho phép ứng dụng biểu thị thông tin thêm vào hay các chọn lựa người dùng. Các menu theo ngữ cảnh có thể được thêm vào các ứng dụng Windows Forms rất dễ dàng. Chúng ta sẽ thêm một menu theo ngữ cảnh vào cửa sổ thực thể dữ liệu của chúng ta, các chọn lựa Save và Cancel khi người dùng click phải ở bất kỳ đâu trên Form. Trang 59
  67. Khoa CNTT [MÔI TRƯỜNG VÀ CÔNG CỤ LẬP TRÌNH] Để thêm một menu ngữ cảnh vào một form, đơn giản thêm control ContextMenu từ thanh công cụ vào Form1. Khi đối tượng ContextMenu được thêm vào form nó sẽ xuất hiện trong vùng footer bên dưới bề mặt thiết kế form. Khi biểu tượng này được chọn, menu chính, nếu nó tồn tại sẽ không xuất hiện trong form và được thay thế với chính menu ngữ cảnh đó. Nó có thể được chỉnh sửa trong bề mặt thiết kế bằng cách gõ các mục menu khác nhau, như là sửa các menu chính. Mặc dù nó xuất hiện, các menu sẽ được hiển thị ở đỉnh của form như menu chính, nó sẽ được ẩn cho đến khi chúng ta gán nó vào form. Sau khi thêm ContextMenu vào Form1, thêm các mục menu dưới đay bằng cách gõ vào các giá trị sau: Menu Item Name Text Property Value mnuSaveContext Save mnuCancelContext Cancel Một lần nữa, mọi mục menu là các đối tượng MenuItem riêng lẽ và có các thuộc tính có thể cài trong cửa sổ Properties và chọn sự kiện Click. Trong dãy thả xuống chọn mnuSave_Click cho mnuSaveContext MenuItem và mnuCancel_Click cho mnuCancelContext MenuItem. Nó sẽ nối những sự kiện này với cùng bộ điều khiển sự kiện được gọi khi các mục menu chính được click. Bây giờ chúng ta có một menu ngữ cảnh, nó xuất hiện khi chúng ta click phải trên Form. Để thêm một menu ngữ cảnh vào một Form, thuộc tính ContextMenu của đối tượng Form phải được cài vào đối tượng ContextMenu của chúng ta. Khi nó được cài, form sẽ tự động hiển thị ContextMenu khi ngừơi dùng click phải. Nó sẽ hiển thị ContextMenu tại vị trí người dùng click phải. Chú ý rằng thuộc tính này có thể được cập Trang 60
  68. Khoa CNTT [MÔI TRƯỜNG VÀ CÔNG CỤ LẬP TRÌNH] nhật trong thời gian chạy. Thật là quan trọng để ghi chú rằng thuộc tính này là một thành viên của lớp Control, có nghĩa là tất cả control Windows Forms đều có thuộc tính này. Cài thuộc tính ContextMenu của Form1 vào contextMenu1 sử dụng cửa sổ properties. Một combo box sẽ hiển thị các đối tượng ContextMenu hiện tại để chọn từ trên form. Multiple ContextMenus có thể được thêm vào một form, mặc dù chỉ một được gán vào form tại một thời điểm. Chạy ứng dụng và click phải bất kỳ đâu trên trên form để thấy menu ngữ cảnh hiển thị hai tuỳ chọn Save và Cancel. 2.2.4 Dialogs Dialogs là một kiểu đặc biệt của Form để lấy thông tin người dùng và tương tác với ngừơi dùng trong các ứng dụng Window. Có một tập các hộp dialog định nghĩa trước để lấy thông tin như vị trí tập tin, màu sắc, và cài đặt máy in. Một ứng dụng tuỳ biến thường sử dụng hộp thoại dialog để thuận tiện chọn dữ liệu từ endusers. Tạo một hộp dialog thì rất giống với tạo một Form chuẩn. Trên thực tế, cùng tiến trình xử lý được dùng để thêm vào một dự án Visual studio.NET. Sự khác chính là bản liệt kê FormBorderStyle, nó phải được cài là Fixel Dialog. Nó tạo cửa sổ không lớn và là nguyên nhân nó giống với hộp dialog Window. Nó cũng là một thực hành Window chuẩn Trang 61
  69. Khoa CNTT [MÔI TRƯỜNG VÀ CÔNG CỤ LẬP TRÌNH] để huỷ ControlBox, MinimizeBox, và MaximizeBox từ một hộp dialog, vì thế các thuộc tính này nên được cài là false trong cửa sổ properties. Bất kỳ control Windows Forms chuẩn nào cũng có thể tồn tại trên một hộp dialog. Bề mặt thiết kế trong Visual studio.NET được dùng để thiết kế các Form chuẩn, và các tuỳ chọn giống nhau có thể dùng cho các nhà phát triển. 2.2.4.1 Modal vs. Modeless Khi chúng ta muốn hiển thị hộp dialog, có hai chọn lựa: modal hay modeless. Hai khái niệm này chỉ cách dialog tương tác với ứng dụng. Một modal dialog ngăn chận các luồng hiện tại và yêu cầu người dùng trả lời vào hộp dialog trứơc khi tiếp tục với ứng dụng. Một Modeless dialog thì giống một cửa sổ chuẩn hơn. 2.2.4.2 Dialog Box Results Thường rất quan trọng để hiểu cách người dùng đóng một hộp dialog. Một ví dụ điển hình đó là một dialog File Open. Nếu người dùng chọn một tập tin thì hành động tiếp theo cho ứng dụng là load tập tin đó, tuy nhiên nếu người dùng click nút Cancel hay đóng hộp dialog thì ứng dụng sẽ không load bất kỳ tập tin nào. Bí quyết để hiểu cách người dùng tương tác với một hộp dialog là bảng liệt kê DialogResult. Các giá trị cho bảng liệt kê này như sau: Value Description Abort Giá trị này thì được trả về khi một người dùng chọn một nút có nhãn Abort. Trong trường hợp này, người dùng muốn huỹ thao tác hiện tại và không lưu sự thay đổi. Cancel Giá trị này thường được trả về khi một người dùng chọn một nút có nhãnlà Cancel, đóng hộp thoại bằng cách nhấn nút "x", hay nấn phím Esc. Người Trang 62
  70. Khoa CNTT [MÔI TRƯỜNG VÀ CÔNG CỤ LẬP TRÌNH] Value Description dùng muốn huỹ các thay đổi và trả về trạng thái trước khi mở hộp thoại. Ignore Giá trị này được trả về khi một người dùng chọn nút có nhãn Ignore. Giá trị này có thể được sử dụng khi ứng dụng cảnh báo người dùng về các điều kiện xảy ra lỗi, nhưng người dùng chọn lệnh Ignore. No Giá trị này thường được trả về khi một người dùng chọn nút có nhãn No. Nó thì bình thường khi hộp thoại được dùng để hỏi người dùng một câu hỏi es/no. Yes Giá trị này thường được trả về khi một ngừời dùng chọn nút có nhãn Yes. Nó là con trỏ đếm đến kết quả trả về Nó và được dùng trong cùng tình huống. None Không có gì được trả về từ hộp thoại. OK Giá trị này đươc trả về khi một người dùng chọn nút có nhãn OK. Nõ thì bình thường trong các hộp thoại và các tin cảnh báo nơi nào quan trong cho người dùng thừa nhận thông tin. Retry Giá trị này được trả về khi một người dùng chọn nút có nhãn Retry. Nó có ích khi một thao tác không thành công sẽ thành công nếu được thử lại. Để truy cập vào giá trị này bạn phải sử dụng thuộc tính DialogResult của Form. Thuộc tính này là public và có thể truy cập ngay khi người dùng đã đóng hộp dialog. 2.2.4.3 Mở một Dialog Có hai cách để mở một hộp dialog, một là hiển thị modal và một là hiển thị modeless. Để hiện một dialog như một modal dialog, thì sử dụng phương thức sau: Trang 63
  71. Khoa CNTT [MÔI TRƯỜNG VÀ CÔNG CỤ LẬP TRÌNH] DialogResult Form.ShowDialog() Phương thức này là một bộ phận của lớp Form. Phương thức này có thể chấp nhận không có tham số hoặc một đối tượng Form như một tham số. Đối tượng Form này đại diện cho ower của hộp dialog, hộp này có ích bởi vì tất cả đối tượng Form có một con trỏ quay về cha mẹ của chúng, cho phép một hộp dialog có thể lấy hoặc cài dữ liệu vào cha mẹ của nó. Còn nếu không tham số thì truyền các mặc định cửa sổ hiện tại vào cha mẹ nó. Chú ý phương thức này trả về một giá trị bảng liệt kê DialogResult. Phương thức này ngăn chặn sự thực thi, và không có mã nào sau khi nó thực thi cho đến khi người dùng đóng hộp dialog. Khi sự việc này xảy ra thì DialogResult mã được trả về và ứng dụng có thể tiếp tục xử lý. Đoạn mã sẽ như sau: if (aDialogObject.ShowDialog() == DialogResult.Yes) { //User selected Yes //Use the properties of the aDialogObject to perform actions } else { //User selected No – do not perform action } Đoạn mã trên hiển thị hộp dialog để hỏi ngừơi dùng nếu họ muốn lưu tập tin hiện tại. Nếu ngừời dùng chọn Yes thì đoạn mã sẽ thả vào khối if nếu chọn No thì khối else sẽ được thực thi. Trang 64
  72. Khoa CNTT [MÔI TRƯỜNG VÀ CÔNG CỤ LẬP TRÌNH] 2.2.4.4 Common Dialogs .NET Framework cung cấp truy cập đến những common dialog này thông qua các lớp sau. Mỗi lớp này tượng trưng một common dialog và có thể được hiển thị như một hộp dialog. Tất cả lớp này tồn tại trong System.Windows.Forms namespace: Class Description ColorDialog Nó cho phép một ngừơi dùng chọn một màu từ bảng màu. FontDialog Hộp dialog này hiển thị tất cả font hiện có trên hệ thống và cho phép người dùng chọn một cái để dùng trong ứng dụng. OpenFileDialog Cho phép một người dùng mở một tập tin sử dụng hộp thoại mở tập tin chuẩn. SaveFileDialog Nó cho phép người dùng chọn một tập tin, thư mục hay địa chỉ mạng để lưu dữ liệu của ứng dụng. PageSetupDialog Nó cho phép người dùng cài kích cỡ trang, canh lề, và các đặc tính in ấn khác. PrintDialog Nó cho phép người dùng cài định dạng trang hiện hành và các đặc tính in ấn qua hộp dialog thuộc tính in ấn chuẩn. PrintPreviewDialog Nó hiển thị một tài liệu như nó xuất hiệnk trên máy in đang chọn với các cài đặt trang hiện hành. Tất cả lớp này thừa kế từ lớp System.Windows.Forms.CommonDialog, ngoại trừ lớp PrintPreviewDialog. Lớp System.Windows.Forms.CommonDialog cung cấp các chức năng cơ bản yêu cầu hiện một hộp combox dialog. Mọi lớp common dialog được hiển thị Trang 65
  73. Khoa CNTT [MÔI TRƯỜNG VÀ CÔNG CỤ LẬP TRÌNH] sử dụng phương thức ShowDialog(), nhưng chúng chứa các thuộc tính tuỳ biến sử dụng để định hình và hỏi chức năng tuỳ biến của chúng. ColorDialog Dialog này hiển thị hộp dialog màu sắc chung. Nó hữu ích khi một ngừơi dùng được cho phép định dạng nền của một Form hay control, và bạn muốn cung cấp chúng như một cách để chọn màu ưu tiên. Thuộc tính chính được dùng với lớp này là thuộc tính Color. Nó chứa màu sắc chọn lựa khi hộp dialog trả điều klhiển cho ứng dụng. Thuộc tính này là một cấu trúc màu, chúng có lợi bởi vì .NET framework cần cấu trúc này trong các phương thức và thuộc tính khác. Một tiện lợi khác là nó cho phép người dùng định nghĩa và sử dụng một tập màu sắc custom-defined. Đặc trưng này có thể hiện bằng mặc định, nhưng có thể bị ẩn bằng cách cài thuộc tính AllowFullOpen bằng giá trị false. Như các hộp dialog khác, giá trị trả về từ ShowDialog() phải được xem xét để hiểu cách người dùng thoát khỏi dialog. Đây là một đoạn mã nhỏ sử dụng lớp này: ColorDialog aClrDialog = new ColorDialog(); aClrDialog.AllowFullOpen = false; aClrDialog.Color = this.BackColor; if (aClrDialog.ShowDialog() == DialogResult.OK) { this.BackColor = aClrDialog.Color; } aClrDialog.Dispose(); Trang 66
  74. Khoa CNTT [MÔI TRƯỜNG VÀ CÔNG CỤ LẬP TRÌNH] Chú ý rằng bạn có thể sử dụng bất kỳ hộp dialog nào như ví dụ này, hay bạn có thể thêm thành phần vào một Form trong Visual studio.NEY. Đoạn mã sẽ rất giống, nhưng lớp dialog sẽ có giá trị cho mọi phương thức trong Form, và tất cả thuộc tính này có thể được cài tại thời gian thiết kế trong cửa sổ Properties. FontDialog Lớp này cho phép một người dùng chọn một kiểu font, size, và color. Nó rất hữu ích trong các ứng dụng, nhưng nó cũng có thể được dùng để cho phép người dùng định dạng bằng cách chọn kiểu font để hiển thị trong nhập liệu và màn hình báo cáo. Lớp này chứa một số lượng lớn các thuộc tính mà có thể được cài để định hình các chức năng của hộp dialog : Property Description Color Thuộc tính này lấy hay cài màu sắc font được chọn. Chú ý thuộc tính ShowColor phải là true để nó hợp lệ. Font Đây là thuộc tính quan trọng nhất củ hộp dialog này. Nó trả về cấu trúc Font mô tả việc chọn font của người dùng. Nó có thể sau đó được áp dụng vào một đối tượng Control hay Form để thay đổi font. MaxSize Lấy và cài kích cỡ điểm lớn nhất mà mọt ngừơi dùng có thể chọn trong hộp dialog. MinSize Lấy hay cài kích cỡ điểm nhỏ nhất mà một người dùng có thể chọn trong hộp dialog. ShowApply Thuộc tính Boolean có thể được cài là true để hiển thị một nút Apply. Nếu nó được dùng, một bộ điều khiển sự kiện được viết để bắt được sựu Trang 67
  75. Khoa CNTT [MÔI TRƯỜNG VÀ CÔNG CỤ LẬP TRÌNH] Property Description kiện Apply khi nó xảy ra. Bởi vì khi người dùng chọ Apply, control vẫn không trả về cho ứng dụng, nhưng bộ điềukhiển sự kiện có thể sau đó xử lý font thay đổi trong ứng dụng. ShowColor Thuộc tính Boolean có thể được cài là true để hiển thị một danh sách màu. Nó cho phép ngừơi dùng chọn màu của font cũng như kiểu hay kích cỡ. ShowEffects Thuộc tính Boolean có thể được cài là true để cho phép ngừời dùng chỉ định các tuỳ chọn strikethrough, underline, và text color. Một bộ điều khiển sự kiện có thể được viết để trả lời sự kiện Apply khi nó được kích bởi người dùng ấn vào nút Apply. FontDialog aFontDialog = new FontDialog(); aFontDialog.ShowColor = true; aFontDialog.ShowEffects = true; aFontDialog.MinSize = 1; aFontDialog.MaxSize = 35; aFontDialog.Font = SomeControl.Font; if (aFontDialog.ShowDialog() == DialogResult.OK) { SomeControl.Font = aFontDialog.Font; } aFontDialog.Dispose (); Trang 68
  76. Khoa CNTT [MÔI TRƯỜNG VÀ CÔNG CỤ LẬP TRÌNH] Đầu tiên chúng ta khởi tạo đối tượng FontDialog() với một vài cài đặt, trong đó có thuộc tính font của SomeControl để mô tả đối tượng ta đang cập nhật. Việc khởi tạo thuộc tính font như trên là rất tốt bởi vì người dùng sẽ thấy font được chọn trong hộp dialog, và họ không lúng túng. Nếu người dùng chọn OK trong FontDialog thì thuộc tính font của SomeControl được cập nhật để phản ánh font mà người dùng chọn. OpenFileDialog Lớp này rất hữu ích, như nhiều ứng dụng yêu cầu người dùng điều hướng hệ thống tập tin để mở và dùng các tập tin dữ liệu. Dialog này là dialog Window chuẩn cho việc mở tập tin, và người dùng nên làm quen với nó. Lớp này chứa nhiều thuộc tính dùng để cài hình thức và cách cư xử của chính hộp dialog đó. Cả hai lớp này và lớp SaveFileDialog thừa kế từ lớp cơ sở FileDialog. Vì nguyên nhân này nhiều thuộc tính được chia sẽ. Thuộc tính chính được trình bày ở trong bảng sau: Property Description CheckFileExists Cài thuộc tính này là true để làm cho hộp dialog hiển thị một cảnh báo nếu người dùng chỉ định một tên tập tin không tồn tại. Cách này làm cho đoạn mã của bạn không phải kiểm tra một đường dẫn. Mặc định là true. FileName Đây là thuộc tính quan trọng được dùng để cài và khôi phục tên tập tin được chọn trong hộp dialog tập tin. FileNames Nếu Multiselect là enabled, thuộc tính này sẽ trả về một mảng tên tập tin mà người dùng chọn. Filter Nó cài chuỗi lọc tên tập tin, để xác định các chọnlựa xuất hiện Trang 69
  77. Khoa CNTT [MÔI TRƯỜNG VÀ CÔNG CỤ LẬP TRÌNH] Property Description trong hộp "Files of type" trong hộp dialog. FilterIndex Chỉ mục của bộ lọc chọn trong hộp dialog tập tin. InitialDirectory Thư mục khởi tạo hiển thị bởi hộp dialog tập tin. Multiselect Thuộc tính Boolean được cài để cho biết hộp dialog có cho phép các tập tin bội có được chọn hay không. Mặc định là false. ReadOnlyChecked Thuộc tính Boolean cho biết nếu check box chỉ đọc được chọn. Mạc định là false. RestoreDirectory Thuộc tính Boolean cho biết hộp dialog có trả lại thư mục hiện tai trước khi đóng hay không. Mặc định là false. ShowHelp Thuộc tính Boolean cho biết nút Help có được hiển thị trong hộp dialog tập tin hay không. ShowReadOnly Thuộc tính Boolean cho biết dialog có chứa một check box chỉ đọc hay không.Mặc định là false. Title Tiều đề của dialog tập tin được hiển thị. Lớp này được dùng để lấy một tên tập tin hay nhiều tên tập tin từ người dùng. Khi nó đựợc thực hiện thì ứng dụng có thể xử lý một tập tin hay nhiều tập tin được cho biết bởi người dùng. Thuộc tính Filter là một khoá để cung cấp một giao diện hữu ích cho người dùng. Bằng cách thu hẹp các tập tin hiển thị thích hợp vào ứng dụng hiện hành, người dùng chắc chắn hơn để tìm tập tin chính xác. Trang 70
  78. Khoa CNTT [MÔI TRƯỜNG VÀ CÔNG CỤ LẬP TRÌNH] Thuộc tính Filter là một chuỗi có thể chứa các tuỳ chọn lọc phức tạp. Mọi filter chứa một diện mạo tóm tắc, theo sau bởi một thanh dọc ( | ) và mẫu filter là một chuỗi tìm kiếm DOS. Các chuỗi cho các tuỳ chọn lọc khác nhau sẽ phân biệt bởi một thanh dọc. Bạn có thể thêm các mẫu đa filter vào một filter đơn bằng cách phân các kiểu tập tin với dấu chấm phẩy. Ví dụ "Image Files(*.BMP;*.JPG;*.GIF)|*.BMP;*.JPG;*.GIF|All files (*.*)|*.*" Đoạn mã sau tạo một đối tượng OpenFileDialog, định hình một số thuộc tính trên nó, và hiển thị nó với người dùng để cho phép họ chọn một tập tin. Ứng dụng có thể sau đó sử dụng thuộc tính FileName hay FileNames để xử lý tham khảo đến tập tin hay các tập tin. OpenFileDialog aOpenFileDialog = new OpenFileDialog(); aOpenFileDialog.Filter = "Text Files (*.txt)|*.txt|Word Documents" + "(*.doc)|*.doc|All Files (*.*)|*.*"; aOpenFileDialog.ShowReadOnly = true; aOpenFileDialog.Multiselect = true; aOpenFileDialog.Title = "Open files for custom application"; if (aOpenFileDialog.ShowDialog() == DialogResult.OK) { //Do something useful with aOpenFileDialog.FileName //or aOpenFileDialog.FileNames } aOpenFileDialog.Dispose(); Hộp dialog này có ba tuỳ chọn trong combo box "Files of Type". Một tuỳ chọn là Text Files, một cái khác là Word Documents, và thứ ba là All Files. Trang 71
  79. Khoa CNTT [MÔI TRƯỜNG VÀ CÔNG CỤ LẬP TRÌNH] SaveFileDialog Hộp dialog này rất giống OpenFileDialog, và trong thực tế chúng thừa hưởng từ một lớp cơ sở. Các chức năng cơ bản của hộp dialog này là cho phép một người dùng chọn một nơi để lưu dữ liệu. Nhiều thuộc tính giống như lớp OpenFileDialog; tuy nhiên các thuộc tính sau là các thành viên của OpenFileDialog và không tồn tại trong lớp SaveFileDialog: CheckFileExists Multiselect ReadOnlyChecked ShowReadOnly Nhưng, hai thuộc tính sau chỉ hợp lệ khi là thành viên của lớp SaveFileDialog: CreatePrompt OverwritePrompt Chú ý rằng các thuộc tính khác tạo cùng một kiểu, bao gồm các thuộc tính Filter, Title, và FileName. Đoạn mã sau trình bày cách sử dụng lớp này: SaveFileDialog aSaveFileDialog = new SaveFileDialog(); aSaveFileDialog.Filter = "Text Files (*.txt)|*.txt|Word Documents" + "(*.doc)|*.doc|All Files (*.*)|*.*"; aSaveFileDialog.CreatePrompt = true; aSaveFileDialog.OverwritePrompt = true; aSaveFileDialog.Title = "Save file for custom application"; if (aSaveFileDialog.ShowDialog() == DialogResult.OK) { //Do something useful with aSaveFileDialog.FileName; Trang 72
  80. Khoa CNTT [MÔI TRƯỜNG VÀ CÔNG CỤ LẬP TRÌNH] } aSaveFileDialog.Dispose(); PageSetupDialog Hộp dialog này được dùng để cài định hướng và lề của trang. Thuộc tính chính trong lớp này là Document. Nó được yêu cầu trước khi phương thức ShowDialog() có thể được gọi, và một ngoại lệ được ném nếu nó không được gán một giá trị. Thuộc tính Document chấp nhạn một đối tượng PrintDocument, đối tượng này là thành viên của System.Drawing.Printing namespace. Đối tượng này là cốt yếu cho tiến trình in ấn trong .NET, và tượng trưng các trang mà ạôt ứng dụng sẽ in. Bằng cách cài các thuộc tính của đối tượng này và sử dụng GDI+ để vẽ bề mặt của nó, ứng dụng có thể in bởi máy in. Ví dụ bên dưới mô tả cách các hộp Dialog được gọi và sử dụng: PageSetupDialog aPageSetup = new PageSetupDialog(); System.Drawing.Printing.PrintDocument aDoc = new System.Drawing.Printing.PrintDocument(); aPageSetup.Document = aDoc; if (aPageSetup.ShowDialog() == DialogResult.OK) { //Do something useful with aPageSetup.Document; } aPageSetup.Dispose(); Đoạn mã này tạo một đối tượng PageSetupDialog mới, liên kết với một đối tượng PrintDocument mới với nó và hiển thị dialog. Trang 73
  81. Khoa CNTT [MÔI TRƯỜNG VÀ CÔNG CỤ LẬP TRÌNH] PrintDialog Dialog này được dùng để chọn máy in, số lượng bản sao, và các trang để in trong một tài liệu. Như dialog trước, đối tượng này yêu cầu một đối tượng PrintDocument có hiệu lực để được liên kết với thuộc tính Document trước khi nó được hiển thị. Đối tượng cũng chứa các thuộc tính khoá sau: Property Description AllowPrintToFile thuộc tính Boolean có thể được cài là true để hiển thị checkbox "Print to file" trog dialog. mặc định là true. AllowSelection Thuộc tính Boolean có thể được cài là true để cho phép in ấn chỉ những phần hiện hành. Mặc định là false. AllowSomePages thuộc tính Boolean có thể được cài là true để cho biết các tuỳ chọn From Page và To Page là enabled. Mặc định là false. Document Thuộc tính PrintDocument mô tả bề mặt in ấn hiện hành. PrintToFile thuộc tính Boolean có thể được cài là true để cho biết checkbox "Print to file" được checked. khi diaog trả về nó có thẻ được check để thấy nếu ngừơi dùng muốn ứng dụng in tài liệu vào một tập tin. Mặc định là false. ShowHelp Thuộc tính Boolean có thể được cài là true để cho biết nut Help nên được hiển thị. Mặc định là false. Giống như các lớp dialog thông thường, các thuộc tính này được định hình trước khi gọi phương thức ShowDialog() để hiển thị hộp dialog chính xác cho người dùng. Đoạn mã dùng lớp này rất giống với đoạn mã trước: Trang 74
  82. Khoa CNTT [MÔI TRƯỜNG VÀ CÔNG CỤ LẬP TRÌNH] PrintDialog aPrintDialog = new PrintDialog(); System.Drawing.Printing.PrintDocument aDoc = new System.Drawing.Printing.PrintDocument(); aPrintDialog.Document = aDoc; aPrintDialog.AllowSomePages = true; aPrintDialog.AllowSelection = true; if (aPrintDialog.ShowDialog() == DialogResult.OK) { //Do something useful with aPrintDialog.Document; } aPrintDialog.Dispose(); PrintPreviewDialog Lớp này cung cấp một cách nhanh chóng để giới thiệu các khả năng duyệt trước khi in vào một ứng dụng. Lớp này chấp nhận đối tượng PrintDocument trong thuộc tính Document của nó, và cùng đoạn mã mà điều khiển việc in ấn của mộtmáy in sẽ trả lại tài liệu cho hộp dialog này. Hộp dialog này hỗ trợ co dãn, thu nhỏ, và đánh số trang và một tập các tuỳ chọn khác . Visual Inheritance .NET Framework lấy khái niệm thừa kế và cho phép một nhà phát triển sử dụng nó để phát triển các ứng dụng Windows Forms. Một đối tượng Form có thể thừa kế từ một đối tượng Form khác, vì thế chiếm được sự truy cập đến tất cả Buttons, TextBoxes, và Menus. Nó là một đặc trưng rất mạnh trong .NET khi sử dụng để giảm số lượng mã yêu cầu cho việc tạo các cửa sổ và màn hình giống nhau. Khái niệm này gọi visual inheritance. Trang 75
  83. Khoa CNTT [MÔI TRƯỜNG VÀ CÔNG CỤ LẬP TRÌNH] Một Form luôn luôn thừa kế từ System.Windows.Forms. Có nghĩa là nó có thể truy cập đến tất cả thành phần dữ liệu và các phương thức của lớp Form cơ bản. Việc thực thi sự thừa kế yêu cầu một nhà phát triển thừa hưởng đối tượng Form từ một lớp Form tuỳ biến thay cho System.Windows.Forms. Đó là nguyên nhân tất cả control và thuộc tính trong lớp Form tuỳ biến truyền qua các lớp Form được tạo mới. Tuy nhiên, có vài điều quan trọng phải nhớ. Cấp truy cập của các control khác nhau phải được hiểu, giống như cấp truy cập của các thừa kế chuẩn. Một thành phần dữ liệu private thì không thể được truy cập bởi bất kỳ đối tượng nào bên ngoài đối tượng ban đầu. Vì thế, nếu một control không được đánh dấu là protected hay public, lớp thừa hưởng sẽ không tham khảo đến control hay override bất kỳ phương thức của control. Sử dụng thừa kế trực quan có thể rất có lợi khi thừa kế tạo ra một số lượng lớn màn hình mà phải có một thiết kế giống nhau và/hoặc làm các chức năng như nhau. Một ví dụ điển hình là một màn hình thực thể dữ liệu. Nếu ứng dụng của chúng ta không cần nhập các mẫu tin cá nhân, mà còn thông tin automobie, sử dụng thừa kế trực quan để định nghĩa một kiểu thông thường phải là một sự chọn lựa tốt. Hiển nhiên, chúng ta sẽ muốn một màn hình trông giống nhau, nhưng vài control sẽ thay đổi. Hãy sửa đổi ví dụ trước của chúng ta để sử dụng kỹ thuật này. Tạo ra một Windows Application mới trongVisual Studio .NET và đặt tên nó là VisualInheritance. Thay đổi các thuộc tính sau của đối tượng Form1 mặc định. Chúng ta sẽ tạo một cửa sổ menu cung cấp cho ngứời dùng các khả năng nhập các mẫu tin cá nhân hay các mẫu tin automobie. FormBorderStyle – FixedDialog MaximizeBox – False MinimizeBox – False Size – 200, 200 Trang 76