V předchozím článku jsem popisoval vytváření prvku s šablonami pro přihlášené a nepřihlášené uživatele, který jsem nazval LoginControl. LoginControl ale prozatím neposkytuje zhýčkanému vývojáři pohodlí, na něž si zvykl u standardních ASP.NET prvků se šablonou. Pohodlí při práci je dosaženo hlavně „drag and drop“ vytvářením obsahu šablon, kdy namísto ručního zadávání značek ovládacích prvků vývojář pouze požadované ovládací prvky přetáhne z panelu nástrojů do šablony. V tomto článku vytvoříme designera pro LoginControl, který „drag and drop“ definici obsahu šablon LoggedInTemplate a AnonymousTemplate v RAD nástrojích zpřístupní.

Nenápadný společník

Designerem se v .NET Frameworku rozumí speciální třída, jež funguje jako inteligentní a nenápadný prostředník mezi RAD nástrojem a komponentou. Pro naše účely rozsah pojmu komponenta zúžíme na serverové ovládací prvky v ASP.NET. Designer RAD nástroji pomáhá vytvořit v návrhovém režimu vizuální reprezentaci ovládacího prvku, aby měl vývojář neustále před sebou výsledky své práce. Designer ale dokáže mnohem více, například výměna deskriptoru vlastnosti DataSource ovládacích prvků způsobí, že po kliknutí na vlastnost DataSource na stránce vlastností jsou vyhledány a zobrazeny všechny dostupné datové zdroje na WWW fomuláři.

Když RAD nástroje při vývoji nepoužíváte, přítomnost ani absenci designeru u ovládacího prvku nijak nezaznamenáte, protože jen RAD nástroj jako je VS.NET plně využije potenciál kvalitního designeru. Psaní designerů je komplikovaná záležitost a nelze ji plně postihnout v jednom článku, proto se dnes soustředím na vytvoření designeru pro prvky se šablonou. K designerům a obecně návrhovému režimu v ASP.NET se ale vrátím v dalších článcích.

Vytvoření designeru pro LoginControl

Designer pro LoginControl s názvem LoginControlDesigner kromě podpory pro drag&drop definici obsahu šablony informuje po přenesení na stránku vývojáře, jak může zvolit šablonu pro editaci, a dokáže v návrhovém režimu renderovat obsah šablony AnonymousTemplate. Designer si můžete ale upravit tak, že bude renderovat obsah šablony LoggedInTemplate či obě šablony najednou. Jestliže při renderování prvku v návrhovém režimu dojde k výjimce, je uživateli zobrazena informace, že prvek nelze vykreslit. Celý kód i s komentáři si můžete stáhnout a otestovat.

public class LoginControlDesigner : TemplatedControlDesigner
{
  private const string LOGGEDIN_TEMPLATE_VERB = „Editovat šablonu LoggedInTemplate“;
  private const string ANONYMOUS_TEMPLATE_VERB = „Editovat šablonu AnonymousTemplate“;
  private TemplateEditingVerb[] m_tEVerbs;
  public LoginControlDesigner() : base()
  {
  }
  public override bool AllowResize
  {
    get
    {
      LoginControl lc = (LoginControl) Component;
      return ((lc.AnonymousTemplate != null) || (lc.LoggedInTemplate != null) || (InTemplateMode));
    }
  }
  public override void Initialize(IComponent component)
  {
    if (!(component is LoginControl))
      throw new ArgumentException(„This designer is only for LoginControl“, „component“);
    base.Initialize (component);
  }
  public override string GetDesignTimeHtml()
  {
    LoginControl lc = (LoginControl) Component;
    if ((lc.AnonymousTemplate == null) && (lc.LoggedInTemplate == null))
      return GetEmptyDesignTimeHtml();
    string retHTML = String.Empty;
    try
    {
      lc.DataBind();
      retHTML = base.GetDesignTimeHtml();
    }
    catch (Exception e)
    {
      retHTML = GetErrorDesignTimeHtml(e);
    }
    return retHTML;
  }
  public override string GetTemplateContent(ITemplateEditingFrame editingFrame, string templateName, out bool allowEditing)
  {
    allowEditing = true;
    LoginControl lc = (LoginControl) Component;
    string retTemplate = String.Empty;
    if ((m_tEVerbs != null) && ((editingFrame.Verb.Text == LOGGEDIN_TEMPLATE_VERB) || (editingFrame.Verb.Text == ANONYMOUS_TEMPLATE_VERB)))
    {
      ITemplate template = (editingFrame.Verb.Text == LOGGEDIN_TEMPLATE_VERB ? lc.LoggedInTemplate : lc.AnonymousTemplate);
      if (template != null)
        retTemplate = GetTextFromTemplate (template);
    }
    return retTemplate;
  }
  public override void SetTemplateContent(ITemplateEditingFrame editingFrame, string templateName, string templateContent)
  {
    LoginControl lc = (LoginControl) Component;
    if ((m_tEVerbs != null) && ((editingFrame.Verb.Text == LOGGEDIN_TEMPLATE_VERB) || (editingFrame.Verb.Text == ANONYMOUS_TEMPLATE_VERB)))
    {
      ITemplate newTemplate = null;
      if ((templateContent != null) && (templateContent != String.Empty))
        newTemplate = GetTemplateFromText(templateContent);
        if (editingFrame.Verb.Text == LOGGEDIN_TEMPLATE_VERB)
          lc.LoggedInTemplate = newTemplate;
        else
          lc.AnonymousTemplate = newTemplate;
    }
  }
  public override void OnComponentChanged(object sender, ComponentChangedEventArgs ce)
  {
    base.OnComponentChanged (sender, ce);
    if (ce.Member != null)
    {
      if ((ce.Member.Name == „Font“) || (ce.Member.Name == „ForeColor“) || (ce.Member.Name == „BackColor“))
        clearVerbs();
    }
  }
  protected override string GetEmptyDesignTimeHtml()
  {
    return CreatePlaceHolderDesignTimeHtml(„Klikněte pravým tlačítkem na prvek a z kontextového menu vyberte šablonu, kterou chcete editovat.“);
  }
  protected override TemplateEditingVerb[] GetCachedTemplateEditingVerbs()
  {
    if (m_tEVerbs == null)
    {
      m_tEVerbs = new TemplateEditingVerb[2];
      m_tEVerbs[0] = new TemplateEditingVerb(LOGGEDIN_TEMPLATE_VERB, 0, this);
      m_tEVerbs[1] = new TemplateEditingVerb(ANONYMOUS_TEMPLATE_VERB, 1, this);
    }
    return m_tEVerbs;
  }
  protected override ITemplateEditingFrame CreateTemplateEditingFrame(TemplateEditingVerb verb)
  {
    ITemplateEditingFrame retFrame = null;
    LoginControl lc = (LoginControl) Component;
    if ((m_tEVerbs != null) && ((verb.Text == LOGGEDIN_TEMPLATE_VERB) || (verb.Text == ANONYMOUS_TEMPLATE_VERB)))
    {
      ITemplateEditingService editingService = (ITemplateEditingService) GetService(typeof(ITemplateEditingService));
      if (editingService != null)
      {
        Style style = lc.ControlStyle;
        string[] templateName = {verb.Text};
        retFrame = editingService.CreateFrame(this, verb.Text, templateName, style, null);
      }
    }
    return retFrame;
  }
  private void clearVerbs()
  {
    foreach (TemplateEditingVerb verb in m_tEVerbs)
      verb.Dispose();
    m_tEVerbs = null;
  }
}

LoginControlDesigner dědí z třídy TemplateControlDesigner. Designery pro serverové ovládací prvky musí přímo či nepřímo dědit z třídy ControlDesigner. Třída TemplatedControlDesigner je potomkem třídy ControlDesigner a obsahuje užitečné metody od tvůrců ASP.NET, které znatelně usnadňují práci se šablonami.

Privátní konstanty LOGGED_IN_TEMPLATE_VERB a ANONYMOUS_TEMPLATE_VERB deklarují názvy takzvaných editačních klíčových slov. I u designerů je na počátku symbolické slovo, i když ne tvořící a většinou ne jedno, ale spíše více slov pro editaci prvku, která si vyžádá RAD nástroj a přidá je například do kontextového menu ovládacího prvku. Editační slovo pro šablonu (TemplateEditingVerb) je speciální objekt, který nás provází po celou dobu vytváření obsahu šablony a dovoluje rozlišovat mezi různými šablonami. Do privátního pole m_tEVerbs budeme ukládat editační slova pro šablony LoggedInTemplate a AnonymousTemplate.

Editační slova vytváří metoda GetCachedTemplateEditingVerbs. Do konstruktoru objektu TemplateEditingVerb je předáván název slova, relativní pozice v menu a odkaz na designer, který objekt vytvořil.

Když uživatel vybere v kontextovém menu LoginControlu, že chce editovat některou ze šablon, je volána chráněná metoda CreateTemplateEditingFrame, která v argumentu verb dostane odkaz na vývojářem zvolené editační slovo. Metoda musí vytvořit editační rámec pro šablonu, do kterého může vývojář umístit prvky. Metoda nejdříve zkontroluje, že editační slova již byla vytvořena a že předané editační slovo je designerem podporováno. Dále je vytvořena instance speciální služby, která podporuje rozhraní ITemplateEditingService. Designer se nestará o to, jakou konkrétní třídu dostane, to je zcela na RAD nástroji.

Jak jste si asi již uvědomili, závislost designeru pouze na rozhraní a nikoli na specifické třídě je velmi výhodná, protože designer je možné použít v libovolném RAD nástroji, který poskytne vlastní specifickou realizaci rozhraní. Z rozhraní použijeme metodu CreateFrame, které předáme odkaz na designer, dále požadovaný nadpis rámce, názvy šablon, které chceme editovat a aktuální styl LoginControlu. Poslední argument nepoužijeme, slouží k zadání dalších editačních stylů. V jednom rámci lze editovat více šablon, stačí do pole templateName přidat název další šablony. Vytvořený editační rámec vrátíme.

Editační rámec musíme naplnit existujícím obsahem šablony. K tomu slouží metoda GetTemplateContent, která vrátí textovou reprezentaci šablony. Šablonu (rozhraní ITemplate) konvertujeme na text pomocnou metodou GetTexFromTemplate z bázové třídy.

Když vývojář skončí s úpravami šablony, musíme z editačního rámce extrahovat novou definici šablony a převést ji na rozhraní ITemplate. Převod zajistíme přepsáním metody SetTemplateContent, v níž budeme volat pomocnou metodu GetTemplateFromText, která z řetězce v argumentu contentTemplate vytvoří rozhraní ITemplate. Novou šablonu uložíme do správné vlastnosti prvku LoginControl.

Vlastnost AllowResize povoluje nebo zakazuje změnu velikosti prvku. Velikost prvku je možné měnit, když existuje alespoň jedna vývojářem vytvořená šablona pro přihlášeného či nepřihlášeného uživatele, a také tehdy, když definice jakékoli šablony právě probíhá.

V metodě Initialize zkontrolujeme, zda komponenta, kterou bude designer v návrhovém režimu podporovat, je instancí prvku LoginControl. Když tomu tak není, vyvoláme výjimku, protože náš designer jiný ovládací prvek nepodporuje.

Metoda GetDesignTimeHtml vrátí HTML značky pro LoginControl, které budou vykresleny v návrhovém režimu na formuláři. Nejdříve proběhne kontrola na existenci uživatelské šablony LoggedInTemplate a AnonymousTemplate. Když ani jedna šablona neexistuje, je pomocí metody GetEmptyDesignTimeHtml vrácen HTML kód pro prvek, který ještě uživatel neupravil. Pokud alespoň jedna šablona existuje, je na LoginControlu zavolána metoda DataBind, takže dojde k vyhodnocení datově orientovaných výrazů a poté je zavolána implementace metody GetDesigTimeHtml bázové třídy, která vrátí výsledek volání metody RenderControl prvku LoginControl. Jak jsem již psal výše, prvek v návrhovém režimu vždy renderuje anonymní šablonu, ať už výchozí nebo definovanou uživatelem, ale toto chování můžete upravit. Když při renderování prvku dojde k chybě, je vrácen výsledek volání metody GetErrorDesignTimeHtml.

Metoda OnComponentChange vyvolává událost ComponentChange při změně vlastností prvku LoginControl. My chceme reagovat na změnu fontu, barvy popředí a barvy pozadí prvku LoginControl. Aby obsah editačního rámce šablon přizpůsobil svůj vzhled novým hodnotám vlastností, smažeme existující editační slova privátní metodou clearVerbs a donutíme tak designer, aby je vytvořil znovu a nové hodnoty vlastností prvku LoginControl přenesl také do prvků v editačních rámcích.

LoginControl je dekorován metaatributem Designer, který RAD nástrojům sděluje, že mají použít náš nový LoginControlDesigner.

[Designer(typeof(LoginControlDesigner))]

Ve VS.NET se po kliknutí pravým tlačítkem myši na LoginControl zobrazí kontextové menu s položkou Editing, v níž jsou příkazy „Editovat šablonu LoggedInTemplate“ a „Editovat šablonu AnonymousTemplate“, které iniciují úpravy příslušné šablony.

Žádný příspěvek v diskuzi

Odpovědět