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

Subscribe
Upozornit na
guest
5 Komentářů
Nejstarší
Nejnovější Most Voted
Inline Feedbacks
View all comments
Š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é.

Enum a statická analýza kódu

Mám jednu univerzální radu pro začínající programátorty. V učení sice neexistují rychlé zkratky, ovšem tuhle radu můžete snadno začít používat a zrychlit tak tempo učení. Tou tajemnou ingrediencí je statická analýza kódu. Ukážeme si to na příkladu enum.