거의 모든 데이터 프레젠테이션 웹 애플리케이션의 경우 데이터가 표시되는 방식을 구성하고 사용자에게 혼란스러운 느낌을 피하는 것이 주요 목표 중 하나입니다. 페이지당 20개의 레코드를 표시하는 것은 확실히 허용되지만 페이지당 10,000개의 레코드를 표시하면 사용자에게 쉽게 불편을 줄 수 있습니다. 이러한 문제를 해결하는 가장 일반적인 방법은 데이터를 여러 페이지로 분할하여 표시하는 것, 즉 데이터를 페이징하는 것입니다.
1. 개요
ASP.NET 자체에서는 데이터 페이징을 지원하는 컨트롤 중 하나인 DataGrid 페이징 컨트롤만 제공하지만, 인트라넷 환경에서는 DataGrid 페이징 컨트롤에서 제공하는 기능이 제공되지 않는 것 같습니다. 유연한 웹 애플리케이션을 구축하기에 충분합니다. 그 이유 중 하나는 DataGrid 컨트롤이 웹 디자이너가 페이징 컨트롤을 배치할 수 있는 위치와 페이징 컨트롤의 모양에 제한을 가하기 때문입니다. 예를 들어 DataGrid 컨트롤은 페이징 컨트롤의 수직 배치를 허용하지 않습니다. 페이징 기술을 활용할 수 있는 또 다른 컨트롤은 Repeater입니다. 웹 개발자는 Repeater 컨트롤을 사용하여 데이터 표시 방법을 신속하게 구성할 수 있지만 페이징 기능을 사용하려면 개발자가 직접 구현해야 합니다. 데이터 소스는 지속적으로 변경되며 데이터 표시 방법은 매우 다양합니다. 이러한 변화하는 조건에 맞게 페이징 컨트롤을 사용자 지정하는 것은 확실히 시간 낭비입니다. 특정 프레젠테이션 컨트롤에 국한되지 않는 범용 페이징 컨트롤을 구성하면 시간을 크게 절약할 수 있습니다.
탁월한 범용 데이터 제어는 일반 페이징 기능을 제공할 뿐만 아니라 다음과 같은 기능도 제공합니다.
⑴ "홈 페이지", "이전 페이지", "다음 페이지" 및 "마지막 페이지" 페이징 탐색 버튼을 제공합니다.
⑵ 데이터 표시 상황에 따라 상태를 조정합니다. 즉, 데이터 감도가 있습니다. 페이지당 10개의 레코드를 표시하도록 페이징 컨트롤이 설정되어 있지만 실제로는 9개의 레코드만 있는 경우 데이터를 표시하기 위해 여러 페이지로 나눌 때 페이징 컨트롤이 표시되어서는 안 됩니다. 첫 번째 페이지 "페이지" 버튼이 표시되어서는 안 되며, 마지막 페이지의 "다음 페이지" 및 "마지막 페이지" 버튼도 표시되어서는 안 됩니다.
⑶ 특정 데이터 표시 컨트롤에 의존할 수 없습니다.
⑷ 다양한 기존 및 미래 데이터 소스에 적응하는 능력.
⑸ 디스플레이 모드는 구성이 쉽고 다양한 애플리케이션에 쉽게 통합되어야 합니다.
⑹ 페이징이 준비되면 다른 컨트롤에게 알림을 보냅니다.
⑺ 경험이 부족한 웹디자이너도 어려움 없이 사용할 수 있어야 합니다.
⑻ 페이징 정보에 대한 속성 데이터를 제공한다.
현재 위의 기능을 제공하는 일부 상용 컨트롤이 시장에 나와 있지만 모두 가격이 비쌉니다. 많은 개발자에게는 범용 페이징 컨트롤을 직접 구성하는 것이 이상적인 선택입니다.
그림 1은 이 기사에서 사용되는 범용 페이징 컨트롤의 실행 인터페이스를 보여줍니다. 여기서 표시에 사용되는 컨트롤은 Repeater 컨트롤입니다. 페이징 컨트롤은 두 가지 유형의 구성요소, 즉 4개의 탐색 버튼과 페이지 번호 링크 세트로 구성됩니다.
사용자는 쉽게 디스플레이 컨트롤을 변경하고 페이징 컨트롤 자체의 모양을 변경할 수 있습니다. 예를 들어 페이징 컨트롤과 협력하는 디스플레이 컨트롤은 DataGrid 컨트롤로 대체되고 페이지 번호 링크와 네 개의 탐색 버튼이 두 행에 표시됩니다.
ASP.NET은 사용자 지정 웹 컨트롤을 만드는 세 가지 방법인 사용자 컨트롤, 복합 컨트롤, 사용자 지정 컨트롤을 지원합니다. 세 번째 컨트롤 유형인 사용자 지정 컨트롤의 이름은 오해의 소지가 있습니다. 실제로 세 가지 컨트롤 모두 사용자 지정 컨트롤로 간주되어야 합니다. 복합 컨트롤과 Microsoft의 소위 사용자 지정 컨트롤 간의 차이점은 전자에는 CreateChildControls() 메서드가 필요하다는 것입니다. CreateChildControls() 메서드를 사용하면 특정 이벤트에 따라 컨트롤이 다시 그려질 수 있습니다. 이 기사의 범용 호출기에서는 복합 컨트롤을 사용합니다.
다음 UML 시퀀스 다이어그램은 범용 페이징 제어의 일반적인 메커니즘을 간략하게 설명합니다.
우리의 목표는 데이터를 나타내는 컨트롤과 독립된 범용 페이징 컨트롤을 만드는 것이지만 페이징 컨트롤이 데이터에 액세스할 수 있는 방법이 있어야 한다는 것은 분명합니다. Control 클래스에서 상속되는 모든 컨트롤은 DataBinding 이벤트를 제공합니다. 호출기 자체를 DataBinding 이벤트에 대한 수신기로 등록하면 호출기가 데이터에 대해 학습하고 데이터를 수정할 수 있습니다. Control 클래스에서 상속되는 모든 컨트롤에는 이 DataBinding 이벤트가 있으므로 호출기 컨트롤은 특정 데이터 표시 컨트롤에 의존하지 않는다는 목표를 달성합니다. 즉, 호출기 컨트롤은 Control 클래스에서 파생되는 모든 컨트롤에 바인딩될 수 있습니다. 즉, 거의 모든 웹 컨트롤에 바인딩될 수 있습니다.
2. 핵심 기능
프레젠테이션 컨트롤이 DataBinding 이벤트를 트리거하면 페이징 컨트롤이 DataSource 속성을 얻을 수 있습니다. 불행하게도 Microsoft는 IdataSourceProvider와 같은 모든 데이터 바인딩 클래스가 구현하는 인터페이스를 제공하지 않으며 Control 또는 WebControl 클래스에서 상속되는 모든 컨트롤에 DataSource 속성이 있는 것은 아니므로 실행 가능한 유일한 방법인 Control 클래스로 업캐스팅할 필요가 없습니다. 방식은 Reflection API를 통해 DataSoruce 속성을 직접 조작하는 방식입니다. 이벤트 처리기 메서드를 논의하기 전에 이벤트 처리기를 등록하려면 먼저 프레젠테이션 컨트롤에 대한 참조를 얻어야 한다는 점에 유의해야 합니다. 페이징 컨트롤은 간단한 문자열 속성 BindToControl을 노출합니다.
공개 문자열 BindToControl
{
얻다
{
if (_bindcontrol == null)
throw new NullReferenceException("페이징 컨트롤을 사용하기 전에 BindToControl 속성을 설정하여 컨트롤에 바인딩하십시오.");
_bindcontrol을 반환합니다;}
설정{_bindcontrol=값;}
}
이 메서드는 매우 중요하므로 표준 NullReferenceException을 발생시키는 대신 보다 명확한 메시지를 발생시키는 것이 가장 좋습니다. 페이징 컨트롤의 OnInit 메서드에서 프레젠테이션 컨트롤에 대한 참조를 확인합니다. 이 예제에서는 생성자가 아닌 OnInit 이벤트 처리기를 사용하여 JIT 컴파일된 aspx 페이지에 BindToControl이 설정되어 있는지 확인해야 합니다.
보호된 재정의 void OnInit(EventArgs e)
{
_boundcontrol = Parent.FindControl(BindToControl);
BoundControl.DataBinding += new EventHandler(BoundControl_DataBound);
base.OnInit(e);
...
}
프리젠테이션 컨트롤 검색 작업은 페이징 컨트롤의 상위 컨트롤을 검색하면 완료됩니다. 여기서 상위는 페이지 자체입니다. 예를 들어, 페이징 컨트롤이 테이블 컨트롤과 같은 다른 컨트롤에 포함되어 있는 경우 Parent 참조는 실제로 Table 컨트롤에 대한 참조가 됩니다. FindControl 메서드는 현재 컨트롤 컬렉션만 검색하므로 프레젠테이션 컨트롤이 컬렉션에 없으면 검색이 불가능합니다. 보다 안전한 접근 방식은 대상 컨트롤을 찾을 때까지 컨트롤 컬렉션을 반복적으로 검색하는 것입니다.
BoundControl을 찾은 후 페이징 컨트롤을 DataBinding 이벤트에 대한 수신기로 등록합니다. 페이징 컨트롤은 데이터 소스에서 작동하므로 이 이벤트 핸들러가 호출 체인의 마지막 핸들러라는 것이 중요합니다. 그러나 프레젠테이션 컨트롤이 OnInit 이벤트 처리기(기본 동작)에 DataBinding 이벤트 처리기를 등록하는 한 페이징 컨트롤이 데이터 소스를 작동할 때 문제가 없습니다.
DataBound 이벤트 처리기는 프레젠테이션 컨트롤의 DataSource 속성을 가져오는 역할을 합니다.
개인 무효 BoundControl_DataBound(개체 전송자,System.EventArgs e)
{
if(HasParentControlCalledDataBinding) 반환;
유형 유형 = sender.GetType();
_datasource = type.GetProperty("데이터소스");
if (_datasource == null)
throw new NotSupportedException("페이징 컨트롤을 사용하려면 프레젠테이션 컨트롤에 DataSource가 포함되어야 합니다.");
개체 데이터 = _datasource.GetGetMethod().Invoke(sender,null);
_builder = 어댑터[data.GetType()];
if (_builder == null)
throw new NullReferenceException("다음 데이터 소스 유형을 처리하기 위한 적절한 어댑터가 설치되지 않았습니다: "+data.GetType());
_builder.Source = 데이터;
ApplyDataSensitivityRules();
BindParent();
raiseEvent(DataUpdate,this);
}
DataBound에서는 Reflection API를 통해 DataSource 속성을 가져온 다음 실제 데이터 소스에 대한 참조를 반환하려고 합니다. 이제 데이터 소스가 알려졌으므로 페이징 컨트롤은 데이터 소스에서 작동하는 방법도 알아야 합니다. 페이징 컨트롤이 특정 프레젠테이션 컨트롤에 종속되지 않도록 하려면 문제가 훨씬 더 복잡해집니다. 그러나 페이징 컨트롤을 특정 데이터 소스에 종속되게 만들면 유연한 페이징 컨트롤을 디자인하려는 목표가 무산됩니다. .NET에서 제공하는 데이터 소스이든 사용자 지정 데이터 소스이든 페이징 컨트롤이 다양한 데이터 소스를 처리할 수 있도록 하려면 플러그인 아키텍처를 사용해야 합니다.
강력하고 확장 가능한 플러그형 아키텍처를 제공하기 위해 [GoF] Builder 패턴을 사용하여 솔루션을 구축합니다.
그림 4
IDataSourceAdapter 인터페이스는 "플러그"에 해당하는 데이터 작동을 위한 페이징 제어에 필요한 가장 기본적인 요소를 정의합니다.
공용 인터페이스 IDataSourceAdapter
{
int TotalCount{get;}
object GetPagedData(int start,int end);
}
TotalCount 속성은 데이터를 처리하기 전에 데이터 소스에 포함된 총 요소 수를 반환하는 반면, GetPagedData 메서드는 원래 데이터의 하위 집합을 반환합니다. 예를 들어 데이터 소스가 20개 요소를 포함하는 배열이라고 가정하면 페이징 컨트롤이 표시됩니다. 데이터가 페이지당 10개 요소인 경우 첫 번째 페이지의 요소 하위 집합은 배열 요소 0-9이고 두 번째 페이지의 요소 하위 집합은 배열 요소 10-19입니다. DataViewAdapter는 DataView 유형 플러그를 제공합니다.
내부 클래스 DataViewAdapter:IDataSourceAdapter
{
개인 DataView _view;
내부 DataViewAdapter(DataView 보기)
{
_보기 = 보기;
}
공개 int TotalCount
{
get{return (_view == null) ? 0 : _view.Table.Rows.Count;}
}
공용 객체 GetPagedData(int start, int end)
{
DataTable 테이블 = _view.Table.Clone();
for (int i = start;i<=end && i<= TotalCount;i++)
{
table.ImportRow(_view[i-1].Row);
}
반환 테이블;
}
}
DataViewAdapter는 원본 DataTable을 복제하고 원본 DataTable의 데이터를 새 DataTable로 가져오는 IDataSourceAdapter의 GetPagedData 메서드를 구현합니다. 이 클래스의 가시성은 웹 개발자에게 구현 세부 정보를 숨기고 Builder 클래스를 통해 더 간단한 인터페이스를 제공하기 위해 의도적으로 내부로 설정됩니다.
공용 추상 클래스 AdapterBuilder
{
개인 개체 _source
개인 무효 CheckForNull()
{
if (_source == null) throw new NullReferenceException("합법적인 데이터 소스를 제공해야 합니다.");
}
공개 가상 객체 소스
{
얻다
{
CheckForNull();
_source를 반환;}
세트
{
_소스 = 값;
CheckForNull();
}
}
공개 추상 IDataSourceAdapter 어댑터{get;}
}
AdapterBuilder 추상 클래스는 IdataSourceAdapter 유형에 대해 보다 관리하기 쉬운 인터페이스를 제공합니다. 추상화 수준이 높아짐에 따라 더 이상 IdataSourceAdapter를 직접 사용할 필요가 없습니다. 동시에 AdapterBuilder는 데이터를 페이징하기 전에 사전 처리를 수행하기 위한 지침도 제공합니다. 또한 이 Builder는 DataViewAdapter와 같은 실제 구현 클래스를 페이징 컨트롤 사용자에게 투명하게 만듭니다.
public class DataTableAdapterBuilder:AdapterBuilder
{
개인 DataViewAdapter _adapter;
개인 DataViewAdapter ViewAdapter
{
얻다
{
if (_adapter == null)
{
DataTable 테이블 = (DataTable)소스;
_adapter = new DataViewAdapter(table.DefaultView);
}
_어댑터를 반환합니다.
}
}
공개 재정의 IDataSourceAdapter 어댑터
{
얻다
{
ViewAdapter를 반환합니다.
}
}
}
공용 클래스 DataViewAdapterBuilder:AdapterBuilder
{
개인 DataViewAdapter _adapter;
개인 DataViewAdapter ViewAdapter
{
얻다
{ // 지연된 인스턴스화
if (_adapter == null)
{
_adapter = new DataViewAdapter((DataView)소스);
}
_어댑터를 반환합니다.
}
}
공개 재정의 IDataSourceAdapter 어댑터
{
get{return ViewAdapter;}
}
}
DataView 유형과 DataTable 유형은 매우 밀접하게 관련되어 있으므로 일반 DataAdapter를 구성하는 것이 합리적일 수 있습니다. 실제로 DataTable을 처리하는 다른 생성자를 추가하는 것만으로도 충분합니다. 불행하게도 사용자가 DataTable을 처리하기 위해 다른 기능이 필요한 경우 전체 클래스를 대체하거나 상속해야 합니다. 동일한 IdataSourceAdapter를 사용하는 새 Builder를 생성하면 사용자는 어댑터 구현 방법을 더 자유롭게 선택할 수 있습니다.
페이징 컨트롤에서 적절한 Builder 클래스를 찾는 작업은 형식이 안전한 컬렉션에 의해 완료됩니다.
공용 클래스 AdapterCollection:DictionaryBase
{
개인 문자열 GetKey(유형 키)
{
키.전체 이름을 반환합니다.
}
공개 AdapterCollection() {}
publicvoid Add(유형 키, AdapterBuilder 값)
{
Dictionary.Add(GetKey(키),값);
}
publicbo 포함(유형 키)
{
return Dictionary.Contains(GetKey(key));
}
publicvoid 제거(유형 키)
{
Dictionary.Remove(GetKey(키));
}
공용 AdapterBuilder this[유형 키]
{
get{return (AdapterBuilder)Dictionary[GetKey(키)];}
set{Dictionary[GetKey(key)]=값;}
}
}
AdapterCollection은 DataSource 유형에 의존하며 DataSource는 BoundControl_DataBound를 통해 교묘하게 도입됩니다. 여기에 사용된 인덱스 키는 각 유형의 인덱스 키의 고유성을 보장하는 동시에 AdapterCollection에 각 유형에 대해 빌더가 하나만 있는지 확인하는 책임도 할당하는 Type.FullName 메서드입니다.
BoundControl_DataBound
메서드에 Builder 검색을 추가하면 결과는 다음과 같습니다.
{
get{반환 _어댑터;}
}
비공개 bool HasParentControlCalledDataBinding
{
get{return _builder != null;}
}
개인 무효 BoundControl_DataBound(개체 전송자,System.EventArgs e)
{
if(HasParentControlCalledDataBinding) 반환;
유형 유형 = sender.GetType();
_datasource = type.GetProperty("데이터소스");
if (_datasource == null)
throw new NotSupportedException("페이징 컨트롤을 사용하려면 프레젠테이션 컨트롤에 DataSource가 포함되어야 합니다.");
개체 데이터 = _datasource.GetGetMethod().Invoke(sender,null);
_builder = 어댑터[data.GetType()];
if (_builder == null)
throw new NullReferenceException("다음 데이터 소스 유형을 처리하기 위한 적절한 어댑터가 설치되지 않았습니다: "+data.GetType());
_builder.Source = 데이터;
ApplyDataSensitivityRules();
BindParent();
raiseEvent(DataUpdate,this);
}
BoundControl_DataBound 메서드는 HasParentControlCalledDataBinding을 사용하여 Builder가 생성되었는지 확인합니다. 그렇다면 더 이상 적절한 Builder를 찾는 작업을 수행하지 않습니다. Adapters 테이블의 초기화는 생성자
public Pager()
에서 수행됩니다.
{
SelectedPager=new System.Web.UI.WebControls.Style();
UnselectedPager = new System.Web.UI.WebControls.Style();
_adapters = 새로운 AdapterCollection();
_adapters.Add(typeof(DataTable),new DataTableAdapterBuilder());
_adapters.Add(typeof(DataView),new DataViewAdapterBuilder());
}
구현할 마지막 메서드는 데이터를 처리하고 반환하는 데 사용되는 BindParent입니다.
개인 무효 BindParent()
{
_datasource.GetSetMethod().Invoke(BoundControl,
새로운 객체[]{_builder.Adapter.GetPagedData(StartRow,ResultsToShow*CurrentPage)});
}
데이터 처리가 실제로 어댑터에 의해 수행되므로 이 방법은 매우 간단합니다. 이 프로세스가 완료된 후 Reflection API를 다시 사용하지만 이번에는 프레젠테이션 컨트롤의 DataSource 속성을 설정합니다.
3. 인터페이스 디자인
지금까지 페이징 제어의 핵심 기능은 거의 구현되었으나, 적절한 표현 방법이 부족하다면 페이징 제어는 그다지 유용하지 않을 것입니다.
프리젠테이션 방법을 프로그램 로직과 효과적으로 분리하기 위한 가장 좋은 방법은 템플릿을 사용하는 것입니다. 보다 구체적으로 Itemplate 인터페이스를 사용하는 것입니다. 실제로 Microsoft는 템플릿의 힘을 명확하게 이해하고 거의 모든 곳에서, 심지어 페이지 파서 자체에서도 템플릿을 사용합니다. 안타깝게도 템플릿은 사람들이 생각하는 것만큼 단순한 개념이 아니며, 그 본질을 제대로 파악하는 데에도 시간이 걸립니다. 다행히 이 분야에는 많은 정보가 있으므로 여기서는 자세히 설명하지 않겠습니다. 페이징 컨트롤로 돌아가면 홈 페이지, 이전 페이지, 다음 페이지, 마지막 페이지 및 각 페이지 번호의 네 가지 버튼이 있습니다. 4개의 탐색 버튼은 LinkButton 클래스 대신 ImageButton 클래스에서 선택됩니다. 전문적인 웹 디자인 관점에서 볼 때 그래픽 버튼은 단조로운 링크보다 확실히 더 유용합니다.
공개 ImageButton FirstButton{get {return First;}}
공개 ImageButton LastButton{get {return Last;}}
공개 ImageButton PreviousButton{get {return Previous;}}
public ImageButton NextButton{get {return Next;}}
페이지 번호는 데이터 원본의 레코드 수와 각 페이지에 표시되는 레코드 수에 따라 달라지므로 동적으로 생성됩니다. 페이지 번호는 패널에 추가되며 웹 디자이너는 패널을 사용하여 페이지 번호를 표시할 위치를 지정할 수 있습니다. 페이지 번호를 만드는 과정은 나중에 자세히 설명하겠습니다. 이제 사용자가 페이징 컨트롤의 모양을 사용자 지정할 수 있도록 페이징 컨트롤에 대한 템플릿을 제공해야 합니다.
[템플릿 컨테이너(typeof(LayoutContainer))]
공개 ITemplate 레이아웃
{
get{return (_layout;}
설정{_layout =값;}
}
공개 클래스 LayoutContainer:Control,INamingContainer
{
공개레이아웃컨테이너()
{this.ID = "페이지";}
}
LayoutContainer 클래스는 템플릿용 컨테이너를 제공합니다. 일반적으로 이벤트를 처리하고 페이지를 호출할 때 문제를 방지하려면 템플릿 컨테이너에 사용자 정의 ID를 추가하는 것이 항상 좋습니다. 다음 UML 다이어그램은 페이징 제어의 표시 메커니즘을 설명합니다.
그림 5
템플릿을 만드는 첫 번째 단계는 aspx 페이지에서 레이아웃을 정의하는 것입니다.
<레이아웃>
<asp:ImageButton id="첫 번째" Runat="서버" imageUrl="play2L_dis.gif"
AlternateText="홈"></asp:ImageButton>
<asp:ImageButton id="이전" Runat="서버" imageUrl="play2L.gif"
AlternateText="이전 페이지"></asp:ImageButton>
<asp:ImageButton id="다음" Runat="서버" imageUrl="play2.gif"
AlternateText="다음 페이지"></asp:ImageButton>
<asp:ImageButton id="마지막" Runat="서버" imageUrl="play2_dis.gif"
AlternateText="마지막 페이지"></asp:ImageButton>
<asp:Panel id="Pager" Runat="server"></asp:Panel>
</LAYOUT>
이 레이아웃 예시에는 표 등의 형식 요소가 포함되어 있지 않습니다. 물론 실제 애플리케이션에서는 형식 요소를 추가할 수 있으며 추가해야 합니다. 나중에 자세한 지침을 참조하세요.
Itemplate 인터페이스는 템플릿을 구문 분석하고 컨테이너를 바인딩하는 InstantiateIn 메서드 하나만 제공합니다.
개인 무효 InstantiateTemplate()
{
_container = 새로운 LayoutContainer();
Layout.InstantiateIn(_container);
첫 번째 = (ImageButton)_container.FindControl("첫 번째");
이전 = (ImageButton)_container.FindControl("이전");
Next = (ImageButton)_container.FindControl("다음");
마지막 = (ImageButton)_container.FindControl("마지막");
홀더 = (패널)_container.FindControl("페이저");
this.First.Click += new System.Web.UI.ImageClickEventHandler(this.First_Click);
this.Last.Click += new System.Web.UI.ImageClickEventHandler(this.Last_Click);
this.Next.Click += new System.Web.UI.ImageClickEventHandler(this.Next_Click);
this.Previous.Click += new System.Web.UI.ImageClickEventHandler(this.Previous_Click);
}
컨트롤의 InstatiateTemplate 메서드가 수행하는 첫 번째 작업은 템플릿을 인스턴스화하는 것입니다. 즉, Layout.InstantiateIn(_container)을 호출합니다. 컨테이너는 실제로 일종의 컨트롤이며 사용법은 다른 컨트롤과 유사합니다. InstantiateTemplate 메서드는 이 기능을 사용하여 4개의 탐색 단추와 페이지 번호를 보관하는 데 사용되는 패널을 찾습니다. 탐색 버튼은 ID로 찾을 수 있습니다. 이는 페이징 컨트롤에 대한 작은 제한 사항입니다. 탐색 버튼에는 First, Previous, Next 및 Last ID가 지정되어야 합니다. 또한 Panel의 ID는 Pager여야 합니다. 설립하다. 불행하게도 이것이 우리가 선택한 프리젠테이션 메커니즘에 대한 더 나은 접근 방식인 것 같습니다. 그러나 적절한 문서화를 통해 이러한 사소한 제한이 어떤 문제도 일으키지 않기를 바랍니다. 또 다른 대안은 각 버튼이 ImageButton 클래스에서 상속되도록 하여 새로운 유형을 정의하는 것입니다. 각 버튼은 서로 다른 유형이므로 컨테이너에서 재귀 검색을 구현하여 다양한 특정 버튼을 찾을 수 있으므로 버튼 ID를 사용할 필요가 없습니다. 기인하다.
네 개의 버튼을 찾은 후 해당 버튼에 적절한 이벤트 핸들러를 바인딩합니다. 여기에서는 InstantiateTemplate을 언제 호출할지와 같은 중요한 결정을 내려야 합니다. 일반적으로 이 유형의 메서드는 CreateChildControls 메서드에서 호출되어야 합니다. 왜냐하면 CreateChildControls 메서드의 주요 목적은 자식 컨트롤을 만드는 이러한 유형의 작업이기 때문입니다. 페이징 컨트롤은 자식 컨트롤을 수정하지 않으므로 일부 이벤트에 따라 표시 상태를 수정하기 위해 CreateChildControls에서 제공하는 기능이 필요하지 않습니다. 자식 컨트롤이 더 빨리 표시될수록 더 좋으므로 InstantiateTemplate 메서드를 호출하기에 이상적인 위치는 OnInit 이벤트입니다.
보호된 재정의 void OnInit(EventArgs e)
{
_boundcontrol = Parent.FindControl(BindToControl);
BoundControl.DataBinding += new EventHandler(BoundControl_DataBound);
InstantiateTemplate();
Controls.Add(_container);
base.OnInit(e);
}
InstantiateTemplate 메서드 호출 외에도 OnInit 메서드에는 페이징 컨트롤에 컨테이너를 추가하는 또 다른 중요한 작업이 있습니다. 호출기의 컨트롤 컬렉션에 컨테이너를 추가하지 않으면 Render 메서드가 호출되지 않으므로 템플릿이 표시되지 않습니다.
Itemplate 인터페이스를 구현하여 템플릿을 프로그래밍 방식으로 정의할 수도 있으며, 이 기능은 유연성을 높이기 위한 조치일 뿐만 아니라 사용자가 aspx 페이지를 통해 템플릿을 제공하지 않을 때 사용할 기본 템플릿을 제공할 수도 있습니다.
공용 클래스 DefaultPagerLayout:ITemplate
{
개인 ImageButton 다음;
개인 ImageButton 먼저;
개인 ImageButton 마지막;
개인 ImageButton 이전;
개인 패널 호출기;
공개 DefaultPagerLayout()
{
다음 = 새로운 ImageButton();
첫 번째 = 새로운 ImageButton();
마지막 = 새로운 ImageButton();
이전 = 새로운 ImageButton();
Pager = new Panel();
Next.ID="다음"; Next.AlternateText="다음 페이지";Next.ImageUrl="play2.gif";
First.ID="첫 번째"; First.AlternateText="홈";First.ImageUrl="play2L_dis.gif";
Last.ID = "마지막"; Last.AlternateText = "마지막 페이지"; Last.ImageUrl="play2_dis.gif";
Previous.ID="이전"; Previous.AlternateText="이전 페이지";Previous.ImageUrl="play2L.gif";
Pager.ID="호출기";
}
공공 무효 InstantiateIn(제어 제어)
{
컨트롤.컨트롤.클리어();
테이블 테이블 = 새 테이블();
table.BorderWidth = Unit.Pixel(0);
테이블.CellSpacing= 1;
테이블.CellPadding =0;
TableRow 행 = 새 TableRow();
row.VerticalAlign = 수직정렬.상단;
테이블.행.추가(행);
TableCell 셀 = 새로운 TableCell();
cell.HorizontalAlign = 수평정렬.오른쪽;
cell.VerticalAlign = 수직정렬.중간;
cell.Controls.Add(첫 번째);
cell.Controls.Add(이전);
행.Cells.Add(cell);
셀 = 새로운 TableCell();
cell.HorizontalAlign=HorizontalAlign.Center;
cell.Controls.Add(Pager);
행.Cells.Add(cell);
셀 = 새로운 TableCell();
cell.VerticalAlign = 수직정렬.중간;
cell.Controls.Add(다음);
cell.Controls.Add(마지막);
행.Cells.Add(셀);
제어.Controls.Add(테이블);
}
}
DefaultPagerLayout은 모든 탐색 요소를 프로그래밍 방식으로 제공하고 이를 aspx 페이지에 추가하지만 이번에는 탐색 요소가 표준 HTML 테이블 형식으로 지정됩니다. 이제 사용자가 프리젠테이션 템플릿을 제공하지 않으면 프로그램이 자동으로 기본 템플릿을 제공합니다.
[TemplateContainer(typeof(LayoutContainer))]
공개 ITemplate 레이아웃
{
get{return (_layout == null)? 새 DefaultPagerLayout():_layout;}
설정{_layout =값;}
}
각 페이지 번호가 생성되는 과정을 살펴보겠습니다. 페이징 컨트롤은 먼저 일부 속성 값을 결정하고 이러한 속성 값을 사용하여 생성할 페이지 번호 수를 결정해야 합니다.
공개 int 현재 페이지
{
얻다
{
string cur = (string)ViewState["CurrentPage"];
return (cur == string.Empty || cur ==null)? 1 : int.Parse(cur);
}
세트
{
ViewState["CurrentPage"] = value.ToString();}
}
공개 int PagersToShow
{
get{return_results;}
설정{_결과 = 값;}
}
공개 int ResultsToShow
{
get{return _resultsperpage;}
설정{_resultsperpage = 값;}
}
CurrentPage는 실제로 페이지 번호의 ViewState에 현재 페이지를 저장합니다. PagersToShow 메서드에 의해 정의된 속성을 사용하면 사용자는 표시할 페이지 수를 지정할 수 있고, ResultsToShow에 의해 정의된 속성을 사용하면 표시할 레코드 수를 지정할 수 있습니다. 각 페이지마다 기본값은 10입니다.
NumberofPagersToGenerate는 생성되어야 하는 현재 페이지 번호 수를 반환합니다.
개인용 int PagerSequence
{
얻다
{
Convert.ToInt32 반환
(Math.Ceiling((double)CurrentPage/(double)PagersToShow));}
}
비공개 int NumberOfPagersToGenerate
{
get{return PagerSequence*PagersToShow;}
}
비공개 int TotalPagesToShow
{
get{return Convert.ToInt32(Math.Ceiling((double)TotalResults/(double)_resultsperpage));}
}
공개 int TotalResults
{
get{return _builder.Adapter.TotalCount;}
}
TotalPagesToShow 메서드는 사용자가 미리 설정한 ResultsToShow 속성에 따라 조정된 표시할 총 페이지 수를 반환합니다.
ASP.NET에서는 몇 가지 기본 스타일을 정의하지만 페이징 컨트롤 사용자에게는 그다지 실용적이지 않을 수 있습니다. 사용자는 사용자 정의 스타일을 통해 페이징 컨트롤의 모양을 조정할 수 있습니다.
공개 스타일 UnSelectedPagerStyle {get {return UnselectedPager;}}
public Style SelectedPagerStyle {get {return SelectedPager;}}
UnSelectedPagerStyle은 페이지 번호가 선택되지 않았을 때 사용되는 스타일을 제공하고, SelectedPagerStyle은 페이지 번호가 선택되었을 때 사용되는 스타일을 제공합니다.
개인 무효 생성 페이지(WebControl 제어)
{
컨트롤.컨트롤.클리어();
int pager = (PagerSequence-1)* PagersToShow +1
for (;pager<=NumberOfPagersToGenerate && pager<=TotalPagesToShow;pager++)
{
LinkButton 링크 = new LinkButton();
link.Text = pager.ToString();
link.ID = 호출기.ToString();
link.Click += new EventHandler(this.Pager_Click);
if (link.ID.Equals(CurrentPage.ToString()))
link.MergeStyle(SelectedPagerStyle);
또 다른
link.MergeStyle(UnSelectedPagerStyle);
control.Controls.Add(링크);
control.Controls.Add(new LiteralControl(" "));
}
}
개인 무효 생성 페이지()
{
페이지 생성(홀더);
}
generatePagers 메서드는 LinkButton 유형의 버튼인 모든 페이지 번호를 동적으로 생성합니다. 각 페이지 번호의 label 및 ID 속성은 루프를 통해 할당되며 동시에 클릭 이벤트는 해당 이벤트 핸들러에 바인딩됩니다. 마지막으로 페이지 번호가 컨테이너 컨트롤(이 경우 Panel 개체)에 추가됩니다. 버튼 ID는 클릭 이벤트를 트리거한 버튼을 식별하는 역할을 합니다. 다음은 이벤트 핸들러의 정의입니다:
private void Pager_Click(object sender, System.EventArgs e)
{
LinkButton 버튼 = (LinkButton) 발신자;
CurrentPage = int.Parse(button.ID);
raiseEvent(PageChanged, this,new PageChangedEventArgs(CurrentPage,PagedEventInvoker.Pager));
업데이트();
}
private void Next_Click(개체 전송자, System.Web.UI.ImageClickEventArgs e)
{
if (CurrentPage<TotalPagesToShow)
현재페이지++;
raiseEvent(PageChanged, this,new PageChangedEventArgs(CurrentPage,PagedEventInvoker.Next));
업데이트();
}
private void Previous_Click(개체 전송자, System.Web.UI.ImageClickEventArgs e)
{
if (현재 페이지 > 1)
현재페이지--;
raiseEvent(PageChanged, this,new PageChangedEventArgs(CurrentPage,PagedEventInvoker.Previous));
업데이트();
}
개인 무효 First_Click(개체 전송자, System.Web.UI.ImageClickEventArgs e)
{
현재페이지 = 1;
raiseEvent(PageChanged, this,new PageChangedEventArgs(CurrentPage,PagedEventInvoker.First));
업데이트();
}
개인 무효 Last_Click(개체 전송자, System.Web.UI.ImageClickEventArgs e)
{
CurrentPage = TotalPagesToShow;
raiseEvent(PageChanged, this,new PageChangedEventArgs(CurrentPage,PagedEventInvoker.Last));
업데이트();
}
이러한 이벤트 핸들러는 먼저 페이징 컨트롤의 현재 페이지를 변경한 다음 바인딩된 컨트롤을 새로 고치는 점에서 유사합니다.
개인 무효 업데이트()
{
if (!HasParentControlCalledDataBinding) 반환;
ApplyDataSensitivityRules();
BindParent();
BoundControl.DataBind();
}
먼저 페이징 컨트롤은 HasParentControlCalledDataBinding 메서드를 호출하여 필요한 어댑터가 초기화되었는지 여부를 확인합니다. 그렇다면 데이터 표시 조건에 따라 컨트롤을 자동으로 조정하기 위해 이전에 지적한 규칙을 현재 컨트롤에 적용하세요. 이러한 규칙은 BoundControl의 데이터 조건에 따라 페이징 컨트롤이 다르게 동작하도록 합니다. 이러한 규칙은 페이징 제어에 의해 내부적으로 제어되지만 필요한 경우 [GoF] 상태 모드를 사용하여 제어 외부로 쉽게 이동할 수 있습니다.
공개 bool IsDataSensitive
{
get{return _isdatasensitive;}
설정{_isdatasensitive = 값;}
}
비공개 bool IsPagerVisible
{
get{return (TotalPagesToShow != 1) && IsDataSensitive;}
}
비공개 부울 IsPreviousVisible
{
얻다
{
(!IsDataSensitive)를 반환합니까?
(현재페이지 != 1);
}
}
비공개 bool IsNextVisible
{
얻다
{
(!IsDataSensitive)를 반환합니까?
(현재페이지 != TotalPagesToShow);
}
}
개인 무효 ApplyDataSensitivityRules()
{
FirstButton.Visible = IsPreviousVisible;
이전Button.Visible = IsPreviousVisible;
LastButton.Visible = IsNextVisible;
NextButton.Visible = IsNextVisible;
if (IsPagerVisible) 생성페이저();
}
ApplyDataSensitivityRules 메서드는 IsPagerVisible, IsPreviousVisible 및 IsNextVisible과 같은 미리 정의된 규칙을 구현합니다. 기본적으로 페이징 컨트롤에는 이러한 규칙이 활성화되어 있지만 사용자는 IsDataSensitive 속성을 설정하여 이를 끌 수 있습니다.
지금까지 페이징 컨트롤의 표시 부분은 기본적으로 설계되었습니다. 마지막 남은 마무리 작업은 다양한 페이징 제어 이벤트가 발생할 때 사용자가 필요한 조정을 할 수 있도록 여러 이벤트 핸들러를 제공하는 것입니다.
공개 대리자 void PageDelegate(개체 전송자,PageChangedEventArgs e);
공개 열거형 PagedEventInvoker{Next,Previous,First,Last,Pager}
공개 클래스 PageChangedEventArgs:EventArgs
{
개인 int 새 페이지;
비공개 Enum 호출자;
공개 PageChangedEventArgs(int newpage):base()
{
this.newpage = 뉴페이지;
}
공개 PageChangedEventArgs(int newpage,PagedEventInvoker 호출자)
{
this.newpage = 뉴페이지;
this.invoker = 호출자;
}
공개 int NewPage {get{return newpage;}}
공개 Enum EventInvoker{get{반환 호출자;}}
}
페이징 컨트롤은 사용자 지정 이벤트 매개 변수를 반환해야 하므로 전용 PageChangedEventArgs 클래스를 정의합니다. PageChangedEventArgs 클래스는 이벤트를 트리거할 수 있는 컨트롤의 열거자인 PagedEventInvoker 유형을 반환합니다. 사용자 정의 이벤트 매개변수를 처리하기 위해 새로운 대리자 PageDelegate를 정의합니다. 이벤트는 다음 형식으로 정의됩니다.
public event PageDelegate PageChanged;
public event EventHandler DataUpdate;
이벤트에 해당하는 이벤트 리스너가 없으면 ASP.NET은 예외를 발생시킵니다. 페이징 컨트롤은 다음과 같은 raiseEvent 메서드를 정의합니다.
개인 무효 raiseEvent(EventHandler e,객체 보낸 사람)
{
this.RaiseEvent(e,this,null);
}
비공개 무효 raiseEvent(EventHandler e,객체 전송자, PageChangedEventArgs args)
{
if(e!=null)
{
e(발신자,인수);
}
}
개인 무효 raiseEvent(PageDelegate e,객체 발신자)
{
this.RaiseEvent(e,this,null);
}
개인 무효 raiseEvent(PageDelegate e,객체 보낸 사람, PageChangedEventArgs 인수)
{
if(e!=null)
{
e(발신자,인수);
}
}
이벤트 핸들러는 이제 개별 Raiseevent 방법을 호출하여 이벤트를 트리거 할 수 있습니다.
4. 응용 예제이
시점에서, 페이징 제어의 설계가 완료되었으며 공식적으로 사용될 수 있습니다. 페이징 컨트롤을 사용하려면 프레젠테이션 컨트롤에 바인딩하십시오.
<ASP : 리피터 ID = "Repeater"runat = "Server">
<아이템템플릿>
列1:
<%# convert.toString (databinder.eval (container.dataitem, "column1"))%>
<br>
列2:
<%# convert.toString (databinder.eval (container.dataitem, "column2"))%>
<br>
3 : : 열 3 : : 컬럼 3.
<%# convert.toString (databinder.eval (container.dataitem, "column3"))%>
<br>
<시간>
</아이템템플릿>
</ASP : 리피터 >
CC1 : Pager ID = "Pager"resultStoshow = "2"runat = "Server"BindToconTrol = "Repeater">
selectedpagerstyle backcolor = "옐로우" />
</cc1 : Pager >
위의 ASPX 페이지는 페이징 컨트롤을 리피터 컨트롤에 바인딩하고 각 페이지에 표시되는 레코드 수를 2로 설정하고 선택한 페이지 번호의 색상은 노란색이며 기본 레이아웃을 사용합니다 그림 1과 같이. 아래는 그림 2와 같이 페이징 제어를 데이터 그라이드에 바인딩하는 또 다른 예입니다.
ASP : DataGrid id = "Grid"runat = "Server"></asp : datagrid >
CC1 : Pager ID = "PagerGrid"resultStoshow = "2"runat = "Server"bindtocontrol = "Grid">
selectedPagerstyle BackColor = "Red"></selectedPagerstyle >
< 레이아웃 >
ASP : ImageButton id = "First"runat = "Server"imageUrl = "play2l_dis.gif"alternatetext = "home"></asp : imageButton >
ASP : ImageButton id = "이전"runat = "server"imageUrl = "play2l.gif"urtnateText = "이전 페이지"></asp : imageButton >
ASP : ImageButton id = "Next"runat = "Server"imageUrl = "play2.gif"onternatext = "다음 페이지"></asp : imageButton >
< asp : imageButton id = "last"runat = "server"imageUrl = "play2_dis.gif"urtnateText = "마지막 페이지"></asp : imageButton >
ASP : 패널 ID = "Pager"runat = "Server"></ASP : PANER >
</레이아웃 out
</cc1 : Pager >
페이징 컨트롤은 특정 프리젠 테이션 컨트롤에 의존하지 않으며 다른 데이터 소스를 쉽게 처리 할 수 있으며이 기사의 끝에서 소스 코드를 다운로드 할 수 있습니다. 완전한 예.
맞춤형 웹 컨트롤을 개발하는 것은 쉬운 일이 아니지만,이 기술을 마스터하는 것의 이점은 자명합니다 이 기사의 페이징 컨트롤은 기존 및 미래의 성능 요구를 충족시키기 위해 보편적 인 컨트롤을 만드는 한 예일뿐입니다.
http://www.cnblogs.com/niit007/archive/2006/08/13/475501.html