V tomto článku se nejen naučíme vytvářet vlastní kompozitní ovládací prvky, ale také vytvoříme užitečný stránkovací prvek, který je – na rozdíl od stránkovatelného GridView – funkční nezávisle na klientském skriptování a můžeme jej pohodlně použít třeba i pro Repeater.

Motivací k vytvoření prvku bylo vytvoření podobného stránkování, jaké známe z prvku DataGrid, avšak pro použití s prvkem Repeater či DataList ve formuláři a se zachováním přístupnosti – nezávislosti na klientském skriptování.

Pro stránkování jsou uživateli nabídnuty volitelně tlačítka vpřed, zpět a nebo sada prvků ukazujících číslo stránky a zvolenou stránku – čili formulářové prvky, a to proto, aby klasický ASP.NET formulář byl korektně funkční a validní. Je zajištěno také inteligentní vykreslení daného počtu čísel stránek a automaticky doplněn odkaz na „další sadu stránek“, přičemž prvky, které nemají v dané situaci smysl, jsou zakázané (například přechod na předchozí stránku v situaci, kdy jsme na první stránce). Protože je prvek složen z běžných prvků formuláře (tlačítka, radiobuttony), je samozřejmě funkční nezávisle na klientském skriptování.

Pro programátora je důležité, že prvek umí vyvolat událost změny zvolené stránky SelectedPageIndexChanged.

Screenshoty příkladů použití ve formuláři

Stylovaný prvek s podporou klientského skriptování (na konci odkaz na "další sadu" stránek - "tři tečky")
Stylovaný prvek s podporou klientského skriptování (na konci odkaz na „další sadu“ stránek – „tři tečky“)

Stylovaný prvek po přechodu na další sadu stránek (na začátku odkaz na předchozí sadu stránek)
Stylovaný prvek po přechodu na další sadu stránek (na začátku odkaz na předchozí sadu stránek)

Bez stylování (na začátku odkaz na předchozí sadu stránek)
Bez stylování (na začátku odkaz na předchozí sadu stránek)

Bez stylování a podpory JavaScriptu (je k dispozici tlačítko pro potvrzení volby stránky - nefunguje zde AutoPostBack)
Bez stylování a podpory JavaScriptu (je k dispozici tlačítko pro potvrzení volby stránky – nefunguje zde AutoPostBack)

Bez stylování a podpory JavaScriptu - na první stránce, tlačítko "Předchozí" je ve stavu "Disabled"
Bez stylování a podpory JavaScriptu – na první stránce, tlačítko „Předchozí“ je ve stavu „Disabled“

Při tvorbě prvku jsem každopádně vyšel z dříve uveřejněných článků seriálu Serverové ovládací prvky v ASP.NET. Náš prvek je však kompozitní – nejde o rozšíření nějakého jednoho základního prvku (nepřepisujeme metodu Render),ale využijeme více standardních ovládacích prvků a tyto čistě přidáme do kolekce Controls našeho prvku v přepsané metodě CreateChildControls().

Zdědění prvku a vytvoření jeho obsahu

Novou třídu našeho prvku zdědíme od základního prvku Control – a neopomeneme implementovat i rozhraní INamingContainer, jinak by se prvek při vícenásobném zanoření v kolekci ostatních ovládacích prvků stránky choval nevyzpytatelně (musíme tedy zajistit, aby všechny ovládací prvky tohoto typu měly v rámci stránky unikátní hierarchický identifikátor).

Dále nadeklarujeme všechny WebControly, ze kterých má být prvek složen:

public class Pagination : System.Web.UI.Control, INamingContainer
{
  protected System.Web.UI.WebControls.Button btnGo = new System.Web.UI.WebControls.Button();
  protected System.Web.UI.WebControls.Button btnPrev = new System.Web.UI.WebControls.Button();
  protected System.Web.UI.WebControls.Button btnNext = new System.Web.UI.WebControls.Button();
  protected System.Web.UI.WebControls.RadioButtonList rblNumeric = new System.Web.UI.WebControls.RadioButtonList();
  protected System.Web.UI.WebControls.Label lblNextPrev = new System.Web.UI.WebControls.Label();
  protected System.Web.UI.WebControls.Label lblGo = new System.Web.UI.WebControls.Label();
  .
  .
}

V konstruktoru rovnou přednastavíme výchozí vlastnosti použitých prvků:

public Pagination(): base()
{
  lblNextPrev.Visible= false;
  btnPrev.Text = „Prev“;
  btnPrev.CausesValidation = true;
  btnPrev.Enabled = false;
  btnNext.Text = „Next“;
  btnNext.CausesValidation = true;
  btnNext.Enabled = false;
  rblNumeric.Visible = false;
  rblNumeric.AutoPostBack = true;
  rblNumeric.RepeatDirection = RepeatDirection.Horizontal;
  rblNumeric.RepeatLayout = RepeatLayout.Flow;
  rblNumeric.CausesValidation = true;
  lblGo.Visible = false;
  btnGo.Text = „Go“;
  btnGo.CausesValidation = true;
}

Přepsáním metody CreateChildControls() našemu prvku vdechneme všechny prvky, ze kterých se má skládat:

protected override void CreateChildControls()
{
  this.Controls.Add(lblNextPrev);
  lblNextPrev.Controls.Add(btnPrev);
  lblNextPrev.Controls.Add(new LiteralControl(„ “));
  lblNextPrev.Controls.Add(btnNext);
  this.Controls.Add(rblNumeric);
  this.Controls.Add(new LiteralControl(„\n<noscript>“));
  this.Controls.Add(lblGo);
  lblGo.Controls.Add(btnGo);
  this.Controls.Add(new LiteralControl(„</noscript>“));
  this.rblNumeric.SelectedIndexChanged += new System.EventHandler(this.rblNumeric_SelectedIndexChanged);
  this.btnPrev.Click += new System.EventHandler(this.btnPrev_Click);
  this.btnNext.Click += new System.EventHandler(this.btnNext_Click);
}

Využíváme metody Controls.Add() pro přidávání podřízených prvků. Všimněte si, že pro přímé vložení statické části XHTML kódu používáme přímo vytvoření nového prvku LiteralControl – prvky, se kterými nepotřebujeme pracovat programově, nemusíme zvlášť deklarovat a nastavovat jim jakékoli vlastnosti.

Interní proměnné, enumerace a vlastnosti

private Int32 _virtualItemCount = 0;
private Int32 _pageCount = 0;
private Int32 _selectedPageIndex = 0;
private Int32 _pageSize = 10;
private Int32 _pageButtonCount = 10;
private static object EventChangeKey = new object();
private PaginationNumericRepeatDirection _numericRepeatDirection = PaginationNumericRepeatDirection.Horizontal;
private PaginationMode _mode = PaginationMode.AllControls;

Vidíme, že vystačíme s několika číselnými (Int32) proměnnými pro držení „aritmetiky“ prvku. Pro optimalizovanou implementaci události nám postačí jedna proměnná typu Object – v našem případě EventChangeKey – a zveřejnění přístupového bodu pro registraci (a deregistraci) odběratelů události (delegátů):

public event EventHandler SelectedPageIndexChanged
{
  add
  {
    Events.AddHandler(EventChangeKey, value);
  }
  remove
  {
    Events.RemoveHandler(EventChangeKey, value);
  }
}

Pro přepínání vzhledu ještě doplníme potřebné dvě enumerace PaginationMode a PaginationNumericRepeatDirection:

public enum PaginationMode
{
  AllControls,
  NextPrev,
  Numeric
}
public enum PaginationNumericRepeatDirection
{
  Horizontal,
  Vertical
}

Obslužné metody

Obslužné metody zajišťují vnitřní „inteligenci“ prvku – aby prvek sám dovedl korektně vypočítat vše potřebné, proto si alespoň stručně popíšeme, která se o co stará.

private void _calculatePageCount()
{
  if (_virtualItemCount > 0 && _pageSize > 0)
  {
    _pageCount = _virtualItemCount/_pageSize;
    if (_virtualItemCount %_pageSize > 0)
      _pageCount++;
  }
  else
    _pageCount = 0;
}

Metoda _calculatePageCount() přepočítává počet stránek ze zadaného počtu položek na stránce (PageSize) a celkového počtu položek (VirtualItemCount).

private void btnPrev_Click(object sender, System.EventArgs e)
{
  if (_selectedPageIndex > 0)
  {
    _selectedPageIndex–;
    ViewState[„SelectedPageIndex“] = _selectedPageIndex;
    OnSelectedPageIndexChange(new EventArgs());
  }
}
private void btnNext_Click(object sender, System.EventArgs e)
{
  _calculatePageCount();
  if (_selectedPageIndex+1 < _pageCount)
  {
    _selectedPageIndex++;
    ViewState[„SelectedPageIndex“] = _selectedPageIndex;
    OnSelectedPageIndexChange(new EventArgs());
  }
}

Metody btnPrev_Click() a btnNext_Click() jsou obsluhy události kliknutí na tlačítka „vpřed“ a „zpět“ – aktualizují vnitřní hodnotu zvolené stránky _selectedPageIndex, uloží ji do ViewState a vyvolají událost změny zvolené stránky (zavolají metodu OnSelectedPageIndex popsanou dále), aby na ni ostatní navázané obsluhy v aplikaci mohly zareagovat.

private void rblNumeric_SelectedIndexChanged(object sender, System.EventArgs e)
{
  Page.Validate();
  _selectedPageIndex = Int32.Parse(rblNumeric.SelectedValue);
  ViewState[„SelectedPageIndex“] = _selectedPageIndex;
  OnSelectedPageIndexChange(new EventArgs());
}

Metoda rblNumeric_SelectedIndexChanged() je podobná předchozím metodám – z hodnoty uživatelem zvoleného radibuttonu vypočítá index zvolené stránky, uloží do ViewState a také vyvolá událost změny zvolené stránky.

private void Page_Load(object sender, System.EventArgs e)
{
  if (this.Page.IsPostBack)
  {
    if (this.ViewState[„SelectedPageIndex“] != null)
      _selectedPageIndex = (Int32) this.ViewState[„SelectedPageIndex“];
    if (this.ViewState[„VirtualItemCount“] != null)
      _virtualItemCount = (Int32) this.ViewState[„VirtualItemCount“];
  }
}

Metoda Page_Load() je klasická metoda volaná při zahajování zpracování stránky – vidíme zde standardní obnovení interních hodnot z ViewState v případě, že došlo k PostBacku stránky.

Neuralgickým bodem ovládacího prvku je metoda přepisující obsluhu události OnPreRender(), právě v ní totiž dochází k finálnímu přepočtu hodnot důležitých pro zobrazení správného počtu a také stavu ovládacích prvků:

override protected void OnPreRender(EventArgs e)
{
  if (_virtualItemCount > 0)
  {
    _calculatePageCount();
    if (_selectedPageIndex < 1)
      btnPrev.Enabled = false;
    else
      btnPrev.Enabled = true;
    if (_selectedPageIndex+1 < _pageCount)
      btnNext.Enabled = true;
    else
    {
      _selectedPageIndex = _pageCount-1;
      btnNext.Enabled = false;
    }
  }
  else
  {
    btnPrev.Enabled = false;
    btnNext.Enabled = false;
  }
  lblNextPrev.Visible = true;
  if (_mode == PaginationMode.Numeric)
    lblNextPrev.Visible = false;
  rblNumeric.Visible = false;
  rblNumeric.Enabled = false;
  btnGo.Enabled = false;
  lblGo.Visible = false;
  if (_mode != PaginationMode.NextPrev)
  {
    rblNumeric.Visible = true;
    rblNumeric.Items.Clear();
    Int32 currentSetFirstItemValue = ((_selectedPageIndex/_pageButtonCount) * _pageButtonCount);
    Int32 actualItemValue = currentSetFirstItemValue;
    if (actualItemValue >= _pageButtonCount)
      rblNumeric.Items.Add(new ListItem(„…“,(actualItemValue-1).ToString()));
    while (actualItemValue < (currentSetFirstItemValue + _pageButtonCount) && actualItemValue < _pageCount)
    {
      rblNumeric.Items.Add(new ListItem((actualItemValue+1).ToString(),actualItemValue.ToString()));
      actualItemValue++;
    }
    if (actualItemValue < _pageCount)
      rblNumeric.Items.Add(new ListItem(„…“,actualItemValue.ToString()));
    if (rblNumeric.Items.Count > 0)
    {
      if (_selectedPageIndex > 0)
        rblNumeric.SelectedValue = _selectedPageIndex.ToString();
      else
        rblNumeric.SelectedIndex = 0;
      if (rblNumeric.Items.Count > 1)
      {
        btnGo.Enabled = true;
        rblNumeric.Enabled = true;
      }
    }
    else
    {
        rblNumeric.Items.Add(new ListItem(„1″,“0“));
        rblNumeric.SelectedIndex = 0;
    }
    lblGo.Visible = true;
  }
  base.OnPreRender(e);
}

Zde se těsně před vykreslením prvku rozhodne o počtu radiobuttonů pro volbu stránky, ale také o tom, zda se má zobrazit odkaz na předchozí či následující sadu stránek, a také, zda povolit tlačítko Předchozí a Další.

Využívá se již zmíněná metoda _calculatePageCount(), následně se pracuje s hodnotami celkového počtu položek i počtu stránek. Podle nich se rozhodne o tom, jestli jsou nějaké stránky před a po aktuálně zvolené stránce a podle toho se povolí nebo zakážou patřičná tlačítka. Podle nastavení vlastnoti PaginationMode se povolí zobrazení buďto jen tlačítek, nebo jen radiobuttonů, nebo se zobrazí obojí.

Pokud je povoleno zobrazení radibuttonů s čísly stránek, provádí se několik dalších výpočtů. Vypočítává se, zda s aktuální zvolenou stránkou nejsme v části, kdy je potřeba doplnit buttony pro předchozí či následující sadu stránek – například pokud jsme na stránce 13 a je nastaven počet buttonů stránek na 10, pak se nacházíme v sadě stránek 11-20 a je potřeba vykreslit button na předchozí sadu stránek 1-10. Obdobně se stanoví, zda vykreslit button pro následující sadu stránek – tyto buttony jsou vidět na ukázkových screenshotech.

Následně se v cyklu doplní buttony s čísly všech stránek, které ještě zbývají do konce aktuální sady stránek – počítá se zde s již přepočítanou hodnotou celkového počtu stránek. Poté se vyhodnotí, zda vůbec je stránek více než jedna, pokud ano, přepínací radiobutton a doplňkové odesílací tlačítko se povolí, pokud je index zvolené stránky větší než 0, pak upravíme SelectedValue v RadioButtonListu s vykreslenými čísly stránek. Pokud je však k dispozici pouze jedna stránka, vykreslí se jeden radiobutton s hodnotou indexu stránky 0 zobrazující číslo stránky 1.

Nakonec pomocí base.OnPreRender(e); zavoláme původní obsluhu této události, aby bylo korektně zachováno veškeré chování prvku zděděného z prvku Control.

Poslední, neméně důležitou součástí je metoda pro vyvolání obsluhy události – právě pomocí ní mohou uživatelé tohoto prvku zareagovat na vyvolanou událost:

protected virtual void OnSelectedPageIndexChange(EventArgs e)
{
  _calculatePageCount();
  EventHandler eh = (EventHandler) Events[EventChangeKey];
  if (eh != null)
    eh(this, e);
}

Tímto máme ovládací vytvořenu logiku (chování) ovládacího prvku. Aby bylo možné prvek použít v aplikaci, je potřeba zveřejnit jeho vlastnosti – aby s nimi aplikace mohla korektně pracovat. To si ukážeme v pokračování tohoto článku, kde bude i ukázka použití v aplikaci a také kompletní kód ke stažení.

Žádný příspěvek v diskuzi

Odpovědět