Přejít k navigační liště

Zdroják » Různé » Dynamicky generované komponenty v Silverlightu 2.0

Dynamicky generované komponenty v Silverlightu 2.0

Články Různé

V článku si na několika jednoduchých příkladech ukážeme, jak lze dynamicky vytvářet komponenty za běhu aplikace, a jak můžete s takovými komponentami pracovat. V jednotlivých příkladech si postup porovnáme s jejich tvorbu pomocí XAML.

Při tvorbě jakýchkoli aplikací narazíte čas od času na problém, že zobrazení určitých komponent či grafického prvku je známo až za běhu aplikace. V praxi tato situace může například nastat, když tvoříte anketu a máte otázku: Kouříte? Jako odpověď máte dva RadioButtony a po zvolení „Ano“ bude chtít zobrazit textové pole s popiskem: Jakou značku cigaret?

Pokud chcete vytvořit v Silverlightu jakoukoli komponentu, máte dvě možnosti. První možností je definování v XAMLu a druhou definování v kódu aplikace (Visual Basic, C#). Pokud se podíváme do knihovny MSDN na jakoukoli komponentu (např. CheckBox ), vždy nalezneme ukázku jak komponentu vytvořit v obou případech.

Jak na to?

Pojďme si na jednoduchém příkladu ukázat, jakou strukturu má zápis komponenty v C#. Vytvoříme si CheckBox, který bychom jinak zapsali v XAMLu následovně:

<CheckBox x:Name="cb"
          Content="Označ mě!"
          FontFamily="Times New Roman"
          FontSize="14"/> 

Kód v C# bude vypadat takto:

CheckBox cb = new CheckBox();
cb.Content = "Označ mě!";
cb.FontFamily = new FontFamily("Times New Roman");
cb.FontSize = 14; 

Struktura zápisu je jednoduchá. Nejprve vytvoříme instanci třídy CheckBox a následně definujeme její atributy. Je nutné dávat pozor na typy atributů, jelikož některé z nich jsou definované pomocí jiného objektu nebo výčtu (enumeration). V ukázce je to vidět například u definice fontu. Dále se s tím setkáte například u definice barvy ( Colors.Orange) nebo u definování stylu písma ( FontStyles.Italic).

Zařazení

Dalším krokem při definování komponent je jejich zařazení. Když tvoříte komponentu v XAMLu a chcete aby byla zařazena v nějaké tabulce, Canvasu nebo StackPanelu, vytvoříte jí prostě uvnitř daného prvku.

Pokud však tvoříte komponenty z logiky musíte nějak říci dané komponentě do jakého „kontejneru“ patří. To uděláme pomocí zápisu:

LayoutRoot.Children.Add(cb); 

Definování cizích (nevlastních) atributů

Stejně tak jako v XAMLu definujeme například zařazení do tabulky pomocí atributů GridRow a GridColumn přímo v těle komponenty, musíme i v C# tyto atributy definovat. Zápis se trochu liší, jelikož atributy GridRow a GridColumn nejsou vlastními atributy komponenty. Definujeme je tedy pomocí metody SetValue() .

XAML:

<CheckBox x:Name="cb"
          Content="Označ mě!"
          FontFamily="Times New Roman"
          FontSize="14"
          Grid.Column="1"
          Grid.Row="1"/> 

C#:

CheckBox cb = new CheckBox();
cb.Content = "Označ mě!";
cb.FontFamily = new FontFamily("Times New Roman");
cb.FontSize = 16;
cb.FontStyle = FontStyles.Italic;
cb.SetValue(Grid.RowProperty, 1);
cb.SetValue(Grid.ColumnProperty, 1); 

Podobně by to vypadalo i v případě Canvasu:

CheckBox cb = new CheckBox();
cb.Content = "Označ mě!";
cb.FontFamily = new FontFamily("Times New Roman");
cb.FontSize = 16;
cb.FontStyle = FontStyles.Italic;
cb.SetValue(Canvas.TopProperty, 10);
cb.SetValue(Canvas.LeftProperty, 10); 

Příklad

Pokud budeme chtít, můžeme si dynamicky vytvořit i celý formulář společně s tabulkou, kterou použijeme pro rozložení formuláře:

//tabulka
Grid gr = new Grid();
gr.Height = 300;
gr.Width = 600;

//definice radku
RowDefinition rd1 = new RowDefinition();
rd1.Height = new GridLength(30);

RowDefinition rd2 = new RowDefinition();
rd2.Height = new GridLength(30);

//prirazeni definice
gr.RowDefinitions.Add(rd1);
gr.RowDefinitions.Add(rd2);

//definice sloupcu
ColumnDefinition cd1 = new ColumnDefinition();
cd1.Width = new GridLength(100);

ColumnDefinition cd2 = new ColumnDefinition();
cd2.Width = new GridLength(500);

//prirazeni definice
gr.ColumnDefinitions.Add(cd1);
gr.ColumnDefinitions.Add(cd2);

//vutvoreni a vlozeni komponent
//Jmeno
TextBlock tb_jmeno = new TextBlock();
tb_jmeno.Text = "Jméno a Příjmení:";
tb_jmeno.HorizontalAlignment = HorizontalAlignment.Right;
tb_jmeno.SetValue(Grid.ColumnProperty, 0);
tb_jmeno.SetValue(Grid.RowProperty, 0);

TextBox tbx_jmeno = new TextBox();
tbx_jmeno.SetValue(Grid.ColumnProperty, 1);
tbx_jmeno.SetValue(Grid.RowProperty, 0);

//Pohlavi
TextBlock tb_pohlavi = new TextBlock();
tb_pohlavi.Text = "Pohlaví:";
tb_pohlavi.HorizontalAlignment = HorizontalAlignment.Right;
tb_pohlavi.SetValue(Grid.ColumnProperty, 0);
tb_pohlavi.SetValue(Grid.RowProperty, 1);

//vytvoreni StackPanelu pro razeni
StackPanel sp = new StackPanel();
sp.Orientation = Orientation.Horizontal;
sp.SetValue(Grid.ColumnProperty, 1);
sp.SetValue(Grid.RowProperty, 1);

RadioButton rb_muz = new RadioButton();
rb_muz.Content = "muž";
rb_muz.GroupName = "pohlavi";
sp.Children.Add(rb_muz);

RadioButton rb_zena = new RadioButton();
rb_zena.Content = "žena";
rb_zena.GroupName = "pohlavi";
sp.Children.Add(rb_zena);


//prirazeni vsech komponent do tabulky
gr.Children.Add(tb_jmeno);
gr.Children.Add(tbx_jmeno);
gr.Children.Add(tb_pohlavi);
gr.Children.Add(sp); 

Ve zdrojovém kódu si můžeme všimnout způsobu tvorby řádků a sloupců v tabulce. Nejprve si vytvoříme tabulku a následně si vytvoříme objekty definice řádků a sloupců, které následně přiřadíme k tabulce.

Události

Ukázali jsme si, jak dynamicky vytvářet komponenty a jak je vkládat do „kontejnerů“. Ale nastanou i případy, kdy po dané vygenerované komponentě budeme chtít, aby něco vykonávala. Pojďme si to ukázat na jednoduchém příkladu, kdy si vytvoříme v XAMLu tlačítko, při jehož stisknutí se vygeneruje další tlačítko, které na stisknutí vygeneruje text.

XAML:

<StackPanel x:Name="LayoutRoot"
            Background="White"
            Orientation="Vertical">

        <Button x:Name="bt_xaml"
                Content="Vygeneruj tlačítko"
                Width="150"
                Height="25"
                Click="bt_xaml_Click"/>

</StackPanel> 

C#:

private void bt_xaml_Click(object sender, RoutedEventArgs e)
{
            //tlacitko pro vygenerovani textu
            Button bt = new Button();
            bt.Content = "Vygeneruj text";
            bt.Width = 150;
            bt.Height = 25;
            bt.Click += new RoutedEventHandler(bt_Click);
            LayoutRoot.Children.Add(bt);
}

void bt_Click(object sender, RoutedEventArgs e)
{
            TextBlock tb = new TextBlock();
            tb.Text = "Toto je vygenerovaný text.";
            tb.Width = 150;
            tb.TextWrapping = TextWrapping.Wrap;
            tb.FontFamily = new FontFamily("Times New Roman");
            tb.Foreground = new SolidColorBrush(Colors.Orange);
            LayoutRoot.Children.Add(tb);
} 

Principem je přiřazení metody k události na komponentě ( bt.Click += new RoutedEventHandler(bt_Click);).

Závěrem

Pokud budete chtít tvořit aplikace v Silverlightu, této problematice se dozajista nevyhnete. Zvláště pokud se pustíte do tvorby her, kde je podmíněné generování komponent zcela běžné.

Faktem je, že tvorba komponent z logiky aplikace v C# nebo Visual Basicu není tak pohodlná, jako když je píšeme v XAMLu. Je tomu tak především ze dvou důvodů:

  • musíme pamatovat na to, jaké atributy jsou jakého typu (jestli jsou objekt, výčet či text)
  • musíme každou komponentu přiřazovat do „kontejneru“ namísto přímého vnoření jako v XAMLu

Pokračování přístě

V příštím článku se blíže podíváme na práci s médii, konkrétně pak na práci s videem.

Zdroje

Používáte dynamicky generované komponenty?

Komentáře

Odebírat
Upozornit na
guest
5 Komentářů
Nejstarší
Nejnovější Most Voted
Inline Feedbacks
Zobrazit všechny komentáře
Štěpán Bechynský

Další možnost, jak dynamicky generovat uživatelské rozhraní, je použít třídu XamlReader ze System.Windows.Markup (http://msdn.microsoft.com/en-us/library/system.windows.markup.xamlreader.aspx). Není pak problém na straně serveru vygenerovat XAML a "podstrčit" ho do již běžící aplikace.

Jiří Knesl

Děkuji za zajímavý článek. Přál bych si na zdrojáku víc takových.

René Stein

Pěkný článek, Když se tu bavíme o dynamickém generování UI, tak bych chtěl upozornit na jednu nepřijšmnou vlastnost-bug v Silverlightu.
Jestliže máte vlastni User Control, ve kterém je Popup a ztento Popup neobsahuje ListBox (a možná další prvky), je možné Popup zobrazit a používat, aniž by byl přidán do kolekce Children. Takto definovaný POPUP funguje bez problémů.

<pexeso:popupbase x:class="RStein.Pexeso.SaveFile" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:pexeso="clr-namespace:RStein.Pexeso">
<grid x:name="LayoutRoot" background="Black">
<popup name="filesPopup">
<popup.child>
<stackpanel orientation="Vertical" background="Red">
<textblock text="Název souboru s uloženou hrou" fontsize="15" margin="5,5,5,0" horizontalalignment="Left" foreground="White" textdecorations="Underline"></textblock>
<stackpanel orientation="Horizontal">
<textbox name="txtFile" margin="5" minwidth="200"></textbox>
<textblock foreground="Yellow" visibility="Collapsed" name="txtError" text="Musíte zadat platný název souboru!" horizontalalignment="Left" verticalalignment="Center" fontweight="Bold" fontsize="10"></textblock>
</stackpanel>
<stackpanel orientation="Horizontal" margin="5">
<button style="{StaticResource DialogButton}" content="Uložit" name="btnSelect" click="btnSelect_Click"></button>
<button style="{StaticResource DialogButton}" content="Zpět" name="btnBack" click="btnCancel_Click"></button>
</stackpanel>
</stackpanel>
</popup.child>
</popup>
</grid>
</pexeso:popupbase>

Jestliže ale Popup obsahuje Listbox (a pravděpodobně i jiné prvky), Popup se zuobrazí, ale při vybrání libovolné položky v ListBoxu celý plugin do obsluhy události UnhandledException a napíše jen něco o interní fatální chybě. Mimochodem, Bety a RC Silverlightu tohle podle mě nedělaly.

Tento popup způsobí pád Silverlightu, jestliže Popup není přidán do kolekce Children.

<pexeso:popupbase x:class="RStein.Pexeso.SelectFile" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:pexeso="clr-namespace:RStein.Pexeso">
<grid x:name="LayoutRoot" background="Black">
<popup name="filesPopup">
<popup.child>
<stackpanel orientation="Vertical" background="Red">
<textblock text="Vyberte uloženou hru" fontsize="15" margin="5,5,5,0" horizontalalignment="Left" foreground="White" textdecorations="Underline"></textblock>
<textblock foreground="Yellow" visibility="Collapsed" fontsize="10" name="txtError"></textblock>
<border cornerradius="20" background="White">
<listbox name="lstFiles" background="Orange" height="200">
<listbox.itemtemplate>
<datatemplate>
<textblock text="{Binding Mode=OneWay}" foreground="White"></textblock>
</datatemplate>
</listbox.itemtemplate>
</listbox>
</border>
<stackpanel orientation="Horizontal">
<button style="{StaticResource DialogButton}" content="Vybrat soubor" name="btnSelect" click="btnSelect_Click"></button>
<button style="{StaticResource DialogButton}" content="Zpět" name="btnBack" click="btnCancel_Click"></button>
</stackpanel>
</stackpanel>
</popup.child>
</popup>
</grid>
</pexeso:popupbase>

Před zobrazením Popupu tedy musíme vždy přidat Popu do kolekce Children a po uzavřeni Popupu jej případně odebrat.

rivate void btnLoad_Click(object sender, RoutedEventArgs e)
{
SelectFile file = new SelectFile();
var files = FileAccessComponent.Instance.GetRootFiles();
if (files.Length == 1)
{
return;
}

file.FileListBox.ItemsSource = files;

LayoutRoot.Children.Add(file);

file.DialogClosed += file_DialogClosed;
showPopup(file.FilesPopup);
file.FileListBox.Focus();
}

DialogClosed je moje vlastní událost v předkovi pro všechny dialogy.

{
SelectFile sfDialog = sender as SelectFile;

try
{
if (sfDialog.LastResult == DialogResult.OK && sfDialog.FileListBox.SelectedItem != null)
{
m_currentGame = PexesoGame.Load(sfDialog.FileListBox.SelectedItem.ToString());
removeButtons();
rebindGameData();
}
}
catch (Exception e1)
{
Console.WriteLine(e1);
}
finally
{
LayoutRoot.Children.Remove(sfDialog); //Pridat do kolekce
sfDialog.DialogClosed -= saveFileDialog_DialogClosed;
hidePopup(sfDialog.FilesPopup);
}

}

janis

Komponenty z toolkitu ani tak v popup nefungují. A bohežel další dost
omezující chyba. Při smázání záznamu z gridu se grid nepřekreslí a
zůstane v něm prázdný řádek po smazaném záznamu. Pokud uživatel
smázne záznamy v gridu všechny, poslední dva řádky se nesmažou a
zůstanou prázdné.

Přístupnost není jen o splnění norem: nový pohled na inkluzivní design

Přístupnost a inkluze možná nepatří mezi nejžhavější témata digitálního světa – dokud o nich nezačne mluvit Vitaly Friedman. Na WebExpo 2024 předvedl, že inkluzivní design není jen o splněných checkboxech, ale hlavně o lidech. S energií sobě vlastní obrátil zažité přístupy naruby a ukázal, že skutečně přístupný web je nejen možný, ale i nezbytný.

Efektivnější vývoj UI nebo API: Co si odnést z WebExpo 2025?

Různé
Komentáře: 0
Jak snadno implementovat moderní uživatelské rozhraní? Které funkce brzdí rychlost vašeho webu? A kdy raději sami přibrzdit, abychom využitím AI nepřekročili etické principy? Debatu aktuálních dev témat rozdmýchá sedmnáctý ročník technologické konference WebExpo, která proběhne v Praze od 28. do 30. května. Který talk či workshop si rozhodně nenechat ujít? Toto je náš redakční výběr z vývojářských hroznů.

Zapřáhněte AI jako nikdy předtím. Květnová konference WebExpo přivítá hvězdy technologického světa

Od 28. do 30. května 2025 promění pražský Palác Lucerna na tři dny technologická konference WebExpo. Na programu je více než 80 přednášek a workshopů od expertů z celého světa. WebExpo tradičně propojuje vývojáře, designéry, marketéry i byznysové lídry a nabízí praktické dovednosti, strategické myšlení a přináší nejnovější trendy nejen v oblasti AI.