1.6.2 사용자 정의 컨트롤 만들기

이번에는 사용자 정의 컨트롤을 만들고 이를 사용하는 Windows Forms 응용 프로그램을 만들어 봅시다.

실습은 도서 관리자 응용을 만드는 것으로 할게요. 도서 관리자 응용은 Windows Forms 응용 프로그램으로 사용자 정의 컨트롤인 BookControlLib와 BookLib 클래스 라이브러리를 참조합니다. 그리고 BookControlLib는 BookLib 클래스 라이브러리를 참조할 것입니다.

도서 관리자 컴포넌트 다이어그램
[그림 1.22] 도서 관리자 컴포넌트 다이어그램

먼저 클래스 라이브러를 생성하여 디폴트로 제공하는 소스 파일명을 Book.cs로 변경하세요.

Book 클래스는 도서 제목, ISBN, 저자, 출판사, 설명을 멤버 속성으로 캡슐화하는 아주 작은 클래스입니다. 이에 관한 설명은 별도로 하지 않겠습니다.

▶ Book.cs

namespace BookLib
{
    /// <summary>
    /// 도서 클래스
    /// </summary>
    public class Book
    {
        static Book empty = new Book(string.Empty,string.Empty,
                                                 string.Empty,string.Empty,string.Empty);
        /// <summary>
        /// 빈 도서 정적 개체 - 가져오기
        /// </summary>
        public static Book Empty
        {
            get
            {
                return empty;
            }
        }
        /// <summary>
        /// ISBN - 가져오기
        /// </summary>
        public string ISBN
        {
            get;
            private set;
        }
        /// <summary>
        /// 제목 - 가져오기
        /// </summary>
        public string Title
        {
            get;
            private set;
        }
        /// <summary>
        /// 저자 - 가져오기
        /// </summary>
        public string Author
        {
            get;
            private set;
        }
        /// <summary>
        /// 설명 - 가져오기
        /// </summary>
        public string Description
        {
            get;
            private set;
        }
        /// <summary>
        /// 출판사 - 가져오기
        /// </summary>
        public string Publisher
        {
            get;
            private set;
        }


        /// <summary>
        /// 생성자
        /// </summary>
        /// <param name="title">제목</param>
        /// <param name="isbn">ISBN</param>
        /// <param name="author">저자</param>
        /// <param name="publisher">출판사</param>
        /// <param name="description">설명</param>        
        public Book(string title,string isbn,string author,string publisher,
                        string description)
        {
            Title = title;
            ISBN = isbn;
            Author = author;
            Description = description;
            Publisher = publisher;
        }
        /// <summary>
        /// ToString 재정의 - 타이틀
        /// </summary>
        /// <returns></returns>
        public override string ToString()
        {
            return Title;
        }
    }
}

이제 Windows Forms 컨트롤 라이브러리 형태의 프로젝트를 추가하여 BookControlLib를 만듭시다. 기본으로 제공하는 컨트롤 이름을 BookControl로 변경하세요. 그리고 도서 정보를 시각화하기 위해 자식 컨트롤을 배치합시다.

BookControl 자식 컨트롤 배치
[그림 1.23] BookControl 자식 컨트롤 배치
BookControl의 자식 컨트롤
[표 1.2] BookControl의 자식 컨트롤

 그리고 BookLib를 참조 추가합니다.

BookControl에 도서 개체 정보를 변경할 때 이 사실을 필요로 하는 곳을 위해 이벤트 인자 형식과 대리자를 정의합시다.

▶ BookEventArgs.cs

using System;
using BookLib;
 
namespace BookControlLib
{
    /// <summary>
    /// 도서 이벤트 대리자
    /// </summary>
    /// <param name="sender">보낸 이</param>
    /// <param name="e">이벤트 처리에 필요한 인자</param>
    public delegate void BookEventHandler(object sender, BookEventArgs e);
    /// <summary>
    /// 도서 이벤트 인자 클래스
    /// </summary>
    public class BookEventArgs:EventArgs
    {
        /// <summary>
        /// 도서 개체 - 가져오기
        /// </summary>
        public Book Book
        {
            get;
            private set;
        }

        /// <summary>
        /// 도서명 - 가져오기
        /// </summary>
        public string Title
        {
            get
            {
                return Book.Title;
            }
            
        }
        /// <summary>
        /// 저자 - 가져오기
        /// </summary>
        public string Author
        {
            get
            {
                return Book.Author;
            }
        }
        /// <summary>
        /// 출판사 - 가져오기
        /// </summary>
        public string Publisher
        {
            get
            {
                return Book.Publisher;
            }
        }
        /// <summary>
        /// 설명 - 가져오기
        /// </summary>
        public string Description
        {
            get
            {
                return Book.Description;
            }
        }
        /// <summary>
        /// 생성자
        /// </summary>
        /// <param name="book">도서 개체</param>
        public BookEventArgs(Book book)
        {
            Book = book;
        }
        /// <summary>
        /// ToString 재정의
        /// </summary>
        /// <returns>도서명</returns>
        public override string ToString()
        {
            return Book.Title;
        }
    }
}

BookControl에는 도서 개체를 멤버 필드와 도서 개체 변경에 관한 이벤트를 멤버로 캡슐화합니다.

public event BookEventHandler ChangedBook = null;
Book book = Book.Empty;

그리고 도서 개체를 가져오기 및 설정하기 속성을 제공합니다. set 블록에서는 도서 변경한 사실을 통보하고 자식 컨트롤의 속성을 변경합니다.

public Book Book
{
    get
    {
        return book;
    }
    set
    {
        if (value == null)
        {
            book = Book.Empty;
        }
        else
        {
            book = value;
        }
        if (ChangedBook != null)
        {
            ChangedBook(this, new BookEventArgs(Book));
        }
        ChangeControlProperty();
     }
}

자식 속성을 변경하는 메서드에서는 각 속성이 string.Empty일 때 디폴트 값으로 설정하고 그 외에는 도서 개체의 속성을 이용하여 설정합니다.

private void ChangeControlProperty()
{
    if (book.Title == string.Empty)
    {
        lb_title.Text = "[제목]";
    }
    else
    {
        lb_title.Text = book.Title;
    }
    if (book.ISBN == string.Empty)
    {
        lb_isbn.Text = "[ISBN]";
    }
    else
    {
        lb_isbn.Text = book.ISBN;
    }
    ...중략...
    tbox_description.Text = book.Description;
}

▶ BookControlLib.cs

using System.Windows.Forms;
using BookLib;
 
namespace BookControlLib
{
    /// <summary>
    /// 도서 컨트롤
    /// </summary>
    public partial class BookControl : UserControl
    {
        /// <summary>
        /// 도서 개체 변경 이벤트
        /// </summary>
        public event BookEventHandler ChangedBook = null;
        Book book = Book.Empty;
        /// <summary>
        /// 도서 개체 - 가져오기, 설정하기
        /// </summary>
        public Book Book
        {
            get
            {
                return book;
            }
            set
            {
                if (value == null)
                {
                    book = Book.Empty;
                }
                else
                {
                    book = value;
                }

                if (ChangedBook != null)
                {
                    ChangedBook(this, new BookEventArgs(Book));
                }

                ChangeControlProperty();
            }
        }

        /// <summary>
        /// 생성자
        /// </summary>
        public BookControl()
        {
            InitializeComponent();
        }
        private void ChangeControlProperty()
        {
            if (book.Title == string.Empty)
            {
                lb_title.Text = "[제목]";
            }
            else
            {
                lb_title.Text = book.Title;
            }
            if (book.ISBN == string.Empty)
            {
                lb_isbn.Text = "[ISBN]";
            }
            else
            {
                lb_isbn.Text = book.ISBN;
            }
            if (book.Author == string.Empty)
            {
                lb_author.Text = "[저자]";
            }
            else
            {
                lb_author.Text = book.Author;
            }
            if (book.Publisher == string.Empty)
            {
                lb_publisher.Text = "[출판사]";
            }
            else
            {
                lb_publisher.Text = book.Publisher;
            }
            tbox_description.Text = book.Description;
        }
    }
}

이제 Windows Forms 응용 프로그램 형태의 도서 관리자 프로젝트를 추가합시다. 그리고 BookControlLib를 참조 추가합니다.

도서 관리자 응용에는 MainForm과 AddForm이 있고 데이터를 관리하는 BookManager 클래스를 정의할 것입니다. 먼저 디폴트로 제공하는 Form1을 MainForm으로 변경하신 후에 AddForm을 추가하세요. 그리고 BookManager 클래스를 추가합니다.

MainForm의 자식 컨트롤 배치
[그림 1.24] MainForm의 자식 컨트롤 배치
MainForm의 자식 컨트롤
[표 1.3] MainForm의 자식 컨트롤
AddForm의 자식 컨트롤 배치
[그림 1.25] AddForm의 자식 컨트롤 배치
AddForm의 자식 컨트롤
[표 1.4] AddForm의 자식 컨트롤

먼저 BookManager 클래스를 구현합시다.

BookManager 개체는 단일 개체로 정의합시다.

static BookManager bm = new BookManager();
internal static BookManager BM
{
    get
    {
        return bm;
    }
}
private BookManager()
{
}

BookManager에는 도서를 추가하였을 때 이를 알고자 하는 곳에 이 사실을 알려주기 위한 이벤트를 멤버로 캡슐화합니다.

internal event BookEventHandler AddedBook = null;

그리고 ISBN을 키로하고 Book 개체를 값으로 하는 사전 개체를 두어 도서 개체를 관리합시다.

Dictionary<string, Book> bdic = new Dictionary<string, Book>();

도서를 추가하는 메서드를 제공합시다.

internal bool AddBook(string title, string isbn, string author, string publisher,
                              string description)

만약 입력 인자로 받은 isbn이 이미 있으면 거짓을 반환합니다.

if (bdic.ContainsKey(isbn))
{
    return false;
}

입력 인자로 받은 isbn이 없으면 Book 개체를 생성하여 사전 개체에 등록합니다.

bdic[isbn] = new Book(title, isbn, author, publisher, description);

그리고 도서 개체 추가 이벤트 멤버가 null이 아니면 도서를 추가한 사실을 구독하기를 원하는 개체가 있는 것이므로 이벤트 핸들러를 수행합니다.

if (AddedBook != null)
{
    AddedBook(this, new BookEventArgs(bdic[isbn]));
}

return true;

▶ BookManager.cs

using System.Collections.Generic;
using BookControlLib;
using BookLib;
 
namespace 도서관리자
{
    class BookManager
    {
        internal event BookEventHandler AddedBook = null;
        Dictionary<string, Book> bdic = new Dictionary<string, Book>(); 
        static BookManager bm = new BookManager();
        internal static BookManager BM
        {
            get
            {
                return bm;
            }
        }
        private BookManager()
        { 
        }
 
        internal bool AddBook(string title, string isbn, string author,
                                      string publisher, string description)
        {
            if (bdic.ContainsKey(isbn))
            {
                return false;
            }
            bdic[isbn] = new Book(title, isbn, author, publisher, description);
            if (AddedBook != null)
            {
                AddedBook(this, new BookEventArgs(bdic[isbn]));
            }
            return true;
        }
    }
}

MainForm에서는 BookManger 개체를 자주 사용할 수 있으므로 쉽게 참조할 수 있게 속성을 정의합니다.

BookManager BM
{
    get
    {
        return BookManager.BM;
    }
}

추가 폼은 최대 하나의 폼만 띄우도록 합시다. 이를 위해 MainForm에는 추가 폼을 참조하는 멤버 필드를 선언합니다.

AddForm af = null;

MainForm의 Load 이벤트 핸들러를 추가합시다.

private void MainForm_Load(object sender, EventArgs e)

이벤트 핸들러에서는 BookManager 단일 개체에게 AddedBook 이벤트 핸들러를 등록하여 도서를 추가하였을 때 이 사실을 통보받아 처리할 수 있게 합니다.

BM.AddedBook += new BookEventHandler(BM_AddedBook);

BM_AddedBook 이벤트 핸들러에서는 도서 개체를 항목으로 추가합니다.

void BM_AddedBook(object sender, BookEventArgs e)
{
    lbox_book.Items.Add(e.Book);
}

추가 버튼인 btn_add의 Click 이벤트 핸들러를 추가합시다.

private void btn_add_Click(object sender, EventArgs e)

만약 추가 폼을 참조하는 af가 null이면 추가 폼을 생성합니다. 그리고 추가 폼이 닫히는 것을 인지하기 위해 af의 FormClosed 이벤트 핸들러를 추가합니다. 그리고 추가 폼을 시각화합니다.

if (af == null)
{
    af = new AddForm();
    af.FormClosed += new FormClosedEventHandler(af_FormClosed);
    af.Show();
}

af_FormClosed 이벤트 핸들러에서는 af를 null로 설정합니다. 이 작업으로 다시 추가 버튼을 클릭하면 추가 폼을 생성하여 시각화할 수 있는 것입니다.

void af_FormClosed(object sender, FormClosedEventArgs e)
{
    af = null;
}

도서 목록 상자의 항목 변경 이벤트 핸들러를 추가합니다. 이벤트 핸들러에서는 선택한 항목의 도서 개체를 참조하여 BookControl인 bc의 Book 속성을 설정합니다.

private void lbox_book_SelectedIndexChanged(object sender, EventArgs e)
{
    if (lbox_book.SelectedIndex == -1)
    {
        return;
    }
    Book book = lbox_book.SelectedItem as Book;
    bc.Book = book;
}

▶ MainForms.cs

using System;
using System.Windows.Forms;
using BookControlLib;
using BookLib;
 
namespace 도서관리자
{
    public partial class MainForm : Form
    {
        AddForm af = null;
        BookManager BM
        {
            get
            {
                return BookManager.BM;
            }
        }
        public MainForm()
        {
            InitializeComponent();
        }
       
        private void MainForm_Load(object sender, EventArgs e)
        {
            BM.AddedBook += new BookEventHandler(BM_AddedBook);
        } 
        void BM_AddedBook(object sender, BookEventArgs e)
        {
            lbox_book.Items.Add(e.Book);
        }
        private void btn_add_Click(object sender, EventArgs e)
        {
            if (af == null)
            {
                af = new AddForm();
                af.FormClosed += new FormClosedEventHandler(af_FormClosed);
                af.Show();
            }
        }
 
        void af_FormClosed(object sender, FormClosedEventArgs e)
        {
            af = null;
        }
 
        private void lbox_book_SelectedIndexChanged(object sender, EventArgs e)
        {
            if (lbox_book.SelectedIndex == -1)
            {
                return;
            }
 
            Book book = lbox_book.SelectedItem as Book;
            bc.Book = book;
        }
    }
}

이제 AddForm을 구현합시다.

MainForm과 마찬가지로 BookManger를 쉽게 참조할 수 있게 속성을 정의합니다.

BookManager BM
{
    get
    {
        return BookManager.BM;
    }
}

추가 버튼의 Click 이벤트 핸들러를 추가합니다.

private void btn_add_Click(object sender, EventArgs e)

도서 정보를 입력한 컨트롤의 Text 속성으로 도서 정보를 얻어옵니다.

string title = tbox_title.Text;
string isbn = tbox_isbn.Text;
string author = tbox_author.Text;
string publisher = tbox_publisher.Text;
string description = tbox_description.Text;

도서 관리자 단일 개체의 AddBook 메서드를 이용하여 도서 정보를 추가합니다. 만약 결과가 false이면 메시지 창으로 추가 실패를 통보합니다.

if (BM.AddBook(title, isbn, author, publisher, description) == false)
{
    MessageBox.Show("추가 실패");
}

마지막으로 컨트롤의 Text 속성을 초기화합니다.

ControlTextInitialize();

도서 정보를 입력하는 TextBox 컨트롤들의 Text 속성을 초기화하는 메서드를 추가합니다.

private void ControlTextInitialize()
{
    tbox_title.Text = string.Empty;
    tbox_author.Text = string.Empty;
    tbox_isbn.Text = string.Empty;
    tbox_publisher.Text = string.Empty;
    tbox_description.Text = string.Empty;
}

취소 버튼의 Click 이벤트 핸들러도 추가하여 ControlTextInitialize 메서드를 호출합니다.

private void btn_cancel_Click(object sender, EventArgs e)
{
    ControlTextInitialize();
}

이상으로 사용자 정의 컨트롤을 정의하고 이를 이용하는 Windows Forms 응용을 만들어 보았습니다. Windows Forms 응용을 만드는 기술 학습으로는 많은 부분에서 부족할 수 있지만 기본적인 사항을 소개하는 수준에서 끝낼게요. 앞으로 검색 엔진을 만드는 과정을 소개하면서 보다 다양한 실습을 할 수 있을 것입니다. 그리고 필요하시면 별도의 레퍼런스를 참고하시기 바랍니다.

▶ AddForm.cs

using System;
using System.Windows.Forms; 
namespace 도서관리자
{
    public partial class AddForm : Form
    {
        BookManager BM
        {
            get
            {
                return BookManager.BM;
            }
        }
        public AddForm()
        {
            InitializeComponent();
        }
        private void btn_add_Click(object sender, EventArgs e)
        {
            string title = tbox_title.Text;
            string isbn = tbox_isbn.Text;
            string author = tbox_author.Text;
            string publisher = tbox_publisher.Text;
            string description = tbox_description.Text;
            if (BM.AddBook(title, isbn, author, publisher, description) == false)
            {
                MessageBox.Show("추가 실패");
            }
            ControlTextInitialize();
        } 
        private void ControlTextInitialize()
        {
            tbox_title.Text = string.Empty;
            tbox_author.Text = string.Empty;
            tbox_isbn.Text = string.Empty;
            tbox_publisher.Text = string.Empty;
            tbox_description.Text = string.Empty;
        }
 
        private void btn_cancel_Click(object sender, EventArgs e)
        {
            ControlTextInitialize();
        }
    }
}