.NET Framework 2.0의 Windows Forms 데이터 바인드 개선점 ( 제 2 부)

Cheryl Simmons
Microsoft Corporation

August 2006

적용 대상:
Microsoft Visual Studio 2005
Microsoft .NET Framework 2.0
Microsoft Windows Forms

요약: 일반 BindingList 에 대해 설명하고, 일반 컬렉션 형식을 확장해 정렬 기능 및 검색 기능의 추가 방법을 설명합니다.

Microsoft 다운로드 센터 (영어) 에서 C# 및 Visual Basic 로 작성된 코드 샘플 (29 KB)을 다운로드해 주세요.

목차

소개
일반 BindingList(generic BindingList)에 대해
코드 샘플 개요
일반 BindingList 검색
일반 BindingList 정렬
다음 단계
요약

소개

Windows Forms 데이터 바인드에서 화제가 되는 새로운 기능의 하나인 BindingSource 구성요소는 통화 관리 기능, 변경통지 기능 및 바인드 된 목록내의 멤버에 간단히 접근하는 기능을 제공하여 개발 작업을 크게 간략화합니다. 데이터 소스에 IBindingList 인터페이스가 구현되어 한층 더 검색 및 정렬 기능이 구현되며, BindingSource 를 사용해 데이터 소스의 검색 및 정렬을 실행할 수 있습니다. IBindingList 인터페이스를 구현하는 형식의 일례로서는 DataView 클래스가 있습니다. 그러나, IBindingList 인터페이스를 구현하는 클래스를 사용하지 않는 경우도 있을 수 있습니다. 이러한 경우에는 IBindingList 인터페이스를 독자적으로 구현하여 사용자 지정 비즈니스 개체 목록을 작성하여 바인드해야 합니다. 이 문서에서는 데이터 바인드 응용 프로그램으로 일반 BindingList를 사용하여 검색 및 정렬 가능한 사용자 지정 비즈니스 개체 목록의 생성 방법을 설명합니다.

일반 BindingList 에 대해

일반 BindingList는 IBindingList 인터페이스의 일반 구현입니다. IBindingList 인터페이스는 Windows Forms 데이터 바인드의 기본 인터페이스인 IList 인터페이스를 확장한 것입니다. IBindingList를 구현하는 클래스는 IList 인터페이스에서 상속된 기능 뿐만이 아니라, 정렬 기능, 검색 기능 및 변경통지 기능도 지원가능합니다. IBindingList 인터페이스는 매우 복잡한 인터페이스입니다. 일반 BindingList 형식의 일반 구현을 제공하여, Windows Forms 데이터 바인드엔진에 의해 이용되는 바인드 가능한 사용자 지정 비즈니스 개체 목록의 작성이 아주 간단해집니다. 일반 BindingList를 사용하여 비즈니스 개체를 데이터 바인드의 시나리오에 보관하면, 목록 삽입이나 목록 삭제를 간단하게 실행할 수 있으며, 목록을 변경했을 경우 변경 통지가 자동적으로 생성됩니다. 필요에 따라 몇가지의 속성 및 메서드를 오버라이드(override) 하여, 일반 BindingList 에 대한 검색 및 정렬 기능을 구현 할 수 있습니다.

코드 샘플 개요

검색 기능 및 정렬 기능을 데이터 소스에 추가하는 방법을 보여주기 위해 간단한 코드 샘플을 작성했습니다. 예를 들어, 복수의 Employee 개체가 포함된 목록이 있습니다. 우선, 이름, 급여 또는 입사일에 의해 특정 직원을 목록에서 검색하는 기능을 응용 프로그램에 구현해야 합니다. 또, 응용 프로그램의 사용자는 목록을 검색할 뿐만 아니라, 이름, 급여, 입사일등 직원 목록도 정렬해야 합니다. 다음의 코드 샘플과 같이, DataGridView 컨트롤에서 바인드되는 일반 BindingList 정렬 기능을 구현하여, 추가 코드를 기술하지 않아도 DataGridView 로 정렬 기능을 구현할 수 있습니다.

Employee 비즈니스 개체 개요

다음의 코드 샘플에서는 단순한 Employee 비즈니스 개체를 사용합니다. Employee 형식 구현은 다음과 같습니다.

public class Employee
{
    private string lastNameValue;
    private string firstNameValue;
    private int salaryValue;
    private DateTime startDateValue;

    public Employee() 
    {
        lastNameValue = "Last Name";
        firstNameValue = "First Name";
        salaryValue = 0;
        startDateValue = DateTime.Today;
    }

    public Employee(string lastName, string firstName, 
        int salary, DateTime startDate)
    {
        LastName = lastName;
        FirstName = firstName;
        Salary = salary;
        StartDate = startDate;
    }

    public string LastName
    {
        get { return lastNameValue; }
        set { lastNameValue = value; }
    }

    public string FirstName
    {
        get { return firstNameValue; }
        set { firstNameValue = value; }
    }

    public int Salary
    {
        get { return salaryValue; }
        set { salaryValue = value; }
    }

    public DateTime StartDate
    {
        get { return startDateValue; }
        set { startDateValue = value; }
    }

    public override string ToString()
    {
        return LastName + ", " + FirstName + "\n" +
        "Salary:" + salaryValue + "\n" +
        "Start date:" + startDateValue;
    }
}
일반 BindingList 확장

이 코드 샘플에서는 Employee 개체의 목록에 대한 검색 기능 및 정렬 기능을 구현하기 위해서 BindingList 에 근거해 SortableSearchableList 라는 이름의 형식을 작성했습니다. 검색 기능 및 정렬 기능을 구현하기위해 오버라이드(override) 할 필요가 있는 메서드 및 속성은 보호되고, 일반 BindingList를 확장해야 합니다. 또, SortableSearchableList 형식을 일반 목록으로 하여, <T> 구문을 클래스 정의로 사용합니다. 확장한 목록을 일반화함으로 다양한 상황에서 목록을 재사용 할 수 있습니다.

public class SortableSearchableList<T> : BindingList<T>

일반 BindingList 검색

일반 BindingList 에 대한 검색 기능을 구현하려면, 몇가지 과정을 거쳐야 합니다. 우선 SupportsSearchingCore 속성을 오버라이드(override) 하여 검색 기능이 지원되는 것을 보여줘야 합니다. 그 다음은 검색을 실행하는 FindCore 메서드를 오버라이드(override) 해야 합니다. 마지막으로 검색 기능을 공개합니다.

검색 지원 표시

SupportsSearchingCore 속성은 오버라이드(override) 해야하는 보호된 읽기 전용 속성입니다. SupportsSearchingCore 속성은 목록 검색이 지원되는지를 보여줍니다. 기본값에서는 이 속성이 false 로 설정되어 있습니다. 검색 기능이 목록에 구현되는 것을 보여주려면, 이 속성을 true 로 설정해야 합니다.

protected override bool SupportsSearchingCore
{
    get
    {
        return true;
    }
}
검색 기능 구현

다음에, FindCore 메서드를 오버라이드(override) 해, 목록을 검색하기 위한 구현을 제공해야 합니다. FindCore 메서드는 목록을 검색하여, 발견된 아이템의 인덱스를 돌려줍니다. FindCore 메서드는 검색하는 데이터 소스 종류의 속성 또는 열을 나타내는 PropertyDescriptor 개체와 검색 값을 나타내는 주요 개체를 받습니다. 다음의 코드는 대문자와 소문자를 구별하는 검색 기능의 간단한 구현을 보여줍니다.

protected override int FindCore(PropertyDescriptor prop, object key)
{
    // Get the property info for the specified property.
    PropertyInfo propInfo = typeof(T).GetProperty(prop.Name);
    T item;

    if (key != null)
    {
        // Loop through the items to see if the key
        // value matches the property value.
        for (int i = 0; i < Count; ++i)
        {
            item = (T)Items[i];
            if (propInfo.GetValue(item, null).Equals(key))
                return i;
        }
    }
    return -1;
}
검색 기능 공개

마지막으로, 퍼블릭 메서드를 사용해 검색 기능을 공개할 수 있습니다. 또는 Find 메서드를 사용하여 기본이 되는 IBindingList 가 검색 기능을 호출할 수도 있습니다. 다음의 코드에서는 검색 기능의 호출을 간략히 하기위해 퍼블릭에 공개된 Find 메서드가 문자열과 검색대상 키를 받습니다. 다음은 속성 문자열이 PropertyDescriptor 개체에 변환됩니다. 정상적으로 변환되면, PropertyDescriptor 및 키가 FindCore 메서드에게 전달됩니다.

public int Find(string property, object key)
{
    // Check the properties for a property with the specified name.
    PropertyDescriptorCollection properties = 
        TypeDescriptor.GetProperties(typeof(T));
    PropertyDescriptor prop = properties.Find(property, true);

    // If there is not a match, return -1 otherwise pass search to
    // FindCore method.
    if (prop == null)
        return -1;
    else
       return FindCore(prop, key);
}

그림 1 에, 실행중인 검색 기능을 보여줍니다. 성을 입력해 [Search] 버튼을 클릭하면, 검색 기능으로 성이 표시된 그리드 안의 첫번째 행이 표시됩니다 (발견되었을 경우).

그림 1. 일반 BindingList 를 사용해 구현 된 검색 기능

일반 BindingList 정렬

응용 프로그램의 사용자는 목록을 검색할 뿐만 아니라, 이름, 급여, 입사일 등의 직원관련 목록을 정렬해야 합니다. SupportsSortingCore 속성과 ApplySortCore 및 RemoveSortCore 메서드를 오버라이드(override) 해서 일반 BindingList 에 정렬 기능을 구현 할 수 있습니다. 또는 SortDirectionCore 및 SortPropertyCore 속성을 오버라이드(override) 하고, 정렬의 방향 및 속성을 각각 가져올수 있습니다. 또, 목록에 추가한 새로운 아이템을 정렬할 경우는 EndNew 메서드를 오버라이드(override) 해야 합니다.

정렬 지원 표시

SupportsSortingCore 속성은 오버라이드(override) 해야 하는 보호된 읽기 전용 속성입니다. SupportsSortingCore 속성은 목록이 정렬을 지원하는지 보여줍니다. 정렬 기능이 목록에 구현되는 것을 보여주려면, 이 속성을 true 로 설정합니다.

protected override bool SupportsSortingCore
{
    get { return true; }
}
정렬 적용

다음은 ApplySortCore 메서드를 오버라이드(override) 하여 정렬 내용을 지정합니다. ApplySortCore 메서드는 정렬 조건으로서 사용하는 목록 아이템 속성을 지정하는 PropertyDescriptor 개체와 목록을 올림차순으로 정렬할지 내림차순으로 정렬할지 지정하는 ListSortDescription 열거형을 인자로 받습니다.

다음의 코드에서는 선택한 속성에 의해서 IComparable 인터페이스가 구현 된 경우에만, 목록을 정렬할 수 있습니다. 이 인터페이스에서는 CompareTo 메서드가 제공됩니다. 문자열, 정수, DateTime 형식 등, 단순한 대부분의 형식에서는 IComparable 인터페이스가 구현 됩니다. Employee 개체의 모든 속성이 단순한 형식이므로, 이와 같습니다.  또, 이 구현에서는 단순한 형식이 아닌 속성으로 데이터 소스를 정렬했을 경우에 통지하는 단순한 오류 처리기능이 제공됩니다.

다음의 코드에서는 정렬 전의 목록의 복사가 RemoveSortCore 메서드로 사용할 수 있도록 보존됩니다. 또한 지정한 속성의 값이 ArrayList 에 복사됩니다. ArrayList Sort 메서드의 호출을 합니다. 그 결과, 배열 멤버의 CompareTo 메서드가 호출됩니다. 또, 정렬 후의 배열내의 값을 사용하고, 일반 BindingList 값이 올림차순 또는 내림차순의 한쪽에 정리됩니다. 마지막으로, 목록이 재설정 된 것을 보여주는 ListChanged 이벤트가 발생해 바인드 된 제어로 목록 값이 업데이트됩니다. 다음 코드는 정렬 기능의 구현을 보여줍니다.

ListSortDirection sortDirectionValue;
PropertyDescriptor sortPropertyValue;

protected override void ApplySortCore(PropertyDescriptor prop, 
    ListSortDirection direction)
{
    sortedList = new ArrayList();

    // Check to see if the property type we are sorting by implements
    // the IComparable interface.
    Type interfaceType = prop.PropertyType.GetInterface("IComparable");

    if (interfaceType != null)
    {
        // If so, set the SortPropertyValue and SortDirectionValue.
        sortPropertyValue = prop;
        sortDirectionValue = direction;

        unsortedItems = new ArrayList(this.Count);

        // Loop through each item, adding it the the sortedItems ArrayList.
        foreach (Object item in this.Items) {
            sortedList.Add(prop.GetValue(item));
            unsortedItems.Add(item);
        }
        // Call Sort on the ArrayList.
        sortedList.Sort();
        T temp;

        // Check the sort direction and then copy the sorted items
        // back into the list.
        if (direction == ListSortDirection.Descending)
            sortedList.Reverse();

        for (int i = 0; i < this.Count; i++)
        {
            int position = Find(prop.Name, sortedList[i]);
            if (position != i) {
                temp = this[i];
                this[i] = this[position];
                this[position] = temp;
            }
        }

        isSortedValue = true;

        // Raise the ListChanged event so bound controls refresh their
        // values.
        OnListChanged(new ListChangedEventArgs(ListChangedType.Reset, -1));
    }
    else
        // If the property type does not implement IComparable, let the user
        // know.
        throw new NotSupportedException("Cannot sort by " + prop.Name +
            ". This" + prop.PropertyType.ToString() + 
            " does not implement IComparable");
}
정렬 해제

다음의 순서에서는 RemoveSortCore 메서드를 오버라이드(override) 합니다. RemoveSortCore 메서드는 목록에 마지막으로 적용된 정렬를 해제하고, ListChanged 이벤트를 호출하고, 목록이 재설정 된 것을 보여줍니다. RemoveSort 메서드는 정렬 해제 기능을 공개합니다.

protected override void RemoveSortCore()
{
    int position;
    object temp;
    // Ensure the list has been sorted.
    if (unsortedItems != null)
    {
        // Loop through the unsorted items and reorder the
        // list per the unsorted list.
        for (int i = 0; i < unsortedItems.Count; )
        {
            position = this.Find("LastName", 
                unsortedItems[i].GetType().
                GetProperty("LastName").GetValue(unsortedItems[i], null));
            if (position > 0 && position != i)
            {
                temp = this[i];
                this[i] = this[position];
                this[position] = (T)temp;
                i++;
            }
            else if (position == i)
                i++;
            else
                // If an item in the unsorted list no longer exists,
                // delete it.
                unsortedItems.RemoveAt(i);
        }
        isSortedValue = false;
        OnListChanged(new ListChangedEventArgs(ListChangedType.Reset, -1));
    }
}

public void RemoveSort()
{
    RemoveSortCore();
}
정렬 방향 및 속성 공개

다음에, 보호되는 SortDirectionCore 및 SortPropertyCore 속성을 오버라이드(override) 합니다. SortDirectionCore 속성은 올림차순 또는 내림차순의 몇가지 정렬 방향을 보여줍니다. SortPropertyCore 속성은 목록이 정렬에 사용되는 속성을 설명하는 것을 보여줍니다.

ListSortDirection sortDirectionValue;
PropertyDescriptor sortPropertyValue;

protected override PropertyDescriptor SortPropertyCore
{
    get { return sortPropertyValue; }
}

protected override ListSortDirection SortDirectionCore
{
    get { return sortDirectionValue; }
}
정렬 기능 공개

이 코드 샘플에서는 Employees 의 사용자 지정 일반 BindingList (SortableSearchableList)를 DataGridView 컨트롤을 바인드 합니다. DataGridView 컨트롤은 정렬 기능이 구현되어 있는지 검색 합니다. 정렬은 구현되어 있으므로, DataGridView 열을 클릭하면, DataGridView 내용이 그 열의 내용에 의해 올림차순으로 정렬할 수 있습니다. 사용자는 열을 한번 더 클릭하고, 아이템을 내림차순으로 정렬할 수 있습니다. DataGridView는 IBindingList 인터페이스를 통해 목록의 호출을 실행합니다.

검색코드와 같이, ApplySortCore 를 호출하는 퍼블릭 메서드를 필요에 따라서 공개할 수 있습니다. 또는 DataGridView 컨트롤과 같이, 기본으로 되는 IBindingList  호출을 실행할 수도 있습니다.

((IBindingList)employees).ApplySort(somePropertyDescriptor,
    ListSortDirection.Ascending);
목록에 추가된 아이템 정렬

Employee 개체에서는 기본값의 생성자가 공개되므로, BindingList 의 AllowNew 속성은 true 를 돌려줍니다. 이것에 의해, 사용자는 새로운 직원을 목록에 추가할 수 있습니다. 새로운 아이템을 추가할 수 있도록 한 후, 목록에 추가된 아이템이 정렬할 수 있도록 (정렬이 적용되는 경우) 할 수도 있습니다. 이것을 실행하기 위해서 EndNew 메서드를 오버라이드(override) 했습니다. 이 메서드 오버라이드(override)에서는 SortPropertyValue 가 null 이외의 값인 것을 확인하여 목록을 정렬하는 것을 것을 체크합니다. 또, 목록의 마지막에 새로운 아이템이 추가되어 있는지 체크해 (목록의 재정렬이 필요한 것을 보여줌), 다음에 목록으로 ApplySortCore 를 호출합니다.

public override void EndNew(int itemIndex)
{
    // Check to see if the item is added to the end of the list,
    // and if so, re-sort the list.
    if (sortPropertyValue != null && itemIndex == this.Count - 1)
        ApplySortCore(this.sortPropertyValue, this.sortDirectionValue);

    base.EndNew(itemIndex);
}

그림 2 에서, 실행중에 정렬 기능을 보여줍니다. DataGridView 열을 클릭하면, 그 내용을 정렬할 수 있습니다. [Remove sort] 버튼을 클릭하면, 마지막에 적용된 정렬이 해제됩니다.

그림 2. 일반 BindingList 를 사용해 구현 된 정렬 기능

다음 단계

지금까지 채택해 온 코드는 일반 BindingList 에 대한 검색 기능 및 정렬 기능의 구현방법이었습니다. 이 코드 샘플에서 사용한 정렬 알고리즘은 IComparable 인터페이스를 구현하는 형식에 따라서 다릅니다. IComparable 인터페이스를 구현하지 않는 속성에 의해 정렬를 실행하려면, 예외로 처리됩니다. 속성이 주로 단순한 형식인 사용자 지정 비즈니스 개체를 정렬하는 경우는 이것으로 충분합니다. 사용자 지정 비즈니스 개체의 속성이 복잡한 형식인 경우는 이 코드 샘플로 한층 더 향상된 기능을 구현하기 위해서 IComparable 인터페이스 구현을 검토해야 합니다. 예를 들어,Employee 개체에 복잡한 형식 Address 의 Address 속성이 있는 경우는 Address 형식을 정렬하기 위한 비즈니스 규칙을 결정하여 Address 형식에 대해 IComparable 인터페이스와 CompareTo 메서드를 구현합니다.

또, 새로운 아이템이 목록의 마지막에 추가되었을 경우, 목록은 자동적으로 다시 정렬할 수 있습니다. 응용 프로그램의 기본 비즈니스 로직에 따라, 아이템이 추가된 장소에 관계없이 목록을 다시 정렬하는 것을 검토하길 바랍니다.

또, 데이터 소스에 필터 설정 기능이나 복수열 정렬 기능을 추가할 수도 있습니다. 그 경우는 데이터 소스에 IBindingListView 인터페이스를 구현하는 것을 검토하길 바랍니다.

요약

BindingSource 구성요소는 Windows Forms 데이터 바인드 시나리오로 매우 높은 효과를 발휘합니다. 그러나, 사용자 지정 비즈니스 개체의 목록을 처리하는 경우는 검색 기능 및 정렬 기능을 추가해야 할 경우도 있습니다. 새로운 일반 BindingList를 사용하면, 검색 및 정렬이 가능한 사용자 지정 비즈니스 개체의 바인드 목록을 간단하게 작성할 수 있습니다. Windows Forms의 BindingList 및 그 외의 기능의 자세한 내용은 다음을 참조해 주세요.

+ Recent posts