Giáo trình Lập trình mạng nâng cao hướng .NET (Phần 2) - Nghề: Lập trình máy tính - Trình độ: Lành nghề bậc cao

pdf 157 trang Gia Huy 17/05/2022 1810
Bạn đang xem 20 trang mẫu của tài liệu "Giáo trình Lập trình mạng nâng cao hướng .NET (Phần 2) - Nghề: Lập trình máy tính - Trình độ: Lành nghề bậc cao", để 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_lap_trinh_mang_nang_cao_huong_net_phan_2_nghe_lap.pdf

Nội dung text: Giáo trình Lập trình mạng nâng cao hướng .NET (Phần 2) - Nghề: Lập trình máy tính - Trình độ: Lành nghề bậc cao

  1. Bài 4 NHỮNG CHỨC NĂNG MỚI TRONG GIAO DIỆN CỬA SỔ CỦA VB.NET MÃ BÀI: ITPRG23.3 Mục tiêu thực hiện: - Nắm được đặc điểm của Windows Forms - Xây dựng được kiến trúc của Windows Forms - Viết code trên các công cụ của Toolbox Trang 87
  2. Nội dung: Sự quan trọng của Windows Forms ? Những điểm căn bản của Windows Forms ? Kiến trúc (Architecture) của Windows Forms ? Những Controls tàn hình được chứa riêng Chọn Startup Form Owned Forms (Forms có chủ) Không phải mọi controls đều bị khoá (locked) Độ đậm (Opacity) của Form Form properties cho Cancel Button và Default Button Sự khác biệt trong các Hộp Giao Thoại (Dialog Boxes) ShowDialog thay vì Show vbModal DialogResult Sự khác biệt về sắp đặt vị trí cho Forms và Controls Property Location Property Size ReSize nhiều controls Tab Order của các Controls Control Arrays Tự động Resize và định chỗ (positioning) Anchoring (bỏ neo) Docking (gắn vào) Control Splitter Các control Providers Controls HelpProvider và ToolTip Control ErrorProvider Menus Context Menus Sửa đổi Menus lúc Runtime Duplicating Menus MDI Forms Toolbars Items là một collection of Strings Items là một Array of Objects ComboBox Trang 88
  3. 1. Sự quan trọng của Windows Forms ? Windows Forms là cách hiển thị màn ảnh tối tân hơn Win32 bình thường. Kỹ thuật nằm phía sau Windows Forms trước đây được phát triển cho Windows Foundation Classes (WFC), để dùng trong Visual J++. Điều nầy cắt nghĩa sự già dặn và vững chải của một sản phẩm hãy còn ở tình trạng Beta. Khi ta nghe nói đến .NET với những hứa hẹn về ứng dụng trên Internet như Web Forms và Web Services, rất dễ cho ta tưởng rằng Microsoft phải cung cấp Windows Forms là cực chẳng đã cho nó trọn vẹn món hàng. Thật ra, Windows Forms là một phần của các base classes của .NET Framework. Cái Namespace dùng cho nó là System.Windows.Forms, một Namespace chứa rất nhiều thứ đến đổi hầu như chúng ta sẽ không cần phải dùng trực tiếp các Windows API về đồ hoạ (Graphics và Drawings) như trong VB6 nữa. Nhu cầu có những áp dụng phía khách (client-based application) phong phú (rich), linh động (flexible) và nhanh chóng (responsive) sẽ vẫn còn đó. Hiện nay, để tránh phí tổn về cài đặt (deployment) các chương trình, người ta bắt đầu có khuynh hướng đặt các chương trình chạy trên Webserver, rồi cho user sử dụng chúng qua WebBrowser. Ngoài công chúng thì dùng Internet, trong hãng xưởng thì dùng Intranet (Intranet là Internet chạy trên Local Area Network - mạng địa phương, không liên lạc gì với bên ngoài), tuy nhiên giao diện trên Web không phong phú hay nhanh như trên desktop và dĩ nhiên công tác lập trình đòi hỏi một thời gian phát triển lâu hơn. Vì .NET Framework chứa đầy đủ mọi thư viện cần thiết cho chương trình, nên một khi đã cài đặt .NET Framework trên máy khách rồi ta chỉ cần XCopy đến đó những folders cần thiết có chứa các tệp (files) chương trình và dữ kiện là đủ. Thực hiện việc nầy trên mạng địa phương (Local Area Network) rất dễ và nhanh, thậm chí ta có thể tự động hóa công tác copy nầy. Trong mô hình lập trình nhiều tầng (multi-tier programming model) mà ta gọi là Windows DNA (Distributed Network Application), quá trình xử lý một công tác được chia ra làm nhiều giai đoạn như: 1. Kiểm chứng các con số user mới điền vào các forms tại máy khách (user interface) 2. Tính toán (business logic) 3. Truy cập cơ sở dữ liệu (database access) Và mỗi giai đoạn nói trên có thể nằm trên một computer khác nhau. Nếu dùng Internet thì giai đoạn 1 nói trên sẽ chạy trong WebBrowser bằng trang Web có chứa JavaScript routines để kiểm chứng các con số user mới đánh vào. Còn các giai đoạn kia có thể chạy trên WebServer. Dĩ nhiên giai đoạn 3 phải chạy trên WebServer, nơi chứa cơ sở dữ kiện. .NET cho phép ta lập trình giai đoạn 1 để chạy trong Windows Forms. Còn các giai đoạn kia có thể để y nguyên. Như thế, giả dụ như ta có một hệ thống đặt hàng, ta có thể cho các telephone operators dùng desktop (Winforms) application với một giao diện được tối ưu hóa, chạy thật nhanh để phục vụ những người đặt hàng bằng điện thoại. Trong khi đó khách hàng cũng có thể đặt hàng qua Internet WebBrowser như bình thường. Cả hai nhóm users nầy dù có giao diện khác nhau nhưng đều xài chung các tầng business logic và database access. Trang 89
  4. Đây là một ưu điểm rất quan trọng của .NET mà ít ai chú ý. Nếu thiết kế khéo, ta có thể lập trình để dùng chung hầu hết phần mềm trên desktop, distributed (phân tán), Internet và Mobile (Mobile phone, Pocket-PC). 2. Những điểm căn bản của Windows Forms ? Trong các bài học và thí dụ trước đây ta đã nói qua, bây giờ ta tóm tắc những điểm căn bản của Windows Forms: Một Windows Form thật sự là một class. Trong .NET không có từ đặc biệt như "form module" để dùng cho nó. Vì một form là một class nên ta không thể load nó mà không nói thẳng thừng ra. Tức là trong VB6 nếu ta Show hay dùng đến một Form thì nó tự động được loaded. Chẳng những thế thôi, cái class Form2 được dùng như một variable Form2 luôn, tức là by default ta có một Object tên Form2. Trong .NET ta phải khai báo (declare) một variable tên myForm2 chẳng hạn rồi instantiate form ấy như một Object của Form2 trước khi dùng nó. Tất cả mọi form đều thừa kế từ class System.Windows.Forms.Form. Giống như tất cả các classes trong .NET Framework, Windows Forms có constructors và destructors. Constructor của form tên là Sub New, đại khái giống như Sub Form_Load trong VB6. Destructor của form tên là Sub Dispose, đại khái giống như Sub Form_Unload trong VB6. Cái visual forms designer của VS.NET nhét rất nhiều code để instantiate form và đặt các controls vào form. Đó là code mà đáng lẽ ta phải tự viết nếu ta dùng notepad để lập trình. Phần code nầy thay thế cái phần nằm ở đầu tệp .frm của VB6 để diễn tả các visual components của form. Mỗi lần ta thêm bớt các controls hay thay thế các properties của controls trên form thì code generated cho form được thay đổi theo. Do đó bạn nên tránh sửa đổi code ấy, trừ khi biết chắc mình đang làm gì, hay là bạn làm một phiên bản trước khi thay đổi để nếu lỡ kẹt thì restore code cũ. Event được xử lý bằng cách linh động hơn. Các events chứa nhiều tin tức hơn. Một Event có thể được xử lý bởi nhiều controls cùng một lúc và mỗi control có một cách xử lý khác nhau. Ngược lại, nhiều Events khác nhau có thể được xử lý bằng một Event Handler duy nhất. Bạn tạo một chương trình Windows Forms bằng cách dùng IDE menu command File | New | Project để hiển thị giao thoại New Project và chọn Template Windows Application. Trang 90
  5. hình 4.1 : giao thoại New Project Trong thí dụ nầy, khi bạn click nút OK thì một subfolder tên (Name:) WindowsApplication4 sẽ được tạo ra trong folder (Location:) E:\NET\HongDevelopment\LessonPreparation để chứa các tệp của Project. Sau nầy, khi bạn build, tức là compile chương trình, thì kết quả sẽ là một tệp .exe chứa trong folder E:\NET\HongDevelopment\LessonPreparation\WindowsApplication4\bin. Dĩ nhiên trước khi click nút OK bạn có thể sửa Name: hay Location: tùy ý. Ngoài ra, vì bạn chọn Windows Application, nên project của bạn tự động có reference đến .NET component System.Windows.Forms.dll. Để xem lướt qua namespace System.Windows.Forms, bạn hãy thử xúc tiến tạo cái project WindowApplication4 nầy. Kế đó bạn chạy Object Browser bằng cách click hình tam giác nhỏ của Class View icon rồi chọn Object Browser: hình 4.2 : lựa chọn Object Browser Trang 91
  6. Trong Object Browser, expand cái System.Windows.Forms tree để xem những types được định nghĩa bên trong và các class members của Form: hình 4.3 : Trong Object Browser 3. Kiến trúc (Architecture) của Windows Forms ? Nếu bạn xem gia phả của form, bạn sẽ thấy tổ phụ (đời thứ nhất) nó là class Object, còn form là con cháu đời thứ bảy. Dưới đây là cái cây của gia phả form và một ít chú thích: Thứ bậc các classes Chú thích Object Ông tổ trong .NET, superclass cao nhất từ đó sanh ra con cháu. MarshalByRefObject Cung cấp các code cần thiết để quản lý cuộc đời của objects. Cung cấp sự gầy dựng căn bản của IComponent interface và Component cho phép các chương trình khác nhau dùng chung một object Trang 92
  7. Đây là base class của mọi component dùng để hiển thị. Nó hỗ trợ những khả năng liên hệ đến vóc dáng và công tác hiển thị từ Show, BringtoFront, Font, Color cho đến Dock, Anchor. Ngoài ra Control nó còn cung cấp các Events của keyboard, mouse và có method WndProc để cho ta truy cập các thông điệp của Windows. Cung cấp chức năng tự động cuốn khi có chứa bên trong một ScrollableControl control cần thêm chỗ để hiển thị. ContainerControl Cho phép một component chứa các controls khác. Form Cửa sổ chính của một chương trình. 4. Những Controls tàn hình được chứa riêng Một thay đổi rất tốt trong .NET từ VB6 là những controls không hiển thị lúc chạy thì khi thiết kế chúng được chứa trong một cái mâm riêng phía dưới. Thí dụ như trong hình dưới đây ta có Timer, Tooltip, Menus và các Dialogs được cho nằm trong một component Tray. Hình 4.5 : giao diện trang Muốn thay đổi properties của Control nào, ta chỉ cần chọn nó rồi right click và chọn Properties. Trang 93
  8. 5. Chọn Startup Form Để chỉ định StartUp Form của chương trình, bạn cần phải mở cửa sổ Properties của Project để đánh vào Startup Object. Bạn có thể làm điều ấy bằng cách dùng IDE menu command Project | Properties hay right click tên của Project trong Solution Explorer rồi chọn Properties. hình 4.6 : chỉ định StartUp Form của chương trình 6. Vị trí ban đầu Nhiều lúc ta muốn form hiện ra ngay giữa màn ảnh khi chương trình khởi động. VB.NET có thể làm việc ấy tự động nếu bạn set property StartPosition của nó thành CenterScreen. Các vị trí khởi đầu bạn có thể set được liệt kê dưới đây: Trị số Vị trí khởi đầu Kết quả Manual Hiển thị form ở vị trí theo giá trị của property Location của form CenterScreen Hiển thị form ở ngay giữa màn ảnh CenterParent Hiển thị form ở ngay giữa form chủ (owner) của nó WindowsDefaultLocation Hiển thị form ở vị trí default của cửa sổ Hiển thị form ở vị trí default của cửa sổ, với kích thước default của WindowsDefaultBounds cửa sổ Trang 94
  9. 7. Owned Forms (Forms có chủ) Khi một form có chủ, nó được minimized và closed theo form chủ của nó. Owned forms, đôi khi còn được gọi là forms nô lệ, luôn luôn nằm lên trên form chủ của nó. Dầu vậy, nó không cản trở form chủ nhận focus. Ta dùng method AddOwnedForm của form chủ để cho thêm owned form vào collection of OwnedForms của nó như sau: Private Sub Form1_Load( ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load Dim myForm2 As New Form2() myForm2.Show() Me.AddOwnedForm(myForm2) End Sub Form chủ có thể truy cập collection của các forms nô lệ qua property OwnedForms. Dưới đây là code để loop qua các forms nô lệ của một form: Private Sub BtnListOwnedForms_Click( ByVal sender As System.Object, ByVal e As System.EventArgs) Handles BtnListOwnedForms.Click Dim OwnedForm As Form For Each OwnedForm In Me.OwnedForms Console.Write(OwnedForm.Text) Next End Sub Form chủ có thể cắt bỏ (remove) một form nô lệ bằng cách dùng method RemoveOwnedForm như: Me.RemoveOwnedForm(myForm2) Khi một form không còn là nô lệ nữa, nó không hẳn bị unloaded, chỉ trở thành một form tự do (không còn liên hệ với form chủ nữa) thôi. Chú ý sự khác biệt giữa form nô lệ và TopMost form là form nô lệ chỉ nằm trên form chủ nó, trong khi TopMost form nằm trên tất cả mọi forms khác. TopMost form cũng không bị minimized hay closed khi một form nào khác của chương trình bị minimized hay closed. Trang 95
  10. 8. Độ đậm (Opacity) của Form Có một property mới của form rất thú vị để dùng, dù rằng sự ích lợi hay mục đích của áp dụng không rõ ràng. Đó là ta có thể thay đổi độ đậm của một form. Ta có thể làm cho nó trong suốt khi set property Opacity của form bằng 0, hay cho nó mờ mờ như ma nếu trị số của Opacity ít hơn 1. Bạn hãy thử đánh code dưới đây vào một form cho Button1 chẳng hạn, rồi chạy chương trình và click Button1 ấy: Private Sub Button1_Click( ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click Dim i As Double For i = 0 To 1 Step 0.01 ' Opacity có trị số từ 0 (trong suốt) đến 1 (đậm đặt) Me.Opacity = i Next End Sub 9. Form properties cho Cancel Button và Default Button Trong VB6, ta có thể set một button để nó như được clicked khi thật ra user bấm phím Esc. Ta thực hiện điều nầy bằng cách set property Cancel của button ấy thành True. Nó được gọi là Cancel button. Tương tự như thế, nếu ta set property Default của một button thành True, nó được gọi là Default button, khi user bấm phím Enter Default button coi như được clicked. Trong VB.NET ta cũng có thể dùng các chức năng ấy, nhưng bây giờ ta không đá động gì đến property nào của các buttons, mà lại set các properties CancelButton và AcceptButton của chính form. Khi ta click bên phải của property AcceptButton trong cửa sổ Properties thì danh sách các buttons có sẵn trên form được liệt kê ra để ta chọn như dưới đây: Trang 96
  11. hình 4.7 : properties CancelButton và AcceptButton của chính form. Ngoài ra ta cũng có thể chọn các CancelButton và AcceptButton lúc đang chạy chương trình, nhất là khi ta muốn bổ nhiệm các công tác nầy cho những buttons khác vì form đang làm việc trong một trạng thái khác như trong code thí dụ dưới đây: Me.CancelButton = BtnCancel2 Me.AcceptButton = BtnAccept2 10. Sự khác biệt trong các Hộp Giao Thoại (Dialog Boxes) Trong VB6, các hộp giao thoại thật ra là những form bình thường nhưng được hiển thị với parameter vbModal, tức là trong Modal mode. Điều nầy khíến cho hộp giao thoại trở nên form tích cực (active form) duy nhất trong chương trình cho đến khi nó đi khuất. Một hộp giao thoại cần một phương tiện để liên lạc với form gọi nó (calling form). Trong VB6, ta giải quyết vấn đề nầy bằng cách chế ra một property tạm gọi là Action. Ta dùng Read-only property Action như sau trong một hộp giao thoại có hai buttons, OK và Cancel: ' VB6 code used for Dialog Boxes Trang 97
  12. Public Enum dialogAction actionOK = 1 actionCancel = 2 End Enum Dim mAction As dialogAction Public Property Get Action() As dialogAction Action = mAction End Property Private Sub cmdOK_Click() ' Get here when user click the OK button mAction = actionOK ' Hide the Dialog Box to return control to calling form Me.Hide End Sub Private Sub cmdCancel_Click() ' Get here when user click the Cancel button mAction = actionCancel ' Hide the Dialog Box to return control to calling form Me.Hide End Sub Chú ý ta dùng Enumerated type dialogAction. Nó có hai trị số: actionOK và actionCancel. Property Action thuộc loại enumerated type nầy. Khi user click một button, ta set trị số cho local variable mAction rồi Hide cái dialog box. Cái Giao thoại phải được dấu đi (hidden) nhưng không unloaded, vì cái calling form còn phải truy cập dialog box để đọc trị số của property Action để biết user vừa mới click button nào. Giả dụ ta đặt tên cho hộp giao thoại đó là frmDialog. Để gọi một hộp giao thoại từ một form khác trong VB6 ta có thể code như sau: Dim Dialog As frmDialog Set Dialog = New frmDialog ' Instantiate a Dialog Box ' Show dialog box in Modal mode Trang 98
  13. Dialog.Show vbModal Nhưng bao nhiêu đó chỉ là hiển thị hộp giao thoại thôi. Sau khi hộp giao thoại đã Hide rồi ta còn phải truy cập nó để đọc trị số của property Action. Do đó ta cần phải viết thêm codes cho đầy đủ sau đây: Dim Dialog As frmDialog Set Dialog = New frmDialog ' Instantiate a Dialog Box ' Show dialog box in Modal mode Dialog.Show vbModal ' Get here after the dialog box has hidden, but still loaded ' Now process the Action Select Case Dialog.Action Case actionOK ' code goes here for normal processing Case actionCancel ' code goes here for user canceling End Select Unload Dialog ' Now we can unload the dialog box Có hai sự thay đổi quan trọng trong VB.NET, đó là dùng ShowDialog và DialogResult. 11. ShowDialog thay vì Show vbModal Argument vbModal không được hỗ trợ trong VB.NET. Thay vào đó, một form có thể dùng method ShowDialog. Dưới đây là sự so sánh của coding trong VB6 và VB.NET. VB6 code: Dim Dialog As frmDialog Set Dialog = New frmDialog ' Instantiate a Dialog Box ' Show dialog box in Modal mode Dialog.Show vbModal Trang 99
  14. VB.NET code: Dim Dialog As New frmDialog() ' Show dialog box in Modal mode Dialog.ShowDialog Để ý là trong VB.NET ở hàng code đầu ta có thể kết hợp hai chuyện khai báo và instantiate form mới trong một statement. Hàng code cuối cho thấy sự thay đổi từ Show vbModal qua ShowDialog. 12. DialogResult Trong VB.NET, khi một form khải thị bằng method ShowDialog, nó đã dự bị sẵn một property tên là DialogResult để calling form có thể truy cập. DialogResult có thể mang một trong những trị số enumerated sau đây: DialogResult.Abort DialogResult.Cancel DialogResult.Ignore DialogResult.No DialogResult.None DialogResult.OK DialogResult.Retry DialogResult.Yes Có điểm rất tiện là khi DialogResult được set cho một trị số thì dialog được dấu đi (hidden) một cách tự động. Cách đơn giản nhất để set trị số cho DialogResult là assign một trị số cho property DialogResult của một button. Khi user click button ấy thì DialogResult của hộp giao thoại lấy trị số của property DialogResult của button và hộp giao thoại Hide. Trang 100
  15. hình 4.8 : property DialogResult của một button Để biểu diễn ShowDialog trong VB.NET, kèm theo đây là mã nguồn của một thí dụ. Trong thí dụ nầy ta tạo một form tên frmDialog có hai button tên OK và Cancel. Ta set property DialogResult của button OK thành OK và property DialogResult của button Cancel thành Cancel. Form frmDialog hoàn toàn không có một hàng code nào cả. Form chính của chương trình, Form1, chỉ có một button tên BtnShowDialog với code cho Event Click như dưới đây: Private Sub BtnShowDialog_Click( ByVal sender As System.Object, ByVal e As System.EventArgs) Handles BtnShowDialog.Click ' Declare and instantiate a Dialog Box Dim Dialog As New frmDialog() ' Show the Dialog Box in Modal mode Dialog.ShowDialog() ' get here after user has clicked a button and the Dialog box has hidden ' Process the DialogResult Select Case Dialog.DialogResult Case DialogResult.OK MsgBox("User clicked OK, se please go ahead") Case DialogResult.Cancel Trang 101
  16. MsgBox("Sorry, but User clicked Cancel") End Select Dialog = Nothing ' Dispose the Dialog Box End Sub Bạn có thể chạy chương trình rồi click button ShowDialog. Khi Dialog box hiển thị, thử click một trong hai buttons trên ấy. So sánh với VB6, ta thấy dùng Dialog Box trong VB.NET đơn giản và tự nhiên hơn. Nếu không dùng Property DialogResult của một button trong Dialog Box để trả về kết quả DialogResult, ta cũng có thể dùng code trong Dialog form như sau: Me.DialogResult = DialogResult.Retry Hàng code trên set DialogResult của Dialog form thành DialogResult.Retry và kềm theo phản ứng phụ là Hide Dialog Box. Calling form sẽ truy cập được kết quả DialogResult.Retry nầy. 13. Property Size Property Size trong VB.NET có cùng một ý niệm như property Location, có điều nó tương xứng với Width và Height. Property Size nhận và trả về một structure tên Size, có chiều cao và chiều rộng để áp dụng cùng một lúc thay vì tuần tự từng chiều. Giống như Left và Top, trong code ta vẫn còn dùng Width và Height được như xưa. Nhưng Width và Height không hiện ra trong cửa sổ Properties của forms hay controls. Để thay đổi Size của một form, ta có thể code như sau: Me.Size = (New Size(300, 400)) 14. Tab Order của các Controls Sắp đặt thứ tự trong Tab của các controls (Tab Order) trên một form đôi khi rất phiền phức trong VB6. VS.NET cho ta một feature rất tiện dụng để làm việc nầy. Để khởi động feature ấy, ta dùng IDE menu command View | Tab Order. Nó sẽ hiển thị một con số nhỏ ở góc trên trái của mỗi control, cho thấy trị số Tab Index của mỗi control. Bây giờ ta chỉ cần click lên từng control một theo thứ tự mà ta muốn. Dưới đây là screenshot của một form sau khi user chỉ định Tab Order cho các controls. Muốn ra khỏi Tab Order mode, ta bấm menu command View | Tab Order một lần nữa. Trang 102
  17. hình 4.9 : đây là screenshot của một form Ghi chú:Trong VB.NET nhiều controls có thể có cùng một Tab Index. Trong trường hợp ấy, thứ tự về Tab của chúng được quyết định dựa vào z-order. Control có z-order cao nhất sẽ nhận focus trước nhất trong nhóm. Z-order của một control có thể được thay đổi bằng cách right click control rồi chọn Bring to Front. 15. Control Arrays Khi nghe nói VB.NET không hỗ trợ Control Arrays chắc bạn buồn năm phút. Có hai lý do tại sao bạn cần Control Arrays: 1. Dùng cùng một Event handler (thí dụ như Sub BtnBrowse_Click) để xử lý Event từ nhiều Controls tương tự. 2. Để dynamically tạo thêm Controls trong form lúc đang chạy program (at runtime). May thay, VB.NET cung cấp cho ta một phương tiện khác để khỏi phải thua thiệt. VB.NET cho phép ta linh động bổ nhiệm các methods để xử lý Events của các controls. Điểm thứ nhất bạn sẽ chú ý là bạn không thể dùng cùng một tên cho nhiều controls nữa. Property Index đã bị khai tử. Trong VB.NET bạn có thể dùng một Event handler duy nhất để xử lý Events đến từ các controls tương tự. Trước đây ta dựa vào Index để biết Event phát xuất từ control nào. Bây giờ bạn dựa vào parameter Sender. Để minh họa điểm nầy, ta sẽ viết một chương trình có hai buttons, Button1 và Button2, nằm trên form chính. Double click Button1 để viết code xử lý Event Button1.Click. Muốn dùng cùng một Event Sub nầy để xử lý luôn Event Click đến từ Button2, bạn chỉ cần thêm chữ Button2.Click vào cuối cái Handles List của Sub Button1_Click. Để cho có vẻ tổng quát ta rename Sub Button1_Click thành Sub Button_Click. Bây giờ ta viết vài dòng code đơn giản để hiển thị cho biết Event Click đến từ Button nào: ' Note that we change the name of the Sub from Button1_Click to Button_Click to ' make it more general, since we're going to use this same Sub to handle Click ' Events originated from many different Buttons ' Also note that we add the word Button2.Click to the end of Sub Button_Click declaration Trang 103
  18. Private Sub Button_Click( ByVal sender As System.Object, ByVal e As System.EventArgs) _ Handles Button1.Click, Button2.Click Dim btnClicked As Button ' Type cast sender to Button btnClicked = CType(sender, Button) ' Show what button was clicked MessageBox.Show("You clicked """ & btnClicked.Text & """") End Sub Thử chạy chương trình và click Button2, bạn sẽ thấy hình dưới đây: hình 4.10 : khi chạy chương trình Để biểu diễn chức năng quản lý Event Handling at runtime, ta sẽ đặt một button tên BtnAddNewButton vào form để nó dynamically add một button thứ ba tên Button3. Ta muốn button nầy cũng sẽ dùng Sub Button_Click để xử lý Event Click của nó. Vì không thể đánh thêm chữ Button3.Click vào cuối câu Sub Button_Click như trước đây ta đã làm với Button2.Click, nên at runtime ta sẽ dùng statement: ' Tell system to use Button_Click as Event Handler for the Event Button3.Click AddHandler newButton.Click, AddressOf Me.Button_Click Mã nguồn đầy đủ của Sub BtnAddNewButton_Click được liệt kê dưới đây: Private Sub BtnAddNewButton_Click( ByVal sender As System.Object, ByVal e As System.EventArgs) Handles BtnAddNewButton.Click Trang 104
  19. ' Declare and instantiate a Button Dim newButton As New Button() ' Set it up on the form With newButton .Text = "Button3" ' Text of this new button .Location = New Point(230, 120) ' define its location on the form .Size = New Size(88, 40) ' define its size End With ' Add the new button to the form's collection of controls Me.Controls.Add(newButton) ' Tell system to use Button_Click as Event Handler for the Event Button3.Click AddHandler newButton.Click, AddressOf Me.Button_Click End Sub Khi user click BtnAddNewButton, Button3 với Size(88,40) sẽ được tạo ra và đặt ở Location(230,120) trên form. Kế nó chương trình thực hiện hai chuyện quan trọng: Add button mới nầy vào collection of controls của form và đăng ký (register) việc dùng Sub Button_Click làm Event Handler của Event Click của nó. Làm xong mấy chuyện nầy rồi, bạn chạy chương trình, click AddNewButton để thêm Button3 vào form, kế đó click Button3, bạn sẽ thấy hình dưới đây: hình 4.11 : sau khi chỉnh sửa và chạy thử 16. Tự động Resize và định chỗ (positioning) Những chương trình ứng dụng chuyên nghiệp ta mua ngoài chợ để dùng thường thường có đặc tính resize các controls hay định vị trí của các controls trên form một cách tự động. Nếu bao giờ Trang 105
  20. bạn đã thử thêm các chức năng ấy cho một chương trình áp dụng viết bằng VB6 của mình, bạn sẽ thông cảm rằng coi vậy chớ đó không phải là chuyện nhỏ. Tưởng tượng là ta phải ghi nhớ vị trí và kích thước của mỗi control trên form để mỗi lần user resizes form thì ta phải theo đó resize và định vị trí của control. Trong lúc thiết kế ta phải cho user một phương tiện để chỉ định rằng họ muốn một control cư xử như thế nào khi form resize. Để chứa tin tức ấy hoặc ta dùng property Tag của control hoặc ta dùng registry. Chỉ việc đọc ra, viết vào để cập nhật hoá các tin tức cũng đủ mệt, chưa nói đến chuyện tính toán để resize và định vị trí của control. Do đó, nhiều khi làm biếng ta dùng đại một third party ActiveX để giúp ta làm các chuyện ấy. .NET cho ta thêm các properties Anchor và Dock cho mỗi control. Ngoài ra .NET còn cung cấp control Splitter để cho phép ta nắm một thanh phân hai kéo qua, kéo lại hay kéo lên, kéo xuống tùy thích, để mở rộng thêm một bên trong khi bên kia bị thu hẹp. Cái áp dụng của Splitter thông dụng nhất là trong Windows Explorer. Trong đó ta có hai phần: bên trái là một Treeview chứa cái cây của disk drives và file folders, bên phải là một Listview chứa icons hay chi tiết của các folder và files. Muốn xem Treeview nhiều hơn, ta nắm thanh phân hai ở giữa kéo qua bên phải một chút. 17. Anchoring (bỏ neo) Khi con tàu bỏ neo là nó đỗ ở đó. Dù con nước chảy thế nào, con tàu vẫn nằm yên một chỗ vì nó đã được cột vào cái neo. Control trong .NET có property Anchor để ta chỉ định nó được buộc vào góc nào của form: Left, Right, Bottom hay Top. Trong lúc thiết kế, sau khi select cái control (thí dụ Button1), ta vào cửa sỗ Properties và click hình tam giác nhỏ bên phải property Anchor. Một hình vuông với bốn thanh ráp lại giống hình chữ thập màu trắng sẽ hiện ra. Mỗi thanh tượng trưng cho một góc mà ta có thể chỉ định để cột control vào form. Khi ta click một thanh, nó sẽ đổi màu thành xám đậm, và một chữ tương ứng với thanh ấy sau nầy sẽ hiển thị trong textbox area của combobox Anchor. Thí dụ ta click vào thanh dưới và hai thanh hai bên, ta sẽ có Bottom, Left, Right như trong hình dưới đây: Trang 106
  21. hình 4.12 : properties Archor Khi Button1 có Anchor là Bottom, Right thì mỗi khi góc phải dưới của form di chuyển vì resize, Button1 cứ chạy theo góc ấy: hình 4.13: Button1 có Anchor là Bottom Nếu Button1 có Anchor là Left, Right, Bottom thì khi form resizes cho lớn ra, Button1 cứ giữ khoảng cách từ nó đến ba cạnh Left, Right, Bottom của form không đổi. Do đó nó phải nở rộng ra như trong hình dưới đây: hình 2.14 : Button1 có Anchor là Left, Right, Bottom Nếu Button1 có Anchor là Top,Bottom,Left, Right thì khi form resizes, Button1 cứ giữ khoảng cách từ nó đến bốn cạnh Left, Right, Top, Bottom của form không đổi. Do đó nó phải nở rộng hay thu nhỏ cả chiều cao lẫn chiều rộng như trong hình dưới đây: Trang 107
  22. hình 4.15 : sau khi resize form Vì property Anchor có hiệu lực lập tức ngay trong lúc ta thiết kế, nên nếu bạn resize form trong lúc thiết kế, các control có Anchor property set cũng resize và di chuyển theo. Có thể bạn không muốn chuyện đó xãy ra, nên tốt nhất là set property Anchor của các control sau khi thiết kế form xong hết rồi. 18. Docking (gắn vào) Khi ta Dock một control vào một cạnh của form có nghĩa là ta dán dính nó vào cạnh đó. Áp dụng ta thường thấy nhất của Docking là ToolBar và StatusBar. ToolBar thì dock vào phía trên của form, còm StatusBar thì dock vào phía dưới của một form. Chúng dãn ra chiếm từ trái qua phải của form, user không thể chỉ định chiều rộng của chúng. Khi form được resized thì ToolBar và StatusBar cũng dãn ra hay co vào theo chiều rộng của form. Property Dock của control trong .NET cũng giống giống như property Align của control trong VB6 StatusBar. Ta chỉ có thể dán một control vào một trong bốn cạnh của form, chớ không có chuyện bắt cá hai, ba tay như trường hợp Anchor có thể neo vào Left, Right, Bottom cùng một lúc. Tuy nhiên, property Dock có trị số Fill để nói control chiếm hết bên trong phần còn lại của container của nó. Trong lúc thiết kế, sau khi select cái control (thí dụ Label1), ta vào cửa sỗ Properties và click hình tam giác nhỏ bên phải property Dock. Một hình vuông nhiều thanh màu xám sẽ hiện ra. Mỗi thanh tượng trưng cho một cạnh mà ta có thể chỉ định để dán control vào form (Top, Bottom, Left hay Right), cái hình vuông ở giữa tượng trưng cho trị số Fill, và thanh dưới chót có chữ None cho phép ta xóa không chọn trị số Dock nào cả Khi ta click một thanh, trị số Docking tương ứng sẽ hiển thị trong textbox area của combobox Dock. Trang 108
  23. hình 4.16 : properties Dock Giả sử ta set Property TextAlign của Label là MiddleCenter bằng cách chọn cái thanh xám nằm ngay giữa trong số 9 thanh tượng trưng cho các vị trí của Text có thể nằm trong Label1 như trong hình dưới đây: hình 4.17 : properties TextAlign Khi chạy chương trình và resize form cho lớn ra, ta sẽ thấy Label1 dãn ra hai bên, nhưng không hề tăng bề cao, và Text của Label1 luôn luôn nằm ở giữa. hình 4.18 : Khi chạy chương trình và resize form cho lớn ra Trang 109
  24. Nếu bạn tìm cách dock nhiều controls vào cùng một cạnh của form thì VB.NET phải quyết định control nào nằm sát cạnh ấy nhất. Qui ước về thứ tự là ngược lại với thứ tự trong z-order. Tức là trong z-order, control nào nằm dưới nhất thì lại được dock trước nhất vào cạnh của form. Do đó, nếu bạn dock hai controls vào một cạnh, và muốn cái control nằm xa cạnh được dock trước nhất (tức là sát cạnh nhất) thì right click control ấy và chọn Send To Back. Nếu bạn muốn chừa một khoảng trống giữa control và cạnh của container thì set Property DockPadding của container. Tự trước đến giờ ta dùng form để đại diện container chứa controls. Thật ra container cũng có thể là một Panel. Bạn có thể set Property DockPadding của các cạnh của container khác nhau bằng cách click dấu + bên trái chữ DockPadding trong cửa sổ Properties để mở ra các chi tiết như trong hình dưới đây: hình 4.19 : properties Top Bạn có thể set tất cả Property DockPadding cùng một trị số bằng cách dùng All setting. 19. Control Splitter Bây giờ bạn đã hiểu rõ các đặc tính, sự khác biệt và cách dùng hai properties Anchor và Dock của control, sau đây ta sẽ áp dụng kiến thức ấy vào việc thiết kế dùng Splitter trong một form. Nếu còn mới với Splitter bạn sẽ dễ bị bực mình khi dùng nó. Do đó, bạn hãy thử làm theo các bước sau đây: 1. Tạo một Application mới, đặt một Panel lên phía trái của form chính để nó chíếm bên trái của form bằng cách set property Dock của nó thành Left. Ta gọi Panel ấy là Panel1. 2. Đặt một Splitter lên form (nhớ tránh đặt nó lên Panel1 vì Panel cũng là một loại container nên có thể chứa Splitter được). Splitter sẽ tự động dock Left vào form tức là nằm bên phải Panel1. Chọn property BoderStyle của Splitter1 làm FixedSingle cho dễ thấy. 3. Đặt một button lên Panel1 và set property Anchor của nó thành Top, Left, Right. Bây giờ form sẽ giống như dưới đây: Trang 110
  25. 4. Kế đó, đặt một Panel lên bên phải của form, gọi là Panel2, và set property Dock nó thành Fill. Có nghĩa là ta muốn Panel2 chiếm hết phần còn lại bên phải của form. 5. Thêm vào trong Panel2 nầy một Button, gọi là Button2, và set property Anchor của nó thành Top, Left, Right. Khi chạy chương trình, mỗi lần bạn nắm Splitter kéo qua phải thì Button1 dãn ra và Button2 co lại: hình 4.20: hiển thị khi chạy trương trình Ngược lại, nếu bạn nắm Splitter kéo qua trái thì Button1 co ra và Button2 dãn lại: Trang 111
  26. Hình 4.21 : hiển thị khi chạy trương trình Trong thí dụ nầy ta để yên chiều rộng của Splitter, nhưng bình thường ta làm cho nó hẹp hơn. Nếu Splitter hẹp thì khó thấy, do đó bạn có thể cho nó một màu đỏ rực trong lúc thiết kế để dễ thấy. Khi thiết kế xong hết rồi, bạn đổi nó lại thành một màu dịu hơn. Nếu bây giờ bạn muốn chia Panel2 thành hai phần, ngăn cách bởi một Horizontal Splitter thì sao? Ta cứ xem Panel2 như một form vậy, tức là cả hai đều là containers, loại control có thể đựng nhiều controls, và lập lại các bước sau: 1. Đặt một Panel lên phía trên của Panel2, gọi nó là Panel3 và set property Dock của nó thành Top. 2. Đặt một Splitter lên Panel2 (nhớ tránh đặt nó lên Panel3), gọi nó là Splitter2 và set property Dock của nó cũng thành Top. Resize Splitter2 cho nó dẹp lại và đổi property Backcolor thành ra ControlDark cho dễ thấy. 3. Đặt một Panel lên phía dưới của Panel2, gọi nó là Panel4 và dời Button2 từ Panel2 qua Panel4 bằng cách Cut and Paste. 4. Set property Dock của Panel4 thành Fill. Bây giờ hãy chạy chương trình và nắm kéo Splitter2 lên xuống. Hình 4.22 : hiển thị khi chạy trương trình Tóm lại, muốn dùng control Splitter trong một form hay panel ta đặt một PanelX với Docking Left hay Top lên trước, kế đó đặt một Splitter với cùng loại Docking với PanelX, rồi đặt PanelY với Docking Fill. Trang 112
  27. 20. Các control Providers Trong Windows Forms có một gia đình controls mới mà ta chỉ có thể dùng khi chúng đi chung với các controls khác trên cùng một form. Chúng được gọi là Provider Controls và có đặc tính là khiến cho các property mới hiện ra trong các controls khác. Provider Controls không hiển thị trên form lúc chạy program. Do đó chúng nằm riêng trong Component Tray lúc ta thiết kế. Hiện giờ có 3 Provider Controls : HelpProvider, ToolTip và ErrorProvider. Cả ba đều làm việc một cách tương tự nhau. 21. Controls HelpProvider và ToolTip Trong VB6, các controls có property HelpContextID để ta chỉ định khi user bấm nút F1 thì chương trình sẽ hiển thị Help ở đúng trang có trị số HelpContextID trong Help file. Còn ToolTip là một Textstring property của mỗi control. Ta chỉ cần dùng cửa sổ Properties để cho vào ToolTip text của một control là trong lúc chạy chương trình, khi nào ta để mouse cursor nằm lên control là chương trình sẽ hiển thị ToolTip text. Hai thứ ấy không còn dùng trong Windows Forms nữa. Thay vào đó, ta phải đặt các Provider Controls lên form để thực hiện các công tác tương đương. Control HelpProvider cho phép các controls khác chỉ định context sensitive help (trợ giúp trong tình huống đương thời) hiển thị khi user bấm nút F1. Khi một control HelpProvider (gọi là HelProvider1 by default) được thêm vào một form, thì mọi controls trên form đều sẽ có thêm các properties dưới đây, chúng sẽ hiển thị trong cửa sổ Properties sau khi ta chọn một control. Property Áp dụng HelpString on Khi control được focus, user bấm nút F1 sẽ popup Tooltip HelpString cho HelpProvider1 control Cung cấp một Topic cho control để dùng trong Help file cho context- HelpTopic on sensitive help. Control HelpProvider1 có một property để ta chỉ định dùng HelpProvider1 Help file nào ShowHelp on Xác định là control HelpProvider có Active cho control nầy không HelpProvider1 Một khi property HelpString đã được cho một Textstring thì trong lúc control nhận được focus, nếu user bấm nút F1 một Tooltip sẽ hiển thị Textstring ấy. HelpProvider có một property để dẫn đến một Help file, hoặc là HTMLHelp file, hoặc là Win32Help file, và trị số trong property HelpTopic sẽ chỉ dẫn đến topic ấy trong Help file. Trang 113
  28. Trong lúc chương trình chạy, ta cũng có thể thay đổi trị số HelpString của Textbox1 như sau: HelpProvider1.SetHelpString(Textbox1, "Một HelpString mới được dùng tại đây.") Control ToolTip cũng hoạt động tương tự, nhưng đơn giản hơn. Nó chỉ cho thêm một property mới tên ToolTip on ToolTip1 vào mỗi control, giả dụ tên của ToopTip provider là ToolTip1. Property nầy làm việc y hệt như ToolTipText trong VB6. Trong lúc chương trình chạy, ta cũng có thể set cho property Tooltip của Textbox txtName một trị số Textstring như sau: ToolTip1.SetToolTip(txtName, "Xin vui lòng đánh tên bạn vào đây") 22. Control ErrorProvider Thông thường sau khi user điền xong các dữ kiện vào một form thì sẽ click một button OK hay Submit chẳng hạn. Để tránh trường hợp cập nhật data của một record với những dữ kiện bất hợp lệ, ta thường kiểm tra lại dữ kiện nằm trong từng Textbox trên form và hiển thị một thông điệp để nhắc nhở và giải thích cho user khi có error. Nếu user lầm lỗi ở nhiều Textboxes thì có thể sẽ có nhiều thông điệp hiển thị lần lượt cái nầy tiếp theo cái kia, mỗi thông điệp liên hệ đến một Textbox có error. Cách ấy cũng tạm được, nhưng có thể khiến cho user bực mình. Control ErrorProvider cung cấp một cách đơn giản và thân thiện để cho user biết Textbox nào có dữ kiện bất hợp lệ. Control ErrorProvider cho các controls trên cùng form một property mới gọi là Error on ErrorProvider1 ( giả dụ là control ErrorProvider mang tên ErrorProvider1). Trong lúc chương trình chạy, nếu kiểm thấy một Textbox có lỗi ta assign một TextString vào property Error on ErrorProvider1 của Textbox ấy. Lúc bấy giờ một icon đỏ hình dấu chấm than trắng sẽ hiển thị bên phải Textbox có Error. Nếu user để mouse cursor lên trên icon ấy thì chương trình sẽ hiển thị một Tooltip với trị số TextString của property Error on ErrorProvider1 giống như trong hình dưới đây: hình 4.23 : chương trình chạy Công việc assign một TextString vào property Error on ErrorProvider1 của một Textbox có thể được coded như sau: Private Sub BtnOK_Click( ByVal sender As System.Object, ByVal e As System.EventArgs) Handles BtnOK.Click Trang 114
  29. ' Set error if TextBox txtName is blank If txtName.Text = "" Then ' Assign error ToolTip message to Textbox txtName ErrorProvider1.SetError(txtName, "You must supply a name!") End If End Sub Trên đây ta dùng Event Click của button BtnOK để kiểm tra dữ kiện trong mọi Textbox. Có một Event của các controls mà ta cũng có thể dùng trong công tác kiểm tra dữ kiện của một TextBox. Đó là Event Validating. Để gây ra Event Validating ta cần phải dùng property CauseValidation của các controls. Thông thường, property CauseValidation của các controls được set thành True. TextBox txtName chỉ tạo ra Event Validating khi chính property CauseValidation của nó là True và khi focus được di chuyển đến một control khác có property CauseValidation là True. Xin lưu ý là không nhất thiết Event Validating được tạo ra khi txtName mất focus. Khi txtName mất focus thì Textbox txtAge được focus (giả dụ txtAge có trị số TabOrder ngay sau txtName) , nhưng nếu property CauseValidation của txtAge không phải là True thì phải đợi đến khi focus đáp lên một control có property CauseValidation là True txtName mới gây ra Event Validating. Ta có thể code cho Sub txtName_Validating như sau: Private Sub txtName_Validating( ByVal sender As Object, ByVal e As System.ComponentModel.CancelEventArgs) _ Handles txtName.Validating ' Set error if TextBox txtName is blank If txtName.Text = "" Then ' Assign error ToolTip message to Textbox txtName ErrorProvider1.SetError(txtName, "You must supply a name!") Else ' Clear the error ToolTip message for Textbox txtName and make error Icon invisible ErrorProvider1.SetError(txtName, "") End If End Sub Cái icon đỏ hình dấu chấm than trắng là default icon của ErrorProvider. Muốn dùng một icon khác ta chỉ cần assign icon ấy vào property Icon của ErrorProvider. 23. Menus Khi nào bạn select control Main Menu trong mâm components là bạn có thể edit các MenuItems. Muốn làm việc với MenuItem nào thì select MenuItem đó. Những chỗ có chữ Type Trang 115
  30. Here là đề nghị cho bạn đánh thêm vào một MenuItem (Type Here nằm phía dưới) , một MenuCommand mới (Type Here nằm bên phải một MenuCommand) hay một MenuSubItem (Type Here nằm bên phải một MenuItem). hình 4.23 : propertive Name Muốn insert một lằn ngang giữa MenuItem Paste và MenuItem Clear All, bạn select MenuItem Clear All rồi right click và chọn Insert Separator trong Pop-Up Menu. hình 4.24 : Insert Separator trong Pop-Up Menu Muốn chỉ định Shortcut cho một MenuItem, bạn select MenuItem ấy rồi vào cửa sổ Properties để chọn trị số cho property Shortcut. Tương tự như vậy cho property Checked để làm một checkmark hiện ra bên trái (phía trước) Text của MenuItem. Trang 116
  31. hình 4.25 : propertive Shortcut Thêm vào các dòng code sau đây cho chương trình. Khi doubleClick lên MenuItem copyMenuItem cửa sổ mã nguồn sẽ mở ra cho bạn đánh code cho Private Sub copyMenuItem_Click: Private Sub copyMenuItem_Click( ByVal sender As System.Object, ByVal e As System.EventArgs) Handles copyMenuItem.Click ' Copy the selected text to the Clipboard Textbox1.Copy() End Sub Private Sub pasteMenuItem_Click( ByVal sender As System.Object, ByVal e As System.EventArgs) Handles pasteMenuItem.Click 'Paste the Clipboard text into Textbox1 Textbox1.Paste() End Sub Private Sub clearAllMenuItem_Click( ByVal sender As System.Object, ByVal e As System.EventArgs) Handles clearAllMenuItem.Click ' Clear everything in Textbox1 Trang 117
  32. Textbox1.Text = "" End Sub Private Sub closeMenuItem_Click( ByVal sender As System.Object, ByVal e As System.EventArgs) Handles closeMenuItem.Click ' Close the form Me.Close() End Sub Khi chạy chương trình, hình dưới đây sẽ hiển thị: hình 4.27: Khi chạy chương trình 24. Context Menus Ta dùng Context Menu để Pop-Up một Menu xứng hợp với tình huống đương thời của program khi user right click một control trên form. Trong VB6, Context Menu cũng là một MenuCommand thông thường nhưng ta thiết kế cho nó invisible, để chỉ khi nào ta muốn Pop-Up nó thì nó mới hiển thị. Trong VB.NET, Context Menu là một control riêng, nhưng ta edit nó cũng giống như Main Menu. Khi đã thêm một control ContextMenu vào form rồi, mỗi lần ta select nó trong mâm components thì Context Menu hiện ra ở cạnh trên của form giống như Main Menu. Lúc Runtime, khi user right click một control có Context Menu thì ContextMenu sẽ hiển thị ở vị trí đó. Bạn hãy doubleClick control ContextMenu trong hộp đồ nghề để thêm một Context Menu vào trong form. Kế đó set up các MenuItem như sau: Trang 118
  33. hình 4.28 : propertive Radio button Để hiển thị cái Radio button bên trái một MenuItem, bạn phải làm hai chuyện: 1. Set property Checked của MenuItem thành True để hiển thị một checkmark hay một hình tròn nhỏ (Radio button). 2. Set property RadioCheck của MenuItem thành True để khi nào nó hiển thị thì có dạng Radio button, thay vì một checkmark. Nhớ là ta dùng checkmark khi muốn cho user chọn nhiều thứ cùng một lúc, và dùng Radio button khi muốn cho user chỉ chọn một nhiệm ý mà thôi, tức là mutually exclusive. Tuy nhiên, khác với khi edit một nhóm Radio buttons trong một container trên form, VB.NET không cản trở ta cho hai Radio buttons trong một menu cùng hiện ra. Do đó, bạn phải tự quản lý vấn đề mutually exclusive trong code của mình. Để chỉ định ContextMenu1 Pop-up khi user right click Textbox1, bạn chỉ cần set property ContextMenu của Textbox1 thành ContextMenu1 (chọn nó trong cái dropdown list của comboxbox của property ContextMenu trong cửa sổ Properties). Khi bạn chạy chương trình và right click Textbox1, ContextMenu1 sẽ hiển thị như dưới đây: hình 4.29 : ContextMenu1 sẽ hiển thị Mã nguồn nằm phía sau các click events của hai MenuItems của ContextMenu1 được liệt kê dưới đây: Trang 119
  34. Private Sub blackOnWhiteMenuItem_Click( ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles blackOnWhiteMenuItem.Click ' Change colors of Textbox1 Textbox1.ForeColor = Color.Black Textbox1.BackColor = Color.White 'Toggle the radio check blackOnWhiteMenuItem.Checked = True WhiteOnBlueMenuItem.Checked = False End Sub Private Sub WhiteOnBlueMenuItem_Click( ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles WhiteOnBlueMenuItem.Click ' Change colors of Textbox1 Textbox1.ForeColor = Color.White Textbox1.BackColor = Color.Blue 'Toggle the radio check blackOnWhiteMenuItem.Checked = False WhiteOnBlueMenuItem.Checked = True End Sub Để ý property Checked của hai MenuItems được coded để hễ cái nầy True thì cái kia phải False, tức là mutually exclusive. Và MenuItem nào có trị số Checked là True thì Radio button hiển thị phía trước nó. 25. Sửa đổi Menus lúc Runtime Ta có thể sửa đổi Menu lúc Runtime, chẳng hạn như Context Menu thường có những dạng khác nhau tùy theo trạng thái của một control hay form. Một thí dụ khác là hiển thị danh sách các files mà chương trình truy cập trong quá khứ. Thông thường ta chứa tên các files ấy trong Registry và khi cần sẽ đọc và load vào Menu. Dưới đây là code chỉ cách cho thêm một MenuItem vào trong một ContextMenu, và cách clear (xóa) mọi MenuItems. Ta biết rằng ContextMenu có một property là collection của những MenuItems. Do đó muốn thêm một MenuItem thì cần trải qua ba bước: 1. Instantiate một MenuItem. 2. Đăng ký Event Handler (ở đây là AddressOf Sub NewMenuItem_Click), mà chương trình sẽ dùng để xử lý Event Click của MenuItem ấy. 3. Thêm MenuItem ấy vào collection MenuItems của control ContextMenu. Trang 120
  35. Thêm vào form hai buttons đặt tên là BtnAddMenuItem và BtnClearContextMenu. Private Sub BtnAddMenuItem_Click( ByVal sender As System.Object, ByVal e As System.EventArgs) _ Handles BtnAddMenuItem.Click 'Add a menu item at the top of ContextMenu1 Dim AnewMenuItem As MenuItem ' Declare a MenuItem variable ' Create the new menu Item AnewMenuItem = New MenuItem("New Menu Item!") ' Register EventHandler for Event Click of this new Menu item AddHandler AnewMenuItem.Click, AddressOf Me.NewMenuItem_Click ' Add it to the collection MenuItems ContextMenu1.MenuItems.Add(0, AnewMenuItem) End Sub Private Sub NewMenuItem_Click( ByVal sender As System.Object, ByVal e As System.EventArgs) MessageBox.Show("You clicked new Menu Item!") End Sub Private Sub BtnClearContextMenu_Click( ByVal sender As System.Object, ByVal e As System.EventArgs) _ Handles BtnClearContextMenu.Click ' Remove all the menu items from ContextMenu1 ContextMenu1.MenuItems.Clear() End Sub Sau khi bạn click nút Add MenuItem, lúc bạn right click Textbox1, Pop-up Menu sẽ có thêm một MenuItem như sau: Trang 121
  36. hình 2.30 : Pop-up Menu Thử click new Menu Item trong ContextMenu1, chương trình sẽ hiển thị thông điệp You clicked new Menu Item!. Bây giờ click nút Clear ContextMenu rồi right click Textbox1. ContextMenu1 đã bị cleared nên sẽ không hiển thị. 26. Duplicating Menus Một việc khác ta có thể làm trong lúc Runtime của chương trình là cloning (tạo object song sinh). Thí dụ, ta muốn dùng Edit menu của MainMenu1 làm ContextMenu (giống giống như trong VB6) cho Textbox1. Để thực hiện việc nầy, ta dùng method CloneMenu(). Dưới đây là code ta dùng để thay thế ContextMenu1 trong chương trình bằng Edit menu của MainMenu1. Private Sub BtnCloneMenu_Click( ByVal sender As System.Object, ByVal e As System.EventArgs) _ Handles BtnCloneMenu.Click ' Instantiate a new ContextMenu object Dim newContextMenu As New ContextMenu() ' Add a clone copy of EditMenu to this new ContextMenu's collection of MenuItems newContextMenu.MenuItems.Add(editMenuItem.CloneMenu) ' Assign this new Context Menu to Textbox1 Textbox1.ContextMenu = newContextMenu End Sub Khởi động chương trình, click nút Clone Menu, rồi right click TextBox1, ContextMenu mới sẽ hiển thị như dưới đây: Trang 122
  37. hình 4.31: ContextMenu Lưu ý: Vì CloneMenu() clone hoàn toàn Object editMenuItem, kể cả các Event Handlers của các SubMenuItems nên ta không cần phải làm thêm gì cả. Muốn trở lại trạng thái cũ, tức là dùng ContextMenu1 cho Textbox1, ta chỉ cần reassign ContextMenu1 vào property ContextMenu của Textbox1 như sau: Textbox1.ContextMenu = ContextMenu1 27. MDI Forms Trong VB6 ta tạo một MDI (Multiple Document Interface) form bằng cách set property MDIChild của form ấy thành True. Một form như thế chỉ có thể được dùng làm child form, tức là nó cần một form MDI parent để hiển thị trong ấy. Ngoài ra, mỗi application chỉ có thể có một form MDI parent duy nhất và chỉ trong lúc thiết kế ta mới có thể chỉ định đặc tính của một form là MDIChild. Một form không thể trở thành một MDIChild lúc Runtime. Trong VB.NET, một form có thể trở thành một MDI child lúc Runtime bằng cách set property MDIParent của form ấy để nhắm vào một form MDI parent. Do đó, một form có thể vừa là MDIchild form, vừa là form bình thường tùy theo hoàn cảnh. Thật ra, ngược với VB6, ta không thể set property MDIParent lúc thiết kế, mà phải làm lúc Runtime. Giống như VB6, trong VB.NET ta có thể hiển thị nhiều forms MDIChild trong một form MDI parent, khi parent form di chuyển thì mang theo các forms con. Khi hiển thị nhiều child forms, ta có thể dùng property ActiveForm để biết child form nào hiện thời là Active. Ta thử khởi động một Windows Application mới. Đổi tên Form1 thành ParentForm và chỉ định nó làm MDI parent bằng cách set property IsMDIContainer của nó thành True. Kế đó thêm một form và đổi tên nó thành ChildForm. Dưới đây là code để thêm hai child forms vào ParentForm và hiển thị chúng: ' Declare child forms of type ChildForm Private WithEvents FirstChild As ChildForm Private WithEvents SecondChild As ChildForm Trang 123
  38. Private Sub ParentForm_Load( ByVal sender As System.Object, ByVal e As System.EventArgs) _ Handles MyBase.Load ' Instantiate an Object of type Childform FirstChild = New ChildForm() ' Make this form the MDI Parent of FirstChild FirstChild.MdiParent = Me FirstChild.Text = "First Child Form" ' Set Title ' Show FirstChild FirstChild.Show() ' Instantiate the second Object of type Childform SecondChild = New ChildForm() ' Make this form the MDI Parent of SecondChild SecondChild.MdiParent = Me SecondChild.Text = "Second Child Form" ' Set Title ' Show SecondChild SecondChild.Show() End Sub Để cung cấp một Menu hiển thị danh sách các forms MDIchild của ParentForm, ta thêm control MainMenu vào ParentForm. Kế đó, tạo một MenuItem tên Windows và set property MDIList của nó thành True. Property nầy sẽ khiến danh sách các forms child tự động hiển thị làm những menu items nằm phía dưới Menu Windows. Danh sách nầy tự động cập nhật khi một child form trở thành Active, được thêm vào, hay bị lấy ra. Parent MDI form có một method tên là LayoutMDI để tự động sắp đặt vị trí các forms child theo kiểu Cascade hay Tile layout. Thêm một MenuItem tên Tile Vertical và nhét mấy hàng code dưới đây vào form để xử lý Event click của nó: Private Sub tileVerticalMenuItem_Click( ByVal sender As System.Object, ByVal e As System.EventArgs) _ Handles tileVerticalMenuItem.Click Me.LayoutMdi(System.Windows.Forms.MdiLayout.TileVertical) End Sub Trang 124
  39. Khởi động chương trình, by default hai forms childs được layout kiểu Cascade. Trong Menu Windows có hiển thị title của hai forms child và cho biết Second Child Form là Active form. hình 4.32 : hiển thị title Nếu bạn click Tile vertical, hai forms child sẽ được layout kiểu Tile như dưới đây: hình 4.33 : hiển thị title 28. Toolbars Toolbars trong .NET đã được nâng cấp bằng cách thêm chức năng cho các ToolBarButtons trong collection của những buttons ấy. Trang 125
  40. Để dùng thử Toolbar control, bạn hãy khởi động một Project mới và đặt một Toolbar vào form chính bằng cách doubleclick lên Toolbar icon trong Toolbox. Một Toolbar sẽ hiện ra nằm ngay dưới tiêu đề của form. Kế đó rightclick lên Toolbar ấy và chọn Properties để edit property Buttons Collection bằng cách click lên chữ (Collection) rồi click ba dấu chấm phía bên phải để hiển thị ToolbarButton Collection Editor. hình : 4.34 : ToolbarButton Collection Editor Bạn hãy Add vào Toolbar ba buttons với những đặc tính sau: Đổi property Text của button thứ nhất (ToolbarButton1) ra Close vì ta muốn đóng chương trình khi user click lên button ấy. By default Style của ToolbarButton là PushButton. Đổi property Style của button thứ nhì (ToolbarButton2) ra Separator vì ta muốn dùng nó để tạo khoảng cách giữa button thứ nhất và button thứ ba. Đổi property Text của button thứ ba (ToolbarButton3) ra Background Colour và property Style ra DropDownButton vì ta muốn dùng nó như một Combobox. Khi chạy thử chương trình ta sẽ thấy hình giống như dưới đây: hình 4.35 : chạy trương trình Trang 126
  41. Bây giờ ta sẽ viết code để xử lý Event Click của Toolbar. Chỉ có một handler, Sub ToolBar1_ButtonClick, được dùng cho tất cả các buttons. Ta phân biệt Button nào dựa vào Index của nó, giống giống như một array of buttons trong VB6. Nếu user click button thứ nhất ta sẽ có ToolBar1.Buttons.IndexOf(e.Button) bằng 0, lúc ấy ta sẽ Close form chính. Private Sub ToolBar1_ButtonClick( ByVal sender As System.Object, ByVal e As System.Windows.Forms.ToolBarButtonClickEventArgs) Handles ToolBar1.ButtonClick Select Case ToolBar1.Buttons.IndexOf(e.Button) Case 0 ' Close Button Me.Close() Case 1 ' Never happens because the Button is a Separator Case 2 ' MessageBox.Show("You clicked the third button") End Select End Sub Nếu không muốn dùng ToolBar1.Buttons.IndexOf(e.Button), bạn cũng có thể so sánh Buttons với operator Is như sau: If e.Button Is ToolBarButton1 Then Me.Close() ElseIf e.Button Is ToolBarButton3 Then MessageBox.Show("You clicked the third button") End If Kế đó chúng ta cho đặt một ContextMenu tên ContextMenu1 vào form và assign nó vào property DropDownMenu của button thứ ba như trong hình dưới đây: Trang 127
  42. 4.36 : property DropDownMenu Nếu không muốn assign ContextMenu1 vào button thứ ba trong lúc thiết kế, bạn có thể thực hiện việc ấy bằng code lúc form mới load như sau: Private Sub frmToolbar_Load( ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load ToolBarButton3.DropDownMenu = ContextMenu1 End Sub Bạn hãy edit hai menuItems cho ContextMenu1: một cái tên mnuXám với Text là Xám và cái kia tên mnuTrắng với Text là Trắng. Khi chạy chương trình, nếu bạn click cái thanh có dấu tam giác đen nằm bên phải button thứ ba, ContextMenu1 sẽ hiện ra để bạn dùng. Nếu bạn click button thứ ba, chương trình cũng generate một Click Event nhưng hiện giờ ta không dùng nó, chỉ hiển thị một sứ điệp nhỏ để xác định là có Event Click ấy. Như thế, ta thấy .NET ghép một ContextMenu vào một ToolbarButton để biến nó thành một DropDownMenu. Có điều sau khi user đã chọn một Item trong ContextMenu/DropDownMenu, Text của Item đó không được hiển thị giống như trong một ComboBox. Nếu bạn khó tính và muốn có chuyện đó thì phải tự làm lấy như cho thấy trong code dưới đây: Private Sub frmToolbar_Load( ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load ToolBarButton3.DropDownMenu = ContextMenu1 ToolBarButton3.Text = "Xám" End Sub Private Sub mnuXám_Click( ByVal sender As System.Object, ByVal e As System.EventArgs) Handles mnuXám.Click Trang 128
  43. MessageBox.Show("Bạn chọn màu Xám") ToolBarButton3.Text = "Xám" End Sub Private Sub mnuTrắng_Click( ByVal sender As System.Object, ByVal e As System.EventArgs) Handles mnuTrắng.Click MessageBox.Show("Bạn chọn màu Trắng") ToolBarButton3.Text = "Trắng" End Sub Khi chạy chương trình bạn sẽ thấy như sau: hình 4.37 : khi chạy trương trình ListBox 29. Items là một collection of Strings Mới dùng đến, ta sẽ thấy .NET ListBox rất giống ListBox trong VB6. Tiện ở chỗ bây giờ ta có thể edit các string Items của ListBox trong một editor nho nhỏ sẽ hiện ra khi ta click vào chữ (Collection) của property Items: Trang 129
  44. hình 4.38 : Collection của property Items Các Items được chứa trong một collection tên Items, do đó ta có thể làm việc với mọi chức năng của một collection như Add, Clear, Insert, Remove, RemoveAt, Count .v.v Thí dụ như ta cho thêm bốn Items vào Listbox1 lúc Form_Load như sau: Private Sub frmListbox_Load( ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load ' Add individual items ListBox1.Items.Add("Kăng-gu-ru") ListBox1.Items.Add("Công") ' Add more than one items by instantiating an object with items list enclosed in curly brackets {} ListBox1.Items.AddRange(New Object() {"Đà điểu", "Gấu Panda"}) End Sub Nếu trong khi chạy chương trình, bạn thêm nhiều Items vào ListBox và muốn tránh update display Listbox nhiều lần, bạn có thể kẹp code giữa hai statements BeginUpdate và EndUpdate như sau: ' Shutdown the painting of the ListBox as items are added. ListBox1.BeginUpdate() ' Loop through and add 50 items to the ListBox. Dim x As Integer For x = 1 To 50 ListBox1.Items.Add("Item " & x.ToString()) Next x ' Allow the ListBox to repaint and display the new items. ListBox1.EndUpdate() Giống như trong VB6, property MultiColumn hiển thị Items trong nhiều cột nếu được set thành True, property SelectionMode nếu bằng MultiExtended thì cho ta select nhiều Items cùng một lúc. Tuy nhiên, các Items được chọn sẽ có mặt trong một collection chớ không phải có Selected(i)=True như trong VB6. Muốn select một Item lúc run-time ta dùng code như sau: ' Select three items (2nd, fourth and sixth) from the ListBox. ListBox1.SetSelected(1, True) ' 1 is index of 2nd item ListBox1.SetSelected(3, True) Trang 130
  45. ListBox1.SetSelected(5, True) Trong thí dụ tại đây ta có ListBox1 với danh sách các con vật trong Sở Thú Saigon. Button List Items sẽ liệt kê danh sách nầy. Để ý cách ta hiển thị một Item với expression Listbox1.Items(i).ToString. Private Sub BtnListItems_Click( ByVal sender As System.Object, ByVal e As System.EventArgs) Handles BtnListItems.Click Dim i As Integer Dim Mess As String ' make up the list of Items separated by CarriageReturn/LineFeed For i = 0 To ListBox1.Items.Count - 1 Mess &= (ListBox1.Items(i).ToString) & vbCrLf Next ' Show the list MessageBox.Show(Mess) End Sub hình 4.39 : List Items Sau khi set property SelectionMode của Listbox1 ra MultiExtended, code dưới đây sẽ liệt kê danh sách các items được chọn với index của chúng: Private Sub BtnListSelectedItems_Click( ByVal sender As System.Object, ByVal e As System.EventArgs) Handles BtnListSelectedItems.Click Dim i As Integer Dim Mess As String Trang 131
  46. ' make up the list of Selected Items separated by CarriageReturn/LineFeed ' Collection SelectedIndices contains the index of selecteditems For i = 0 To ListBox1.SelectedItems.Count - 1 Mess &= (ListBox1.SelectedIndices(i).ToString) & ":" & (ListBox1.SelectedItems(i).ToString) & vbCrLf Next ' Show the list MessageBox.Show(Mess, "Selected Items", MessageBoxButtons.OK, MessageBoxIcon.Information) End Sub hình 4.40 : listBox ra MultiExtended 30. Items là một Array of Objects ListBox của .NET không hổ trợ ItemData như trong VB6. ItemData là một array chứa các con số tương ứng với những Items trong List array của ListBox trong VB6. Tức là mỗi ListBox Item trong Vb6 có thể được chỉ định trước một con số đại diện nó. Khi user select List(i), ta có thể lấy ra ItemData(i) của List Item ấy. Thật ra Items của .NET Listbox cũng có thể là một Array of Objects, không nhất thiết phải là một collection of Strings như ta đã dùng. Dưới đây là code ta định nghĩa một Class tên LBItem, đoạn dùng code thể Add một Array of Objects loại LBItem vào Listbox1: Public Class LBItem Private mList As String Private mItemData As Integer ' List Item of Listbox Public Property List() As String Get Trang 132
  47. Return mList End Get Set ( ByVal Value As String) mList = Value End Set End Property ' ItemData of Listbox Public Property ItemData() As Integer Get Return mItemData End Get Set ( ByVal Value As Integer) mItemData = Value End Set End Property ' Function to return a string representing this item for display Overrides Function ToString() As String Return mList End Function End Class Sau khi Add một Array of Objects vào ListBox1 ta phải chỉ định làm thế nào để hiển thị một Item. Thí dụ như dùng property List của LBItem như dưới đây: ' Indicate that Property List of LBItem will be used to display ListBox1.DisplayMember = "List" Nếu ta không chỉ định DisplayMember, tức là ListBox1.DisplayMember = "" thì ListBox1 sẽ dùng Function ToString của LBItem để hiển thị. Ngoài ra, để trả về một value giống như ItemData của List Item ta chỉ định ValueMember như dưới đây: Private Sub BtnAddOjects_Click( ByVal sender As System.Object, ByVal e As System.EventArgs) Handles BtnAddOjects.Click ' Clear all items in Listbox1 ListBox1.Items.Clear() Trang 133
  48. Dim Objs(5) As LBItem ' Create an array of 6 Objects of LBItem Dim i As Integer For i = 0 To 5 Objs(i) = New LBItem() Objs(i).List = "Line " & i.ToString Objs(i).ItemData = i + 100 Next ' Add the array of objects to Listbox1 ListBox1.DataSource = Objs ' Indicate that Property List of LBItem will be used to display ListBox1.DisplayMember = "List" ' Indicate that Property ItemData of LBItem will be used to return a value ListBox1.ValueMember = "ItemData" End Sub Khi chạy chương trình nầy, sau khi click nút Add Objects để clear ListBox1 và Add 6 Objects mới, nếu bạn click hàng thứ 4 trong ListBox sẽ thấy hình dưới đây hình 4.41 : list box line 3 Code xử lý Event SelectedIndexChanged (tức là Event Click trước đây) của ListBox1 giống như dưới đây: Private Sub ListBox1_SelectedIndexChanged( ByVal sender As System.Object, ByVal e As System.EventArgs) Handles ListBox1.SelectedIndexChanged Try If ListBox1.SelectedValue <> “” Then MessageBox.Show(ListBox1.SelectedValue & “ of “ & ListBox1.SelectedItem.ToString, Trang 134
  49. “Selected value”) End If Catch ex As Exception ‘ Do nothing, ignore this error End Try End Sub Như thế ta đã implemented (thi hành) cho .NET ListBox một chức năng tương đương với ItemData của ListBox trong VB6. .NET ListBox không hổ trợ Style Checkbox, nhưng ta có thể dùng CheckedListBox. 31. ComboBox Vì ComboBox thừa kế từ ListBox nên tất cả những gì ta biết về ListBox đều áp dụng cho ComboBox. Đặc biệt bây giờ ComboBox có property MaxDropDownItems cho ta quyết định hiển thị bao nhiêu items khi danh sách được mở ra. Kèm theo đây là một chương trình biểu diễn ComboBox trong đó ta dùng Property ValueMember của ComboBox để trả về một trị số đại diện Item. Data trong ComboBox1 được loaded từ một Access2000 database table bằng code sau đây: Private Sub frmCombo_Load( ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load Dim ds As New DataSet () ‘ Instantiate a Dataset ‘ Instantiate an OleDbDataAdapter for Access2000 database Authors.mdb and return table Authors Dim myData As New OleDbDataAdapter(“Select * from Authors”, “Provider=Microsoft.Jet.OLEDB.4.0;Data Source= \Authors.mdb”) myData.Fill(ds, “Authors”) ‘ Load table Authors into Dataset With ComboBox1 ‘ Bind Table Authors to ComboBox1 .DataSource = ds.Tables(“Authors”) ‘ Make Property/Datafield FullName the DisplayMember of ComboBox1 .DisplayMember = “FullName” ‘ Make Property/Datafield AuthorID the ValueMember of ComboBox1 .ValueMember = “AuthorID” End With End Sub Trang 135
  50. Chúng ta chỉ định record datafield FullName làm DisplayMember của ComboBox1 và datafield AuthorID làm ValueMember của ComboBox1. Ta truy cập data của cơ sở dữ liệu bằng cách dùng một DataAdapter loại OleDbDataAdapter khi cho nó một SQL CommandText: “Select * from Authors” và một connection string, trong đó có cho biết database driver: Microsoft.Jet.OLEDB.4.0 và tên của database \Authors.mdb. File Authors.mdb nằm chung với mã nguồn của chương trình trong parent folder của folder bin, nơi chứa ComboBox.exe. Kế đó ta dùng DataAdapter để bỏ table Authors vào dataset ds. Cách làm việc nầy tương tự như ADO (Active Data Object) trong VB6. Có điểm khác là Dataset có thể chứa nhiều tables (recordsets) và nó hoạt động như một cached disconnected database trong bộ nhớ. Kỹ thuật nầy có tên là ADO.NET và ta sẽ bàn thêm nhiều về nó trong tương lai. Mỗi lần user select một item mới từ ComboBox1, chương trình sẽ hiển thị AuthorId, là ValueMember trong Label1. Private Sub ComboBox1_SelectedIndexChanged( ByVal sender As System.Object, ByVal e As System.EventArgs) Handles ComboBox1.SelectedIndexChanged Try ‘Display the selected valueMember Label1.Text = ComboBox1.SelectedValue Catch End Try End Sub Ở đây có hai cách để ta select một ComboBox item bằng coding. Cách thứ nhất là cho biết AuthorId (ValueMember), user clicks button Select by AuthorId để thấy kết quả: Private Sub BtnSelectbyAuthorId_Click_1( ByVal sender As System.Object, ByVal e As System.EventArgs) Handles BtnSelectbyAuthorId.Click ‘Use Try to ignore error if operation fails Try ‘ Select the ComboBox Item whose valueMember equal txtAuthorId.Text ComboBox1.SelectedValue = txtAuthorId.Text Catch End Try End Sub và cách thứ hai là cho biết FullName (DisplayMember), user clicks button Select by Name để thấy kết quả: Trang 136
  51. Private Sub BtnSelectByName_Click( ByVal sender As System.Object, ByVal e As System.EventArgs) Handles BtnSelectByName.Click ‘Use Try to ignore error if operation fails Try ‘ Select the ComboBox Item whose DisplayMember equal txtFullName.Text ‘ FindString returns the index of the found item ComboBox1.SelectedIndex = ComboBox1.FindString(txtFullName.Text) Catch End Try End Sub Khi chạy chương trình, bạn sẽ thấy hình như dưới đây. Trong hình ấy, MaxDropDownItems của ComboBox1 đã được set bằng 4. hình 4.42 : MaxDropDownItems của ComboBox1 Trang 137
  52. Bài 5 DATAGRID MÃ BÀI: ITPRG23.5 Mục tiêu thực hiện: - Nắm được các khái niệm của ADO.net - Dùng được XML làm cơ sở dữ liệu - Trích lọc và sắp xếp được dữ liệu - Sửa được XML file dựa trên XML Schema Nội dung: Giới thiệu ADO.NET Dùng thẳng XML làm cơ sở dữ liệu Tạo DataSet từ XML Schema Dùng DataGrid Bind DataSource của DataGrid vào một Dataset Hiển thị các cột data theo ý mình Dùng Dataview để Filter và Sort Làm việc với một Row trong DataGrid Edit XML file dựa trên XML Schema Typed Dataset Dùng Dataform wizard để phát sinh form từ Dataset Trang 138
  53. 1. Giới thiệu ADO.NET Trong .NET, ý niệm Recordset đã được thay thế bằng Dataset. Trong một bài tới ta sẽ học chi tiết về Data Access trong VB.NET, nên hiện giờ chỉ cần biết đại khái về Dataset để dùng trong các thí dụ áp dụng DataGrid. Trong ADO (ActiveX Data Object) của VB6 ta dùng Connection để nối chương trình áp dụng của mình với cơ sở dữ liệu và lấy ra một Recordset. Cái connection ấy vẫn được giữ nguyên trong khi chương trình ta làm việc với Recordset. Trong ADO.NET của .NET sau khi thiết lập connection với cơ sở dữ liệu ta copy một hay nhiều Recordset vào Dataset. Các Recordset nầy có thể có mối liên hệ Master/Slave Relation với nhau. Thí dụ như Invoice/InvoiceDetails, trong đó các InvoiceDetails liên hệ với Invoice qua InvoiceID chẳng hạn, tức là InvoiceID là Primary Key của Invoice và cũng là Foreign Key của InvoiceDetails . Sau đó ta chỉ làm việc với Dataset mà thôi. Cái connection coi như đã bị cắt đứt. Do đó Dataset được xem như là disconnected database nho nhỏ nằm trong bộ nhớ. Chắc chắn bạn sẽ không an lòng và hỏi nếu có hai người cùng copy các Recordset ra dùng thì liệu khi update ta có bị mất những sửa đổi nào không. Đó là vấn đề khó khăn cổ điển về Multiuser lúc hai người copy cùng một record ra để Edit. Người Update record sau có thể viết chồng lên Edited Record của người trước khiến cho những thay đổi người trước đánh vào bị mất. Dataset của ADO.NET cho ta các lợi ích thực tiển như: Hoàn toàn trong bộ nhớ: Một Table trong Dataset là một Array of Rows, nên ta có thể dùng thẳng (direct access) một record bằng cách nói đến cái Row chứa nó, chớ không cần phải dùng MoveNext, MovePrev,.v.v. Làm nhẹ công tác của cơ sỡ dữ kiện chính: Vai trò của Dataset đối với cơ sỡ dữ kiện chính (Oracle, Informix, SQLServer .v.v.) cũng giống như mười năm trước đây ta bắt đầu dùng Workstations để làm nhẹ công tác của Mainframe computer. Chuyện nào Workstation làm được thì ta giao cho nó, vừa nhanh, vừa linh động, khỏi cần phiền đến Mainframe. Tất cả mọi công tác sửa đổi dữ kiện đều được thực hiện trong Dataset. Dataset có thể được biểu diển bằng một XML (eXtensible Marked Language): Ta có thể dùng các công cụ của XML để làm việc với Dataset, trao đổi Dataset giữa các computers trên mạng dưới dạng XML, thậm chí có thể chứa một cơ sở dữ kiện nho nhỏ dưới dạng một XML. 2. Dùng thẳng XML làm cơ sở dữ liệu Chúng ta muốn hiển thị các records của một Table trong Dataset để biểu diễn các chức năng của DataGrid. Hôm nay mình chơi nổi, nên sẽ tạo một XML file để dùng nó làm một Dataset, thay vì trích một Table từ một SQLServer hay Access database. Trước hết bạn hãy khởi động một dự án mới, đặt tên nó là AlarmList. Kế đó, trong Solution Explorer rename tên của file Form1.vb thành frmAlarmList.vb và thay đổi property Text của form thành Alarm List, property Name của form thành frmAlarmList. Trang 139
  54. hình 5.2.1 : Dùng thẳng XML làm cơ sở dữ liệu Lưu ý là chỉ khi bạn thay đổi property Name của form bạn mới thấy tên của Class của form thay đổi từ Form1 ra frmAlarmList như ta thấy trong hình dưới đây: hình 5.2 from Alarmlist Nếu bây giờ bạn right click lên tên của project AlarmList trong Solution Explorer và chọn PopupMenuItem Properties, dialog AlarmList Property Pages hiện ra. Trong ComboBox của Startup object vẫn còn có chữ Form1. Bạn có thể click lên cái cái tam giác bên phải của hộp Combobox để select chữ frmAlarmList để chỉ định nó làm Startup form. Trang 140
  55. hình 5.2.2 : Startup from Trong chương trình nầy ta lo về an ninh và muốn hiển thị các cánh cửa trong tòa nhà bị mở cửa ngoài giờ làm việc. Nếu áp dụng ngoài đời thì Real-time Data sẽ được thu thập từ các dụng cụ gọi là Data Acquisition hay Telemetry Monitoring Devices và báo cáo cho hệ thống trung ương để cập nhật hóa Dataset mà ta dùng để hiển thị trong DataGrid. Bạn hãy click IDE menu command Project | Add New Item rồi chọn XML File và đặt tên nó là AlarmList.xml như trong hình dưới đây: hình 5.2.3 : tạo file XML mới Kế đó ta sẽ đánh vào data của AlarmList. Hàng đầu của XML file là một Processing Instruction (huấn thị cách xử lý) tuyên bố rằng ta dùng tiêu chuẩn xml version 1.0 với Unicode encoding utf-8. Phần chính của cả cái XML file nằm giữa cặp Tag (gọi là opening Tag) và (gọi là closing Tag). Ta gọi alarmlist là Element. Bên trong chính alarmlist là nhiều Trang 141
  56. Elements tên alarm. Các Element alarm không cần có closing Tag vì chúng không có chứa gì bên trong. Trong trường hợp nầy ta chấm dứt opening Tag bằng />. Tuy nhiên mỗi Element alarm có chứ nhiều Attributes như priority, datetime, pointid .v.v bên trong opening Tag của nó. Mỗi Attribute có dạng TêncủaAttribute="valuecủaAttribute". Lưu ý valuecủaAttribute nằm giữa dấu ngoặc kép hay ngoặc đơn. Bây giờ click MenuCommand XML | Create Schema như dưới đây: hình 5.2.4 : XML |Create Schema Trang 142
  57. Chú ý là MenuCommand XML chỉ hiện ra khi ta làm việc với XML file của project (doubleclick tên AlarmList.xml trong Solution Explorer). Một Schema của file AlarmList.xml, tên AlarmList.xsd, sẽ hiện ra trong Solution Explorer và bạn có thể doubleclick tên AlarmList.xsd để làm việc với file ấy. .NET IDE cho ta hình của Schema để ta có thể Edit dễ dàng. Thông thường, IDE chỉ xác nhận thứ nào là Element (có chữ E nằm bên trái), thứ nào là Attribute (có chữ A nằm bên trái). Còn về Datatype thì nó nhắm mắt nói mọi thứ là string. Do đó ta phải sửa đổi lại theo ý mình, để nói Attribute priority là integer và các Attributes alarm, isolate, fault, ackn là Boolean. hình 5.2.5 : Boolean AlarmList.xsd cũng là một XML file, nó chứa chi tiết về cấu trúc của AlarmList.xml và Datatype (loại data ) của từng Element và Attribute trong file AlarmList.xml. Nếu bạn click Tab XML kế bên Tab DataSet phía dưới bạn sẽ thấy hình dưới đây: hình 5.2.6 : click Tab XML Trang 143
  58. 3. Tạo DataSet từ XML Schema Bây giờ click trở lại Tab DataSet, đoạn dùng MenuCommand Schema | Generate Dataset để tạo ra cấu trúc của một DataSet dựa vào Schema AlarmList.xsd. hình 5.3.1: Schema | Generate Dataset Một file tên AlarmList.vb sẽ được tạo ra trong source code folder của project. Nó định nghĩa và cung cấp mọi method cần thiết để dùng cho DataSet loại alarmlist. 4. Dùng DataGrid Để hiển thị data trong DataGrid, ta có thể bind (buộc) nó vào Dataset, Array hay Collection. Thật ra, nếu bạn muốn, bạn có thể câu cả DataGrid vào một Set của Listbox Items. DataGrid trong .NET có thể hiển thị cả Master/Detail records nên nó đảm nhận luôn chức năng của MSHFlexGrid trong VB6. 5. Bind DataSource của DataGrid vào một Dataset Ta hãy trở lại form frmAlarmList. Đặt lên nó một DataGrid tên DataGrid1 và thêm một Button tên BtnLoadXMLData với Text Load XML Data. Doubleclick lên Button BtnLoadXMLData để cho vào các hàng codes sau đây: ' Declare a Dataset of type alarmlist from AlarmList.vb that was created from the schema AlarmList.xsd Dim DS As alarmlist Private Sub BtnLoadXMLData_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) _ Handles BtnLoadXMLData.Click ' Instantiate a DataSet type alarmlist DS = New alarmlist() ' Load the XML data from file AlarmList.xml in the source code folder. Note that the program Trang 144
  59. EXE resides ' in the bin subfolder DS.ReadXml(" /AlarmList.xml") ' Bind the Datagrid DataSource to this new DataSet table alarm DataGrid1.DataSource = DS.alarm End Sub Bây giờ bạn có thể chạy chương trình của chúng ta. Click nút Load XML Data bạn sẽ thấy như sau: hình 5.5. : Bind DataSource của DataGrid vào một Dataset 6. Hiển thị các cột data theo ý mình Trong hình trên tất cả datafields của mỗi record được hiển thị với cùng bề ngang và với datafield name của chúng dùng làm đề tựa của mỗi cột. Ta có thể dùng DataGridTableStyle object để hiển thị các cột data theo ý mình bằng cách gọi Sub AddCustomDataTableStyle() (có code liệt kê dưới đây) trong Sub BtnLoadXMLData_Click. Private Sub AddCustomDataTableStyle() ' Instantiate a DataGridTableStyle object Dim ts1 As New DataGridTableStyle() ts1.MappingName = "alarm" ' Name of data table ts1.AlternatingBackColor = Color.Beige ' Make alternating row beige ts1.RowHeadersVisible = False ' Make RowHeaders (on the left of DataGrid) invisible ' Only two types are supported: Text and Boolean ' Add the display field in order of display from left to right 'Just ignore a datafield if you don't want to display it Dim boolCol1 As New DataGridBoolColumn() ' Boolean boolCol1.MappingName = "ackn" ' Name of datafield boolCol1.HeaderText = "Ackn" boolCol1.Width = 35 Trang 145
  60. ts1.GridColumnStyles.Add(boolCol1) ' Dim TextCol1 As New DataGridTextBoxColumn() ' Text TextCol1.MappingName = "priority" TextCol1.HeaderText = "Prio" TextCol1.Width = 30 ts1.GridColumnStyles.Add(TextCol1) ' Dim TextCol2 As New DataGridTextBoxColumn() TextCol2.MappingName = "datetime" TextCol2.HeaderText = "Time" TextCol2.Width = 110 ts1.GridColumnStyles.Add(TextCol2) ' Dim boolCol2 As New DataGridBoolColumn() boolCol2.MappingName = "alarm" boolCol2.HeaderText = "ALM" boolCol2.Width = 30 ts1.GridColumnStyles.Add(boolCol2) ' Dim boolCol3 As New DataGridBoolColumn() boolCol3.MappingName = "isolate" boolCol3.HeaderText = "ISO" boolCol3.Width = 30 ts1.GridColumnStyles.Add(boolCol3) ' Dim boolCol4 As New DataGridBoolColumn() boolCol4.MappingName = "fault" boolCol4.HeaderText = "FLT" boolCol4.Width = 30 ts1.GridColumnStyles.Add(boolCol4) ' Dim TextCol3 As New DataGridTextBoxColumn() TextCol3.MappingName = "pointid" Trang 146
  61. TextCol3.HeaderText = "PointID" TextCol3.Width = 70 ts1.GridColumnStyles.Add(TextCol3) ' Dim TextCol4 As New DataGridTextBoxColumn() TextCol4.MappingName = "description" TextCol4.HeaderText = "Description" TextCol4.Width = 210 ts1.GridColumnStyles.Add(TextCol4) ' Dim TextCol5 As New DataGridTextBoxColumn() TextCol5.MappingName = "statusvalue" TextCol5.HeaderText = "Status/Value" TextCol5.Width = 150 ts1.GridColumnStyles.Add(TextCol5) ' Dim TextCol6 As New DataGridTextBoxColumn() TextCol6.MappingName = "diagram" TextCol6.HeaderText = "Diag" TextCol6.Width = 40 ts1.GridColumnStyles.Add(TextCol6) ' Now add the DataGridTableStyle object to Collection TableStyles of Datagrid1 DataGrid1.TableStyles.Add(ts1) End Sub Trong code bên trên, ta thấy DataGridTableStyle tên ts1 chứa một collection of GridColumnStyles. Muốn hiển thị datafield nào của record ta tạo cho nó một cột hoặc là Text hoặc là Boolean. Nếu là Text thì ta instantiate một object loại DataGridTextBoxColumn, nếu là Boolean thì ta instantiate một object loại DataGridBoolColumn. MappingName của DataGridTextBoxColumn hay DataGridBoolColumn là tên của Datafield. HeaderText là Tiêu đề nằm phía trên của cột datafield ấy. Width là bề ngang của cột mà bạn phải thí nghiệm các con số vài lần cho nó hiển thị vừa vặn. Cột nào ta cho vô trước thì hiển thị bên trái, vô sau thì nằm bên phải. Nếu ta cố ý không nhắc đến một datafield nào, thì nó không được hiển thị. Sau khi chuẩn bị DataGridTableStyle đầy đủ rồi, ta cho nó vào Collection TableStyles của DataGrid1. Kế đó Edit property Text của DataGrid1 cho nó câu "Dùng DataGridTableStyle để hiển thị theo ý mình". Chạy chương trình lại và click nút Load XML Data bạn sẽ thấy như sau: Trang 147
  62. Hình 5.6 :DataGridTableStyle 7. Dùng Dataview để Filter và Sort Thường thường, khi điều khiển trong thời gian thật (real-time control), là Operator, ta muốn các alarms có ưu tiên cao và mới xãy ra nhất được hiển thị trên hết. Đôi khi, ta chỉ muốn thấy các alarm priority 3 (ưu tiên cao nhất) mà thôi. Để thực hiện các việc nầy, ta dùng Dataview Object. Thay vì dùng thẳng table alarm của DataSet alarmlist làm datasource của DataGrid1, ta sẽ dùng một DataView derived from (đến từ) table alarm. Ta có thể Sort (sắp theo thứ tự) các alarms/records theo Priority hay áp dụng Filter (sàn lọc) vào DataView để chỉ thấy những thứ gì mình muốn, thí dụ chỉ có alarms priority 3 thôi. Nên nhớ là nằm đàng sau vẫn là table alarm, nhưng Dataview đóng vai trò cặp kiếng mát màu giúp cho ta thấy những thứ gì và theo cách ta muốn. Mỗi khi ta thay một cặp kiếng, ta lại thấy những thứ khác. Dưới đây là Sub BtnLoadXMLData_Click được sửa lại một chút để dùng DataView: Private Sub BtnLoadXMLData_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) _ Handles BtnLoadXMLData.Click ' Instantiate a DataSet type alarmlist DS = New alarmlist() ' Load the XML data from file AlarmList.xml in the source code folder. Note that the program EXE resides ' in the bin subfolder DS.ReadXml(" /AlarmList.xml") ' Bind the Datagrid DataSource to this new DataSet table alarm ' DataGrid1.DataSource = DS.alarm ' Create a Dataview from DS DV1 = New System.Data.DataView(DS.alarm) ' Sort alarms by priority, then datetime ' DESC stands for descending order,i.e. biggest on top DV1.Sort = "priority DESC, datetime DESC" Trang 148
  63. ' Bind the Datagrid DataSource to Dataview DataGrid1.DataSource = DV1 AddCustomDataTableStyle() ' Display the number of alarms in each priority DisplayTotal() End Sub Để ý Dataview object DV1 được derived từ DS.alarm. Sau đó ta Sort các alarms theo thứ tự ưu tiên, rồi trong số những alarm có cùng priority ta lại Sort chúng theo datetime (ở đây data type của datetime chỉ là string). Ngoài ra để đếm con số các alarms thuộc mỗi priority ta có thể dùng Dataview với filter rồi xem property Count của nó như sau: Private Sub DisplayTotal() ' Create a Dataview object from table DS.alarm Dim DVP1 As New System.Data.DataView(DS.alarm) ' Apply filter DVP1.RowFilter = "priority = 1" ' Display Count of records in this Dataview NumPrio1.Text = "Prio1: " & DVP1.Count.ToString Dim DVP2 As New System.Data.DataView(DS.alarm) DVP2.RowFilter = "priority = 2" NumPrio2.Text = "Prio2: " & DVP2.Count.ToString Dim DVP3 As New System.Data.DataView(DS.alarm) DVP3.RowFilter = "priority = 3" NumPrio3.Text = "Prio3: " & DVP3.Count.ToString NumTotal.Text = "Total: " & DS.alarm.Rows.Count.ToString Dim bmb As BindingManagerBase = Me.BindingContext(DataGrid1.DataSource, DataGrid1.DataMember) NumDisplayed.Text = "Displayed: " & bmb.Count.ToString End Sub Chắc bạn đã để ý thấy thay vì iterate qua mỗi record để đếm con số alarms thuộc priority 1,2 hay 3, ta đã dùng ba Dataviews để filter ra alarms thuộc ba priorities khác nhau rồi lấy trị số Count của mỗi Dataview. Đây là lối lập trình dựa vào những gì có sẵn càng nhiều càng tốt để tránh tạo ra bugs. Ngoài ra, để đếm con số hàng alarms được thật sự hiển thị bất cứ lúc nào ta dùng BindingManagerBase object trong hai hàng code dưới đây: Trang 149
  64. Dim bmb As BindingManagerBase = Me.BindingContext(DataGrid1.DataSource, DataGrid1.DataMember) NumDisplayed.Text = "Displayed: " & bmb.Count.ToString Ta đặt thêm ba buttons để filter alarms với code sau đây: Private Sub Btn1and2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) _ Handles Btn1and2.Click DV1.RowFilter = "priority < 3" Dim bmb As BindingManagerBase = Me.BindingContext(DataGrid1.DataSource, DataGrid1.DataMember) NumDisplayed.Text = "Displayed: " & bmb.Count.ToString End Sub Private Sub Btn1Only_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) _ Handles Btn1Only.Click DV1.RowFilter = "priority = 1" Dim bmb As BindingManagerBase = Me.BindingContext(DataGrid1.DataSource, DataGrid1.DataMember) NumDisplayed.Text = "Displayed: " & bmb.Count.ToString End Sub Private Sub BtnAllAlarms_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) _ Handles BtnAllAlarms.Click DV1.RowFilter = "" Dim bmb As BindingManagerBase = Me.BindingContext(DataGrid1.DataSource, DataGrid1.DataMember) NumDisplayed.Text = "Displayed: " & bmb.Count.ToString End Sub Bạn có thể chay chương trình và bấm các nút vừa mới thêm vào để xem các alarms được filtered như thế nào. Trang 150
  65. hình 5.7 : Show Alam List hình 5.7.2: From Alam list 8. Làm việc với một Row trong DataGrid Khi một alarm mới được báo cáo và hiển thị, hệ thống điều khiển real-time thường hay phát ra những tiếng Beep nho nhỏ để nhắc Operator xử lý sự cố tạo ra alarm. Việc đầu tiên Operator sẽ làm là Acknowledge (xác nhận là tôi biết rồi, khổ lắm, nói mãi!) cái alarm bằng cách right click lên Row hiển thị alarm rồi click menuCommand Acknowledge từ PopupMenu. Khi bạn đã Acknowledge một alarm rồi thì cái ACKN checkbox sẽ được đánh dấu và nếu hệ thống không còn alarm nào chưa được acknowledged thì nó sẽ ngừng Beep. Ngoài ra, có khi vì bạn biết là lý do gây ra một alarm nào đó không quan trọng (thí dụ nhân viên kỹ thuật đang sửa và thử cái sensor của alarm ấy) và bạn không muốn alarm ấy được báo cáo trong tương lai, bạn có thể Isolate (cô lập hóa) nó. Khi nào muốn cho nó hoạt động bình thường trở lại, bạn sẽ Enable (tác động) nó. Bây giờ bạn hãy đặt một ContextMenu control vào form và Edit cho nó ba menuCommands tên mnuAckn(Acknowledge), mnuIsolate(Isolate) và mnuEnable(Enable) như trong hình dưới đây: Trang 151
  66. Mỗi khi user right click lên một hàng alarm, ContextMenu1 sẽ hiển thị chỉ những menuCommands thích hợp với tình huống. Tức là nếu alarm chưa được acknowledged thì mới có menuCommand Acknowledge, khi alarm chưa bị isolated thì mới có menuCommand Isolate, nếu đã bị isolated rồi thì chỉ có MenuCommand Enable. Bình thường, nếu bạn click lên một checkbox còn trống trong DataGrid1, checkbox ấy sẽ được đánh dấu. Nhưng trong chương trình của chúng ta tại đây ta không muốn cho user làm việc ấy mà phải Acknowlege, Isolate hay Enable bằng PopupMenu. Do đó bạn hãy cho property ReadOnly của DataGrid1 bằng True. Lúc chương trình nhận được Event MouseDown từ DataGrid1 ta sẽ tìm cách xác định lúc bấy giờ Mouse đang nằm trên alarm line nào bằng cách chạy Method HitTest của DataGrid1. Khi DataGrid1 HitTest vị trí của Mouse với instruction myGrid.HitTest(e.X, e.Y), nó sẽ cho ta một Object HitTestInfo. Property Row của HitTestInfo là hàng thứ mấy trong DataGrid1. Để lấy ra đúng DataRowView nào đang hiển thị ở HitTestInfo.Row ấy ta phải dựa vào BindingManagerBase. Cái DataRowView mà ta đang tìm chính là DataRowView của BindingManagerBase với position bằng HitTestInfo.Row ấy. Trong chương trình nầy, ta sẽ chứa DataRowView ấy trong variable drv. Dưới đây là code để xử lý Event MouseDown của DataGrid1, để ý là ta hiển thị bên dưới cái description của alarm được clicked bằng statement Label1.Text = drv("description") để cho user một feedback: ' Variable used to store selected DataRowView Dim drv As DataRowView Private Sub DataGrid1_MouseDown(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) _ Handles DataGrid1.MouseDown ' Only proceed when Mouse Right Button was clicked If e.Button <> MouseButtons.Right Then Exit Sub ' Typecast sender to DataGrid data type. myGrid is actually DataGrid1 Dim myGrid As DataGrid = CType(sender, DataGrid) ' Declare a HitTestInfo variable Dim hti As DataGrid.HitTestInfo ' Obtain the info about Mouse location hti = myGrid.HitTest(e.X, e.Y) ' Only proceed when a Cell was hit If hti.Type = DataGrid.HitTestType.Cell Then Try ' Obtain BindingManagerBase of DataGrid1 Dim bmb As BindingManagerBase = Me.BindingContext(myGrid.DataSource, myGrid.DataMember) ' Position at DataRowView corresponding to the physical row that was hit bmb.Position = hti.Row Trang 152
  67. ' Store the found DataRowView in temporary variable drv drv = bmb.Current ' Display description of the alarm line as a feedback Label1.Text = drv("description") If Not (drv Is Nothing) Then ' Only display the MenuCommands that are appropriate to this context Dim ctx As DataRow = drv.Row If Not (ctx Is Nothing) Then If drv("ackn") = True Then mnuAckn.Visible = False Else ' Only display menuCommand Ackn when alarm is not yet acknowledged mnuAckn.Visible = True End If If drv("isolate") = True Then ' If alarm is already isolated then only display MenuCommand Enable mnuIsolate.Visible = False mnuEnable.Visible = True Else mnuIsolate.Visible = True mnuEnable.Visible = False End If ' Popup context menu ContextMenu1.Show(myGrid, New Point(e.X, e.Y)) End If End If Catch ex As Exception MessageBox.Show(ex.ToString()) End Try End If End Sub Khi User click một trong các Popup menu commands ta thay đổi các boolean value Ackn hay Isolate và viết xuống AlarmList.xml data file như sau: Trang 153
  68. Private Sub mnuAckn_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles mnuAckn.Click If Not drv Is Nothing Then drv("ackn") = True UpdatePoint() End If End Sub Private Sub mnuIsolate_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles mnuIsolate.Click If Not drv Is Nothing Then drv("isolate") = True UpdatePoint() End If End Sub Private Sub mnuEnable_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles mnuEnable.Click If Not drv Is Nothing Then drv("isolate") = False UpdatePoint() End If End Sub Private Sub UpdatePoint() ' Accept the changes in DataSet DS.AcceptChanges() ' Write updated data to XML file to persist the information DS.WriteXml(" \AlarmList.xml") End Sub Khi khởi động chương trình và right click lên alarm point 402-2-9 để Acknowledge nó bạn sẽ thấy như sau: Trang 154
  69. Hình 5.8.1 : Show Alam List Edit một XML Schema Trong chương trình mới nầy ta sẽ có một áp dụng khác cho DataGrid. Ta muốn tạo ra một DataForm để Edit chi tiết của các Operators, những người được phép dùng hệ thống điều khiển Real-time On-line để Acknowledge alarm, mở, tắt các quạt, máy bơm,.v.v DataForm nầy sẽ có một DataGrid nằm bên dưới cho ta một hình spreadsheet của tất cả các Operator records, và ta có thể chọn làm việc với record nào bằng cách click bên trái của record ấy trong spreadsheet. Trước hết bạn hãy khởi động một project mới tên Operators. Kế đó dùng IDE MenuCommand Project | Add New Item để thêm một XML Schema bằng cách click lên icon XML Schema. Chỗ Name XMLSchema1.xsd hãy sửa nó thành operatorlist.xsd như trong hình dưới đây: hình 5.8.2 : XML Schema Để Edit cái schema mới nầy, bạn hãy doubleclick lên tên operatorlist.xsd trong Solution Explorer. Một khung vàng nhạt còn trống sẽ hiện ra ở giữa. Bạn hãy drag icon E Element từ XML Schema Toolbox (nằm bên trái ) vào khung vàng nhạt. Hãy click lên chữ Element1 (nằm bên phải chữ E) trong hình mới tạo ra và sửa nó thành operator như trong hình dưới đây: Kế đó drag icon A Attribute từ XML Schema Toolbox vào drop nó ngay trên hàng nằm dưới Element operator. Sửa tên Attribute đó thành operatorid. Click bên phải chữ string của operatorid để chọn datatype integer từ ComboBox. Lập lại cùng những thao tác ấy cho Attributes username, password và level. Click bên phải chữ string của level để chọn datatype integer từ ComboBox. Trang 155
  70. Hình 5.8.3 : datatype integer từ ComboBox Hể trị số level của một operator càng lớn thì operator ấy càng có quyền để làm nhiều chuyện. MasterUser là người có level cao nhất. Mỗi command trong hệ thống điều khiển Real-time được cho một level mà user phải có một level với trị số ít nhất là bằng nó thì mới dùng command ấy được. Thí dụ Command Modify Username/Password/Level có level bằng 5 thì chỉ có MasterUser với level bằng 5 mới dùng nó được. Đến đây ta đã định nghĩa xong các Attributes của một Element operator mà ta sẽ dùng làm record trong Table operator trong cơ sỡ dữ liệu XML. Để có một XML hợp lệ ta cần phải gói các Element operator vào trong một Element gốc (root) mà ta sẽ gọi nó là operatorlist. Bạn hãy drag icon E Element từ XML Schema Toolbox vào khung vàng nhạt và sửa tên của Element đó thành operatorlist. Kế đó nắm góc trái trên (top left) của hình Element oparator và drag drop nó vào ngay hàng dưới chữ operatorlist của Element operatorlist. Bạn sẽ thấy hình dưới đây: hình 5.8.3 : operatorlist của Element operatorlist Trang 156
  71. Nếu bây giờ bạn click Tab XML phía dưới của khung vàng nhạt bạn sẽ thấy mã nguồn XML của Schema operatorlist.xsd như sau: Mã nguồn XML của Schema operatorlist.xsd cho thấy Element operatorlist là Element gốc (root) còn gọi là DocumentElement của XML nầy. Bên trong Element operatorlist có Element operator. Mỗi Element operator có những Attributes là operatorid, username, password và level. Để ý datatype của Attributes operatorid và level là integer. Trở lại hình của Schema bằng cách click Tab Schema phía dưới, ta thấy hàng đầu tiên của mỗi khung chữ nhật chứa tên của Element chủ của khung ấy. Từ hàng thứ nhì trở xuống là định nghĩa những gì thuộc về Element ấy. 9. Edit XML file dựa trên XML Schema Sau khi xác định cấu trúc và datatypes của các Elements và Attributes của table operator, bây giờ ta sẽ cho data vào table ấy. Bạn hãy dùng IDE menuCommand Project | Add New Item để thêm một XML file (click lên icon XML file) vào project. Sửa Name của file ấy từ XMLFile1 thành alarmlist.xml. Right click lên trang trống của XML, chỉ mới có câu: Chọn PopupMenu command Properties để hiển thị dialog DOCUMENT Property Pages. hình 5.9.1 :genneral target Scherma Trong cái Dropdown Combo, chọn làm Target Schema cho operatorlist.xml của chúng ta. Sau đó bạn sẽ thấy Element operatorlist có thêm một Attribute mới tên xmlns (chữ ns trong xmlns là viết tắc cho namespace) với trị số Điều nầy có nghĩa là ta áp đặt Schema operatorlist.xsd lên cấu trúc và các dữ kiện bên trong XML file operatorlist.xml. Trang 157
  72. Bây giờ, bạn có thể bắt đầu đánh data vào trang operatorlist.xml. Để ý là vì IDE biết ta đang dùng Schema operatorlist.xsd, nên nó có thể áp dụng Intellisense để giúp ta edit chính xác và nhanh như trong hình dưới đây: hình 5.9.2 : operationlist password Mỗi khi bạn đánh xong opening Tag operator là closing Tag của nó tự động hiện ra. Bên trong opening Tag của operator, Intellisense sẽ hiển thị tên các Attributes để bạn chọn. Mỗi khi bạn đánh dấu = sau tên một Attribute thì IDE sẽ tự động insert một cặp dấu ngoặc kép. Sau khi đánh xong dữ kiện của một số operators, nếu bạn click MenuCommand XML | Validate XML Data để IDE validate các dữ kiện ta vừa mới cho vào để xem nó có đúng như định nghĩa trong cái Schema operatorlist.xsd không thì sẽ thấy IDE than phiền như sau: E:\NET\HongDevelopment\LessonPreparation\Operators\operatorlist.xml(4): Element ' has invalid child element ' Đó là vì trong Schema không có nói rõ là bên trong Element operatorlist có nhiều Element operator. Để giải quyết trở ngại nầy ta phải edit trực tiếp trong mã nguồn XML của Schema. Bạn hãy doubleclick lên file name operatorlist.xsd của Solution Explorer và nếu cần thì click Tab XML của trang Schema để hiển thị mã nguồn XML của operatorlist.xsd. Thay thế cái cặp Tags bằng cặp Tags như trong hình dưới đây để nói rằng có nhiều Elements operator trong Element operatorlist : Trang 158
  73. 5.9.3 : operator trong Element operatorlist 10. Typed Dataset Trong bài 13 ta đã tạo Dataset từ Schema bằng cách dùng IDE MenuCommand XML | Schema Generate Dataset. Sau đó mỗi khi muốn nói đến một datafield của record ta dùng tên của datafield ấy. Thí dụ để nói đến datafield description trong DataRowView drv ta viết như sau: Label1.Text = drv("description") Một cách viết dễ đọc và tự nhiên hơn là: Label1.Text = drv.description Coding cách nầy được thêm lợi điểm là ngay trước khi chạy chương trình, compiler sẽ cho biết ngay nếu ta đánh vần không đúng chữ description chẳng hạn. Thêm nữa, Intellisense có thể hổ trợ ta trong lúc đánh code vào bằng cách hiển thị danh sách của tất cả datafields của drv. So với trường hợp ta dùng tên datafield, nếu đánh vần không đúng chữ description thì cho đến run-time chương trình mới khám phá ra việc ấy. Loại Dataset cho phép ta code drv.description được gọi là Typed Dataset và ta có thể phát sinh (generate) nó từ Schema của XML file bằng cách dùng line command: xsd.exe /d /l:VB operatorlist.xsd /n:operatorlistDS Ở đây ta dùng chương trình dụng cụ xsd.exe để phát sinh từ Schema operatorlist.xsd một dataset trong ngôn ngữ (language) lập trình VB với namespace tên operatorlistDS. Nếu bạn không thấy xsd.exe thì tìm nó trong folder \Program Files\Microsoft Visual Studio .NET\FrameworkSDK\Bin rồi có thể copy nó vào project folder. Lưu ý là bản xsd.exe dùng trong final version của VS.NET thì khác với bản xsd.exe dùng trong version Beta 2. Kế đó, bạn có thể thêm Typed Dataset nầy vào project bằng cách dùng IDE MenuCommand Project | Add Existing Item rồi chọn operatorlist.vb từ project folder. 11. Dùng Dataform wizard để phát sinh form từ Dataset Ta sẽ dùng Dataform Wizard để generate một dataform. Nhưng trước đó, ta cần phải compile project với IDE menuCommand Build | Build Operators để chốc nữa Wizard thấy được Typed Dataset. Bạn hãy dùng IDE MenuCommand Project | Add Windows Form, click lên icon Data Form Wizard và đổi Name của form thành frmOperator.vb như trong hình dưới đây: Trang 159
  74. hình 5.11.1 : Data Form Wizard Tiếp theo đó bạn sẽ thấy Data Form Wizard dialog với tên của Typed Dataset hiện ra trong ComboBox của Option Use the following dataset: như dưới đây: 5.11.2 : use the folowing dataset Để y nguyên và click Next: Trang 160
  75. 5.11.3 : which method should the from use to fill the datset Click Checkbox Include an Update button, rồi click Next: hình 5.11.4 : master or single table Để y nguyên và click Next: Trang 161
  76. hình 5.11.5 : choose the dislay style Chọn các options như trong hình bên trên rồi click Finish. Form frmOperator sẽ được generated và tự động cho vào Project. Bây giờ bạn hãy right click lên tên file Form1.vb trong Solution Explorer để delete nó và right click tên project Operators rồi chọn command Properties từ PopupMenu để đổi Startup object thành frmOperator. Kế đó hãy sắp xếp các object trên form frmOperator cho gọn lại như trong hình dưới đây: 5.11.6 : sắp xếp cho gọn lại như trên Để hiển thị các operator records như một Spreadsheet phía dưới, bạn hãy thêm một DataGrid tên DataGrid1 vào form. Và để dấu các chữ của Password trong Dataform, ta sẽ Edit Property PasswordChar của TextBox editpassword thành * như dưới đây: Trang 162
  77. 5.11.7 : Edit Property PasswordChar Để load data vào DataForm sau khi khởi động chương trình bạn hãy doubleclick lên button Load rồi viết code sau đây cho Event Click: Private Sub btnLoad_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnLoad.Click ' Read operator data from XML file Me.objoperatorlist.ReadXml(" \operatorlist.xml") ' Bind DataGrid1's Datasource to table operator DataGrid1.SetDataBinding(Me.objoperatorlist, "operator") ' Define our owned display style. Do not display Passwords AddCustomDataTableStyle() ' Display current record position Me.objoperatorlist_PositionChanged() End Sub Ta sẽ gọi một Sub AddCustomDataTableStyle để hiển thị các records của operators trong DataGrid1. RowHeaders của DataGrid1 sẽ được để yên visible để cho user có thể click bên trái một record khi muốn làm việc với record ấy. Đồng thời ta cũng cố ý không hiển thị Datafield password. Coding của Sub AddCustomDataTableStyle được liệt kê dưới đây: Private Sub AddCustomDataTableStyle() ' Instantiate a DataGridTableStyle object Dim ts1 As New DataGridTableStyle() ' Map table operator to it ts1.MappingName = "operator" Trang 163
  78. ' Set other properties. ts1.AlternatingBackColor = Color.Beige ' Add a first column style. Dim TextCol1 As New DataGridTextBoxColumn() TextCol1.MappingName = "operatorid" TextCol1.HeaderText = "OperId" TextCol1.Width = 50 ts1.GridColumnStyles.Add(TextCol1) DataGrid1.TableStyles.Add(ts1) ' Add a second column style. Dim TextCol2 As New DataGridTextBoxColumn() TextCol2.MappingName = "username" TextCol2.HeaderText = "Operator" TextCol2.Width = 120 ts1.GridColumnStyles.Add(TextCol2) ' Add a third column style. Dim TextCol3 As New DataGridTextBoxColumn() TextCol3.MappingName = "level" TextCol3.HeaderText = "Level" TextCol3.Width = 35 ts1.GridColumnStyles.Add(TextCol3) ' Now add ts1 to the Datagrid1's collection of TableStyles DataGrid1.TableStyles.Add(ts1) End Sub Bây giờ hãy chạy chương trình và click nút Load. Data của các operators sẽ được loaded vào cả DataForm lẫn DataGrid1. Khi bạn click các nút navigators để di chuyển đến record sau hay record trước, cái hình tam giác nho nhỏ trong DataGrid RowHeaders nằm bên trái DataGrid1 cũng di chuyển theo như trong hình dưới đây: Trang 164