\

Školení Návrhové vzory, OOP a UML


 Monday, October 09, 2006
ASP.NET - jednoduchý přístup z kódu na webovém formuláři k prvkům deklarovaným v šabloně
.Net Framework 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("&nbsp;");
            
            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("&nbsp;");
            
            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 09, 2006 3:42:53 PM (Central Europe Standard Time, UTC+01:00)       
Comments [2]  .NET Framework | ASP.NET


 Wednesday, September 13, 2006
Windows Forms DataGridView - nevykreslování záhlaví sloupce
.Net Framework

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)       
Comments [0]  .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)       
Comments [19]  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)       
Comments [2]  Programátorské hádanky


 Sunday, August 20, 2006
UPDATE 1.9. 2006 - Pozvání na kurz Objektovými principy a návrhovými vzory řízený design a vývoj kvalitních aplikací v termínu 25-27.10.

Update 1.9. 2006  - Kurz je obsazen

Kvůli velkému zájmu o zářijový termín kurzu Objektovými principy a návrhovými vzory řízený design a vývoj kvalitních aplikací se bude stejný kurz konat také v termínu 25-27.10. 2006. Přihlásit se je opět možné zasláním emailu na adresu petra@renestein.net

Přejít na podrobné informace o kurzu včetně přihlášky na kurz



Sunday, August 20, 2006 4:59:53 PM (Central Europe Standard Time, UTC+01:00)       
Comments [0]  Kurzy UML a OOP | Návrhové vzory | UML


 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)       
Comments [0]  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)       
Comments [7]  Literární a jiné humanitní úlety


 Sunday, July 09, 2006
Chyba ve VS.NET 2005?
Chyba vulgo Bug

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 09, 2006 4:43:43 PM (Central Europe Standard Time, UTC+01:00)       
Comments [10]  .NET Framework


 Sunday, July 02, 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, July 02, 2006 2:40:08 PM (Central Europe Standard Time, UTC+01:00)       
Comments [3]  .NET Framework | Programátorské hádanky


 Friday, June 30, 2006
Update 24.7. 2006 : Kurz objektovými principy a návrhovými vzory řízený design a vývoj kvalitních aplikací - 6.9. - 8.9. 2006

Update 24.7. 2006  - 1 volné místa v termínu 6.9. - 8.9. 2006

Update 18.7. 2006  - 2 zájemci se přehlásili na říjnový termín - zbývají tedy 2 volná místa v termínu 6.9. - 8.9. 2006

 Pokud byste měli zájem o stejný kurz v
termínu 25.-27.10. 2006, přihlašte se prosím předběžně na adrese petra@renestein.net

Rád bych Vás pozval na další běh kurzu "Objektovými principy a návrhovými vzory řízený design a vývoj kvalitních aplikací", který  proběhne 6.9 - 8.9. 2006.

Podrobné informace o kurzu a přihlášení na kurz.



Friday, June 30, 2006 10:12:22 AM (Central Europe Standard Time, UTC+01:00)       
Comments [0]  Kurzy UML a OOP