\


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


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


 Friday, 30 June 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, 30 June 2006 10:12:22 (Central Europe Standard Time, UTC+01:00)       
Comments [0]  Kurzy UML a OOP


 Saturday, 24 June 2006
Vyřazení blogu ze služby Weblogy.cz - aktualizujte prosím své RSS čtečky

Protože můj blog již pravděpodobně nesplňoval všechny podmínky pro zařazení do seznamu zdrojů Weblogy (hlavně tedy určitě nemám blog o webdesignu a navíc z  html kódu generovaného DasBlogem se chce občas zvracet i mě, který na svatou inkvizici všech náctiletých webdesignérů, zřízenou za účelem šikanování ostatních pomocí W3C tortury a zaštítěnou magickou mocí svatých slov CSS, SEO, Pixy apod.:-),  nebere zřetel), prosím všechny ty, kdo byli zvyklí můj blog přes tuto agregační službu sledovat, aby si přihlásili odběr RSS nebo ATOM zdroje přímo na mém blogu.

Jestliže nemáte zájem o všechny spoty, což by mě tedy docela udivilo, ale lidé mají zvláštní úchylky a potřeby :-D, RSS jednotlivých tématických kategorií naleznete v pravém sloupci stránky.

Omlouvám se za toto mírné a mnou nezaviněné nepohodlí. ;-) 

 



Saturday, 24 June 2006 22:54:08 (Central Europe Standard Time, UTC+01:00)       
Comments [2]  Ostatní


Programátorská hádanka - jak metodě v delegátu ThreadStart předat argumenty?

Jak asi víte, delegát ThreadStart v .Net Frameworku může ukazovat pouze na metodu, která nemá žádné argumenty a vrací void.

Jak nejlépe metodě v delegátu ThreadStart předat argumenty? A navíc  - dokázali byste, aby metoda spuštěná v delegátu ThreadStart vrátila hodnotu, nebo si teď už opravdu jen vymýšlím po dobrém sobotním bourbonu nějaké rozkošné .Net 3.0 pohádky? ;-) Napadlo mě totiž docela elegantní řešení, tak by mě zajímalo, jestli ho někdo z vás už zná a používá? :-)

 



Saturday, 24 June 2006 22:28:13 (Central Europe Standard Time, UTC+01:00)       
Comments [12]  .NET Framework | Programátorské hádanky


 Friday, 19 May 2006
Sloupec v GridView s potvrzením vymazání záznamu (na klientovi)

Jestliže chcete mít v GridView sloupec, který uživatele požádá na klientovi (s využitím JavaScriptu) o potvrzeni smazání záznamu, je doporučeno používat TemplateField, jehož šablona ItemTemplate obsahuje ve vlastnosti OnClientClick tlačítka klientský kód.

<asp:TemplateField ShowHeader="False">

   <ItemTemplate>

      <asp:ImageButton ID="ImageButton1" runat="server" CausesValidation="False" CommandName="Delete" ImageUrl="~/Images/delete.gif" OnClientClick="javascript:return confirm('Opravdu smazat záznam?');" />

   </ItemTemplate>

</asp:TemplateField>

ButtonField by byl sice vhodnější, ale tvůrci ASP.NET zadání kódu v JavaScriptu pro ButtonField nepodporují. Třída ButtonField nenabízí ani přístup k vlastnostem prvku Button, který je vyrenderován na každém řádku GridView.

Chceme-li napsat svůj vlastní typový sloupec pro mazání záznamů, který budeme používat ve všech svých projektech, použijeme třídu ButtonField alespoň jako předka svého nového sloupce a lehce "dirty" kódem v v přepsané metodě InitializeCell přidáme tlačítku vytvořenému v "našem" sloupci pro každý řádek GridView jednoduchý kód v JavaScriptu, jenž bude vyžadovat od uživatele potvrzení smazání záznamu.

    /// <summary>
    /// Nový typ sloupce pro GridView obsahující tlačítko pro smazání záznamu s potvrzením na klientovi (JS)
    /// </summary>
    public class DeleteButtonField : ButtonField
    {
        #region Private constants
        private const string DEFAULT_CONFIRM_MESSAGE = "Opravdu smazat záznam?";
        private const string DELETE_COMMAND = "Delete";
        private const string DELETE_JS = "javascript: if (!confirm('{0}')) return false;";
        #endregion Private constants

        public DeleteButtonField() : base()
        {

        }

        #region Public properties
        /// <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

        #region Public properties

        /// <summary>
        /// Metoda volaná při incializaci buňky Gridu
        /// </summary>
        /// <param name="cell">Inicializovaná buňka</param>
        /// <param name="cellType">Typ buňky</param>
        /// <param name="rowState">Stav řádku buňky</param>
        /// <param name="rowIndex">Index řádku buňky</param>
        public override void InitializeCell(DataControlFieldCell cell, DataControlCellType cellType, DataControlRowState rowState, int rowIndex)
        {
            base.InitializeCell(cell, cellType, rowState, rowIndex);
            if (cell.Controls.Count > 0)
            {
                IButtonControl control = cell.Controls[0] as IButtonControl;

                if (control != null)
                {
                    control.CommandName = DELETE_COMMAND;
                  
                    LinkButton lb = control as LinkButton;
                    Button btn = control as Button;
                    ImageButton iBtn = control as ImageButton;

                    string fullConfirmMessage = String.Format(DELETE_JS, ConfirmMessage);
                    if (lb != null)
                    {
                        lb.OnClientClick = fullConfirmMessage;
                    }

                    else if (btn != null)
                    {
                        btn.OnClientClick = fullConfirmMessage;
                    }
                    
                    else if (iBtn != null)
                    {
                        iBtn.OnClientClick = fullConfirmMessage;
                    }
                }
            }
        }
        #endregion Public methods
    }

Kód není nijak složitý. Kromě konstant, jejichž význam je dostatečně zřejmý z jejich názvu, máme v našem novém sloupci vlastnost ConfirmMessage, která obsahuje text zobrazovaný v dialogu pro potvrzení smazání záznamu. V metodě InitializeCell nejdříve zavoláme implementaci InitializeCell třídy ButtonField. Poté zjistíme, jestli buňka gridu (cell) obsahuje nějaké ovládací prvky. Jestliže ne, byla metoda volána pro řádek reprezentující záhlaví nebo zápatí gridu, a v ní žádné tlačítko nebude. Když buňka obsahuje alespoň jeden ovládací prvek, zjistíme zda první prvek v kolekci1 je typu IButtonControl (rozhraní IButtonControl implementují třídy LinkButton, Button i ImageButton) a nastavíme jeho vlastnost CommandName na text Delete. Jak asi víte, řetězec "Delete" je jedním z příkazů, které GridView rozeznává a speciálně ošetřuje, takže při bublání událost Click tlačítka s CommandName nastaveným na "Delete" je vyvolána událost RowDeleting (a případně další) místo generické události RowCommand.

Protože rozhraní IButtonControl nenabízí vlastnost OnClientClick pro zadání potvrzujícího kódu v Javascriptu, musíme přetypovat na konkrétní třídu Button a poté teprve můžeme použít vlastnost OnClientClick. Další "dirty" kód :-) - byl bych raději, kdyby byla vlastnost OnClientClick přímo součástí rozhraní IButtonControl, anebo by mohl existovat společný abstraktní předek s vlastností OnClientClick pro třídy ImageButton, LinkButton a Button.

Nový sloupec ve svém projektu použijete takto:

Zaregistrujete nový sloupec na stránce jako běžný serverový ovládací prvek zadáním jména jeho assembly, jmenného prostoru a rezervací prefixu značek pro své prvky v direktivě @Register.

<%@ Register Assembly="Rstein.Web.Ui" Namespace="Rstein.Web.Ui" TagPrefix="custom" %>

Sloupec přidejte do kolekce Columns prvku GridView a nastavte dle svých požadavků vlastnosti sloupce. 

<asp:GridView ID="GridView1" runat="server" AutoGenerateColumns="False" OnRowDeleting="GridView1_RowDeleting">

   <Columns>

         <custom:DeleteButtonField Text="Smazat" ButtonType="Button" ConfirmMessage="Smazat záznam?"/>

   </Columns>

</asp:GridView>

Příště si ukážeme, jak podobné sloupce pro Grid udělat lépe s využitím šablon vytvořených v kódu a také doplníme pro své sloupce kompletní podporu pro design time.

 

  1. Zde je ten zmiňovaný "dirty" postup. I když jsem si ověřil Reflectorem, že prvním prvkem kolekce je vždy IButtonControl vytvářený třídou ButtonColumn, jde o typický případ svazování svého kódu s kódem, jehož  změny nemáte pod kontrolou a přitom se nespoléháte jen na jeho "typově bezpečné" veřejné API, takže se v dalších verzích můžete dočkat nepříjemných překvapení  :( 


Friday, 19 May 2006 17:44:04 (Central Europe Standard Time, UTC+01:00)       
Comments [0]  .NET Framework | ASP.NET


 Thursday, 11 May 2006
První zkušenosti s navigačním programem iGO

Program iGO má nejen exotický maďarský původ, ale také jde v poslední verzi o slušně zpracovaný navigační systém, který vás bez větších problémů dovede tam, kam máte namířeno. A hlavně jako u všech dobrých navigací dorazíte do cílového místa, aniž byste předtím museli vytvářet itinerář cesty hledáním v tištěném Autoatlasu nebo internetové mapě, a k rodinné pohodě přispěje i tím, že už nemusíte za jízdy manželce v roli navigátora po několikerém špatném odbočení nervózně a zvýšeným hlasem vysvětlovat, jak se pozná na papírové mapě, jestli máte odbočit vpravo či vlevo. ;-)

I když jsem k novému programu přistupoval s despektem, musím říci, že moje obavy z práce neznámých maďarských programátorů byly pouhé předsudky a samotné iGO se může i přes drobné chybky v kvalitě navigace směle měřit s programy jako jsou Dynavix či TomTom.

Všechny možné grafické a funkční serepetičky navigace oceníte jen a pouze při práci s navigací v křesle domova, protože za jízdy těžko budete obdïvovat krásné textury objektů na dispĺeji. Plech vašeho vozu by totiž mohl být za chvíli ilustrací fyzikálního zákona pojednávajícího o následcích srážky s pohyblivými i statickými objekty v reálném světě. Proto mám na navigační programy tyto tři hlavní požadavky.

  1. Nejdůležitější součástí každého navigačního programu je intuitivní hlasová navigace - ideální hlasová navigace po celou dobu jízdy přichází s tak přesnými instrukcemi, že se uživatel nemusí ani jednou podívat na displej, aby věděl, jak má na křižovatce zabočit. To znamená, že hlasová navigace by dnes již neměla mít jen jednoduché instrukce ve stylu "zabočte vlevo", "zabočte vpravo" nebo "po 150 m zabočte doprava", ale měla by navigovat uživatele například i v křižovatkách, kde po sobě rychle následují dvě odbočky doprava a jasně mu říci, kterou odbočku má zvolit. Při jízdě po hlavní silnici, kdy dodatková tabulka 'opravdový tvar křižovatky' u značky 'Hlavní silnice' sděluje, že hlavní silnice pokračuje doleva či doprava, musí hlasová navigace v dostatečném předstihu sdělit "jeďte po hlavní silnici" či "pokračujte po hlavní silnici - mírně vpravo", abyste se opět nemuseli o dalším směru jízdy ("rovně po vedlejší" nebo "dále po hlavní") rozhodovat podle toho, co je momentálně zobrazeno na displeji. 
  2. Žádný navigační program zatím s ideální hlasovou navigací nepřišel, a proto se občas při složitějších křižovatkách kouknutí na displej PDA nevyhneme. Abychom zorientováním se v informacích na displeji netrávili zbytečně mnoho času, je nutné, aby navigační program zobrazoval všechny podstatné informace, perfektně vykreslil schéma následujího manévru a nastavil měřítko (zoom) mapy tak, abychom ihned dokázali "namapovat" reálnou křižovatku na zobrazení křížovatky na displeji a věděli, jak ji máme projet. Za standard již považuji 3D pohled. Když je 3D pohled dobře naprogramován, je orientace v mapě citelně jednodušší než při méně přirozeném "plochém" 2D pohledu. Přeplácaná grafika orientaci na mapě  stěžuje - čím více zbytečných detailů, které oceníte snad jen v hrách typu Need For Speed, tím pomalejší orientace v mapě. Od podrobností abstrahující a jen schematické zobrazení míst, kterými právě projíždíte, je pro navigaci mnohem přínosnější než všemi barvami a grafickými efekty opentlená mapa. 
  3.  I když dnes již snad každý program trochu alibisticky zobrazuje upozornění, které nás důrazně varuje před používáním programu za jízdy, občas se přeci jen ovládání programu při řízení nevyhneme. Když se rozhodneme někde naobědvat, budeme chtít rychle vyhledat v zájmových bodech (POI) nejbližší restauraci a nechat se k ní navigovat. Je proto důležité, aby menu bylo přehledné a aby ikonky jednotlivých funkcí v něm byly dostatečně velké. Ikonky musí být čitelné na displeji PDA i z větší dálky a celé menu musí být ovladatelné dotykem prstu, jehož průměr bývá u většiny normálně vyvinutých jedinců přeci jen o něco větší než průměr stylu. K bezpečnému ovládání za jízdy také velkou měrou přispívá velikost tlačítek na softwarové klávesnici pro zadávání názvů měst, ulic a filtrování seznamu zobrazených POI.

Jak si poradí iGO s těmito požadavky ?

Hlasová navigace v iGo je na slušné úrovni. Na výběr máte mužský i ženský hlas v češtině, slovenštině a dalších jazycích. Já jsem většinu času jezdil s navigací v anglickém jazyce, protože mi angličtina přišla příjemnější a méně "strojová" než čeština a slovenština.

Místo zmatených hlášek ve městě s mnoha bočními uličkami, které pouze říkají, co máte udělat za 150 m, ale již Vám nesdělí, jak máte těch 150 m v autě změřit, se dovíte, že  máte zabočit vpravo do následující (první, druhé) ulice. Všechna hlášení jsou také vydávána s dostatečným předstihem  - občas si ale říkáte, že po nájezdu na dálnici není nutné ihned vědět, co se stane za 40 Km, a i by podle mě mohl být zredukován počet hlášení v různých vzdálenostech od odbočky.

Při přímém porovnání s TomTomem byla hlasová navigace přesnější v Benešově při nájezdu na most. iGo naviguje "zabočte mírně vlevo a pak ihned vpravo", kdežto TomTom říká pouze "na konci ulice zahněte doprava", což může být pro řidiče, který se ve městě nevyzná, trochu matoucí. Jednak lze konec mostu za konec ulice považovat jen s velkou dávkou fantazie a také oznámení o prvním mírném zabočení doleva lépe vystihuje to, že při přímé jízdě před  začátkem mostu bychom vjeli do ulice označené značkou zákaz vjezdu. Také v Praze, při mírném odbočování vlevo z ulice U Zdravotního ústavu do ulice Ruská , kde TomTom sveřepě mlčí, i když odbočení doleva při jízdě z opačného směru hlásí, iGo přesně naviguje instrukcí, abychom se drželi na hlavní silnici a odbočili mírně vlevo. TomTom měl zase při hlasové navigaci navrch při nájezdu na dálnici u Mirošovic směrem na Prahu  - aby mě dostal na "správnou" stranu dálnice (tedy do směru na Prahu a ne na Brno), přikáže před nájezdem na dálnici "nyní se budete držet vlevo", což vám zabrání sjet do pravého pruhu a vydat se směrem do Brna. IGo pouze sdělí, že máte najet na dálnici. Také hlášení TomToma, že se blíží sjezd z dálnice, přichází oproti iGo mnohem dříve. Záleží na rychlosti auta - při rychlosti 130 Km/h TomTom oznamuje poprvé sjezd cca 2 km před samotným sjezdem, zatímco u iGo se musíte spokojit s prvním upozorněním ve vzdáleností cca 500 metrů před sjezdem. Přesto je iGo TomTomovi v kvalitě hlasové navigace dobrou konkurencí a v leckterých složitějších dopravních situacích ho i předčí. Za špičku hlasové navigace považuji stále Dynavix  - i v centru Prahy to byla opravdu jediná navigace, která mě dokázala spolehlivě vést jen hlasovými instrukcemi. TomToma i iGO bych dal při hodnocení kvality hlasové navigace společně na druhé místo ihned za Dynavix.

Grafika iGO je vynikající a při prvním pohledu TomToma předčí. Vlaječky označující počátek a konec trasy jsou krásně animované, názvy ulic ve městech perfektně čitelné a vždy české a ne ceske.  Přesto se neorientuji v 2D ani 3D pohledu tak dobře jako u TomToma, ale to může být dáno tím, že iGo používám zatím velmi krátkou dobu. Schéma následujícího manévru v levé části obrazovky a automatický zoom při příjezdu na křižovatku jsou zpracovány dobře a pokud nemáte s čím srovnávat, budete zcela spokojeni. 

Mapové podklady pro Českou republiku mají oba programy od společnosti TeleAtlas a jejich podrobnost je skvělá. U iGo ale nemáte takové množství POI, které existují pro TomToma. Nepříjemně mě překvapilo, že v obchodních centrech zcela absentuje třeba Hypernova Průhonice. Také zamrzí, že nemůžete z nejbližších POI vyfiltrovat jen ty, které leží přímo na trase.

Ovládání menu iGo "jedním prstem" sice možné je, ale při přímém srovnání s ergonomií ovládání TomToma jsou patrné zásadní nedostatky a nedomyšlenosti. Do dalších úrovní menu se dostáváte složitě a také mi moc nevyhovuje, že při hledání místa je nejdříve zobrazen formulář pro vyhledání ulic ve městě, které jste zadali při posledním hledání. Teprve po volbě změnit město se zobrazí města hledaná dříve a když chcete zadat město, které není v historii hledaných měst, musíte volbou 'Jiné město' přejít na obrazovku, kde zadáte několik počátečních písmen z názvu města. Program iGO na formuláři pro zadání názvu města ukazuje jen počet měst vyhovujících zadávaným písmenům, ale samotná města uvidíte až po stisknutí tlačítka Hotovo. Celý postup mi přijde nesmyslně zdlouhavý a krkolomný. Navíc mi tlačítka softwarové  klávesnice připadají příliš titěrná a to i po jejich zvětšení stisknutím tlačítka Klávesy.

Také první a graficky netradiční rozcestník hlavního menu volbou barev a písma nechtěně vizuálně potlačuje centrální a nejdůležitější položku menu s názvem "Vyhledat a jet". Co naopak u iGo chválím je plánování složitějších cest přes více míst ("lomových bodů"). 

V ergonomii ovládání TomTom jasně vítězí a iGo bude muset hodně zapracovat na odstranění zmíněných nedostatků.

Závěrečné postřehy:

  • U iGo stejně jako u TomToma bych vyzdvihl bezproblémovou konfiguraci a práci s GPS. (BT GPS Holux 230 - spárována s MDA Vario). 
  • Program s celou mapou Evropy se vejde na jednu 1GB SD kartu. Program iGo se prodává na SD kartě a k jeho instalaci dojde po zasunutí SD karty do slotu. Jak asi tušíte, na kartě je adresář s "wellknown" názvem 2577, ze kterého Windows Mobile po zasunutí karty vždy automaticky spustí  soubor nazvaný autorun.exe.
  • Zadání zeměpisných souřadnic.
  • Upozornění na překročení zadané rychlosti.
  • ""Ladění" algoritmu pro výpočet trasy - volba mezi nejrychlejší, nejkratší a ekonomickou trasou. "Profily" trasy pro vozidla, kola, chodce. Možnost zakázat dálnice nebo jen placené dálnice a také třeba nezpevněné cesty.
  • Zakázání obratů do protisměru. Tato funkce ale podle mě nefunguje správně - i když jsem obraty do protisměru zakázal, přesto po mě iGo párkrát při odchýlení se od stanovené trasy obrat do protisměru vyžadoval.
  • Možnost zakázat při výpočtu vnitrostátní trasy cestu přes hranice s okolními státy.
  • Automatické přepínání mezi denním a nočním barevným profilem.

Obrázková galerie iGo:

Rychlý přístup k některým položkám menu   Hlavní menu  Menu v mapě  Režim navigace  Seznam POI  Body zájmu (POI) - kina   Nastavení parametrů trasy   Mapa  - ulice Vinohradská, Praha

Thursday, 11 May 2006 15:41:31 (Central Europe Standard Time, UTC+01:00)       
Comments [3]  Mobilitky | Navigace


 Monday, 01 May 2006
Odstranění problému(ů) s instalací Compact .NET Frameworku 2.0 na Windows Mobile 5.0

Již několikrát jsem se setkal s tím, že na zařízení s Windows Mobile 5.0 byl problém nainstalovat Compact .Net Framework. Pár lidí si mi teď v poslední době v mailu nebo na ICQ také stěžovalo, že jim CNF nejde nainstalovat a že při pátrání po příčinách potíží dostanou nanejvýš jen hlášku, že došlo k problému a že by bylo dobré resetovat zařízení a zkusit instalaci znovu. ;-) Tomu říkám skvělé design guidelines v praxi - "nikdy neobtěžujte uživatele zbytečnými technickými podrobnostmi". :-D

Odstranění problému není složité - z důvodu známému asi jen vývojářskému týmu CNF v Microsoftu a mystérióznímu pro běžného smrtelníka ActiveSync při instalaci CNF z počítače přes Application Manager zkopíruje do zařízení soubor nazvaný NETCFv2.wce5.armv4i.cab místo správného NETCFv2.wm.armv4i.cab. NETCFv2.wm.armv4i.cab patří na všechna současná zařízení používající procesor s ARM V4 kompatibilní instrukční sadou.

Takže selže-li vám instalace, najděte na počítači soubor NETCFv2.wm.armv4i.cab, a zkopírujte jej "ručně" (v Exploreru nebo třeba v v Total Commanderu s pluginem pro CE zařízení)  do PDA. V PDA pak jen cab soubor spusťte a VŽDY zvolte instalaci CNF do zařízení - instalace na kartu (SD, CF) je sice možná, ale při prvním použití CNF dojde stejně k nakopírování všech knihoven do storage v zařízení.

Pokud máte stále problémy s instalací:

  1. Resetujte (soft-reset) zařízení a zkuste nainstalovat CNF znovu. Teď už má reset smysl. ;-)
  2. Nezabere-li ani to, vypněte po dobu instalace všechny Today pluginy, restartujte (soft reset) a znovu instalujte.

Přinejhorším bod 2 zabere podle mých zkušeností vždy - ještě se mi nestalo, že bych na nějaké PDA s WM 5 CNF 2 nenainstaloval ;-)

 



Monday, 01 May 2006 21:50:26 (Central Europe Standard Time, UTC+01:00)       
Comments [0]  Compact .Net Framework | Mobilitky