Thiết kế giao diện cho winforms – Series Giải pháp winforms (2)

25

Thiết kế giao diện chắc chắn là phần hấp dẫn nhất đối với các bạn mới bắt đầu làm việc với winform, đặc biệt các bạn mới chuyển từ console sang. Tuy vậy, không phải ai cũng biết cách phát huy các khả năng hỗ trợ của winform khi thiết kế giao diện.

Như đã phân tích trong phần 1 – các lỗi thường gặp trong lập trình winform – các bạn thường làm thủ công, mất nhiều công sức, dễ gặp lỗi. Việc thay đổi giao diện chương trình chắc chắn là một ác mộng!

Trong bài viết này, tôi sẽ giúp bạn tránh hết những phiền toái đó.

Loạt bài “Các giải pháp dành cho lập trình winform”:
Phần 1 – Lỗi thường gặp trong lập trình winforms
Phần 2 – Thiết kế giao diện với Data Sources và BindingSource
Phần 3 – Phân chia code thành module sử dụng Interface
Phần 4 – Sử dụng thư viện DevExpress cho winforms
Phần 5 – Sử dụng Data Binding
Phần 6 – Sử dụng Entity Framework

Về mô hình kiến trúc UI trong thiết kế giao diện winform

Mô hình kiến trúc UI là gì?

Mô hình kiến trúc UI là một vấn đề tương đối “cao cấp” mà ít bạn biết tới khi học lập trình. Tuy nhiên, nó không thực sự quá xa lạ. Bạn hẳn đã nghe ở đâu đó về “mô hình MVC” trong phát triển ứng dụng web cũng như một số framework xây dựng theo mô hình này. MVC chính là một kiến trúc UI rất thông dụng dành cho giao diện người dùng. Ngoài MVC còn có nhiều mô hình khác được sử dụng với những công nghệ nhất định.

Các mô hình kiến trúc UI có vai trò rất quan trọng khi xây dựng các project lớn. Nó giúp phân chia code ra các thành phần (module) một cách logic, tiện lợi cho việc sửa đổi và bảo trì về sau.

Đại thể, các mô hình kiểu này hướng tới tuân thủ nguyên lý SoC (Separation of Concern). Ý tưởng cơ bản của SoC là phân chia code ra các thành phần tương đối độc lập theo nhiệm vụ cụ thể để một chương trình có tính modular.

Các mô hình UI hướng tới phân chia client UI ra làm giao diện (view), logic, dữ liệu (model). Tùy vào sự tương tác giữa các thành phần này sẽ tạo ra các mẫu kiến trúc khác nhau.

Mô hình UI cho giao diện winform

Vậy đối với windows forms thì sao? Bạn có cần đến một mô hình UI khi thiết kế giao diện winform? Bạn có thể tự tạo một mô hình UI riêng hay phải dùng các mô hình đã có?

Windows Forms không được xây dựng cùng với một mô hình kiến trúc UI nào từ đầu. Trong khi các công nghệ mới hơn như WPF có riêng một mô hình kiến trúc UI gọi là MVVM (Model-View-ViewModel).

Khi cần đến SoC cho UI trong winform, người ta thường áp dụng mô hình MVP ( Model-View-Presenter, biến thể supervising controller hoặc passive view). Khi MVVM ra đời, do winforms cũng hỗ trợ khá tốt data binding, mô hình này cũng được áp dụng trở lại cho winforms.

Tuy nhiên, cả MVVM và MVP đều rất khó thực thi cho winforms. Vì vậy, nếu không có những nhu cầu đặc biệt (như testability) thì không nhất thiết phải áp dụng các mô hình này.

Mô hình UI tự tạo cho winform

Để thuận lợi cho làm những project vừa và nhỏ, chúng ta có thể tự đưa ra mô hình riêng.

Giải pháp của chúng ta dựa trên kết hợp mô hình MVVM và MVP nhưng đơn giản hóa và cải biên cho windows forms.

Mô hình cơ bản của giải pháp cho winforms

Trong mô hình này, project winform sẽ được chia làm 3 phần: View – ViewModel – Model.

View là cái người dùng nhìn thấy, cũng chính là lớp Form mà bạn đang xây dựng. Trên Form đặt các điều khiển khác nhau để tương tác với người dùng. Cái này hoàn toàn quen thuộc với bạn. Tuy nhiên, để phù hợp với mô hình UI này, chúng ta phải vận dụng các kỹ thuật thiết kế nhằm khai thác khả năng Databinding. Nếu không, dữ liệu sẽ khó đồng bộ giữa ViewModel và View.

ViewModel chứa toàn bộ các logic để chuẩn bị dữ liệu cho View. Tất cả dữ liệu View phải trả về ViewModel để xử lý. Tất cả sự kiện phát sinh trên View (ví dụ, người dùng bấm nút trên Form) cũng được delegate về ViewModel để xử lý.

Điều này cũng có nghĩa là trong lớp Form bạn đang xây dựng sẽ không có phương thức xử lý sự kiện nào. Nó sẽ hoàn toàn khác biệt với những gì bạn đã quen làm trước đây: xử lý sự kiện của điều khiển ngay trong Form.

Chúng ta sẽ tự tạo ra một cơ chế gọi là “Command” để ViewModel có thể tương tác với View, ví dụ, phát lệnh mở Form mới, mở hộp thoại.

Model là phần dữ liệu được lưu trữ ở đâu đó, có thể trong bộ nhớ, trong file hoặc ở cơ sở dữ liệu. ViewModel sẽ làm nhiệm vụ lấy dữ liệu này, biến đổi nó theo logic định sẵn, thông qua cơ chế Data Binding để đồng bộ dữ liệu với View. ViewModel phải thực hiện đủ các thao tác CRUD với dữ liệu.

Để View có thể hiển thị dữ liệu, nó phải biết đến cấu trúc (schema) của Model. Tuy nhiên, View không cần quan tâm đến dữ liệu.

Nghe rất lằng nhằng phải không ạ? Tôi cá là bạn không hiểu mấy đâu, và cũng bắt đầu nghi ngờ, liệu có nên đọc tiếp không. Nó bắt đầu xa lạ rồi.

Nhưng bạn đừng lo, có cả series 5 bài liên tục để giúp bạn. Hãy đi cùng chúng tôi đến cuối, bạn sẽ thấy thực ra nó rất đơn giản. Đơn giản hơn nhiều so với những gì mô tả ở đây.

Đến khi hiểu nó, bạn sẽ thấy nó vô cùng hữu ích. Đảm bảo rằng nếu bạn làm đề tài/đồ án mà sử dụng những giải pháp ở loạt bài này, bạn sẽ tiết kiệm được một nửa thời gian.

Thật đấy!

Để hiểu mô hình này, chúng ta bắt đầu trước bằng thực hiện ví dụ minh họa (đã bắt đầu từ bài trước).

Thiết kế giao diện winform sử dụng Data Sources và BindingSource

Chuẩn bị cấu trúc mã nguồn

Bước 1. Trước hết xóa bỏ Form1 được tạo cùng project App. Thêm hai form mới vào project, đặt tên lần lượt là Contacts và Detail.

Contacts sẽ hiển thị danh sách liên hệ của danh bạ. Detail sẽ hiển thị chi tiết về contact đang được chọn.

Bước 2. Mở file Program.cs và sửa dòng Application.Run(new Form1()); thành Application.Run(new Contacts());, do chúng ta đã xóa bỏ Form1. Giờ Contacts trở thành form khởi động của ứng dụng.

Bước 3. Thêm thư mục ViewModels vào project này. Trong thư mục ViewModels tạo một class mới có tên ContactsViewModel (file ContactsViewModel.cs).

Bước 4. Thiết lập để App trở thành startup project (project sẽ chạy khi ấn F5).

Cấu trúc App winforms project
Cấu trúc App winforms project

Tạo Data Sources cho project

Khi thiết kế giao diện, để tận dụng được khả năng data binding của lập trình winforms, chúng ta cần dùng một công cụ gọi là Data Sources cùng với class BindingSource.

Bước 1. Mở tab Data Sources (View => Other Windows => Data Sources, hoặc Shift+Alt+D), bấm nút link Add New Data Source.

Bước 2. Trong cửa sổ Data Source Configuration Wizard, bước Choose a Data Source Type chọn Object và Next.

Bước 3. Trong mục Select the Data Objects mở rộng node Models => Models và chọn Contact. Ấn Finish để kết thúc. Nếu ở bước này không nhìn thấy node Models thì đóng cửa sổ này lại và Build project Models. Sau đó lặp lại từ bước 1.

Các bước tạo Data Source cho project
Các bước tạo Data Source cho project

Lưu ý rằng tab Data Sources thể hiện nội dung khác nhau tùy vào project nào đang lựa chọn.

Data Sources thay đổi tùy project
Data Sources thay đổi tùy project
Các file tạo ra cho Data Source
Các file tạo ra cho Data Source

Thiết kế giao diện form Contact

Sử dụng SplitContainer và GroupBox để tạo ra cái khung cho giao diện như sau

Khung Contacts form
Khung Contacts form

Hi vọng bạn đã biết vai trò và cách sử dụng của SplitContainer và GroupBox.

Bước 1. Chuyển Contact Data Source thành DataGridView

Bước 2. Kéo – thả Contact DataGridView vào giao diện

Bước 3. Chuyển Contact Data Source thành Details

Bước 4. Kéo – thả Contact Details vào Detail GroupBox; Kéo Emails và Phones lần lượt thả vào hai panel bên dưới Detail GroupBox

Thao tác xây dựng giao diện Contacts
Thao tác xây dựng giao diện Contacts

Chúng ta nhanh chóng thu được một giao diện ứng dụng như sau

Giao diện Contacts form sau khi hoàn thành
Giao diện Contacts form sau khi hoàn thành

Đến đây bạn có thể tùy ý tinh chỉnh để giao diện nhìn “chuyên nghiệp” hơn.

Lưu ý đổi thuộc tính Enabled của contactBindingNavigatorSaveItem thành True (mặc định là False). Đây là nút Save trên thanh navigator.

Một số lưu ý trong thiết kế giao diện cho winform

Các bạn có thể thấy, nhờ Data Source mà quá trình thiết kế giao diện đã đơn giản hơn rất nhiều: bạn không cần kéo thả thủ công từng điều khiển lên form nữa.

Data Source đã cho winforms biết thông tin về dữ liệu (metadata hoặc schema). Từ dữ liệu meta winforms tự xác định loại điều khiển nào cần dùng để hiển thị trường dữ liệu tương ứng.

Dĩ nhiên, bạn hoàn toàn có thể tự kéo thả các điều khiển và sau đó tự điều chỉnh data binding cho chúng. Nhưng nếu đã có hỗ trợ tự động thì tội gì làm thủ công! Hãy tự xem cách bind các điều khiển này trong nhóm Data => DataBindings của cửa sổ Properties (F4).

Tất cả các điều khiển kéo thả lên form đều đã được được liên kết với dữ liệu (data bound controls). Nguồn dữ liệu chung của tất cả các điều khiển này là object contactBindingSource. Đây là một object của lớp BindingSource.

BindingSource là một lớp trung gian đặc biệt đóng vai trò nguồn dữ liệu và làm data binding trong lập trình winforms đơn giản và hiệu quả hơn. Lớp này giúp đồng bộ hóa dữ liệu giữa tất cả các điều khiển kết nối tới nó. Khi có thay đổi về dữ liệu, BindingSource tự phát đi các thông báo để cập nhật lại điều khiển. Lớp này cũng có nhiều sự kiện khác nhau liên quan đến sự biến đổi giá trị của dữ liệu.

Viết code xử lý cho các chức năng chính

Ở trên chúng ta đã thiết kế xong giao diện nhưng chưa thực hiện được gì khác. Theo mô hình UI tự tạo, tất cả logic và tính toán phải được thực hiện trong một class khác.

Lớp ContactsViewModel

Ở giai đoạn chuẩn bị bên trên bạn đã tạo ra lớp ContactsViewModel. Đây là class chịu trách nhiệm xử lý tất cả các loại logic liên quan đến dữ liệu và tính toán phát sinh trên giao diện, cũng như xử lý sự kiện từ các điều khiển.

Đối với bài toán này, một số thao tác sau cần xử lý:

  • Tải dữ liệu từ file (hoặc cơ sở dữ liệu)
  • Lưu dữ liệu trở lại file (hoặc cơ sở dữ liệu)
  • Xóa bản ghi đang được chọn
  • Cập nhật bản ghi đang được lựa chọn
  • Thêm một bản ghi contact mới

Khi chọn một contact bất kỳ sẽ thực hiện được các yêu cầu sau:

  • Hiển thị các thông tin chi tiết của contact đó trên cùng form hoặc trên một form riêng
  • Hiển thị danh sách email và số điện thoại
  • Thực hiện được các thao tác CRUD đối với danh sách email và điện thoại

Đọc/ghi dữ liệu từ file

Viết code cho lớp ContactsViewModel như sau

using System.Collections.Generic;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using System.Windows.Forms;
using Models;
namespace App.ViewModels
{
    public class ContactsViewModel
    {
        public BindingSource ContactBindingSource { get; set; }
        public void Load()
        {
            List<Contact> contacts;
            var formatter = new BinaryFormatter();
            if (!File.Exists("data.dat"))
            {
                contacts = new List<Contact>();
                var stream = File.Create("data.dat");
                formatter.Serialize(stream, contacts);
                stream.Close();
            }
            else
            {
                using (var stream = File.OpenRead("data.dat"))
                {
                    contacts = formatter.Deserialize(stream) as List<Contact>;
                }
            }
            ContactBindingSource.ResetBindings(false);
            ContactBindingSource.DataSource = contacts;
        }
        public void Save()
        {
            using (var stream = File.OpenWrite("data.dat"))
            {
                var formatter = new BinaryFormatter();
                formatter.Serialize(stream, ContactBindingSource.DataSource);
            }
        }
    }
}

Mở code của form Contacts (F7) và code như sau:

using System.Windows.Forms;
namespace App
{
    public partial class Contacts : Form
    {
        private ViewModels.ContactsViewModel _vm = new ViewModels.ContactsViewModel();
        public Contacts()
        {
            InitializeComponent();
            _vm.ContactBindingSource = contactBindingSource;
            contactBindingNavigatorSaveItem.Click += delegate { _vm.Save(); };
            Load += delegate {  _vm.Load(); };
        }
    }
}

Dịch và chạy thử chương trình. Nhập vào một số dữ liệu và ấn nút Save. Đóng chương trình và chạy lại để xem dữ liệu có được lưu và tải lên không.

Thiết kế giao diện Chương trình quản lý danh bạ trong winform
Chương trình quản lý danh bạ

Các bạn có thể thấy, chúng ta đã rất nhanh chóng tạo ra một chương trình hoạt động. Hầu hết các hoạt động xử lý CRUD cơ bản đã được Binding Navigator và Binding Source hỗ trợ. Chúng ta chỉ cần xử lý chính cho việc đọc/ghi dữ liệu với file.

Thay thế thanh Navigator

Thanh công cụ Navigator là một điều khiển rất hữu dụng và tiện lợi khi sử dụng cùng BindingSource. Tuy nhiên, không phải ai cũng thích thanh công cụ này. Trong phần này chúng ta sẽ tự thêm một thanh công cụ khác có chức năng tương tự.

Trước hết kéo thả một Panel vào và thiết lập thuộc tính Dock thành Bottom. Đặt lên Panel một số Button rồi đặt thuộc tính Text và Name như hình dưới đây.

Tạo Panel điều khiển mới
Tạo Panel điều khiển mới

Bổ sung code sau vào ContactsViewModel

public void New()
{
    var contact = ContactBindingSource.AddNew() as Contact;
    contact.DateOfBirth = DateTime.Now;
    contact.ContactName = "new conatct";
}
public void Delete() => ContactBindingSource.RemoveCurrent();
public void First() => ContactBindingSource.MoveFirst();
public void Last() => ContactBindingSource.MoveLast();
public void Previous() => ContactBindingSource.MovePrevious();
public void Next() => ContactBindingSource.MoveNext();

Sửa code behind của Contacts như sau

using System.Windows.Forms;
namespace App
{
    public partial class Contacts : Form
    {
        private ViewModels.ContactsViewModel _vm = new ViewModels.ContactsViewModel();
        public Contacts()
        {
            InitializeComponent();
            _vm.ContactBindingSource = contactBindingSource;
            
            Load += delegate {  _vm.Load(); };
            buttonNew.Click += delegate { _vm.New(); };
            buttonDelete.Click += delegate { _vm.Delete(); };
            buttonSave.Click += delegate { _vm.Save(); };
            buttonFirst.Click += delegate { _vm.First(); };
            buttonLast.Click += delegate { _vm.Last(); };
            buttonPrevious.Click += delegate { _vm.Previous(); };
            buttonNext.Click += delegate { _vm.Next(); };
        }
    }
}

Dịch và chạy thử ứng dụng xem các button mới hoạt động ra sao.

Thiết kế giao diện Master – details trong winform

Master-details là một loại view được sử dụng đặc biệt nhiều trong ứng dụng quản lý (Line-of-Business, LOB). Loại view này thể hiện quan hệ 1-n về dữ liệu. Như trong bài toán của chúng ta, 1 contact chứa nhiều email và nhiều phone.

Ứng dụng của chúng ta đã sử dụng loại view này ngay từ đầu. Mỗi khi chọn một contact bất kỳ sẽ hiển thị thông tin chi tiết, danh sách email, danh sách phone.

Nhờ BindingSource, master-detail view được thực hiện một cách đơn giản. Trên form Contacts, emailsDataGridViewDataSourceemailsBindingSource. Bản thân emailsBindingSource lại có DataSourcecontactBindingSource. Thuộc tính DataMember của của emailsBindingSource nhận giá trị Emails, vốn là tên trường tương ứng của Contact nơi chứa danh sách email.

Hãy cùng thêm một số chức năng để tương tác với danh sách email và phone.

Trên thanh điều khiển mới thêm một số nút bấm như hình dưới đây

Thêm nút xử lý email và phone
Thêm nút xử lý email và phone

Viết thêm code sau vào cuối ContactsViewModel:

public BindingSource EmailBindingSource { get; set; }
public BindingSource PhoneBindingSource { get; set; }
public void NewEmail()
{
    var email = EmailBindingSource.AddNew() as Email;
    email.EmailAddress = "@gmail.com";
}
public void DeleteEmail() => EmailBindingSource.RemoveCurrent();
public void NewPhone()
{
    var phone = PhoneBindingSource.AddNew() as Phone;
    phone.Number = "(+84) ";
}
public void DeletePhone() => PhoneBindingSource.RemoveCurrent();

Điều chỉnh code behind của Contacts form:

using System.Windows.Forms;
namespace App
{
    public partial class Contacts : Form
    {
        private ViewModels.ContactsViewModel _vm = new ViewModels.ContactsViewModel();
        public Contacts()
        {
            InitializeComponent();
            _vm.ContactBindingSource = contactBindingSource;
            _vm.EmailBindingSource = emailsBindingSource;
            _vm.PhoneBindingSource = phonesBindingSource;
            Load += delegate {  _vm.Load(); };
            buttonNew.Click += delegate { _vm.New(); };
            buttonDelete.Click += delegate { _vm.Delete(); };
            buttonSave.Click += delegate { _vm.Save(); };
            buttonFirst.Click += delegate { _vm.First(); };
            buttonLast.Click += delegate { _vm.Last(); };
            buttonPrevious.Click += delegate { _vm.Previous(); };
            buttonNext.Click += delegate { _vm.Next(); };
            buttonNewEmail.Click += delegate { _vm.NewEmail(); };
            buttonDeleteEmail.Click += delegate { _vm.DeleteEmail(); };
            buttonNewPhone.Click += delegate { _vm.NewPhone(); };
            buttonDeletePhone.Click += delegate { _vm.DeletePhone(); };
        }
    }
}

Dịch và chạy thử chương trình với các chức năng mới

Chương trình với các chức năng mới
Chương trình với các chức năng mới

Bind trực tiếp giao diện với VM

Trong các phần trên chúng ta đều bind gián tiếp các điều khiển với BindingSource. Giờ giả sử chúng ta muốn rằng khi chọn bất kỳ contact nào, tên của nó sẽ xuất hiện trên dòng tiêu đề của cửa sổ. Ví dụ, khi chọn contact số 1 thì tiêu đề cửa sổ sẽ biến thành “Contact – Donald Trump”.

Để thực hiện yêu cầu này chúng ta có thể bind trực tiếp thuộc tính Text của Form với một thuộc tính của view model ContactsViewModel.

Điều chỉnh khai báo của lớp ContactsViewModel để lớp này thực thi interface INotifyPropertyChanged.

public class ContactsViewModel : INotifyPropertyChanged
{

Việc này là bắt buộc để chúng ta có thể thông báo về sự thay đổi giá trị của object tới điều khiển. Khi nhận được thông báo này thì điều khiển mới cập nhật giá trị của mình.

Bổ sung code sau vào cuối ContactsViewModel

public string Title
{
    get {
        if (ContactBindingSource.Current == null) return "Contacts";
        return $"Contact - {(ContactBindingSource?.Current as Contact)?.ContactName}";
    }
}
public void Initialize()
{
    ContactBindingSource.CurrentChanged += delegate { Notify("Title"); };
}
public event PropertyChangedEventHandler PropertyChanged;
private void Notify([CallerMemberName] string property = "")
{
    PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property));
}

Điều chỉnh code behind của Contacts

using System.Windows.Forms;
namespace App
{
    public partial class Contacts : Form
    {
        private ViewModels.ContactsViewModel _vm = new ViewModels.ContactsViewModel();
        public Contacts()
        {
            InitializeComponent();
            _vm.ContactBindingSource = contactBindingSource;
            _vm.EmailBindingSource = emailsBindingSource;
            _vm.PhoneBindingSource = phonesBindingSource;
            _vm.Initialize();
            Load += delegate {  _vm.Load(); };
            buttonNew.Click += delegate { _vm.New(); };
            buttonDelete.Click += delegate { _vm.Delete(); };
            buttonSave.Click += delegate { _vm.Save(); };
            buttonFirst.Click += delegate { _vm.First(); };
            buttonLast.Click += delegate { _vm.Last(); };
            buttonPrevious.Click += delegate { _vm.Previous(); };
            buttonNext.Click += delegate { _vm.Next(); };
            buttonNewEmail.Click += delegate { _vm.NewEmail(); };
            buttonDeleteEmail.Click += delegate { _vm.DeleteEmail(); };
            buttonNewPhone.Click += delegate { _vm.NewPhone(); };
            buttonDeletePhone.Click += delegate { _vm.DeletePhone(); };
            
            DataBindings.Add("Text", _vm, "Title");
        }
    }
}

Dịch và chạy thử chương trình

Bind windows title with property
Bind windows title with property

Một số nhận xét

Tôi tin rằng bạn có thể thực hiện phần thực hành bên trên một cách dễ dàng. Bạn chắc cũng thấy nó không hề khó khăn và rất ít code. Thậm chí, nếu so với cách trước đây bạn thường làm, nó còn đơn giản hơn là đằng khác.

Thực ra, qua bài thực hành vừa rồi bạn đã thực hiện hầu hết các yêu cầu của mô hình UI tự tạo.

Thật vậy.

Bạn đã phân tách code ra thành phần ViewModel (lớp ContactsViewModel), thành phần View (form Contacts). Bản thân Model (lớp Contact, Phone, Email) bạn đã tạo ra từ bài trước rồi.

ContactsViewModel chịu trách nhiệm cho xử lý dữ liệu và xử lý sự kiện. Form Contacts chỉ đảm nhiệm hiển thị dữ liệu và nhận input từ người dùng.

Bạn có lẽ thấy, việc xây dựng một ứng dụng LOB giờ không còn quá lằng nhằng về code nữa. Trong code behind của form giờ chỉ còn thực hiện binding (dữ liệu) hoặc delegate (sự kiện) về ViewModel.

Cũng nên để ý rằng, nhờ cơ chế Databinding, sự thay đổi dữ liệu trên giao diện sẽ được phản hồi ngay về kho dữ liệu đang nằm ở ViewModel. Và sự thay đổi dữ liệu ở ViewModel sẽ được tự động cập nhật trên View. Bạn không cần cập nhật bằng tay. Quá tốt phải không ạ!

Bạn hãy so sánh thử với cách trước đây bạn thiết kế giao diện và lập trình winform xem có khác nhau không? Và bạn có thể tiết kiệm bao nhiêu thời gian khi làm theo cách mới này? Chưa kể đến việc bạn sẽ ít mắc lỗi hơn (viết code ít hơn mà!), và chương trình sẽ ổn định hơn.

Bạn cũng đã thấy, giờ đây quy trình xây dựng chương trình đã rất rõ ràng: (1) Xây dựng các lớp Model, tức là các lớp thể hiện các thực thể cần quản lý; (2) Xây dựng các lớp ViewModel để xử lý dữ liệu, logic, sự kiện; (3) Thiết kế giao diện; (4) Ghép nối giao diện với ViewModel tương ứng.

Vấn đề cốt lõi là bạn phải phân tích bài toán cần giải quyết một cách chi tiết.

Kết phần

Bài viết này đã đưa ra một mô hình UI tự tạo đơn giản làm cơ sở cho việc xây dựng ứng dụng trên winform. Từ mô hình này, bạn đã biết cần phân chia code ra các thành phần, cũng như trình tự xây dựng các phần. Bạn cũng đã học được kỹ thuật thiết kế giao diện của winform một cách nhanh chóng và dễ dàng.

Tuy nhiên, nếu so với phân tích ở phần 1 của loạt bài này thì còn một số vấn đề nữa chúng ta vẫn chưa giải quyết xong. Mời bạn tiếp tục theo dõi phần 3 của loạt bài này.

[wpdm_package id=’10299′]

+ Nếu bạn thấy site hữu ích, trước khi rời đi hãy giúp đỡ site bằng một hành động nhỏ để site có thể phát triển và phục vụ bạn tốt hơn.
+ Nếu bạn thấy bài viết hữu ích, hãy giúp chia sẻ tới mọi người.
+ Nếu có thắc mắc hoặc cần trao đổi thêm, mời bạn viết trong phần thảo luận cuối trang.
Cảm ơn bạn!

Loạt bài “Các giải pháp dành cho lập trình winform”:
Phần 1 – Lỗi thường gặp trong lập trình winforms
Phần 2 – Thiết kế giao diện với Data Sources và BindingSource
Phần 3 – Phân chia code thành module sử dụng Interface
Phần 4 – Sử dụng thư viện DevExpress cho winforms
Phần 5 – Sử dụng Data Binding
Phần 6 – Sử dụng Entity Framework

Theo dõi
Thông báo của
guest

25 Thảo luận
Cũ nhất
Mới nhất
Phản hồi nội tuyến
Xem tất cả bình luận
Ken Nguyễn

Khi chạy chương trình đến:
var contacts = formatter.Deserialize(stream) as List;
báo lỗi System.Runtime.Serialization.SerializationException: ‘Attempting to deserialize an empty stream.’
Xin chỉ giúp làm thế nào để fix
Cảm ơn!

Mai Chi

Đây là do file data.dat trong thư mục bin\Debug chưa có nội dung gì dẫn đền FileStream từ file này trống rỗng. BinaryFormatter đọc từ luồng rỗng sẽ báo lỗi trên. Bạn thử xóa bỏ file data.dat và viết lại code của phương thức Load như sau: public void Load() { if (!File.Exists(“data.dat”)) { using (var stream = File.OpenWrite(“data.dat”)) { var formatter = new BinaryFormatter(); var contacts = new List(); ContactBindingSource.ResetBindings(false); ContactBindingSource.DataSource = contacts; formatter.Serialize(stream, contacts); } } using (var stream = File.OpenRead(“data.dat”)) { var formatter = new BinaryFormatter(); var contacts = formatter.Deserialize(stream) as List; ContactBindingSource.ResetBindings(false); ContactBindingSource.DataSource = contacts; }… Đọc tiếp »

Re2devils

đến phần thiết kế giao diện
ở phần data source của mình không hiện ra giống như hình được ah
không chọn được datagridview ah
Xin chỉ giáo ah

Mai Chi

Để datasource chuyển thành combobox (với 3 option – DataGridView, Detail, [None]), bạn cần mở giao diện thiết kế form trên một tab và để tab đó là tab hoạt động hiện thời. Nghĩa là bạn phải đồng thời nhìn thấy cả cửa sổ DataSources và giao diện thiết kế form.
Lý do là DataSource thay đổi cách hiển thị của nó theo tab nào đang hoạt động. Nếu tab hoạt động là một file code thì DataSource hiển thị như một cấu trúc cây mà không chuyển thành combobox để lựa chọn.

Thụ

Bài viết rất hay, cảm ơn tác giả nhiều.

caokhoa

Trong class ContactsViewModel mình thấy có lệnh này:
public event PropertyChangedEventHandler PropertyChanged;
Tuy nhiên mình không thấy có lệnh nào để đăng ký sự kiện cho PropertyChanged.
Mong tác giả hướng dẫn.

Cám ơn nhiều!

Mai Chi

public event PropertyChangedEventHandler PropertyChanged; là khai báo event bắt buộc để class thực thi interface INotifiyPropertyChanged.
Sự kiện này được các điều khiển (có hỗ trợ DataBinding) đăng ký sử dụng chứ chúng ta không trực tiếp sử dụng. Sự kiện này chính là cách class (chúng ta xây dựng) báo ngược lại cho điều khiển biết rằng “giá trị của property đã thay đổi” để điều khiển tự cập nhật lại nó theo giá trị mới.

hoang thanh

bai viet rat hay cam on tac gia

Nguyễn Văn Trung

Ở Bước 4 ý chị Mai Chi, Thiết lập App thành Startup Project làm như thế vậy Chị Mai chi ơi

Mai Chi

Bạn click phải vào tên project và chọn Set as startup project.

Nguyễn Linh

cho e hỏi với tại sao e code form của e khi e code
 _vm.RepoBindingSource = repoBindingSource;
      repoBindingNavigatorSaveItem.Click += delegate { _vm.Save(); };
nó báo lỗi ở repoBindingSource với repoBindingNavigatorSaveItem ạ. Thế là e sai ở đâu và fix như thế nào ạ

Mai Chi

Bạn copy giúp mình thông báo lỗi với ạ.

Nguyễn Linh

Đây ạ. Nó báo 2 lỗi nhưng e ko biết sửa kiểu gì ạ

Error CS0103 The name ‘repoBindingSource’ does not exist in the current context
Error CS0103 The name ‘repoBindingNavigatorSaveItem’ does not exist in the current context

Lần cuối chỉnh sửa 3 năm trước bởi Nguyễn Linh
Mai Chi

Lỗi này có nghĩa là hai biến repoBindingSource và repoBindingNavigatorSaveItem không tồn tại trên form bạn đang xây dựng.
Có thể là khi bạn đặt component BindingSource lên form bạn chưa đổi tên nó thành repoBindingSource.
repoBindingNavigatorSaveItem là một nút bấm trên thanh navigator, bạn xem thử tên của thanh navigator có phải là repoBindingNavigator chưa.

Tamu

Chị ơi !
private void Notify([CallerMemberName] string property = “”)
{
   PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property));
}
Em bị lỗi chỗ tô đen gạch dưới ạ.
Chương trình không hiểu Lỗi .
Dạ lỗi này lá lỗi gì và mình sẽ có giải pháp như thế nào ạ.
Cảm ơn chị nhiều !

Tutung

Chào Cô

Sao bước chuyển sang Contact data source sang datagridview không duoc ạ

EM xài VS2019

Mong CÔ chỉ giúp ạ

Cảm ơn Cô

Lần cuối chỉnh sửa 2 năm trước bởi Tutung
Tutung

Chào Cô
Em làm duoc rồi ạ
Cảm ơn Cô

Tutung

Chào Cô

Khi copy code của form Contact thì báo lỗi ạ

” There were build errors.Would you like to continue ad run the last successful build”

Em nhấn Yes thì trở về màn hình cũ, còn nhấn No thì xài lại bản chưa copy đạon code

Mong Cô chỉ giúp

TB : trong cửa sổ này sao em ko chụp màn hình lỗi bỏ vào để Cô dễ xem được ạ

Tutung

Chào CÔ

Dạ lỗi vầy ạ

‘Contacts’ does not contain a definition for ‘splitContainer2_Panel1_Paint’ and no accessible extension method ‘splitContainer2_Panel1_Paint’ accepting a first argument of type ‘Contacts’ could be found (are you missing a using directive or an assembly reference?)

Looi này xuat hien khi em copy doan code vào form COntact ạ

Lần cuối chỉnh sửa 2 năm trước bởi Tutung
Nhật Linh

Chào bạn. Bạn đang viết code nhầm cho phương thức xử lý sự kiện.
splitContainer2_Panel1_Paint() là phương thức xử lý sự kiện vẽ cho control splitContainer2 – điều khiển giúp co giãn Panel1. Nó không phải là phương thức xử lý sự kiện chúng ta cần sử dụng.
Để dễ xử lý giúp bạn, bạn hãy chụp giúp cửa sổ Visual Studio khi đang báo lỗi và post vào Facebook chat của page nhé.
Qua mô tả của bạn mình không xác định được những nhầm lẫn nào trong code.
Sorry vì phản hồi chậm!

Tutung

Dạ em đã nhận duoc thông tin phản hồi bên Facebook
CẢm ơn Thầy Cô ạ

Nhat Linh

Bạn double click vào dòng thông báo lỗi thì nó sẽ chuyển bạn sang vị trí lỗi trong file code. Ở đây bạn đang có một lỗi cú pháp. Bạn xóa dòng lỗi đó đi là được.

Lần cuối chỉnh sửa 2 năm trước bởi Nhật Linh
Thanhcong

Hay quá ạ, dân ngoại đạo xem xong sáng mắt ra luôn.

dellos

hết download code được rồi bạn ơi. Để lên github đi bạn.

dhatuan

Chi Mai có dậy kèm 1 1 không ạ. Mình tiếp thu hơi kém nên có nhiều cái không hiểu. Nếu có dậy kèm thì cho em 1 xuất. em cảm ơn