Pokročilá práce s daty, vzhled a témata

7. díl - Pokročilá práce s daty, vzhled a témata

Tomáš Herceg       23. 1. 2008       C#, VB.NET, ASP.NET WebForms, HTTP/HTML       12203 zobrazení

V tomto díle seriálu o ASP.NET dokončíme naši aplikaci z minula, ukážeme si, jak editovat záznamy přímo uvnitř komponenty GridView, a kromě dalších nových poznatků si povíme něco málo o rozšiřitelnosti ASP.NET. Napíšeme si totiž vlastní BoundField, který v režimu editace bude hlídat, aby uživatel neodeslal zpět prázdné pole.

Minule jsme začali psát aplikaci pro evidenci hudebních disků, kterou jsme naučili přidávat, upravovat a editovat jednotlivé disky. Dnes přidáme možnost vytvářet a upravovat seznam skladeb na jednotlivých discích.

Přidání seznamu skladeb do editační šablony

Otevřete si projekt z minula a přepněte se na stránku AlbumDetail.aspx do režimu Source. Najděte v komponentě FormView značku EditItemTemplate a smažte v ní první řádek <table>. Místo něj vložte tento kód:

            <div style="float: right; width: 430px">
<asp:SqlDataSource ID="SqlDataSource3" runat="server"></asp:SqlDataSource>

<asp:GridView ID="GridView1" runat="server" DataSourceID="SqlDataSource3">
</asp:GridView>

</div>

<table style="width: 450px">

Tím jsme před tabulku vložili oddíl div (zarovnaný doprava) a jak tomuto oddílu, tak i tabulce jsme nastavili pevnou šířku. Komponentě FormView nastavte ještě hodnotu vlastnosti Width na 900px, abychom měli komponenty správně srovnané.

Jak je vidět, dovnitř našeho oddílu jsem přidal komponenty SqlDataSource3 a GridView1, které budou sloužit k zobrazení a úpravě seznamu skladeb. Nyní se přepněte do režimu Design, abychom mohli datový zdroj nastavit. Proklikejte se do editace šablony EditItemTemplate podle obrázků:

 Vizuální editace šablony (krok 1)

Vizuální editace šablony (krok 2)

Nastavení datového zdroje

V konfiguraci datového zdroje nastavte SQL dotaz takto:

Výběr sloupců a tabulky

Vyberte tabulku Songs a všechny sloupce kromě AlbumId.

Nastavení řazení záznamů

Seřadíme písničky podle jejich čísla vzestupně.

Nastavení podmínky pro dané album

Zobrazíme jenom písničky z alba, které je předáno jako parametr id v adrese URL.

Vygenerování příkazů pro vkládání, úpravy i mazání 

A necháme si vygenerovat i SQL příkazy pro přidávání, úpravy a mazání záznamů.

Proklikejte průvodce až do konce a komponenta GridView se okamžitě přizpůsobí podle zadaného dotazu. Musíme ji samozřejmě trochu upravit, aby vyhovovala našim potřebám. Přepněte se do režimu návrhu a změňte kód komponenty GridView1 takto:

                <asp:GridView ID="GridView1" runat="server" DataSourceID="SqlDataSource3" AutoGenerateColumns="False">
<Columns>
<asp:BoundField DataField="Number" HeaderText="Číslo" SortExpression="Number" ItemStyle-Width="60px" />
<asp:BoundField DataField="Title" HeaderText="Název skladby" SortExpression="Title" ItemStyle-Width="160px" />
<asp:BoundField DataField="Length" DataFormatString="{0:m:ss}" HeaderText="Délka" HtmlEncode="False" SortExpression="Length" ItemStyle-Width="80px" />

</Columns>
</asp:GridView>

Upravování záznamů přímo uvnitř GridView

Budeme chtít umožnit úpravu záznamů a protože tyto záznamy jsou jednoduché, můžeme je nechat uživatele upravit přímo v tabulce. GridView je totiž velice chytrá komponenta, která toto všechno zvládne. Stačí přidat sloupec typu CommandField, který umí přepínat stav komponentu z režimu zobrazení do režimu úprav řádku.

Přepněte se tedy opět do režimu Design a proklikejte se až do šablony EditItemTemplate, kde rozbalte podokno úloh komponenty GridView1 a klikněte na položku Edit Columns.... V dialogovém okně přidejte nový sloupec typu CommandField/Edit, Update, Cancel.

Přidání nového sloupce CommandField

Nyní dialog potvrďte a v režimu Source nahraďte kód komponenty GridView tímto (je to rychlejší než kdybych popisoval, které vlastnosti máte nastavit na které hodnoty).

                <asp:GridView ID="GridView1" runat="server" DataSourceID="SqlDataSource3" AutoGenerateColumns="False" DataKeyNames="SongId">
<Columns>
<asp:BoundField DataField="Number" HeaderText="Číslo" SortExpression="Number" ItemStyle-Width="60px" ControlStyle-Width="40px" />
<asp:BoundField DataField="Title" HeaderText="Název skladby" SortExpression="Title" ItemStyle-Width="160px" ControlStyle-Width="140px" />
<asp:BoundField DataField="Length" DataFormatString="{0:m:ss}" HeaderText="Délka" HtmlEncode="False" SortExpression="Length" ItemStyle-Width="80px" ControlStyle-Width="60px" />
<asp:CommandField ShowEditButton="True" EditText="Upravit" CancelText="Zrušit" UpdateText="Uložit" ItemStyle-Width="80px" />

</Columns>
</asp:GridView>

Prvním třem sloupcům jsem přidal vlastnosti ControlStyle-Width, které definují šířku TextBoxu, který se zobrazí při editaci sloupce. Vlastnost ItemStyle-Width nastavuje šířku celého sloupce.

Sloupci CommandField jsem nastavil vlastnosti EditText, CancelText a UpdateText tak, aby byly česky (pokud nemáte český .NET framework, standardně by byly anglicky).

Pokud si nyní stránku otevřete v prohlížeči, zobrazí se seznam skladeb. Pokud kliknete na některý odkaz Upravit, změní se texty v řádku na TextBoxy, ve kterých můžete hodnoty rovnou upravit.

image

Validace na několik způsobů

Jediná věc, na kterou musíme dát pozor, je správnost vložených dat. Pokud uživatel do prvního sloupce zadá nějaký nesmysl, nastane nám chyba, protože databáze očekává číslo a když dostane místo čísla třeba text, asi z toho nebude příliš nadšená. Tato situace se dá řešit několika způsoby, já bych zde zmínil dva:

  • změnit dotyčný sloupec na TemplateField a vytvořit mu EditItemTemplate s TextBoxem a validátorem, který už správnost dat ohlídá
  • napsat si vlastní třídu EditableNumericBoundField, která se bude chovat úplně stejně jako BoundField s tím rozdílem, že si sama bude kontrolovat, jestli do ní uživatel zadal číslo nebo ne

První způsob je jednodušší a rychlejší, ale pokud bychom v projektu měli tabulek 10 a chtěli například změnit vzhled validátoru, už bychom to museli dělat na více místech, což asi není vhodné. Druhý způsob je o dost těžší a vyžaduje znalosti objektově orientovaného programování, obzvláště pak dědičnost, ale na druhou stranu můžeme náš EditableNumericBoundField, který jsme si sami napsali, použít nejen v této aplikaci kde chceme a kdy chceme, ale lze jej použít i v jiných webových aplikacích.

Vzhledem k tomu, že zde máme sloupce 3 a každý nějakou validaci potřebuje (první musí být číslo, druhý nesmí být prázdný a třetí musí být platný časový údaj nebo prázdná hodnota), napíšeme si vlastní field pro druhý sloupec, pro první a třetí použijeme TemplateField. Přidáme do něj validátor, který nám zkontroluje, jestli je v daném poli číselná hodnota. Přepněte se tedy do režimu Design a proklikejte se až do dialogu pro editaci sloupců komponenty GridView. Označte první sloupec a klikněte na odkaz Convert this field into TemplateField.

V režimu Source změňte kód, který nám vygeneroval průvodce, takto:

                        <asp:TemplateField HeaderText="Číslo" SortExpression="Number" ItemStyle-Width="60px">
<EditItemTemplate>
<asp:TextBox ID="TextBox1" runat="server" Text='<%# Bind("Number") %>' Width="40px"></asp:TextBox>
<asp:CustomValidator ID="CustomValidator1" runat="server" ErrorMessage="*" ValidateEmptyText="true" ControlToValidate="TextBox1"></asp:CustomValidator>
</EditItemTemplate>
<ItemTemplate>
<asp:Label ID="Label1" runat="server" Text='<%# Bind("Number") %>'></asp:Label>
</ItemTemplate>
</asp:TemplateField>

Tím jsme do editační šablony přidali komponentu CustomValidator, což je obecný validátor, ve kterém si přesně můžeme určit kritéria validace. Vlastnost ValidateEmptyText říká, že se má provést kontrola i když jsme nic nezadali. Kritéria pro samotnou validaci musíme již naprogramovat. Nahoře v okně kódu vyberte v prvním rozbalovacím seznamu komponentu CustomValidator a ve druhém její událost ServerValidate. Podle toho, ve kterém programovacím jazyce píšete, nyní vložte dovnitř této procedury tento kód:

Kód v jazyce Visual Basic .NET
        ' zkontrolovat správnost čísla skladby
If String.IsNullOrEmpty(args.Value) Then
args.IsValid = False
Else
Dim num As Integer
If (Integer.TryParse(args.Value, num)) AndAlso (num > 0) Then
args.IsValid = True
Else
args.IsValid = False
End If
End If
Kód v jazyce C#
        // zkontrolovat správnost čísla skladby
if (string.IsNullOrEmpty(args.Value))
args.IsValid =
false;
else
{
int num;
if ((int.TryParse(args.Value, out num)) && (num > 0))
args.IsValid =
true;
else
args.IsValid = false;
}

V této proceduře nejprve zkontrolujeme, jestli není args.Value (text, který kontrolujeme) prázdný. Pokud je, ihned výsledek validace args.IsValid na False, čímž říkáme, že údaj není správný. Pro zjištění jsme použili funkci String.IsNullOrEmpty, která je k tomuto účelu určena. Pokud text prázdný není, vytvoříme si proměnnou num typu Integer a zkusíme zavolat metodu Integer.TryParse, která zkusí převést hodnotu na celé číslo. Pokud to nejde, vrátí False, pokud to jde, převedené číslo uloží do této proměnné a pak ještě testujeme, jestli je toto číslo větší než nula.

Ve VB.NET je velmi důležité použít operátor AndAlso (samotný And nestačí), protože chceme druhou podmínku vyhodnocovat pouze pokud je první splněna (pokud hodnota není číslo, testovat nic nechceme). Operátor And vyhodnotí podmínky obě a pak teprve se rozhodne, jestli jsou obě dvě splněny. AndAlso otestuje první a pokud selže, na druhou se vykašle úplně, protože i když by platila, stejně celý výraz už neplatí.

Podobně ověříme i třetí sloupec s časovým údajem. Změňte jeho kód takto:

                        <asp:TemplateField HeaderText="Délka" SortExpression="Length" ItemStyle-Width="80px">
<EditItemTemplate>
<asp:TextBox ID="TextBox2" runat="server" Text='<%# Bind("Length", "{0:m:ss}") %>' Width="60px"></asp:TextBox>
<asp:CustomValidator ID="CustomValidator2" runat="server" ErrorMessage="*" ValidateEmptyText="false" ControlToValidate="TextBox2" OnServerValidate="CustomValidator2_ServerValidate"></asp:CustomValidator>
</EditItemTemplate>
<ItemTemplate>
<asp:Label ID="Label2" runat="server" Text='<%# Bind("Length", "{0:m:ss}") %>'></asp:Label>
</ItemTemplate>
</asp:TemplateField>

Vytvořte proceduru události ServerValidate komponenty CustomValidator2 a vložte do ní tento kód:

Kód v jazyce Visual Basic .NET
        ' zkontrolovat správnost délky skladby 
Dim length As DateTime
args.IsValid = DateTime.TryParseExact(args.Value,
"m:ss", Nothing, System.Globalization.DateTimeStyles.None, length)
Kód v jazyce C#
        // zkontrolovat správnost délky skladby
DateTime length;
args.IsValid =
DateTime.TryParseExact(args.Value, "m:ss", null, System.Globalization.DateTimeStyles.None, out length);

Pomocí DateTime.TryParseExact zkusíme převést text na časový údaj ve formátu m:ss, čili minuty:sekundy. Pokud se to podaří, args.IsValid bude nastaveno na True.

Nyní můžeme v prohlížeči editaci vyzkoušet, ale má to bohužel jeden háček - úprava časového údaje nebude fungovat správně. Při převodu textu z políčka ASP.NET neví, v jakém formátu data jsou, a pokusí se to uhodnout, ale špatně. Předpokládá totiž čas ve formátu hodiny:minuty a ne minuty:sekundy. Musíme to tedy upravit tak, aby se převod provedl správně. Vytvořte tedy proceduru události RowUpdating komponenty GridView1, která se spustí těsně před uložením upraveného záznamu do databáze. V parametrech této události máme možnost změnit kolekci NewValues, která obsahuje hodnoty, které se po skončení této události předají do parametrů komponenty SqlDataSource1 a spustí se příslušný SQL příkaz. Do této procedury vložte tento kód:

Kód v jazyce Visual Basic .NET
        ' správně převést čas 
If (e.NewValues("Length") IsNot Nothing) AndAlso (Not String.IsNullOrEmpty(e.NewValues("Length").ToString())) Then
e.NewValues("Length") = DateTime.ParseExact(e.NewValues("Length").ToString(), "m:ss", Nothing, System.Globalization.DateTimeStyles.None)
End If
Kód v jazyce C#
        // správně převést čas
if ((e.NewValues["Length"] != null) && (!string.IsNullOrEmpty(e.NewValues["Length"].ToString())))
e.NewValues[
"Length"] = DateTime.ParseExact(e.NewValues["Length"].ToString(), "m:ss", null, System.Globalization.DateTimeStyles.None);

Nyní již editace času bude fungovat tak jak má. Hodnotu typu String v kolekci NewValues jsme rovnou převedli na příslušný čas typu DateTime.

Píšeme vlastní Field s validátorem

Na ASP.NET je skvělá jedna věc - je modulární a rozšiřitelné. Jakákoliv věc, která se nám nelíbí, lze přepsat a změnit tak, aniž bychom museli kvůli tomu měnit zbytek aplikace. Nyní si zkusíme napsat vlastní BoundField, který bude mít při editaci navíc validátor znemožňující vyplnit prázdnou hodnotu. Jak ale takový BoundField napsat? Vcelku snadno - využijeme dědičnosti a vytvoříme třídu odvozenou od třídy BoundField. Upravíme si věci, které se nám nelíbí, případně si přidáme svoje vlastní, a je to. Někdo by si mohl myslet, že k tomu budeme potřebovat zdrojové kódy třídy BoundField, ale ve většině případů je to zbytečné. Navíc úprava, kterou provádíme, je velmi jednoduchá, pouze přidáváme validátor.

Přidejte si tedy do projektu novou položku typu Class - třídu.

Přidání nové položky do projektu

Vytvoření a pojmenování třídy

V dialogovém okně zapište název souboru ValidatedBoundField.vb nebo ValidatedBoundField.cs podle toho, jaký programovací jazyk chcete. Po potvrzení dialogu se nás Visual Studio zeptá, jestli chceme soubor umístit do složky App_Code. Tuto volbu potvrďte. Soubory s kódem, které nejsou součástí konkrétní webové stránky, patří právě do složky App_Code.

Vše, co je nyní v okně s kódem smažte, a vložte tam tento kód. Tím jsme vytvořili třídu v našem vlastním jmenném prostrou MyWebControls. Je důležité zařadit třídu do nějakého prostoru, budeme totiž její umístění potřebovat později. Dále jsme třídu zdědili od třídy BoundField.

Kód v jazyce Visual Basic .NET
Imports System.Web.UI
Imports System.Web.UI.WebControls

Namespace MyWebControls

Public Class ValidatedBoundField
Inherits BoundField

End Class
End
Namespace
Kód v jazyce C#
using System.Web.UI;
using System.Web.UI.WebControls;

namespace MyWebControls
{

public class ValidatedBoundField : BoundField
{

}
}

Protože pole může být součástí složitější validace, bylo by asi vhodné přidat mu vlastnost ValidationGroup, která se nastaví jak TextBoxu, tak i validátoru, pro případ, že bychom potřebovali přiřadit tlačítko do nějaké validační skupiny. Pokud totiž máme na stránce více tlačítek, každé z nich může potřebovat validovat jiné komponenty. Tím, že nastavíme jak tlačítku, tak i komponentám a jejich validátorům hodnotu vlastnosti ValidationGroup na stejnou hodnotu, docílíme toho, že se při kliknutí na tlačítko budou kontrolovat jen komponenty ze stejné skupiny. Přidáme tedy dovnitř třídy vlastnost, její hodnotu si budeme uchovávat v normální privátní proměnné:

Kód v jazyce Visual Basic .NET
        Private _ValidationGroup As String
Public Property ValidationGroup() As String
Get
Return _ValidationGroup
End Get
Set(ByVal value As String)
_ValidationGroup = value
End Set
End Property
Kód v jazyce C#
        private string _ValidationGroup;
public string ValidationGroup
{
get { return _ValidationGroup; }
set { _ValidationGroup = value; }
}

A nyní nám zbývá poslední věc - přidat do metody InitializeCell kód, který zjistí, jestli je uvnitř buňky TextBox a pokud ano, přidá mu pomocí kódu validátor. Metoda InitializeCell se v komponentě BoundField zavolá v okamžiku, kdy je třeba vytvořit komponenty. Protože je naše třída odvozená od třídy BoundField, máme zde tuto proceduru také, stačí ji tedy pomocí klíčového slova override upravit.

Za deklarací vlastnosti udělejte několik volných řádků a pak napište ve VB.NET overrides a v C# jen override. Jakmile stisknete mezerník, ukáže se seznam metod a vlastností, které můžeme přepsat. Vybereme metodu InitializeCell a stiskneme mezerník znovu. Vygeneruje se kostra metody, uvnitř ní bude jeden řádek: MyBase.InitializeCell(cell, cellType, rowState, rowIndex) či base.InitializeCell(cell, cellType, rowState, rowIndex);. Pokud bychom chtěli kód této metody úplně nahradit naším vlastním, tento řádek bychom museli smazat. My jej tam ale necháme a za něj přidáme náš další kód. Tím dosáhneme toho, že se nejprve spustí kód původní metody ve třídě BoundField a potom ten náš. Po přidání našeho kódu bude metoda vypadat takto:

Kód v jazyce Visual Basic .NET
        Public Overrides Sub InitializeCell(ByVal cell As System.Web.UI.WebControls.DataControlFieldCell, ByVal cellType As System.Web.UI.WebControls.DataControlCellType, ByVal rowState As System.Web.UI.WebControls.DataControlRowState, ByVal rowIndex As Integer)
MyBase.InitializeCell(cell, cellType, rowState, rowIndex)

'pokud jsme v buňce s daty
If cellType = DataControlCellType.DataCell Then

'pokud jsme v režimu editace nebo přidávání
If (rowState And DataControlRowState.Edit) <> 0 OrElse (rowState And DataControlRowState.Insert) <> 0 Then

'pokud je první komponenta TextBox, pak přidáme validátor
If cell.Controls.Count > 0 AndAlso TypeOf cell.Controls(0) Is TextBox Then

'našli jsme TextBox
Dim txb As TextBox = CType(cell.Controls(0), TextBox)

'vytvoříme validátor
Dim v As New RequiredFieldValidator()

'nastavit validační skupinu
If Not String.IsNullOrEmpty(_ValidationGroup) Then
v.ValidationGroup = _ValidationGroup
txb.ValidationGroup = _ValidationGroup
End If

'nastavit validátor
v.ErrorMessage = "*"
txb.ID = "TextBox" & DataField
v.ControlToValidate = txb.ID
v.ID = txb.ID &
"Validator"

'přidat validátor za TextBox
cell.Controls.Add(v)
End If
End If
End If
End Sub
Kód v jazyce C#
        public override void InitializeCell(System.Web.UI.WebControls.DataControlFieldCell cell, System.Web.UI.WebControls.DataControlCellType cellType, System.Web.UI.WebControls.DataControlRowState rowState, int rowIndex)
{
base.InitializeCell(cell, cellType, rowState, rowIndex);

//pokud jsme v buňce s daty
if (cellType == DataControlCellType.DataCell)
{

//pokud jsme v režimu editace nebo přidávání
if ((rowState & DataControlRowState.Edit) != 0 || (rowState & DataControlRowState.Insert) != 0)
{

//pokud je první komponenta TextBox, pak přidáme validátor
if (cell.Controls.Count > 0 && cell.Controls[0] is TextBox)
{

//našli jsme TextBox
TextBox txb = (TextBox)cell.Controls[0];

//vytvoříme validátor
RequiredFieldValidator v = new RequiredFieldValidator();

//nastavit validační skupinu
if (!string.IsNullOrEmpty(_ValidationGroup))
{
v.ValidationGroup = _ValidationGroup;
txb.ValidationGroup = _ValidationGroup;
}

//nastavit validátor
v.ErrorMessage = "*";
txb.ID =
"TextBox" + DataField;
v.ControlToValidate = txb.ID;
v.ID = txb.ID +
"Validator";

//přidat validátor za TextBox
cell.Controls.Add(v);
}
}
}
}

A jak naše metoda funguje? Nejprve se zavolá kód původního BoundField. Pak je podmínka, která nás pustí dál jen pokud je typ buňky DataCell, tzn. pokračujeme jen když jsou v naší buňce data (přeskakujeme tím buňky záhlaví a zápatí). Další podmínka otestuje stav řádku - pokud je řádek v režimu Edit nebo Insert (může mít více hodnot zároveň, proto to testujeme takto podivným způsobem). Třetí podmínka zjistí, jestli jsou v buňce nějaké komponenty (kolekce cell.Controls) a pokud ano, zjistí, jestli je první z nich TextBox. Pokud ano, máme vyhráno a vytváříme validátor.

Do proměnné txb si uložíme první prvek z kolekce Controls a přetypujeme jej na typ TextBox. Kolekce Controls může totiž uchovávat všechny komponenty, které jsou odvozeny od typu Control. Abychom však viděli vlastnosti TextBoxu, musíme provést to přetypování. Na dalším řádku vytvoříme nový RequiredFieldValidator, tedy validátor, který hlídá, aby políčko nebylo prázdné.

Pokud jsme nastavili hodnotu vlastnosti ValidationGroup, pak ji nastavíme jak TextBoxu, tak i novému validátoru. Dále nastavíme validátoru vlastnost ErrorMessage na hodnotu *, takže pokud textové pole nebude vyplněno správně, ukáže se právě tato hvězdička.

Abychom mohli propojit TextBox a validátor, potřebujeme TextBoxu přiřadit nějaké ID, které by bylo unikátní (aby žádná jiná komponenta uvnitř GridView neměla toto ID stejné). Dostatečně unikátní název by měl být TextBox + název sloupce v databázi, ten je v rámci tabulky unikátní. Validátoru tedy nastavíme potřebné ID do vlastnosti ControlToValidate a nakonec pojmenujeme i validátor a to tak, že vezmeme ID TextBoxu a přidáme za něj slovo Validator.

Na závěr do kolekce komponent v buňce přidáme i náš vytvořený validátor, který se již o zbytek funkcionality postará sám. To je v zásadě vše. Je jasné, že bychom mohli přidat do komponenty ještě hromadu dalších vlastností, mohli bychom validovat i čísla, regulární výrazy atd., ale to už je nad rámec tohoto článku. Pokud chápete princip, jakým jsme tento vlastní BoundField vytvořili, jistě si jej budete moci rozšířit sami. Pokud ne, nastudujte si něco o objektově orientovaném programování, určitě se vám to bude ještě hodit.

Použití vlastní komponenty ve stránce

Abychom mohli naši komponentu použít ve stránce AlbumDetail.aspx, musíme nahoru přidat direktivu Register, které řekneme, že vše, co bude začínat prefixem my má hledat v namespace MyWebControls, kde je naše komponenta.

<%@ Register Namespace="MyWebControls" TagPrefix="my" %>

Tento řádek tedy přidejte úplně nahoru do stránky AlbumDetail.aspx, hned za první direktivu Page.

Nyní najděte opět kód komponenty GridView1 a skoro naposledy jej změňte takto:

                <asp:GridView ID="GridView1" runat="server" DataSourceID="SqlDataSource3" DataKeyNames="SongId" AutoGenerateColumns="False" OnRowUpdating="GridView1_RowUpdating">
<Columns>
<asp:TemplateField HeaderText="Číslo" SortExpression="Number" ItemStyle-Width="60px">
<EditItemTemplate>
<asp:TextBox ID="TextBox1" runat="server" Text='<%# Bind("Number") %>' Width="40px"></asp:TextBox>
<asp:CustomValidator ID="CustomValidator1" runat="server" ErrorMessage="*" ValidateEmptyText="true" ControlToValidate="TextBox1" OnServerValidate="CustomValidator1_ServerValidate"></asp:CustomValidator>
</EditItemTemplate>
<ItemTemplate>
<asp:Label ID="Label1" runat="server" Text='<%# Bind("Number") %>'></asp:Label>
</ItemTemplate>
</asp:TemplateField>

<my:ValidatedBoundField DataField="Title" HeaderText="N&#225;zev skladby" SortExpression="Title" ItemStyle-Width="160px" ControlStyle-Width="140px" />

<asp:TemplateField HeaderText="Délka" SortExpression="Length" ItemStyle-Width="80px">
<EditItemTemplate>
<asp:TextBox ID="TextBox2" runat="server" Text='<%# Bind("Length", "{0:m:ss}") %>' Width="60px"></asp:TextBox>
<asp:CustomValidator ID="CustomValidator2" runat="server" ErrorMessage="*" ValidateEmptyText="false" ControlToValidate="TextBox2" OnServerValidate="CustomValidator2_ServerValidate"></asp:CustomValidator>
</EditItemTemplate>
<ItemTemplate>
<asp:Label ID="Label2" runat="server" Text='<%# Bind("Length", "{0:m:ss}") %>'></asp:Label>
</ItemTemplate>
</asp:TemplateField>

<asp:CommandField ShowEditButton="True" EditText="Upravit" CancelText="Zrušit" UpdateText="Uložit" ItemStyle-Width="80px" />
<asp:CommandField ShowDeleteButton="True" DeleteText="Smazat" ItemStyle-Width="50px" />
</Columns>
</asp:GridView>

Všimněte si, že místo složitého TemplateFieldu jsme akorát přidali náš ValidateBoundField, který je na jeden řádek a dělá skoro to samé, co složitý a dlouhý TemplateField. Pokud máme tabulku jednu, je vytváření vlastního fieldu zbytečně složité a zdlouhavé, pokud ale v projektu máme desítky tabulek, je určitě lepší jednou ho napsat a pak jej všude používat.

Mazání skladeb

Mazání záznamů je víceméně hotové, protože jsem záměrně přidal do minulé ukázky jeden sloupec typu CommandField, který umí záznamy mazat, stačí mu nastavit vlastnost ShowDeleteButton na True. Jediná funkce, která by zde byla nasnadě, je dialogové okno, které se uživatele zeptá, jestli záznam opravdu chce smazat. Stalo se mi již několikrát, že se uživatlé přehlédli a smazali si něco, co neměli. S dialogovým oknem se jim to jen tak nestane.

Nehodlám tím ale zbytečně prodlužovat článek, klidně si sami zkuste vytovořit vlastní field odvozený od CommandField, najít tam komponentu LinkButton (případně Button nebo ImageButton, to záleží na nastavení vlastnosti ButtonType) a nastavit jí hodnotu vlastnosti OnClientClick na "javascript: return confirm('Opravdu chcete tento záznam smazat?');", aby se v prohlížeči před kliknutím zobrazila hláška? A zkuste to ještě tak, že tomuto CommandFieldu přidáte vlastnost Question, kde půjde nastavit text této otázky. A nezapomeňte, že pokud bude hodnota této vlastnosti obsahovat apostrof, tak to nebude fungovat!

Přidávání skladeb

Pro přidávání skladeb k albu použijeme ještě komponentu FormView. Její kód bude vypadat takto, takže jej přidejte hned za náš GridView:

                <asp:FormView ID="FormView2" runat="server" DataSourceID="SqlDataSource3" DefaultMode="Insert" OnItemInserting="FormView2_ItemInserting">
<InsertItemTemplate>
<table>
<tr>
<td>
<asp:TextBox ID="TextBox1" runat="server" Text='<%# Bind("Number") %>' Width="40px" ValidationGroup="InsertSong"></asp:TextBox>
<asp:CustomValidator ID="CustomValidator1" runat="server" ErrorMessage="*" ValidateEmptyText="true" ControlToValidate="TextBox1" ValidationGroup="InsertSong" OnServerValidate="CustomValidator1_ServerValidate"></asp:CustomValidator>
</td>
<td>
<asp:TextBox ID="TextBox3" runat="server" Text='<%# Bind("Title") %>' Width="140px" ValidationGroup="InsertSong"></asp:TextBox>
<asp:RequiredFieldValidator ID="RequiredFieldValidator3" runat="server" ErrorMessage="*" ControlToValidate="TextBox3" ValidationGroup="InsertSong"></asp:RequiredFieldValidator>
</td>
<td>
<asp:TextBox ID="TextBox2" runat="server" Text='<%# Bind("Length", "{0:m:ss}") %>' Width="60px" ValidationGroup="InsertSong"></asp:TextBox>
<asp:CustomValidator ID="CustomValidator2" runat="server" ErrorMessage="*" ValidateEmptyText="false" ControlToValidate="TextBox2" ValidationGroup="InsertSong" OnServerValidate="CustomValidator2_ServerValidate"></asp:CustomValidator>
</td>
<td>
<asp:LinkButton ID="LinkButton1" runat="server" CommandName="Insert" ValidationGroup="InsertSong">Přidat</asp:LinkButton>
</td>
</tr>
</table>
</InsertItemTemplate>
</asp:FormView>

Pro první a třetí políčko jsem použil stejný kód, který je v jejich šablonách EditItemTemplate v komponentě GridView. Oba dva validátory CustomValidator jsou napojeny na ty samé procedury, takže budou validovat stejně jako uvnitř GridView. Do druhého sloupce jsem přidal normální RequiredFieldValidator a na konec jsem přidal tlačítko LinkButton, které musí mít nastavenou vlastnost ItemCommand na hodnotu Insert, aby FormView věděl, že kliknutím na toto tlačítko se má přidat záznam. Máme zde ovšem ještě 2 problémy, které musíme vyřešit.

Když se pozorně podíváme na InsertCommand v komponentě SqlDataSource3, vidíme, že se nevyplňuje sloupec AlbumId, což zřejmě skončí s chybou, protože nebude jasné, do kterého alba novou písničku zařadit. Upravte tedy kód SqlDataSource3 takto:

                <asp:SqlDataSource ID="SqlDataSource3" runat="server" ConnectionString="<%$ ConnectionStrings:ConnectionString %>" 
DeleteCommand="DELETE FROM [Songs] WHERE [SongId] = @SongId"
InsertCommand="INSERT INTO [Songs] ([AlbumId], [Number], [Title], [Length]) VALUES (@AlbumId, @Number, @Title, @Length)"
SelectCommand="SELECT [SongId], [Number], [Title], [Length] FROM [Songs] WHERE ([AlbumId] = @AlbumId) ORDER BY [Number]"
UpdateCommand="UPDATE [Songs] SET [Number] = @Number, [Title] = @Title, [Length] = @Length WHERE [SongId] = @SongId">
<SelectParameters>
<asp:QueryStringParameter Name="AlbumId" QueryStringField="id" Type="Int32" />
</SelectParameters>
<DeleteParameters>
<asp:Parameter Name="SongId" Type="Int32" />
</DeleteParameters>
<UpdateParameters>
<asp:Parameter Name="Number" Type="Int32" />
<asp:Parameter Name="Title" Type="String" />
<asp:Parameter Name="Length" Type="DateTime" />
<asp:Parameter Name="SongId" Type="Int32" />
</UpdateParameters>
<InsertParameters>
<asp:Parameter Name="Number" Type="Int32" />
<asp:Parameter Name="Title" Type="String" />
<asp:Parameter Name="Length" Type="DateTime" />
<asp:QueryStringParameter Name="AlbumId" QueryStringField="id" Type="Int32" />
</InsertParameters>
</asp:SqlDataSource>

Do vlastnosti InsertCommand jsem akorát přidal sloupec AlbumId a do sekce InsertParameters jsem přidal parametr AlbumId, jehož hodnota se vytáhne z parametru id v adrese stránky (z QueryStringu). To by byla první věc.

Jako druhou věc musíme vyřešit opět ten samý problém s převodem délky skladby, vytvořte tedy proceduru události ItemInserting komponenty FormView. Tato událost se spustí těsně před přidáním záznamu do databáze. Stačí stejným způsobem jako u GridView upravit hodnotu v kolekci args.Values.

Kód v jazyce Visual Basic .NET
        ' správně převést čas 
If (e.Values("Length") IsNot Nothing) AndAlso (Not String.IsNullOrEmpty(e.Values("Length").ToString())) Then
e.Values(
"Length") = DateTime.ParseExact(e.Values("Length").ToString(), "m:ss", Nothing, System.Globalization.DateTimeStyles.None)
End If
Kód v jazyce C#
        // správně převést čas
if ((e.Values["Length"] != null) && (!string.IsNullOrEmpty(e.Values["Length"].ToString())))
e.Values[
"Length"] = DateTime.ParseExact(e.Values["Length"].ToString(), "m:ss", null, System.Globalization.DateTimeStyles.None);

A to je celé přidávání záznamů, teď by měly jít přidávat, upravovat i mazat jednotlivé skladby u každého alba.

Dokončení aplikační logiky

Aplikace má ještě jednu nepříjemnou vlastnost, kterou vyřeším až příště. Pokud přidáváme nové album, nemůžeme přidávat skladby. To jde až při úpravě, což je na jednu stranu praktické pro nás (nemusíme řešit, jak přidat skladby do alba, které ještě v databázi není), ale nepraktické pro uživatele. Musí nejprve přidat album, pak jej na úvodní stránce najít, zobrazit si jeho detail a kliknout na tlačítko Upravit, aby mohl přidávat skladby. Ale můžeme to udělat tak, že po přidání alba bude uživatel automaticky přesměrován hned na detail tohoto alba v režimu úprav.

V příštím díle si také podrobně ukážeme, jak pracovat se vzhledem ASP.NET aplikací a jak jednoduše a snadno vytvořit skiny pro komponenty. Pro dnešek je to vše.

 

hodnocení článku

2 bodů / 2 hlasů       Hodnotit mohou jen registrované uživatelé.

 

Mohlo by vás také zajímat

Práce s časovými pásmy a letním časem v aplikaci a databázi - díl 3.: DateTimeOffset v .NET Frameworku

DateTimeOffset je méně využívanou alternativou struktury DateTime v .NET Frameworku. Navíc dovoluje ukládat časové pásmo a pohodlně s ním pracovat.

Co se to děje s .NETem

Jeden antipattern, který dokáže asynchronní programování pořádně znepříjemnit

 

 

Nový příspěvek

 

Diskuse: Pokročilá práce s daty, vzhled a témata

Dobrý večer,

Přidal jsem řádek

<%@ Register Namespace="MyWebControls" TagPrefix="my" %>

a na to mi to vyhodí chybu

Warning 2 Namespace or type specified in the Imports 'MyWebControls' doesn't contain any public member or cannot be found. Make sure the namespace or the type is defined and contains at least one public member. Make sure the imported element name doesn't use any aliases. E:\Documents and Settings\Petr\dokumenty\visual studio 2010\Projects\CDs\CDs\AlbumDetail.aspx 1 1 CDs

Nevíte někdo co s tím?

PetrS

nahlásit spamnahlásit spam 0 odpovědětodpovědět

Diskuse: Pokročilá práce s daty, vzhled a témata

Dobrý den,

prosím o radu, napsal jste.."Nahoře v okně kódu vyberte v prvním rozbalovacím seznamu komponentu CustomValidator a ve druhém její událost ServerValidate." ... ale nevidim tam takovou komponentu, jen dokument nebo windows resp. nevim jak se dostat k udalostem. děkuji

nahlásit spamnahlásit spam 0 odpovědětodpovědět

Mám stejný problém :-(

nahlásit spamnahlásit spam 0 odpovědětodpovědět

Kod si dopiste rucne, jakje u

<asp:CustomValidator ID="CustomValidator1" runat="server" ErrorMessage="*" ValidateEmptyText="true" ControlToValidate="TextBox1" OnServerValidate="CustomValidator1_ServerValidate"></asp:CustomValidator>

tak si dopiste metodu: protected void CustomValidator1_ServerValidate(object sender, ServerValidateEventArgs args)

a u: <asp:CustomValidator ID="CustomValidator2" runat="server" ErrorMessage="*" ValidateEmptyText="false" ControlToValidate="TextBox2" OnServerValidate="CustomValidator2_ServerValidate"></asp:CustomValidator>

zase metodu: protected void CustomValidator2_ServerValidate(object sender, ServerValidateEventArgs args)

Mel jsem stejny problem.

nahlásit spamnahlásit spam 1 / 1 odpovědětodpovědět

A pro VBéčkáře jak jsme zvyklí:

Protected Sub CustomValidator1_ServerValidate(ByVal sender As Object, ByVal e As ServerValidateEventArgs)
        ' zkontrolovat správnost čísla skladby
        If String.IsNullOrEmpty(e.Value) Then
            e.IsValid = False
        Else
            Dim num As Integer
            If (Integer.TryParse(e.Value, num)) AndAlso (num > 0) Then
                e.IsValid = True
            Else
                e.IsValid = False
            End If
        End If
    End Sub

nahlásit spamnahlásit spam 1 / 1 odpovědětodpovědět

Diskuse: Pokročilá práce s daty, vzhled a témata

Kdy je vhodné ( např. pro zobrazení a editaci dat typu Master/detail ) použít přímo SQLDataSource jak je popsáno v příkladu a kdy ObjectDataSource připojený na DataSet ( podobný příklad zobrazení s použitím DataSet je popsán na http://www.asp.net/learn/videos/video-49... ) ? Chápu správně že DataSet vytváří obraz databáze a tedy se dále pracuje s obrazem a pomocí SQLDataSource se pracuje s daty přímo z databáze ?

Ještě jeden dotaz - jde pomocí DataSet ( v případě že je to tedy vhodné ) udělat podobný příklad zobrazení dat ( jak je popsán v článku Album,Žánr ) a hlavně editaci údajů z více tabulek ( při použití JOIN mi nejde udělat INSERT ..).

Je nějaký zdroj informací jak se používají v DataSetu ( mezi adaptéry ) relace ( např. zda je to lepší než relace definované v SQLDataSource pomocí JOIN ) ?

Díky

nahlásit spamnahlásit spam 0 odpovědětodpovědět

SqlDataSource bych doporučoval jen pro menší aplikace, kde nemá cenu dělat nějakou složitější datovou vrstvu. Jinak DataSety bych obecně zavrhl, ObjectDataSource také (je dost pomalý), a nechal bych si nějakou "datovou vrstvu" vygenerovat přes nějaký ORM mapper (Entity framework, Linq to SQL) a bindoval data buď rovnou do komponent (bez data source) anebo přes LinqDataSource.

SqlDataSource v tutoriálech používám pro jeho jednoduchost - na pochopení principů to stačí, psát takhle velké aplikace by se nemělo.

nahlásit spamnahlásit spam 0 odpovědětodpovědět

Díky.

Obecně se tedy dá říci že pro 4-5 propojených tabulek nemá cenu dělat datovou vrstvu a je nejlepší využít SQLdatasource ?

Jaká rizika hrozí při použití SQLdatasource u složitějších vazeb ( nebo spíše při editaci údajů více uživateli současně) ?

Relace je u jednoduchých databází možné dělat jen na úrovni logiky ( ASP.NET - ...JOIN) , nebo je vždy vhodné je udělat i v SQL serveru ( moc mi není jasné kde se zabezpečuje vlastně např. promazání svázaných dat... zda to dělá dotaz nebo sám SQL server .

Omlouvám se za možná hloupé otázky ( věřím že máte jinou práci než odpovídat na podobné ).

Váš web je moc super (myslím, že jeden z nejlepších v této oblasti- kam se hrabe www.asp.net)

Ještě jednou díky

:-)

nahlásit spamnahlásit spam 0 odpovědětodpovědět

Dalo by se to tak říct. SqlDataSource by neměl mít problém při editaci více uživateli současně (ve většině běžných aplikací stačí přístup poslední upravující vyhrává). Když máte větší projekt, tak je víceméně nutnost mít datovou vrstvu, kde máte definované nějaké složitější procesy (založení objednávky, což je třeba insert do více tabulek, jsou tam nutné transakce atd., stará se to o cacheování, můžete tam dělat nějaké dodatečné validace atd.). Navíc datovou vrstvu můžete využívat jak z webové aplikace, tak i z nějakého těžkého klienta atd., může být sdílená.

Provázání tabulek je "nutné" již na úrovni databáze, pomocí cizích klíčů. Těm se dá říci ON DELETE CASCADE (pokud smažu objednávku, smažou se i její položky, které jsou k ní přidružené) atd., více informací je v článcích o SQL, které tady máme.

Jinak díky za pochvalu webu a neomlouvejte se za hloupé otázky, ty vaše zrovna nejsou hloupé ani trochu.

nahlásit spamnahlásit spam 1 / 1 odpovědětodpovědět

Díky.

Obecně se tedy dá říci že pro 4-5 propojených tabulek nemá cenu dělat datovou vrstvu a je nejlepší využít SQLdatasource ?

Jaká rizika hrozí při použití SQLdatasource u složitějších vazeb ( nebo spíše při editaci údajů více uživateli současně) ?

Relace je u jednoduchých databází možné dělat jen na úrovni logiky ( ASP.NET - ...JOIN) , nebo je vždy vhodné je udělat i v SQL serveru ( moc mi není jasné kde se zabezpečuje vlastně např. promazání svázaných dat... zda to dělá dotaz nebo sám SQL server .

Omlouvám se za možná hloupé otázky ( věřím že máte jinou práci než odpovídat na podobné ).

Váš web je moc super (myslím, že jeden z nejlepších v této oblasti- kam se hrabe www.asp.net)

Ještě jednou díky

:-)

nahlásit spamnahlásit spam 0 odpovědětodpovědět

Diskuse: Pokročilá práce s daty, vzhled a témata

Zdravím!

Mám problém s odesíláním FormView, když chci odesílat data z DropDown, který je v UpdatePanelu.

V DropDownu totiž potřebuji měnit data podle jiného Dropdownu. A když potom dám

<asp:DropDownList ID="DropDownList2" runat="server" SelectedValue='<%#Bind("okres") %>'>

spolu s prvním DropDownem do UpdatePanelu, tak mi při odeslílání dojde k chybě (v SQL nesmí být sloupec NULL) Nevíte co s tím?

A potom mám další problém, že když nemám DropDowny v tom UpdatePanelu, tak se k nim nedostanu v kódu obsluhy událostí.

nahlásit spamnahlásit spam 0 odpovědětodpovědět

Jinak kód pro "naplnění" DropDownu je:

    protected void DropDownList2_Load(object sender, EventArgs e)
    {
        if (!this.IsPostBack)
        {
            SqlDataSource3.SelectCommand = "SELECT [id], [nazev] FROM [okresy] "
                + "WHERE (kraj IN (SELECT MIN(id) AS value FROM kraje))";
            DropDownList2.DataSourceID = "SqlDataSource3";
            DropDownList2.DataTextField = "nazev";
            DropDownList2.DataValueField = "id";
        }
    }


    protected void DropDownList1_TextChanged(object sender, EventArgs e)
    {
        SqlDataSource3.SelectCommand = "SELECT [id], [nazev] FROM [okresy] "
            + "WHERE (kraj =" + DropDownList1.Items[DropDownList1.SelectedIndex].Value + ")";
        DropDownList2.DataSourceID = "SqlDataSource3";
        DropDownList2.DataTextField = "nazev";
        DropDownList2.DataValueField = "id";
    }

nahlásit spamnahlásit spam -1 / 1 odpovědětodpovědět

Diskuse: Pokročilá práce s daty, vzhled a témata

Ahoj,

potřebuji za běhu aplikace vytvořit novou tabulku do databáze. Zatím jsem ale nenašel, jak by se to dalo udělat. Objekty DataSet, či GridView mají funkce pro změnu již načtených tabulek, o přidávání tabulek však není nikde ani zmínka.

Za odpověď předem díky.

nahlásit spamnahlásit spam 0 odpovědětodpovědět

Budete asi muset poslat SQL příkaz CREATE TABLE ... databázi normálně kódem, viz. článek Komunikace s MS SQL databází v sekci Databáze. DataSet na tohle funkci nemá.

Jen tak mimochodem, vytvářet v databázi tabulky za běhu aplikace je v 90% případů zbytečné. Pokud neděláte aplikaci pro vytváření a správu databází, pak je to špatně pochopená logika práce s databázemi.

nahlásit spamnahlásit spam 1 / 1 odpovědětodpovědět

Diskuse: Pokročilá práce s daty, vzhled a témata

Mám problém, nevím jestli je to u mě, nebo někde jinde, ale nechce se mi zkompilovat ValidatedBoundField v jazyce C# a to z tohoto důvodu:

'System.Web.UI.Control.Controls' cannot be used like a method.

jedná se o tuto část kódu:

//pokud je první komponenta TextBox, pak přidáme validátor

if (cell.Controls.Count > 0 && cell.Controls(0) is TextBox)

{

//našli jsme TextBox

TextBox txb = (TextBox)cell.Controls(0);

jako chyba jsou podtrhnuty Controls za cell. ve dvou případech výše uvedeného kódu.

Děkuji za pomoc.

Odra Vychodil

nahlásit spamnahlásit spam 6 / 6 odpovědětodpovědět

Omlouvám se, měl jsem v článku chybu. Ta nula má být v hranatých závorkách, Controls je totiž kolekce.

nahlásit spamnahlásit spam 1 / 1 odpovědětodpovědět
                       
Nadpis:
Antispam: Komu se občas házejí perly?
Příspěvek bude publikován pod identitou   anonym.

Nyní zakládáte pod článkem nové diskusní vlákno.
Pokud chcete reagovat na jiný příspěvek, klikněte na tlačítko "Odpovědět" u některého diskusního příspěvku.

Nyní odpovídáte na příspěvek pod článkem. Nebo chcete raději založit nové vlákno?

 

  • Administrátoři si vyhrazují právo komentáře upravovat či mazat bez udání důvodu.
    Mazány budou zejména komentáře obsahující vulgarity nebo porušující pravidla publikování.
  • Pokud nejste zaregistrováni, Vaše IP adresa bude zveřejněna. Pokud s tímto nesouhlasíte, příspěvek neodesílejte.

přihlásit pomocí externího účtu

přihlásit pomocí jména a hesla

Uživatel:  
Heslo:  

zapomenuté heslo

 

založit nový uživatelský účet

zaregistrujte se

 
zavřít

Nahlásit spam

Opravdu chcete tento příspěvek nahlásit pro porušování pravidel fóra?

Nahlásit Zrušit

Chyba

zavřít

feedback