Jazyk XAML nejen pro WPF a Silverlight

Tomáš Holan       8. 11. 2011       Architektura, XML       6281 zobrazení

Jazyk XAML je nejvíce spojován s technologiemi WPF, Silverlight a pak ještě Windows Workflow Foundation 4.0. Zatímco u prvních dvou zmiňovaných technologiích se používá pro deklarativní popis uživatelského rozhraní, u třetí se používá pro definici modelovaných workflow procesů. O těchto technologiích ale dnes řeč nebude. XAML je totiž samostatný jazyk, který nemusí být využíván výhradně s těmito technologiemi, ale naopak ho můžeme použít i v podstatě libovolně kdekoliv jinde. V tomto článku se tedy podíváme na to co nám XAML umožňuje ve spojení s našimi vlastními objekty.

Pro příklad budeme uvažovat scénář, kdy máme vlastní infrastrukturu modulů, které autonomně plní nějaké činnosti (backup dat, import dat z externího datového zdroje apod.). Chceme konfigurovat, jaké z těchto modulů se mají při startu aplikace (např. NT služba) zavést a s jakým nastavením. Dále chceme umožnit, aby tyto moduly mohli případně pocházet z různých rozšiřujících assembly, které by (kromě změny konfigurace) stačilo k aplikaci přihrát.

V našem případě použijeme XAML právě pro popis konfigurace zmiňovaných modulů a napíšeme obecný kód, který podle ní bude moduly zavádět.

Nejprve potřebujeme definici konkrétních tříd, představující naše moduly. V našem ilustrativním příkladu to budou tyto třídy:

  • Backup (v namespace XamlImportSample)
  • ImportCsv (v namespace XamlImportSample.Import)
  • ImportDb (v namespace XamlImportSample.Import)
using System;

namespace XamlImportSample
{
    internal interface IModule : IDisposable
    {
        void Initialize();
    }

    public class Backup : IModule
    {
        #region member varible and default property initialization
        public string OutputDirectory { get; set; }
        public TimeSpan StartTime { get; set; }
        public TimeSpan Interval { get; set; }
        public bool RunOnStart { get; set; }
        #endregion

        #region constructors and destructors
        public Backup()
        {
            this.Interval = TimeSpan.FromDays(1);
        }
        #endregion

        #region action methods
        public void Initialize()
        {
            //Inicializace scheduleru
            Console.WriteLine("Backup: OutputDirectory=\"{0}\", StartTime={1}, Interval={2}, RunOnStart={3}", this.OutputDirectory, this.StartTime, this.Interval, this.RunOnStart);
        }

        public void Dispose()
        {
            //Uvolnění objektu
        }
        #endregion
    }
}

namespace XamlImportSample.Import
{
    public abstract class Import : IModule
    {
        #region action methods
        public abstract void Initialize();
        public virtual void Dispose(bool disposing) { }

        public void Dispose()
        {
            Dispose(true);
        }
        #endregion
    }

    public class ImportCsv : Import
    {
        #region member varible and default property initialization
        public string InputFilename { get; set; }
        public string WorkingFolder { get; set; }
        public string ArchiveFolder { get; set; }
        public bool IgnoreFirstRow { get; set; }
        #endregion

        #region action methods
        public override void Initialize()
        {
            //Inicializace filewatcheru
            Console.WriteLine("ImportCsv: InputFilename=\"{0}\", WorkingFolder=\"{1}\", ArchiveFolder=\"{2}\", IgnoreFirstRow={3}", this.InputFilename, this.WorkingFolder, this.ArchiveFolder, this.IgnoreFirstRow);
        }

        public override void Dispose(bool disposing)
        {
            //Uvolnění objektu
        }
        #endregion
    }

    public class ImportDb : Import
    {
        #region constants
        private const string DefaultProviderName = "System.Data.SqlClient";
        #endregion

        #region member varible and default property initialization
        public string ConnectionString { get; set; }
        public string ProviderName { get; set; }
        public TimeSpan StartTime { get; set; }
        public TimeSpan Interval { get; set; }
        public bool RunOnStart { get; set; }
        #endregion

        #region constructors and destructors
        public ImportDb()
        {
            this.Interval = TimeSpan.FromDays(1);
        }
        #endregion

        #region action methods
        public override void Initialize()
        {
            if (string.IsNullOrEmpty(this.ProviderName))
            {
                this.ProviderName = DefaultProviderName;
            }

            //Inicializace scheduleru
            Console.WriteLine("ImportDb: ConnectionString=\"{0}\", ProviderName=\"{1}\", StartTime={2}, Interval={3}, RunOnStart={4}", this.ConnectionString, this.ProviderName, this.StartTime, this.Interval, this.RunOnStart);
        }

        public override void Dispose(bool disposing)
        {
            //Uvolnění objektu
        }
        #endregion
    }
}

Všechny tyto třídy implementují společný interface IModule, který bude umožňovat tyto moduly inicializovat nebo uvolňovat. Při inicializaci je ve třídách proveden pouze výpis naplnění všech public vlastností aktuálního objektů.

Nyní si ukážeme jak může vypadat příklad konkrétního dokumentu v jazyce XAML tj. konfigurace, která bude definovat, které s těchto modulů se mají aktuálně zavést:

<?xml version="1.0" encoding="utf-8" ?>
<Modules xmlns="clr-namespace:XamlImportSample;assembly=XamlImportSample" xmlns:import="clr-namespace:XamlImportSample.Import;assembly=XamlImportSample">
  <Backup StartTime="01:00" OutputDirectory="c:\XamlImportSample\Backup"/>
  <import:ImportCsv InputFilename="c:\XamlImportSample\Import\Input.csv" WorkingFolder="c:\XamlImportSample\Import\Work" ArchiveFolder="c:\XamlImportSample\Import\Archive"/>
  <import:ImportDb ConnectionString="Data Source=.;Initial Catalog=ExternalDb;Integrated Security=True" ProviderName="System.Data.SqlClient" Interval="01:00:00"/>
</Modules>

Jak vidíme, jedná se opravdu o validní XAML dokument se všemi náležitostmi (výjimkou je root element “Modules”). Jen ve stručnosti zopakuji, že elementy odpovídají třídám a atributy jejich veřejným vlastnostem. U elementů je důležitý XML namespace , protože ten přes clr-namespace: definuje o jaký .NET datový typ se jedná a v jaké assembly se tento typ nachází. Přitom může být použit výchozí i pojmenovaný XML namespace. Podmínkou dále je, že se musí vždy jednat o public datové typy.

Téměř posledním krokem bude vytvoření pomocné třídy, která bude provádět vlastní načtení XAML konfigurace a inicializaci modulů. Třídu pojmenujeme ModuleManager:

/// <summary>
/// Načte externí moduly
/// </summary>
internal class ModuleManager : IDisposable
{
    #region member varible and default property initialization
    private IList<IDisposable> Disposables;
    #endregion

    #region constructors and destructors
    public ModuleManager(string modules)
    {
        this.Disposables = new List<IDisposable>();

        var root = XElement.Parse(modules);

        foreach (var el in root.Elements())
        {
            try
            {
                var module = (IModule)XamlServices.Parse(el.ToString());
                module.Initialize();

                this.Disposables.Add(module);
            }
            catch (System.Exception ex)
            {
                throw new System.Configuration.ConfigurationErrorsException(string.Format("Module '{0}' initialization error: ", el.Name.LocalName) + ex.Message, ex);
            }
        }
    }
    #endregion

    #region action methods
    public void Dispose()
    {
        if (this.Disposables != null)
        {
            foreach (var disposable in this.Disposables)
            {
                disposable.Dispose();
            }
            this.Disposables = null;
        }
    }
    #endregion
}

Update: Kód jsem lehce poupravil tak, aby místo původní třídy XamlReader (z namespace System.Windows.Markup) používal pouze třídu XamlServices (z namespace System.Xaml). Rozdíl je v tom, že třída XamlReader je primárně určena pro WPF-XAML (vnitřně používá XamlServices, ale navíc podporuje nějaké prvky specifické jen pro WPF, které se v našem scenáři vyskytovat nebudou) a je potřeba navíc reference na assembly PresentationFramework.dll.


Vše důležité se děje v konstruktoru této třídy, který používá objekt XamlServices (z namespace System.Xaml). Pro jeho použití musíme ještě do našeho projektu přidat reference na assembly System.Xaml.dll. XamlServices nám právě umožňují automaticky vytvořit a vrátit objektový strom popsaný jazykem XAML. Všimněte si ale, že my moduly načítáme postupně po jednom tj. ke každému podelementu elementu “Modules” se chováme jako k samostatnému XAML dokumentu. To je hlavně z toho důvodu, aby volaná inicializace modulu proběhla dříve než konstrukce modulu dalšího (pro korektní pořadí akcí v případě výjimky).

Tím je vše připravené a můžeme naší implementovanou “infrastrukturu” otestovat:

static void Main()
{
    string xml = 
      @"<Modules xmlns=""clr-namespace:XamlImportSample;assembly=XamlImportSample"" xmlns:import=""clr-namespace:XamlImportSample.Import;assembly=XamlImportSample"">
          <Backup StartTime=""01:00"" OutputDirectory=""c:\XamlImportSample\Backup""/>
          <import:ImportCsv InputFilename=""c:\XamlImportSample\Import\Input.csv"" WorkingFolder=""c:\XamlImportSample\Import\Work"" ArchiveFolder=""c:\XamlImportSample\Import\Archive""/>
          <import:ImportDb ConnectionString=""Data Source=.;Initial Catalog=ExternalDb;Integrated Security=True"" ProviderName=""System.Data.SqlClient"" Interval=""01:00:00""/>
        </Modules>";

    using (var moduleManager = new ModuleManager(xml))
    {
        Console.ReadLine();
    }
}
Backup: OutputDirectory="c:\XamlImportSample\Backup", StartTime=01:00:00, Interval=1.00:00:00, RunOnStart=False
ImportCsv: InputFilename="c:\XamlImportSample\Import\Input.csv", WorkingFolder="c:\XamlImportSample\Import\Work", ArchiveFolder="c:\XamlImportSample\Import\Archive", IgnoreFirstRow=False
ImportDb: ConnectionString="Data Source=.;Initial Catalog=ExternalDb;Integrated Security=True", ProviderName="System.Data.SqlClient", StartTime=00:00:00, Interval=01:00:00, RunOnStart=False

Výsledkem je objekt ModuleManager “naplněn” zavedenými moduly, viz uvedený výpis.

Dále si můžete vyzkoušet, že v případě chybné konfigurace nebo v případě vyhození výjimky v metodě Initialize některého z modulů, by byla vyhozena výjimka ConfigurationErrorsException s informací jaký modul se nepovedlo zavést.

Jazyk XAML a třídu XamlServices lze tímto způsobem použít i pro konstrukci složitějších objektových stromů, včetně vnořených objektů, kolekcí apod., zaleží pak čistě na konkrétním scénáři.

Jiný příklad na použití třídy XamlServices také naleznete například zde.

 

hodnocení článku

2 bodů / 2 hlasů       Hodnotit mohou jen registrované uživatelé.

 

Nový příspěvek

 

RE: Jazyk XAML nejen pro WPF a Silverlight

Ještě doplním další netradiční použití jazyka XAML.

V XAMLu se totiž dá popsat i uživatelské rozhraní v technologii Windows.Forms, přiklad takového XAML může vypadat např. takto:

<Form x:Class="XAMLForWinform.Winform"

xmlns="clr-namespace:System.Windows.Forms;assembly=System.Windows.Forms"

xmlns:x="http://schemas.microsoft.com/winfx/2006/..."

MaximizeBox="False" MinimizeBox="False" Text="A Form" Width="300" Height="150" BackColor="AliceBlue">

<Form.Controls>

<GroupBox x:Name="ctlGroupBox" Text="My Winform Application" Dock="Fill">

<GroupBox.Controls>

<Label x:Name="ctlLabel" AutoSize="True" Text="Message:" Location="12, 31" Size="36, 13"/>

<Button x:Name="ctlButton" Text="Show Message" UseVisualStyleBackColor="True" Location="179, 54" Size="93, 23"

Click="Button_Click"/>

<TextBox x:Name="ctlTextBox" Location="70, 28" Size="200, 20"/>

</GroupBox.Controls>

</GroupBox>

</Form.Controls>

</Form>

Více se můžete dočíst na http://code.msdn.microsoft.com/Use-XAML-....

nahlásit spamnahlásit spam 0 odpovědětodpovědět
                       
Nadpis:
Antispam: Komu se občas házejí perly?
Příspěvek bude publikován pod identitou   anonym.

Nyní zakládáte pod článkem nové diskusní vlákno.
Pokud chcete reagovat na jiný příspěvek, klikněte na tlačítko "Odpovědět" u některého diskusního příspěvku.

Nyní odpovídáte na příspěvek pod článkem. Nebo chcete raději založit nové vlákno?

 

  • Administrátoři si vyhrazují právo komentáře upravovat či mazat bez udání důvodu.
    Mazány budou zejména komentáře obsahující vulgarity nebo porušující pravidla publikování.
  • Pokud nejste zaregistrováni, Vaše IP adresa bude zveřejněna. Pokud s tímto nesouhlasíte, příspěvek neodesílejte.

přihlásit pomocí externího účtu

přihlásit pomocí jména a hesla

Uživatel:  
Heslo:  

zapomenuté heslo

 

založit nový uživatelský účet

zaregistrujte se

 
zavřít

Nahlásit spam

Opravdu chcete tento příspěvek nahlásit pro porušování pravidel fóra?

Nahlásit Zrušit

Chyba

zavřít

feedback