Thursday, 20 July 2006
Sloupec v GridView s potvrzením vymazání záznamu (na klientovi) II - sloupec se šablonou
V prvním spotu jsem ukázal, jak napsat pro GridView nový sloupec zobrazující tlačítko pro vymazání záznamu. Po stisknutí tlačítka se zobrazilo okno vytvořené v Javascriptu, ve kterém byl uživatel požádán o potvrzení vymazání záznamu. Náš nový sloupec byl potomkem všem důvěrně známého sloupce ButtonField. Jak jsme ale viděli v kódu třídy DeleteButtonColumn, neobešli jsme se bez několika ne úplně čistých praktik.
Stejný sloupec se dá také vytvořit jako potomek třídy TemplateField. Místo deklarativní šablony na stránce ale vytvoříme vlastná šablonu pro náš nový sloupec implemetací rozhraní ITemplate. Jak se asi shodneme, šablony jsou jedna z nejlepších věcí v ASP.NET již od verze 1.0 - místo toho, aby nám tvůrci ASP.NET diktovali, jak a z čeho musejí být ASP.NET prvky vystavěny, můžeme v šablonách dodat vlastní obsah ASP.NET prvku či jedné jeho vlastnosti a přitom si stále užívat všech výhod událostního modelu i předdefinovaného chování ASP.NET komponenty.
Nejprve kód našeho nového sloupce pro GridView:
namespace RStein.Web.UI
{
/// <summary>
/// Nový typ sloupce pro GridView obsahující tlačítko pro smazání záznamu s potvrzením na klientovi (JS)
/// </summary>
public class DeleteButtonField2 : TemplateField
{
#region Public constants
/// <summary>
/// Id vygenerovaného tlačítka
/// </summary>
public const string DELETE_BUTTON_ID = "DeleteButton";
#endregion Public constants
#region Private constants
public const string DELETE_COMMAND = "Delete";
public const string DEFAULT_BUTTON_TEXT = "Smazat";
public const string DEFAULT_CONFIRM_MESSAGE = "Opravdu smazat záznam?";
public const string DELETE_JS = "javascript: if (!confirm('{0}')) return false;";
#endregion Private constants
#region Private fields
private DeleteButtonTemplate m_currTemplate;
#endregion Private fields
#region Constructors
/// <summary>
/// Konstruktor
/// </summary>
public DeleteButtonField2()
{
m_currTemplate = new DeleteButtonTemplate(this);
ItemTemplate = m_currTemplate;
AlternatingItemTemplate = m_currTemplate;
EditItemTemplate = m_currTemplate;
}
#endregion Constructors
#region Public properties
/// <summary>
/// Typ tlačítka (odkaz-LinkButton, Běžné tlačítko-Button)
/// </summary>
/// <exception cref="NotSupportedEception">Pokus o nastavení vlastnosti ButtonType na nepodporovaný typ Image</exception>
public ButtonType ButtonType
{
get
{
object buttType = ViewState["ButtonType"];
if (buttType == null)
{
return ButtonType.Button;
}
return ((ButtonType)buttType);
}
set
{
if (value == ButtonType.Image)
{
throw new NotSupportedException("Image button is currently not supported");
}
ViewState["ButtonType"] = value;
}
}
/// <summary>
///Text na tlačítku
/// </summary>
public string ButtonText
{
get
{
string btnDescription = (string)ViewState["ButtonDescription"];
if (btnDescription == null)
{
return DEFAULT_BUTTON_TEXT;
}
return (btnDescription);
}
set
{
ViewState["ButtonDescription"] = value;
}
}
/// <summary>
///Text varování, které se má zobrazit uživateli při pokusu vymazat záznam
/// </summary>
public string ConfirmMessage
{
get
{
string confirmMessage = (string)ViewState["ConfirmMessage"];
if (confirmMessage == null)
{
return DEFAULT_CONFIRM_MESSAGE;
}
return (confirmMessage);
}
set
{
ViewState["ConfirmMessage"] = value;
}
}
#endregion Public properties
}
}
Třída DeleteButtonField2 je potomkem třídy Template Field. Ve svém konstruktoru do zděděných vlastností ItemTemplate (výchozí šablona pro řádek), AlternatingItemTemplate (šablona pro alternativní zobrazení řádku) a EditItemTemplate (šablona použitá při editaci řádku) dosadí instanci šablony DeleteButtonTemplate, jejíž kód uvidíme za chvíli. Šabloně je do kostruktoru předán odkaz na rodičovský sloupec, protože šablona DeleteButtonTemplate vytváří svůj obsah také podle hodnot veřejných vlastností třídy DeleteButtonField. Vlastnost ButtonType určuje typ zobrazovaného tlačítka. Podporujeme nyní pouze varianty Button (běžné tlačítko) a LinkButton (tlačítko ve formě hypertextového odkazu). Ve vlastnosti ButtonText je uložen popisek tlačítka a do vlastnosti ConfirmMessage můžeme uložit text potvrzující zprávy, která má být uživateli zobrazena v javascriptovém okně (text JS dialogu Confirm).
Jedním z důvodů, proč vlastnosti v třídě DeleteButtonField2 nedelegují při přístupu ke svým vlastnostem vykonání přímo na stejné vlastnosti, které by mohly být nadeklarovány v šabloně DeleteButtonTemplate, je potřeba využívat ViewState. Náš sloupec participuje na ViewState automaticky, kdežto do šablony DeleteButtonTemplate bychom podporu pro ViewState museli dodat vlastní realizací rozhraní IStateManager. Pro naše účely je ale využití ViewState v třídě DeleteButtonField2 dostačující.
namespace RStein.Web.UI
{
/// <summary>
/// Šablona s tlačítkem pro potvrzení smazání záznamu z <see cref="GridView"/>
/// </summary>
internal class DeleteButtonTemplate : ITemplate
{
#region Private members
private DeleteButtonField2 m_parent;
#endregion Private members
#region Constructors
/// <summary>
/// Konstruktor
/// </summary>
public DeleteButtonTemplate(DeleteButtonField2 parent)
{
if (parent == null)
{
throw new ArgumentNullException("parent");
}
m_parent = parent;
}
#endregion Constructors
#region Public methods
#region ITemplate Members
/// <summary>
/// Metoda, v níž jsou vytvořeny ovládací prvky šablony a přidány do kolekce Controls argumentu container;
/// </summary>
/// <param name="container">Ovládací prvek, v němž je šablona instanciována</param>
public void InstantiateIn(Control container)
{
IButtonControl newButton = null;
if (m_parent.ButtonType == ButtonType.Button)
{
newButton = new Button();
((Button)newButton).OnClientClick = String.Format(DeleteButtonField2.DELETE_JS, m_parent.ConfirmMessage);
}
else
{
newButton = new LinkButton();
((LinkButton)newButton).OnClientClick = String.Format(DeleteButtonField2.DELETE_JS, m_parent.ConfirmMessage);
}
newButton.CommandName = DeleteButtonField2.DELETE_COMMAND;
newButton.Text = m_parent.ButtonText;
Control newControl = newButton as Control;
newControl.ID = DeleteButtonField2.DELETE_BUTTON_ID;
container.Controls.Add(newControl);
}
#endregion ITemplate Members
#endregion Public methods
}
}
Třída DeleteButtonTemplate musí implementovat rozhraní ITemplate. Rozhraní ITemplate je složeno z jediné metody s názvem InstantiateIn. Metoda InstantiateIn dostane v argumentu container odkaz na "nadřízený" ovládací prvek, jehož součástí je šablona ITemplate, a její odpovědností je přidat do kolekce ovládacích prvků "nadřízeného" ovládacího prvku ovládací prvky (celý obsah) šablony.
V metodě InstantiateIn vytvoříme tlačítko (Buton nebo LinkButton), kód pro pro potvrzení vymazání záznamu vložíme do vlastnosti OnClientClick, a zadáme uživatelský popisek (Text) nového tlačítka. Dále nastavíme jméno přlkazu (CommandName) tlačítka na řetězec 'Delete', který je pro GridView při zpracovávání událostí "wellknown" signálem, že si uživatel přeje vymazat záznam. Nastavíme Id nového tlačítka a nakonec jej vložíme do kolekce Controls "nadřízeného" prvku v argumentu container.
Použití nového sloupce se ničím neliší od použití sloupce vytvořeného v přechozím spotu:
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="_Default" %>
<%@ Register Assembly="ClassLibrary1" Namespace="RStein.Web.UI" TagPrefix="custom" %>
<!
DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<
html xmlns="http://www.w3.org/1999/xhtml" >
<
head runat="server">
<title>Untitled Page</title>
</head>
<body>
<form id="form1" runat="server">
<div>
<asp:GridView ID="GridView1" runat="server" AutoGenerateColumns="False" OnRowDeleting="GridView1_RowDeleting" DataKeyNames="Id" DataSourceID="SqlDataSource1">
<Columns>
<asp:BoundField DataField="Name" HeaderText="Name" SortExpression="Name" />
<asp:BoundField DataField="State" HeaderText="State" SortExpression="State" />
<custom:DeleteButtonField2 ButtonText="Smazat" ButtonType="Button" ConfirmMessage="Smazat z znam?"/>
</Columns>
</asp:GridView>
</div>
</form>
</body>
</html>
Thursday, 20 July 2006 13:08:06 (Central Europe Standard Time, UTC+01:00)
ASP.NET
Sunday, 16 July 2006
Po delší době literární hádanka...
Poznáte autora veršů? Pokud ano, smekám, protože o žádného "profláknutého" a v čítánkách "přišpendleného" autora nejde.
A jen tak na okraj - tahle báseň je pro mě důkazem toho, kolik (sebe)lítosti můžete stočit do slov a přitom nemít nic společného s citovým kriplem dnešního věku, takzvaným homo sentimentalis, kterému z huby odkapává při každé větě težkopádný rozněžnělý patos nad sebou samým. Patos, jehož nejbohatším zřídlem jsou histrionské citové výrony v soap operách a jiných televizních veledílech. :)
Kolik jí bude až ji zas potkám
Kolik jí bude až ji zas potkám?
A co já?
Řeka už nekouzlí
neschová.
Jen zrcadlo
hladina
na portrét tiše
zhasíná.
Kolik jí bude až ji zas potkám?
Stromy se skloní v temnou spleť?
Půjdeme spolu podél řeky?
Úlomky větví tenký led?
Tenký led.
Sunday, 16 July 2006 19:35:29 (Central Europe Standard Time, UTC+01:00)
Literární a jiné humanitní úlety
Sunday, 09 July 2006
Chyba ve VS.NET 2005?
Včera a dneska jsem narazil na jedno "omezení-chybu" VS.NET 2005. Vždy, když jsem se snažil zkompilovat projekt, jsem dostal ve VS.NET pouze hlášku.
"An error has occured which the C# compiler is unable to report due to low memory or possible heap corruption. It is recommended that you save all your files, close and restart Visual Studio"
Restart VS.NET ale nezabral a dokonce ani nepomohl jindy všemocný restart počítače - měl jsem podezření, že po opakované hibernaci notebooku, která ve Windows XP na počítačích s více než jedním GB paměti není ani zdaleka dokonalá, má problémy VS.NET se správou paměti přesně tak, jak se snaží reportovat v chybové zprávě.
Zkusil jsem projekt zkompilovat v příkazovém řádku:
C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\Csc.exe /noconfig /nowarn:1701,1702 /errorreport:prompt
/warn:4 /define:DEBUG;TRACE /reference:"C:\Documents and Settings\Rene\My Documents\Visual Studio 2005\Projects\RStein.RS\RStein.RS\RStein.RS.DataStorage\bin\Debug\RStein.RS.DataStorage.dll"
/reference:C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727
\System.configuration.dll /reference:C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\System.Data.dll /reference:C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\System.dll /reference:C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\System.Xml.dll /debug+ /debug:full /optimize- /out:obj\Debug\RStein.RS.BusinessEntities.dll /target:library Articles\Article.cs
Articles\ArticleCategory.cs Articles\ArticleChapter.cs Articles\ArticleKeyword.cs
Articles\ArticlePhotoCategory.cs Articles\ArticleSerial.cs Articles\ArticleState.cs Task.cs Articles\ArticleTheme.cs Attachment.cs AttachmenType.cs Collections\ArticleCategoryCollection.cs
Collections\ArticleChapterCollection.cs Collections\TaskCollection.cs Collections\ArticleThemeCollection.cs Collections\BusinessCollectionBase.cs
Collections\CollectionChangeEventArgs.cs Collections\HWInformationTypeCollection.cs
CommonInterfaces\IUIDataProvider.cs Helpers\BasicValidations.cs BusinessObjectBase.cs Category.cs
Collections\AttachmentCollection.cs Collections\CategoryCollection.cs Collections\DeviceInfoCollection.cs Collections\ObservationCollection.cs Helpers\CentralConfigData.cs
Helpers\GlobalConstants.cs Helpers\IdentityMap.cs HWInformationDataType.cs DeviceInfo.cs
Helpers\DataCacheHelper.cs Observation.cs CodeTableBase.cs
Collections\HWCategoryCollection.cs HWCategory.cs
HWInformationType.cs Language.cs Measurement.cs
Properties\AssemblyInfo.cs
CategoryObservation.cs Quantity.cs Unit.cs UnitType.cs UrlAttachment.cs
A zjistil jsem, že VS.NET svádí na kompilátor C# chybu, se kterou si nedokáže poradit samo. Kompilátor totiž bez reptání nareportoval chybu ve zdrojovém kódu.
Collections\TaskCollection.cs(10,18): error CS0309: The type
'RStein.RS.BusinessEntities.Task' must be convertible to
'RStein.RS.BusinessEntities.BusinessObjectBase' in order to use it as
parameter 'T' in the generic type or method
'RStein.RS.BusinessEntities.BusinessCollectionBase<T>'
Collections\BusinessCollectionBase.cs(8,18): (Location of symbol related to
previous error)
Task.cs(7,18): (Location of symbol related to previous error)
VS.NET si tedy evidentně neumí vypořádat s touto kombinací:
Máte bázovou třídu pro typové kolekce s generickým typem T, u kterého vyžaduji, že musí být potomkem třídy BussinessObjectBase.
public class BusinessCollectionBase<T> : Collection<T>
where T : BusinessObjectBase
{
}
Dále máte odvozenou typovou kolekci pro business objekt - např. pro třídu Task.
public class TaskCollection : BusinessCollectionBase<Task>
{
}
A Třída Task není potomkem třídy BusinessObjectBase.
public class Task
{
}
Poté dostanete ve VS.NET výše uvedenou docela zmatečnou hlášku.
Stačí doplnit bázovou třídu BusinessObjectBase k třídě Task a kompilace i ve VS.NET proběhne tak jak má.
public class Task : BusinessObjectBase
{
}
Můžete prosím někdo toto chování ověřit na svém počítači, abych mohl zaměstnat Lady Bug Microsoftu? :) Díky.
Sunday, 09 July 2006 16:43:43 (Central Europe Standard Time, UTC+01:00)
.NET Framework
Sunday, 02 July 2006
Řešení programátorské hádanky z 24.6.2006 - ThreadStartDelegate
Jak bylo patrné ze zadání "hádanky", snažil jsem se, abyste mi napsali, jaké postupy používáte pro předání argumentů a získání návratové hodnoty z metody, když jste omezeni signaturou delegátu ThreadStartDelegate. Třída Thread dokáže pracovat pouze s metodami bez návratových hodnot (void) a také:
a) Metoda nesmí mít žádné argumenty - delegát ThreadStartDelegate
b) Metody přijímající jeden argument typu object - delegát ParameterizedThreadStart (pouze v .Net Frameworku 2.0).
Jak jste se zmínili v komentářích - když chceme předat argumenty kódu vykonávanému v samostatném threadu, můžeme použít třídy s vlastním stavem a návratovou hodnotu získat třeba tak, že si zaregistrujeme "callback" funkci.
Mně by se ale líbilo, kdybych měl jen jednu "stavovou" třídu s argumenty pro metody s jakoukoli signaturou a tedy společnou pro všechny rozdílné typy delegátů a hlavně chci objekt z této třídy přímo předat do konstruktoru třídy Thread, aby se můj kód nijak nelišíl od přímého použití delegátů ThreadStart a ParameterizedThreadStart. Proto jsem v komentářích mluvil o eleganci zvoleného řešení :)
class ThreadStartDelegateWrapper
{
#region Private members
private Delegate m_innerDelegate;
private object[] m_methodParameters;
private object m_returnValue;
#endregion Private members
#region Construtors
/// <summary>
/// Konstruktor
/// </summary>
/// <param name="del">Delegát ukazující na metodu, která poběží v jiném threadu</param>
public ThreadStartDelegateWrapper(Delegate del)
{
if (del == null)
{
throw new ArgumentNullException("del");
}
m_innerDelegate= del;
}
/// <summary>
/// Konstruktor
/// </summary>
/// <param name="del">Delegát ukazující na metodu, která poběží v jiném threadu</param>
///<param name="methodParameters">Argumenty metody</param>
public ThreadStartDelegateWrapper(Delegate del, params object[] methodParameters) : this(del)
{
m_methodParameters = methodParameters;
}
#endregion Construtors
#region Public properties
/// <summary>
/// Argumenty metody
/// </summary>
public object[] MethodParameters
{
get
{
return m_methodParameters;
}
set
{
m_methodParameters = value;
}
}
/// <summary>
/// Návratová hodnota metody
/// </summary>
public object ReturnValue
{
get
{
return m_returnValue;
}
}
#endregion Public properties
#region Operators
/// <summary>
/// Implicitní konverzní operátor, který instanci třídy <see cref="ThreadStartDelegateWrapper"/> převede na delegáta <see cref="ThreadStartDelegate"/>
/// </summary>
/// <param name="originalWrapper">Instance třídy <see cref="ThreadStartDelegateWrapper"/></param>
/// <returns>Instanci delegáta <see cref="ThreadStartDelegate"/>, který ukazuje na metodu zapouzdřující zavolání jakéhokoli delegáta</returns>
public static implicit operator ThreadStart(ThreadStartDelegateWrapper originalWrapper)
{
if (originalWrapper == null)
{
throw new ArgumentNullException("originalWrapper");
}
return new ThreadStart(originalWrapper.InvokeDelegate);
}
#endregion Operators
#region private methods
//Metoda, jejíž signatura odpovídá signatuře deklarované delegátem ThreadStartDelegate
private void InvokeDelegate()
{
m_returnValue = m_innerDelegate.DynamicInvoke(m_methodParameters);
}
#endregion private methods
}
Třídě ThreadStartDelegateWrapper můžete do konstruktoru předat odkaz na jakéhokoli delegáta a také můžete předat všechny potřebné argumenty metodě, na kterou delegát ukazuje (argument methodParameters v konstruktoru). Předané argumenty můžete kdykoli upravit, protože jsou zveřejněny ve vlastnosti MethodParameters.
Abych mohl svoji třídu použít kdekoli je očekáván delegát ThreadStart, napsal jsem implicitní konverzní operátor třídy ThreadStartDelegateWrapper na delegát ThreadStartDelegate.
Návratovou hodnotu metody vykonané v jiném threadu nalezneme ve vlastnosti ReturnValue - nic nám také nebrání přidat událost, ve které získanou návratovou hodnotu budeme sami aktivně distribuovat všem zájemcům.
A tady je jednoduchý ukázkový kód.
class Test
{
public delegate string TestDelegate(string message);
public string WriteMessage(string message)
{
Console.WriteLine(message);
return "OK";
}
}
class Program
{
static void Main(string[] args)
{
Test myTestClass = new Test();
Test.TestDelegate myDelegate = myTestClass.WriteMessage;
ThreadStartDelegateWrapper wrapper = new ThreadStartDelegateWrapper(myDelegate, new object[] { "Hello world" });
Thread myThread = new Thread(wrapper);
myThread.Start();
myThread.Join();
Console.WriteLine(wrapper.ReturnValue);
Console.Read();
}
}
Je jednoduché rozšířit třídu ThreadStartDelegateWrapper o podporu dalších delegátů v .NET Frameworku (např. WaitCallback), takže na metody s různými signaturami mohou ukazovat typičtí delegáti v .Net Frameworku s využitím jedné jediné třídy fungující jako obecný adaptér mezi naším kódem a kódem v .Net Frameworku.
Sunday, 02 July 2006 14:40:08 (Central Europe Standard Time, UTC+01:00)
.NET Framework | Programátorské hádanky