작성자: 스콧 미첼
정렬된 데이터의 긴 목록을 표시할 때 구분 기호 행을 도입하여 관련 데이터를 그룹화하면 매우 유용할 수 있습니다. 이 자습서에서는 이러한 정렬 사용자 인터페이스를 만드는 방법을 살펴보겠습니다.
소개
정렬된 열에 소수의 다른 값만 있는 정렬된 데이터의 긴 목록을 표시할 때 최종 사용자는 차이점 경계가 발생하는 위치를 파악하기가 어려울 수 있습니다. 예를 들어 데이터베이스에는 81개의 제품이 있지만 9개의 다른 범주 선택 항목(8개의 고유한 범주와 NULL
옵션)만 있습니다. 해산물 범주에 속하는 제품을 검사하려는 사용자의 경우를 고려합니다. 단일 GridView의 모든 제품을 나열하는 페이지에서 사용자는 모든 해산물 제품을 함께 그룹화할 범주별로 결과를 정렬하는 것이 가장 좋은 선택이라고 결정할 수 있습니다. 범주별로 정렬한 후 사용자는 목록을 찾아 해산물 그룹화된 제품이 시작되고 끝나는 위치를 찾아야 합니다. 결과는 해산물 제품을 찾는 범주 이름으로 사전순으로 정렬되기 때문에 어렵지 않지만 그리드의 항목 목록을 면밀히 검사해야 합니다.
정렬된 그룹 간의 경계를 강조 표시하기 위해 많은 웹 사이트에서는 이러한 그룹 간에 구분 기호를 추가하는 사용자 인터페이스를 사용합니다. 그림 1에 표시된 것과 같은 구분 기호를 사용하면 사용자가 특정 그룹을 보다 빠르게 찾고 해당 경계를 식별할 수 있을 뿐만 아니라 데이터에 존재하는 고유 그룹을 확인할 수 있습니다.
그림 1: 각 범주 그룹이 명확하게 식별됨(전체 크기 이미지를 보려면 클릭)
이 자습서에서는 이러한 정렬 사용자 인터페이스를 만드는 방법을 살펴보겠습니다.
1단계: 표준 정렬 가능한 GridView 만들기
GridView를 보강하여 향상된 정렬 인터페이스를 제공하는 방법을 살펴보기 전에 먼저 제품을 나열하는 정렬 가능한 표준 GridView를 만들어 보겠습니다. 먼저 폴더에서 CustomSortingUI.aspx
PagingAndSorting
페이지를 엽니다. 페이지에 GridView를 추가하고, 해당 ID
속성을 ProductList
로 설정하고, 새 ObjectDataSource에 바인딩합니다. ObjectDataSource를 구성하여, ProductsBLL
클래스를 사용하여 GetProducts()
메서드로 레코드를 선택합니다.
다음으로, GridView를 ProductName
, CategoryName
, SupplierName
, UnitPrice
등 BoundFields와 중단된 CheckBoxField만 포함하도록 구성합니다. 마지막으로, GridView를 정렬을 지원하도록 구성하려면 스마트 태그에서 '정렬 사용' 확인란을 선택하거나 속성을 AllowSorting
로 설정하십시오.
CustomSortingUI.aspx
페이지에 이러한 항목을 추가한 후, 선언적 마크업은 다음과 유사하게 표시되어야 합니다.
<asp:GridView ID="ProductList" runat="server" AllowSorting="True"
AutoGenerateColumns="False" DataKeyNames="ProductID"
DataSourceID="ObjectDataSource1" EnableViewState="False">
<Columns>
<asp:BoundField DataField="ProductName" HeaderText="Product"
SortExpression="ProductName" />
<asp:BoundField DataField="CategoryName" HeaderText="Category"
ReadOnly="True" SortExpression="CategoryName" />
<asp:BoundField DataField="SupplierName" HeaderText="Supplier"
ReadOnly="True" SortExpression="SupplierName" />
<asp:BoundField DataField="UnitPrice" DataFormatString="{0:C}"
HeaderText="Price" HtmlEncode="False" SortExpression="UnitPrice" />
<asp:CheckBoxField DataField="Discontinued" HeaderText="Discontinued"
SortExpression="Discontinued" />
</Columns>
</asp:GridView>
<asp:ObjectDataSource ID="ObjectDataSource1" runat="server"
OldValuesParameterFormatString="original_{0}" SelectMethod="GetProducts"
TypeName="ProductsBLL"></asp:ObjectDataSource>
브라우저에서 지금까지 진행 상황을 볼 수 있습니다. 그림 2는 데이터가 범주별로 사전순으로 정렬될 때 정렬 가능한 GridView를 보여줍니다.
그림 2: 정렬 가능한 GridView의 데이터가 범주별로 정렬됨(전체 크기 이미지를 보려면 클릭)
2단계: 구분 기호 행을 추가하기 위한 기술 탐색
제네릭하고 정렬 가능한 GridView가 완료되면, 남은 것은 각 고유한 정렬된 그룹 앞에 구분 행을 추가하는 것입니다. 그러나 이러한 행을 GridView에 어떻게 삽입할 수 있을까요? 기본적으로 GridView의 행을 반복하고 정렬된 열의 값 간에 차이가 발생하는 위치를 확인한 다음 적절한 구분 기호 행을 추가해야 합니다. 이 문제에 대해 생각할 때 솔루션이 GridView의 RowDataBound
이벤트 처리기 어딘가에 있는 것이 당연해 보입니다.
데이터 기반 사용자 지정 서식 자습서에서 설명한 것처럼 이 이벤트 처리기는 행 데이터를 기반으로 행 수준 서식을 적용할 때 일반적으로 사용됩니다. 그러나 RowDataBound
이 이벤트 처리기에서 프로그래밍 방식으로 행을 GridView에 추가할 수 없으므로 이벤트 처리기는 여기에 솔루션이 아닙니다. 실제로 GridView의 Rows
컬렉션은 읽기 전용입니다.
GridView에 행을 더 추가하려면 다음 세 가지 옵션이 있습니다.
- GridView에 바인딩된 실제 데이터에 이러한 메타데이터 구분 기호 행 추가
- GridView가 데이터에 바인딩된 후 GridView의 컨트롤 컬렉션에 추가
TableRow
인스턴스를 추가합니다. - GridView 컨트롤을 확장하고 GridView 구조 생성을 담당하는 메서드를 재정의하는 사용자 지정 서버 컨트롤 만들기
이 기능이 여러 웹 페이지 또는 여러 웹 사이트에서 필요한 경우 사용자 지정 서버 컨트롤을 만드는 것이 가장 좋습니다. 그러나 꽤 많은 코드와 GridView 내부 작업의 깊이에 대한 철저한 탐색이 수반됩니다. 따라서 이 자습서에서는 이 옵션을 고려하지 않습니다.
다른 두 가지 옵션은 GridView에 바인딩되는 실제 데이터에 구분 행을 추가하고, 바인딩된 후에 GridView의 컨트롤 컬렉션을 조작하는 방식으로 문제를 다르게 접근합니다. 이러한 방법들은 논의할 가치가 있습니다.
GridView에 바인딩된 데이터에 행 추가
GridView가 데이터 원본에 바인딩되면 데이터 원본에서 반환된 GridViewRow
각 레코드에 대한 값을 만듭니다. 따라서 GridView에 바인딩하기 전에 데이터 원본에 구분 기호 레코드를 추가하여 필요한 구분 기호 행을 삽입할 수 있습니다. 그림 3에서는 이 개념을 보여 줍니다.
그림 3: 데이터 원본에 구분 기호 행 추가와 관련된 한 가지 기술
특수 구분 기호 레코드가 없으므로 따옴표로 구분 기호 레코드를 사용합니다. 대신 데이터 원본의 특정 레코드가 일반 데이터 행이 아닌 구분 기호로 사용되도록 플래그를 지정해야 합니다. 이 예제에서는 ProductsDataTable
인스턴스를 GridView에 바인딩합니다. GridView는 ProductRows
로 구성되어 있습니다. 우리는 해당 레코드를 구분 기호 행으로 표시하기 위해 해당 CategoryID
속성을 -1
로 설정할 수 있습니다(이러한 값은 정상적으로 존재할 수 없기 때문에).
이 기술을 활용하려면 다음 단계를 수행해야 합니다.
- GridView(
ProductsDataTable
인스턴스)에 바인딩할 데이터를 프로그래밍 방식으로 검색합니다. - GridView
SortExpression
및SortDirection
속성에 따라 데이터 정렬 -
ProductsDataTable
에서ProductsRows
을 반복 탐색하여 정렬된 열에서 차이점이 있는 위치를 찾습니다. - 각 그룹 경계에서 구분자 레코드
ProductsRow
인스턴스를 DataTable에 삽입합니다. 이는CategoryID
-1
으로 설정되어 있어야 합니다(또는 그 레코드를 구분자 레코드로 표시하기로 결정된 명칭). - 구분 기호 행을 삽입한 후 프로그래밍 방식으로 GridView에 데이터를 바인딩합니다.
이러한 5단계 외에도 GridView 이벤트에 RowDataBound
대한 이벤트 처리기를 제공해야 합니다. 여기서는 각 DataRow
을 확인하고 CategoryID
설정이 -1
로 되어 있는지, 즉 구분 기호 행인지 판단합니다. 그렇다면 해당 서식이나 셀에 표시된 텍스트를 조정하려고 할 것입니다.
정렬 그룹 경계를 삽입하기 위해 이 기술을 사용하려면 GridView의 Sorting
이벤트에 대한 이벤트 처리기를 제공하고 SortExpression
값과 SortDirection
값을 추적해야 하므로 위에서 설명한 것보다 약간 더 많은 작업이 필요합니다.
데이터 바인딩된 후 GridView의 컨트롤 컬렉션 조작
GridView에 바인딩하기 전에 데이터를 메시징하는 대신 데이터가 GridView에 바인딩된 후 구분 기호 행을 추가할 수 있습니다. 데이터 바인딩 프로세스는 GridView의 컨트롤 계층 구조를 구축하며, 실제로는 각각 셀 컬렉션으로 구성된 행 컬렉션으로 구성된 인스턴스일 뿐입니다 Table
. 특히 GridView의 컨트롤 컬렉션은 루트에 있는 Table
개체, GridView에 바인딩된 DataSource
의 각 레코드에 대한 GridViewRow
개체(이는 TableRow
클래스에서 파생됨), 그리고 DataSource
의 각 데이터 필드에 대한 각 GridViewRow
인스턴스 내의 TableCell
개체를 포함합니다.
각 정렬 그룹 간에 구분 기호 행을 추가하려면 컨트롤 계층이 만들어지면 이 컨트롤 계층을 직접 조작할 수 있습니다. 페이지가 렌더링될 때까지 GridView의 컨트롤 계층 구조가 마지막으로 생성되었다고 확신할 수 있습니다. 따라서 이 방법은 클래스의 Page
메소드 Render
를 재정의합니다. 이때 GridView의 최종 제어 계층 구조가 필요한 구분 기호 행을 포함하도록 업데이트됩니다. 그림 4에서는 이 프로세스를 보여 줍니다.
그림 4: GridView의 컨트롤 계층 구조를 조작하는 대체 기술(전체 크기 이미지를 보려면 클릭)
이 자습서에서는 이 후자의 접근 방식을 사용하여 정렬 사용자 환경을 사용자 지정합니다.
비고
이 자습서에서 제시하는 코드는 Teemu Keiski 의 블로그 항목인 GridView 정렬 그룹화에서 비트 재생에 제공된 예제를 기반으로 합니다.
3단계: GridView의 컨트롤 계층 구조에 구분 기호 행 추가
해당 페이지 방문에서 마지막으로 컨트롤 계층을 만들고 만든 후에만 GridView 컨트롤 계층 구조에 구분 기호 행을 추가하려고 하므로 페이지 수명 주기의 끝에서 실제 GridView 컨트롤 계층이 HTML로 렌더링되기 전에 이 추가를 수행하려고 합니다. 이 작업을 수행할 수 있는 최종 시점은 Page
클래스의 Render
이벤트로, 이는 다음 메서드 시그니처를 사용하여 코드 숨김 클래스에서 재정의할 수 있습니다.
protected override void Render(HtmlTextWriter writer)
{
// Add code to manipulate the GridView control hierarchy
base.Render(writer);
}
Page
클래스의 원래 Render
메서드가 호출되면 base.Render(writer)
페이지의 각 컨트롤이 렌더링되고 해당 컨트롤 계층 구조에 따라 태그가 생성됩니다. 따라서 우리는 페이지가 렌더링되도록 base.Render(writer)
와 함께 호출하고, base.Render(writer)
를 호출하기 전에 GridView의 컨트롤 계층 구조를 조작하여 구분 행이 GridView의 컨트롤 계층 구조에 추가되도록 해야 합니다.
정렬 그룹 헤더를 삽입하려면 먼저 사용자가 데이터를 정렬하도록 요청했는지 확인해야 합니다. 기본적으로 GridView의 콘텐츠는 정렬되지 않으므로 그룹 정렬 헤더를 입력할 필요가 없습니다.
비고
페이지가 처음 로드될 때 GridView를 특정 열별로 정렬하려면 첫 번째 페이지 방문 시 GridView 메서드 Sort
를 호출합니다(후속 포스트백에서는 아님). 이렇게 하려면 Page_Load
이벤트 처리기 내의 조건부에 이 호출을 추가합니다 if (!Page.IsPostBack)
. 메서드에 대한 자세한 Sort
내용은 페이징 및 정렬 보고서 데이터 자습서 정보를 다시 참조하세요.
데이터가 정렬되었다고 가정할 때 다음 작업은 데이터가 정렬된 열을 확인한 다음 해당 열 값의 차이를 찾는 행을 검사하는 것입니다. 다음 코드는 데이터가 정렬되었는지 확인하고 데이터가 정렬된 열을 찾습니다.
protected override void Render(HtmlTextWriter writer)
{
// Only add the sorting UI if the GridView is sorted
if (!string.IsNullOrEmpty(ProductList.SortExpression))
{
// Determine the index and HeaderText of the column that
//the data is sorted by
int sortColumnIndex = -1;
string sortColumnHeaderText = string.Empty;
for (int i = 0; i < ProductList.Columns.Count; i++)
{
if (ProductList.Columns[i].SortExpression.CompareTo(ProductList.SortExpression)
== 0)
{
sortColumnIndex = i;
sortColumnHeaderText = ProductList.Columns[i].HeaderText;
break;
}
}
// TODO: Scan the rows for differences in the sorted column�s values
}
GridView가 아직 정렬되지 않은 경우 GridView의 SortExpression
속성이 설정되지 않습니다. 따라서 이 속성에 값이 있는 경우에만 구분 기호 행을 추가하려고 합니다. 이 경우 다음으로 데이터가 정렬된 열의 인덱스가 결정되어야 합니다. 이 작업은 GridView의 Columns
컬렉션을 반복하고 해당 속성이 SortExpression
GridView의 SortExpression
속성과 같은 열을 검색하여 수행됩니다. 열 인덱스 외에도 구분 행을 표시할 때 사용되는 HeaderText
속성을 가져옵니다.
데이터가 정렬되는 열의 인덱스를 사용하여 마지막 단계는 GridView의 행을 열거하는 것입니다. 각 행에 대해 정렬된 열 값이 이전 행의 정렬된 열 값과 다른지 확인해야 합니다. 그렇다면 컨트롤 계층 구조에 새 GridViewRow
인스턴스를 삽입해야 합니다. 이 작업은 다음 코드로 수행됩니다.
protected override void Render(HtmlTextWriter writer)
{
// Only add the sorting UI if the GridView is sorted
if (!string.IsNullOrEmpty(ProductList.SortExpression))
{
// ... Code for finding the sorted column index removed for brevity ...
// Reference the Table the GridView has been rendered into
Table gridTable = (Table)ProductList.Controls[0];
// Enumerate each TableRow, adding a sorting UI header if
// the sorted value has changed
string lastValue = string.Empty;
foreach (GridViewRow gvr in ProductList.Rows)
{
string currentValue = gvr.Cells[sortColumnIndex].Text;
if (lastValue.CompareTo(currentValue) != 0)
{
// there's been a change in value in the sorted column
int rowIndex = gridTable.Rows.GetRowIndex(gvr);
// Add a new sort header row
GridViewRow sortRow = new GridViewRow(rowIndex, rowIndex,
DataControlRowType.DataRow, DataControlRowState.Normal);
TableCell sortCell = new TableCell();
sortCell.ColumnSpan = ProductList.Columns.Count;
sortCell.Text = string.Format("{0}: {1}",
sortColumnHeaderText, currentValue);
sortCell.CssClass = "SortHeaderRowStyle";
// Add sortCell to sortRow, and sortRow to gridTable
sortRow.Cells.Add(sortCell);
gridTable.Controls.AddAt(rowIndex, sortRow);
// Update lastValue
lastValue = currentValue;
}
}
}
base.Render(writer);
}
이 코드는 GridView 컨트롤 계층 구조의 루트에 있는 개체를 프로그래밍 방식으로 참조 Table
하고 이름이 지정된 lastValue
문자열 변수를 만드는 것으로 시작합니다.
lastValue
는 현재 행의 정렬된 열 값을 이전 행 값과 비교하는 데 사용됩니다. 다음으로 GridView의 Rows
컬렉션이 열거되고 각 행에 대해 정렬된 열의 값이 변수에 currentValue
저장됩니다.
비고
특정 행의 정렬된 열 값을 확인하려면 셀의 Text
속성을 사용합니다. 이는 BoundFields에 적합하지만 TemplateFields, CheckBoxFields 등에 대해 원하는 대로 작동하지 않습니다. 곧 대체 GridView 필드를 고려하는 방법을 살펴보겠습니다.
currentValue
그런 다음 변수와 lastValue
변수를 비교합니다. 다른 경우 컨트롤 계층 구조에 새 구분 기호 행을 추가해야 합니다. 이 작업은 Table
객체의 Rows
컬렉션에서 GridViewRow
의 인덱스를 구하고, 새 GridViewRow
및 TableCell
인스턴스를 생성한 다음, TableCell
및 GridViewRow
을 컨트롤 계층 구조에 추가하여 수행됩니다.
구분 기호 행의 단독 TableCell
은 GridView의 전체 너비에 걸쳐 있고, SortHeaderRowStyle
CSS 클래스를 사용해 서식이 되어 있으며, Text
속성을 조정하여 정렬 그룹 이름(예: 범주)과 그룹 값(예: 음료)을 모두 표시할 수 있습니다. 마지막으로 lastValue
이(가) currentValue
값으로 업데이트됩니다.
정렬 그룹 머리글 행의 서식을 지정하는 데 사용되는 CSS 클래스는 Styles.css
파일에 지정해야 합니다. 어떤 스타일 설정도 자유롭게 사용할 수 있습니다. 다음을 사용했습니다.
.SortHeaderRowStyle
{
background-color: #c00;
text-align: left;
font-weight: bold;
color: White;
}
현재 코드를 사용하여 정렬 인터페이스는 BoundField를 기준으로 정렬할 때 정렬 그룹 헤더를 추가합니다(공급자별로 정렬할 때 스크린샷을 보여주는 그림 5 참조). 그러나 다른 필드 형식(예: CheckBoxField 또는 TemplateField)을 기준으로 정렬하는 경우 정렬 그룹 머리글을 찾을 수 없습니다(그림 6 참조).
그림 5: BoundFields로 정렬할 때 정렬 그룹 머리글이 포함된 정렬 인터페이스(전체 크기 이미지를 보려면 클릭)
그림 6: CheckBoxField를 정렬할 때 그룹 머리글 정렬이 없습니다(전체 크기 이미지를 보려면 클릭).
CheckBoxField를 기준으로 정렬할 때 정렬 그룹 머리글이 누락되는 이유는 코드가 현재 각 Text
행에 대해 정렬된 열의 값을 결정하기 위해 s 속성만 TableCell
사용하기 때문입니다. CheckBoxFields TableCell
의 경우 Text
속성은 빈 문자열입니다. 대신, 해당 값은 TableCell
컬렉션 내에 있는 CheckBox 웹 컨트롤을 통해 사용할 수 있습니다.
BoundFields 이외의 필드 형식을 처리하려면 변수가 할당된 currentValue
코드를 보강하여 컬렉션에 CheckBox TableCell
Controls
가 있는지 확인해야 합니다. 이 코드를 사용하는 currentValue = gvr.Cells[sortColumnIndex].Text
대신 다음 코드로 바꿉다.
string currentValue = string.Empty;
if (gvr.Cells[sortColumnIndex].Controls.Count > 0)
{
if (gvr.Cells[sortColumnIndex].Controls[0] is CheckBox)
{
if (((CheckBox)gvr.Cells[sortColumnIndex].Controls[0]).Checked)
currentValue = "Yes";
else
currentValue = "No";
}
// ... Add other checks here if using columns with other
// Web controls in them (Calendars, DropDownLists, etc.) ...
}
else
currentValue = gvr.Cells[sortColumnIndex].Text;
이 코드는 현재 행의 정렬된 열을 TableCell
검사하여 컬렉션에 Controls
컨트롤이 있는지 확인합니다. 있는 경우 첫 번째 컨트롤이 CheckBox인 currentValue
경우 변수는 CheckBox의 Checked
속성에 따라 예 또는 아니요로 설정됩니다. 그렇지 않으면 값은 TableCell
s Text
속성에서 가져옵니다. GridView에 있을 수 있는 모든 TemplateFields에 대한 정렬을 처리하도록 이 논리를 복제할 수 있습니다.
위의 코드가 추가되면 중단된 CheckBoxField를 기준으로 정렬할 때 정렬 그룹 헤더가 표시됩니다(그림 7 참조).
그림 7: CheckBoxField를 정렬할 때 그룹 머리글 정렬이 표시됩니다(전체 크기 이미지를 보려면 클릭).
비고
만약에 NULL
제품에 CategoryID
, SupplierID
, UnitPrice
필드의 데이터베이스 값이 있다면, 기본적으로 GridView에서는 이 값들이 빈 문자열로 나타납니다. 즉, NULL
값이 있는 해당 제품에 대해 구분 행이 '범주: 음료'처럼 표시되지만 '범주:' 다음에 이름이 없는 상태로 보입니다. 여기에 값을 표시하려면 BoundFields NullDisplayText
속성을 표시할 텍스트로 설정하거나 구분 기호 행의 Text
속성에 할당 currentValue
할 때 Render 메서드에 조건문을 추가할 수 있습니다.
요약
GridView에는 정렬 인터페이스를 사용자 지정하기 위한 많은 기본 제공 옵션이 포함되어 있지 않습니다. 그러나 약간의 하위 수준 코드를 사용하면 GridView의 컨트롤 계층 구조를 조정하여 보다 사용자 지정된 인터페이스를 만들 수 있습니다. 이 자습서에서는 정렬 가능한 GridView에 대한 정렬 그룹 구분 기호 행을 추가하는 방법을 알아보았습니다. 이 행은 고유 그룹 및 해당 그룹 경계를 보다 쉽게 식별합니다. 사용자 지정된 정렬 인터페이스의 추가 예제는 Scott Guthrie 의 몇 가지 ASP.NET 2.0 GridView 정렬 팁 및 요령 블로그 항목을 확인하세요.
행복한 프로그래밍!
작성자 정보
7개의 ASP/ASP.NET 책의 저자이자 4GuysFromRolla.com 창립자인 Scott Mitchell은 1998년부터 Microsoft 웹 기술을 연구해 왔습니다. Scott은 독립 컨설턴트, 트레이너 및 작가로 일합니다. 그의 최신 책은 Sams Teach Yourself ASP.NET 2.0 in 24 Hours입니다. 그에게 mitchell@4GuysFromRolla.com으로 연락할 수 있습니다.