Sunday, October 29, 2006
Analytická hádanka - žhnoucí vztahy mezi třídami
Slovo "žhnoucí" v nadpisu spotu není zavedením dlouho očekávané extenze do UML pro označení libidinózního vztahu mezi třídami , ale jen a pouze hodnotí vztahy, které jsou kvalitními kandidáty na epicentra pořádného požáru, v němž veškeré výhody OOP návrhu jsou devótně obětovány božstvu "najděte v aplikaci podstatná jména, nasekejte z nich třídy a pak si dejte panáka na další úspěch".
Diagram je záměrně neúplný, takže bych chtěl slyšet, jak by se dal upravit/rozvinout a jaké předpoklady jsou v něm obsaženy.
A zatím jde jen o triviální problém, ale na subtilnější kličky narazíme brzy v dalších hádankách :)
Sunday, October 29, 2006 10:41:37 PM (Central Europe Standard Time, UTC+01:00)
Kontrola duplicitního spuštění aplikace v Compact .Net Frameworku - Windows CE
I když samotný Compact .Net Framework v souladu s doporučeními Microsoftu pro vývoj Windows Mobile aplikací zajišťuje, že je vždy spuštěna nanejvýš jedna instance aplikace, v Compact .Net Frameworku spuštěném na "čistých" Windows CE již zmíněné pravidlo neplatí a kontrolu na opakované spuštění aplikace musíme doplnit sami.
Tento kód funguje pro Compact .Net Framework od verze 1.x a byl otestován na Windows CE 4.2 a vyšších.
private const int ERROR_ALREADY_EXISTS = 183;
[DllImport("CoreDll.dll")]
private static extern int GetLastError();
[DllImport("CoreDll.dll", EntryPoint="CreateMutexW")]
private static extern int CreateMutex( IntPtr
lpMutexAttributes, bool InitialOwner, string MutexName );
public static bool AppAlreadyStarted()
{
string myID =
System.Reflection.Assembly.GetExecutingAssembly().GetName().Name;
if( CreateMutex(IntPtr.Zero, true, myID) != 0 )
{
return (GetLastError() == ERROR_ALREADY_EXISTS);
}
return false;
}
Můžeme také najít formulář již spuštěné aplikace podle titulku, přenést jej do popředí a duplicitní instanci aplikace ukončit.
namespace DeviceApplication1
{
static class Program
{
[DllImport("coredll", EntryPoint="FindWindow")]
private static extern IntPtr FindWindow(
string lpClassName,
string lpWindowName);
[DllImport("coredll", EntryPoint="SetForegroundWindow")]
private static extern bool SetForegroundWindow(IntPtr hWnd);
[MTAThread]
static void Main()
{
IntPtr mainWindowHwnd = FindWindow(null, "DeviceMain");
if (mainWindowHwnd.Equals(IntPtr.Zero))
{
Application.Run(new Form1());
}
else
{
SetForegroundWindow(mainWindowHwnd);
Application.Exit();
}
}
}
Sunday, October 29, 2006 8:52:11 PM (Central Europe Standard Time, UTC+01:00)
Compact .Net Framework
Monday, October 9, 2006
ASP.NET - jednoduchý přístup z kódu na webovém formuláři k prvkům deklarovaným v šabloně
V ASP.NET 1.x jsme při přístupu k prvkům v šabloně (šablonou rozumíme vlastnost serverového ovládacího prvku typu ITemplate) museli zavolat metodu FindControl a předat ji Id hledaného prvku. A protože návratovou hodnotou metody FindControl je pouze rozhraní třídy Control, byli jsme nuceni přetypovávat na odvozený ovládací prvek.
Label lblMess = LoginControl1.FindControl("lblMessage") as Label;
U prvků DataList, Repeater a dalších, kteří používají šablony pro opakování stejného obsahu pro každý řádek v datovém zdroji (ItemTemplate, AlternateItemTemplate) je použití metody FindControl odůvodněné. Ovládací prvky v šabloně jsou vytvořeny opakovaně pro všechny řádky v datovém zdroji a rodičovský prvek šablony implementací rozhraní INamingContainer zajišťuje, že každá instance šablony je v html formuláři složena z html elementů s unikátními hierarchickými názvy. Metoda FindControl na řádku Datalistu, Repeateru si můžeme zjednodušeně představit jako "překladač" dlouhého, hierarchického a automaticky generovaného Id na jednoduché Id zadané v šabloně. Vidíme-li na stránce Id LoginControl1_ctl00_lblMessage, můžeme se k prvku s Id 'lblMessage' dostat tak, jak jsme si ukázali v kódu výše.
Proč si ale komplikovat život, když máme na stránce vždy maximálně jednu instanci šablony? Šablona prvku WizardStep bude na stránce právě jednou, protože šablonu jednoho kroku v průvodci (asp:wizard) nepoužíváme pro opakovanou instanciaci stejného obsahu, ale jen pro vytvoření vlastního vzhledu jednoho kroku průvodce. Šablonu nám prvek wizard nabízí jen proto, abychom si mohli vytvořit pěkné vlastní uživatelské rozhraní a nebyli sešněrováni ve svém tvůrčím rozletu představami ASP.NET týmu. Pak ale není žádný důvod, abychom k prvkům šablony přistupovali přes metodu FindControl. Prvky jsou na stránce pouze jednou a je bezpečné na ně odkazovat přímo na úrovni stránky jako na každý jiný ovládací prvek v ASP.NET formuláři, protože nehrozí kolize jejich Id.
ASP.NET 2.0 dovoluje u každé vlastnosti typu ITemplate určit, jestli bude šablona instanciována na jedné stránce opakovaně, anebo zda se na šablona stránce vyskytne nanejvýš jednou. Informaci o tom, jak budete šablonu používat, nese nový atribut TemplateInstance, kterým dekorujete vlastnost ITemplate.
[TemplateInstance(TemplateInstance.Single)]
Hodnotu Single z enumerace TemplateInstance ASP.NET interpretuje jako příkaz k vygenerování typových proměnných na úrovni stránky pro všechny ovládací prvky v šabloně.
Když atribut TemplateInstance nepoužijeme nebo zvolíme režim TemplateInstance.Multiple, k vygenerování proměnných nedojde a ASP.NET 2.0 se k prvkům k šabloně chová stejně jako ASP.NET 1.x.
Zde je jednoduchý serverový ovládací prvek LoginControl, který obsahuje dvě šablony - jednu pro anonymní uživatele a druhou, asi nepřekvapivě, pro přihlášené uživatele. Obě šablony jsou dekorovány atributem TemplateInstance s hodnotou TemplateInstance.Single - jako autoři ovládacího prvku víme, že určitě nebudeme používat více instancí jedné šablony.
namespace RStein.Web.UI.WebControls
{
/// <summary>
/// Serverový ovládací prvek, který dovoluje definovat odlišné šablony pro anonymního a přihlášeného uživatele
/// </summary>
[DefaultProperty("AnonymousUserMessage")]
[DefaultEvent("LoginRequest")]
[ToolboxData("<{0}:LoginControl runat=server></{0}:LoginControl>")]
public class LoginControl : CompositeControl
{
#region Delegates
public delegate void LoginItemCommandEventHandler (object sender, CommandEventArgs e);
#endregion Delegates
#region Public Constants
/// <summary>
/// popisek na tlačítko pro událost <see cref="LoginRequest"/>
/// </summary>
public const string LOGIN_BUTTON_NAME = "Login";
/// <summary>
/// Konstanta reprezentuje popisek na tlačítko pro událost <see cref="LogoutRequest"/>
/// </summary>
public const string LOGOUT_BUTTON_NAME = "Logout";
#endregion Public Constants
#region Events Keys
/// <summary>
/// Klíč události <see cref="LoginRequest"/>
/// </summary>
public static readonly Object LoginRequestKey = new Object();
/// <summary>
/// Klíč události <see cref="LoginRequest"/>
/// </summary>
public static readonly Object LogoutRequestKey = new Object();
/// <summary>
/// Klíč události <see cref="ItemCommand"/>
/// </summary>
public static readonly Object ItemCommandKey = new Object();
#endregion Events Keys
#region Public Events
/// <summary>
/// Událost 'Přihlásit uživatele' je vyvolána po kliknutuí na tlačítko s popiskem <see cref="LoginButtonName"/>
/// </summary>
public event EventHandler LoginRequest
{
add
{
Events.AddHandler(LoginRequestKey, value);
}
remove
{
Events.RemoveHandler(LoginRequestKey, value);
}
}
/// <summary>
/// Událost 'Odhlásit uživatele' je vyvolána po kliknutuí na tlačítko s popiskem <see cref="LogoutButtonName"/>
/// </summary>
public event EventHandler LogoutRequest
{
add
{
Events.AddHandler(LogoutRequestKey, value);
}
remove
{
Events.RemoveHandler(LogoutRequestKey, value);
}
}
/// <summary>
/// Událost zprostředkovává události vnořených ovládacích prvků
/// </summary>
public event LoginItemCommandEventHandler ItemCommand
{
add
{
Events.AddHandler(ItemCommandKey, value);
}
remove
{
Events.RemoveHandler(ItemCommandKey, value);
}
}
#endregion Public Events
#region Private variables
private LoginControlContent m_content;
private ITemplate m_anonymousTemplate;
private ITemplate m_loggedInTemplate;
#endregion Private variables
#region Contructors
/// <summary>
/// Konstruktor
/// </summary>
public LoginControl()
{
}
#endregion Contructors
#region Public properties
/// <summary>
/// Vlastnost dovoluje přistupovat k prvkům šablony přes FindControl
/// </summary>
[Browsable(false)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public LoginControlContent Content
{
get
{
EnsureChildControls();
return m_content;
}
}
/// <summary>
/// Šablona pro přihlášeného uživatele
/// </summary>
[Browsable(false)]
[DefaultValue(null)]
[PersistenceMode(PersistenceMode.InnerProperty)]
[TemplateContainer(typeof(LoginControlContent))]
[TemplateInstance(TemplateInstance.Single)]
public ITemplate LoggedInTemplate
{
get
{
return m_loggedInTemplate;
}
set
{
m_loggedInTemplate = value;
}
}
/// <summary>
/// Šablona pro nepřihlášeného uživatele
/// </summary>
[Browsable(false)]
[DefaultValue(null)]
[PersistenceMode(PersistenceMode.InnerProperty)]
[TemplateContainer(typeof(LoginControlContent))]
[TemplateInstance(TemplateInstance.Single)]
public ITemplate AnonymousTemplate
{
get
{
return m_anonymousTemplate;
}
set
{
m_anonymousTemplate = value;
}
}
/// <summary>
/// Informace zobrazená nepřihlášenému uživateli
/// </summary>
[Category("Behavior")]
[Bindable(true)]
[DefaultValue("")]
[Description("Informace zobrazená nepřihlášenému uživateli")]
public string AnonymousUserMessage
{
get
{
string message = (string) ViewState["AnonymousUserMessage"];
return (message == null ? String.Empty : message);
}
set
{
ViewState["AnonymousUserMessage"] = value;
}
}
/// <summary>
/// Informace zobrazená přihlášenému uživateli
/// </summary>
[Category("Behavior")]
[Bindable(true)]
[DefaultValue("")]
[Description("Informace zobrazená přihlášenému uživateli")]
public string LoggedInMessage
{
get
{
string message = (string) ViewState["LoggedInMessage"];
return (message == null ? String.Empty : message);
}
set
{
ViewState["LoggedInMessage"] = value;
}
}
#endregion Public properties
#region Protected properties
/// <summary>
/// Prvek bude uzavřen v HTML značce DIV
/// </summary>
protected override HtmlTextWriterTag TagKey
{
get
{
return HtmlTextWriterTag.Div;
}
}
#endregion Protected properties
#region Protected methods
/// <summary>
/// Vytvoření vnořených ovládacích prvků
/// </summary>
protected override void CreateChildControls()
{
Controls.Clear();
if (Page.User.Identity.IsAuthenticated)
{
m_content = new LoginControlContent(Page.User.Identity.Name, AnonymousUserMessage, LoggedInMessage);
ITemplate template = null;
if (m_loggedInTemplate != null)
{
template = m_loggedInTemplate;
}
else
{
template = new DefaultLoggedInTemplate();
}
template.InstantiateIn(m_content);
}
else
{
m_content = new LoginControlContent(String.Empty, AnonymousUserMessage, LoggedInMessage);
ITemplate template = null;
if (m_anonymousTemplate != null)
{
template = m_anonymousTemplate;
}
else
{
template = new DefaultAnonymousTemplate();
}
template.InstantiateIn(m_content);
}
this.Controls.Add(m_content);
}
/// <summary>
/// Přpsání metody, která zachycuje bublané události
/// </summary>
/// <param name="source">Zdroj bublané události</param>
/// <param name="args">Argumenty bublané události</param>
/// <returns><see cref="true"/> - Událost byla zpracována, <see cref="false"/> - Událost nebyla zpracována</returns>
protected override bool OnBubbleEvent(object source, EventArgs e)
{
CommandEventArgs ex = e as CommandEventArgs;
if (ex != null)
{
if (ex.CommandName.ToLower() == LOGIN_BUTTON_NAME.ToLower())
{
OnLoginRequest(new EventArgs());
}
else if (ex.CommandName.ToLower() == LOGOUT_BUTTON_NAME.ToLower())
{
OnLogoutRequest(new EventArgs());
}
else
{
OnItemCommand(ex);
}
return true;
}
return false;
}
/// <summary>
/// Metoda odpovědná za vyvolání události loginRequest
/// </summary>
/// <param name="e">Parametry události</param>
protected virtual void OnLoginRequest(EventArgs e)
{
EventHandler eh = (EventHandler) Events[LoginRequestKey];
if (eh != null)
{
eh(this, e);
}
}
/// <summary>
/// Metoda odpovědná za vyvolání události LogoutRequest
/// </summary>
/// <param name="e">Parametry události</param>
protected virtual void OnLogoutRequest(EventArgs e)
{
EventHandler eh = (EventHandler) Events[LogoutRequestKey];
if (eh != null)
{
eh(this, e);
}
}
/// <summary>
/// Metoda odpovědná za vyvolání události ItemCommand
/// </summary>
/// <param name="e">Parametry události</param>
protected virtual void OnItemCommand(CommandEventArgs e)
{
LoginItemCommandEventHandler eh = (LoginItemCommandEventHandler) Events[ItemCommandKey];
if (eh != null)
{
eh(this, e);
}
}
#endregion Protected methods
}
}
LoginControl použijeme na testovací stránce a zadáme anonymní šablonu.
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="_Default" %>
<%@ Register Assembly="WebControlLibrary1" Namespace="RStein.Web.UI.WebControls"
TagPrefix="cc2" %>
<!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>Testovací stránka</title>
</head>
<body>
<form id="form1" runat="server">
<div>
<cc2:LoginControl ID="LoginControl1" runat="server" AnonymousUserMessage="Přihlašte se prosím" OnLoginRequest="LoginControl1_LoginRequest">
<AnonymousTemplate>
<asp:label runat="server" ID="lblMessage"><%#Container.AnonymousMessage%> </asp:label> <br />
<asp:LinkButton Id="Login" runat="server" Text="Přihlásit se" />
</AnonymousTemplate>
</cc2:LoginControl>
<
</div>
</form>
</body>
</html>
Tlačítko Login i popisek lblMessage deklarované v šabloně jsou přímo dostupné ze stránky, jak si můžeme ověřit, když v události PreRender stránky změníme barvu pozadí popisku lblMessage. Nemusíme používat metodu FindControl, ani přetypovávat, což jsou docela příjemná vylepšení kódu ;)
private void Page_PreRender(object sender, EventArgs e)
{
LoginControl1.DataBind();
if (lblMessage != null)
{
lblMessage.BackColor = Color.Cyan;
}
}
Abyste mohli ovládací prvek LoginControl zkompilovat, následuje kód používaných pomocných tříd.
using System;
using System.Web.UI.WebControls;
using System.Web.UI;
using System.ComponentModel;
namespace RStein.Web.UI.WebControls
{
/// <summary>
/// Kontejner pro šablony
/// </summary>
[ToolboxItem(false)]
public class LoginControlContent : Control, INamingContainer
{
#region Private variables
private string m_userName;
private string m_anonymousMessage;
private string m_loggedInMessage;
#endregion Private variables
/// <summary>
/// Konstruktor
/// </summary>
/// <param name="userName">Jméno uživatele</param>
/// <param name="anonymousMessage">Zpráva pro nepřihlášeného uživatele</param>
/// <param name="loggedInMessage">Zpráva pro přihlášeného uživatele</param>
internal LoginControlContent(string userName, string anonymousMessage, string loggedInMessage)
{
m_userName = userName;
m_anonymousMessage = anonymousMessage;
m_loggedInMessage = loggedInMessage;
}
/// <summary>
/// Jméno uživatele
/// </summary>
public string UserName
{
get
{
return m_userName;
}
}
/// <summary>
///Zpráva pro nepřihlášeného uživatele
/// </summary>
public string AnonymousMessage
{
get
{
return m_anonymousMessage;
}
}
/// <summary>
///Zpráva pro přihlášeného uživatele
/// </summary>
public string LoggedInMessage
{
get
{
return m_loggedInMessage;
}
}
}
}
using System;
using System.Web.UI.WebControls;
using System.Web.UI;
namespace RStein.Web.UI.WebControls
{
/// <summary>
/// Standardní šablona pro nepřihlášeného uživatele
/// </summary>
public class DefaultAnonymousTemplate : ITemplate
{
#region Constructors
/// <summary>
/// Konstruktor
/// </summary>
internal DefaultAnonymousTemplate()
{
}
#endregion Constructors
#region public methods
#region ITemplate Members
/// <summary>
/// Implementace InstantiateIn - vytvoření ovládacích prvků anonymní šablony
/// </summary>
/// <param name="container">Ovládací prvek, do jehož kolekce Controls bzdou prvky šablony přidány</param>
public void InstantiateIn(Control container)
{
Label lblMessage = new Label();
lblMessage.ID = "lblMessage";
lblMessage.DataBinding += new EventHandler(lblMesssage_DataBind);
LiteralControl separator = new LiteralControl(" ");
LinkButton btnLogin = new LinkButton();
btnLogin.ID = "btnLogin";
btnLogin.Text = "Přihlásit";
btnLogin.CommandName = LoginControl.LOGIN_BUTTON_NAME;
container.Controls.Add(lblMessage);
container.Controls.Add(separator);
container.Controls.Add(btnLogin);
}
#endregion ITemplate Members
#endregion public methods
#region private methods
//Handler události DataBind prvku lblMessage
private void lblMesssage_DataBind(object sender, EventArgs e)
{
Label lblMessage = (Label) sender;
LoginControlContent currContent = lblMessage.NamingContainer as LoginControlContent;
lblMessage.Text = currContent.AnonymousMessage;
}
#endregion private methods
}
}
using System;
using System.Web.UI.WebControls;
using System.Web.UI;
namespace RStein.Web.UI.WebControls
{
/// <summary>
/// Standardní šablona pro přihlášeného uživatele
/// </summary>
public class DefaultLoggedInTemplate : ITemplate
{
#region constructors
/// <summary>
/// Konstruktor
/// </summary>
internal DefaultLoggedInTemplate()
{
}
#endregion constructors
#region public methods
#region ITemplate Members
/// <summary>
/// Implementace InstantiateIn - vytvoření ovládacích prvků anonymní šablony
/// </summary>
/// <param name="container">Ovládací prvek, do jehož kolekce Controls bzdou prvky šablony přidány</param>
public void InstantiateIn(Control container)
{
Label lblMessage = new Label();
lblMessage.ID = "lblMessage";
lblMessage.DataBinding += new EventHandler(lblMesssage_DataBind);
LiteralControl separator = new LiteralControl(" ");
LinkButton btnLogout = new LinkButton();
btnLogout.ID = "btnLogout";
btnLogout.Text = "Odhlásit";
btnLogout.CommandName = LoginControl.LOGOUT_BUTTON_NAME;
container.Controls.Add(lblMessage);
container.Controls.Add(separator);
container.Controls.Add(btnLogout);
}
#endregion ITemplate Members
#endregion public methods
#region private methods
//Handler události DataBind prvku lblMessage
private void lblMesssage_DataBind(object sender, EventArgs e)
{
Label lblMessage = (Label) sender;
LoginControlContent currContent = lblMessage.NamingContainer as LoginControlContent;
lblMessage.Text = currContent.LoggedInMessage + " " + currContent.UserName;
}
#endregion private methods
}
}
Monday, October 9, 2006 3:42:53 PM (Central Europe Standard Time, UTC+01:00)
.NET Framework | ASP.NET
Wednesday, September 13, 2006
Windows Forms DataGridView - nevykreslování záhlaví sloupce
V .Net konferenci na serveru builder se objevil dotaz, jak skrýt záhlaví (header) sloupce v DataGridView. Kolegové, zvyklí pravděpodobně stále na poněkud nehrabaný DataGrid z verze 1.x .Net Frameworku, radili vytvořit nový sloupec a nové záhlaví.
Nejjednodušší řešení ale představuje odchycení události CellPainting DatagridView a a v ní zamezíte vykreslení sloupce nastavením vlastnosti Handled na true. DataGridView má ale mnohem lepší objektový model než DataGrid a jednoduchých změn ve vykreslování DataGridView dosáhneme rychle v obsluze událostí CellPainting, RowPrePaint, RowPostPaint a dalších.
const int hiddenRowIndex = -1; //záhlaví má index -1
const int hiddenColumnIndex = 0; //skryjeme první sloupec
private void dataGridView1_CellPainting(object sender, DataGridViewCellPaintingEventArgs e)
{
if (e.RowIndex == hiddenRowIndex && e.ColumnIndex == hiddenColumnIndex)
{
e.Handled = true;
}
}
Update : Samozřejmě nemusíte zcela rezignovat na vykreslení buňky a chcete vybarvit alespoň její pozadí.
Opět je to jednoduché - vyplníme pozadí a nastavíme e.Handled na true
e.Graphics.FillRectangle(myColor, e.CellBounds);
e.Handled = true;
Wednesday, September 13, 2006 5:17:52 PM (Central Europe Standard Time, UTC+01:00)
.NET Framework | Windows Forms
Monday, September 11, 2006
Fórum o OOP, UML, návrhových vzorech, MDA, DSL ... - chtěli byste?
Nadpis vyjadřuje v kostce vše. Hraji si právě teď s překladem a nastavením YetAnotherForum a napadlo mě, že bych na doméně forum.renestein.net spustil fórum, kde bychom společně diskutovali o návrhu aplikací, systémovém designu, OOP, UML, Model Driven Architecture, DSL, zuřivě bychom se hádali nad best practices, vášnivě "flamovali" nad podporou OOP v různých programovacích jazycích :) nebo bychom si vyměňovali linky na zajímavé články. Pro každé větší téma by existovalo samostatné fórum.
Vím. že některá česká fóra se OOP a analýzou zabývají, ale kvůli svému neodvolatelně finálním stavu "mrtvé" fórum s občasnými "self" přechody, spuštěnými přijetím jedné OT zprávy s nabídkou domácích zásob viagry nějakého momentálně insolventního a celoživotně impotentního spammera, se v nich nic zajímavého neděje.
Takže - máte zájem? :)
Monday, September 11, 2006 2:55:02 PM (Central Europe Standard Time, UTC+01:00)
Návrhové vzory | Ostatní | UML
Programátorská hádanka - 11.9.2006
Máte jednoduchý (a dnes bych řekl dokonce až urážlivě triviální) kód. A cituji jednoduchou otázku Mistra kódování a alžbětinského Sira W. Shakespeara - Jak se vám líbí?
class MessageQueue
{
private static object messageLock = new object();
private List<Message> list;
public MessageQueue()
{
list = new List<Message>();
}
public void RemoveMessage(Message message)
{
lock (messageLock)
{
list.Remove(message);
}
}
}
Monday, September 11, 2006 1:31:35 PM (Central Europe Standard Time, UTC+01:00)
Programátorské hádanky
Sunday, August 20, 2006
Thursday, July 20, 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, July 20, 2006 1:08:06 PM (Central Europe Standard Time, UTC+01:00)
ASP.NET
Sunday, July 16, 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, July 16, 2006 7:35:29 PM (Central Europe Standard Time, UTC+01:00)
Literární a jiné humanitní úlety
Sunday, July 9, 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, July 9, 2006 4:43:43 PM (Central Europe Standard Time, UTC+01:00)
.NET Framework