Automaticky generované GUI pomocí reflexe - Formulář pro editaci detailu objektu

Seznam článků

Formulář pro editaci detailu objektu

Komponentu zobrazující formulář pro editaci hodnot objektu můžeme připravit podobným způsobem. Tentokrát nám na vstupu stačí již pouze jeden objekt, nikoli celý seznam. Z jeho třídy (typu) se dozvíme vše potřebné pro vytvoření prvků formuláře, a z tohoto objektu (z instance třídy) získáme data, která v tomto formuláři zobrazíme a umožníme jejich editaci.
Co se týče rozložení prvků ve formuláři, tak v současné době je moderní a zároveň i velmi praktické, jednotlivé prvky řadit pod sebe tak, že nejprve je uveden popisek (název editované vlastnosti) a pod ní editor hodnoty vlastnosti. Pro definici takovéhoto typu rozmístění je v Xamarin.Forms nejvhodnější použít polohovací prvek StackLayout (ve WPF existuje totéž pod názvem StackPanel). Grid by se hodil pro starší typ rozložení, kdy popisky byly vlevo (před) prvkem pro editaci hodnoty, které pak byly vpravo v témže řádku, obojí zarovnáno do sloupce. Tento typ formuláře se sice tak neroztahoval do výšky, ale za to do šířky. Navíc pokud měla některá z položek delší popis, nezbyl na samotné editory (všechny) dostatek horizontálního prostoru a celé rozložení, zvláště na menších displejích to tak značně komplikovalo. Novější typ řazení prvků pod sebe možná není tak vzhledný na větších monitorech, ale větší množství prvků formuláře se dá vždy vyřešit obyčejným ScrollViewem (s možností vertikálního posouvání), popř. nějakým rozdělením do kategorií (ty pak mohou být i vedle sebe) či záložek např. pomocí TabbedPage.
V příkladu se ale zaměříme na prostý jednostránkový formulář bez kategorií, nejprve v jeho nejjednodušší formě. 

public class DataForm : StackLayout
{
    public void SetData(object record)
    {
        var typ = record.GetType().GetTypeInfo();
        foreach (var prop in typ.GetProperties())
        {               
            // Editor
            View editor = null;
            if (prop.PropertyType == typeof(string) || prop.PropertyType == typeof(int))
            {
                editor = new Entry();
                editor.SetBinding(Entry.TextProperty, prop.Name);
            }
            else if (prop.PropertyType == typeof(DateTime))
            {
                editor = new DatePicker();
                editor.SetBinding(DatePicker.DateProperty, prop.Name);
                editor.HorizontalOptions = LayoutOptions.Start;
            }
            // Donastavení a přidání editoru
            if (editor != null)
            {
                Children.Add(new Label() { Text = prop.Name }); // Popisek editoru
                editor.Margin = new Thickness(0, 2, 0, 10);     // Odsazení
                Children.Add(editor);                           // Přidání do StackLayoutu
            }
        }
        // Provázání editovaného objektu s formulářem
        BindingContext = record;
    }

    public object GetData() => BindingContext;

    // Vytvoření nového formuláře na základě seznamu objektů
    public static DataForm CreateFormWithData(object record)
    {
        var form = new DataForm();
        form.SetData(record);
        return form;
    }
}

Pokud bychom chtěli za předka třídy naší komponenty místo StackLayout třeba Grid, nebo i cokoli jiného, může být, StackLayout bychom v metodě SetData vytvořili a vložili jej do tohoto jiného prvku jako jediný podprvek (Children) či obsah (Content).
Metoda SetData si tedy, podobně jako tomu bylo u generování přehledu záznamů, zjistí informace o třídě (typu) záznamu/objektu (record) a z něj získá seznam všech jeho vlastností. Pro každou z nich pak vytvoří vhodný editor (pro text a celé číslo je zde použito Entry, pro datum pak DatePicker), prováže ho s vlastností objektu (SetBinding), editor doformátuje, vloží nad něj popisek (název vlastnosti) a hned pod něj přidá i tento editor.
Komponentu typu DataForm pak lze vložit do XAML kódu, popř. vygenerovat dynamicky v C# kódu statickou metodou CreateFormWithData

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:Reflexe"
             x:Class="Reflexe.DetailPage"
             Title="Detail osoby">
    <ContentPage.Content>
        <Grid>
            <Grid.RowDefinitions>
                <RowDefinition Height="*" />
                <RowDefinition Height="Auto" />
            </Grid.RowDefinitions>
            <ScrollView>
                <Grid>
                    <local:DataForm x:Name="dForm" Margin="20,10" />
                </Grid>
            </ScrollView>
            <StackLayout Grid.Row="1" HorizontalOptions="End" Margin="20,10" Orientation="Horizontal">
                <Button Text="OK" WidthRequest="70" />
                <Button Text="Zrušit" WidthRequest="70" Margin="10,0,0,0" />
            </StackLayout>
        </Grid>
    </ContentPage.Content>
</ContentPage>

Následně stačí už jen metodou SetData ("dForm.SetData(osoba);") nastavit objekt pro editaci a formulář je hotov.

Automaticky vygenerovaný formulář pro editaci dat

Výhodou je, že ať přes SetData vložíme formuláři jakýkoli objekt jakékoli třídy, formulář se tomu vždy přizpůsobí. Jelikož jsou prvky pro editaci provázány s editovaným objektem pomocí vázání dat (Binding), jakákoli změna kteréhokoli údaje ve formuláři se okamžitě ukládá zpět do objektu. To sice může být problém v případě, že uživatel hodnoty změní a pak okno/stránku opustí tlačítkem Zrušit, ale dá se snadno řešit buď klonováním objektu, nebo v případě databáze tím, že místo Commit se zavolá Rollback a data objektu se znovu načtou z databáze, což by při návratu na přehled objektů stejně nepochybně následovalo v každém případě.

 

Pokračování příště...

 

on 27 kvě 2018