MVVM ve WPF a Silverlightu, část 5: Data trigger a custom akce

Tomáš Holan       08.04.2011       WPF, Silverlight, Architektura, XML       12030 zobrazení

Od minule víme, že interaction trigger se skládá ze dvou částí: vlastního triggeru a akce, kterou trigger vyvolává. Těchto akcí může být přitom pro jeden trigger případně definováno i více. Triggery jsme také již používali jak standardní, tak i naše vlastní, akcí triggeru bylo ale dosud pouze volání commadu tj. nějaké definované operace na ViewModelu.

Trigger a akce triggeru lze ale využít i pro opačný směr interakce tj. volání nějaké akce manipulující s prvky View a inicializovat tuto akci z ViewModelu. V MVVM se pro to používá tzv. Data trigger. Tento název je odvozen z toho, že akce je inicializována změnou datové vlastnosti ViewModelu. Přitom se stejně jako u prezentace dat i zde využívá data binding. Zatímco vlastní trigger bude standardní, akce triggeru budeme implementovat sami.

Pozn.: Druhý způsob jak manipulovat s prvky View je pomoci události ViewModelu a jejího obsloužení v codebehind, tak jak to bylo dříve předvedeno např. pro uzavírání dialogového okna. Výhodou přístupu datového triggeru je ale to, že akci definujeme pouze jednou, a pak jí pouze deklarativně používáme opakovaně na různých ovládacích prvcích (případně prvcích stejného datového typu). V codebehind by akce byla “natvrdo” svázána s konkrétním pojmenovaným prvkem, čemuž se v MVVM snažíme vyhnout.

Ještě jednou tedy shrnu celý mechanizmus:

  • Na ViewModelu dojde ke změně vlastnosti, která je bindovaná data triggerem na ovládacím prvku ve View.
  • Trigger, případně v závislosti na tom o jakou změnu se jedná, vyvolá akci definovanou ve View.
  • Akce triggeru bude naše vlastní a provede potřebnou manipulaci s daným ovládacím prvkem.

Základním scénářem pro data trigger může být nastavení focusu na daný control, přepnutí tabu TabControlu, “nascrollování" na začátek v nějakém seznamu, rozbalení jedné úrovně ve stromu apod.

Pro prvním uvedený scénář máme v našem příkladu přihlašovacího formuláře již ve ViewModelu připravené volání metody SetFocusTo(), která je pochází ze základní třídy ViewModelBase. Připomenu, že její implementace je následující:

protected void SetFocusTo(string controlName)
{
    DispatcherHelper.Dispatcher.BeginInvoke(() =>
        {
            this.SetFocusControlName = controlName;
            this.SetFocusControlName = null;
        });
}

Změna veřejné vlastnosti SetFocusControlName bude právě spouštět data trigger. Budeme přitom chtít reagovat na změnu této vlastnosti na některou z dříve zvolených hodnot "LoginName”, "Password" nebo "LoginErrorOK" podle toho u jakého ovládacího prvku bude data trigger uveden.

Ještě nám ale chybí implementace vlastní akce, kterou bude data trigger vyvolávat. Tuto akci pojmenujeme SetFocusAction.

/// <summary>
/// Set focus to control
/// </summary>
public class SetFocusAction : TriggerAction<Control>
{
    #region action methods
    /// <summary>
    /// Overrides Invoke
    /// </summary>
    protected override void Invoke(object parameter)
    {
        var control = this.AssociatedObject;

#if SILVERLIGHT
        control.Focus();
#else
        System.Windows.Input.Keyboard.Focus(control);
#endif
    }
    #endregion
}

Třída dědí ze základní generické třídy TriggerAction<T>, kde T je typ ovládacího prvku, ke kterému akci půjde použít. V našem případě bude tedy AssociatedObject typu Control. Vlastní implementace je jen v metodě Invoke(), kterou trigger spouští. Nastavení focusu je odlišné u verze pro Silverlight a WPF.

A nyní již nic nebrání doplnit k ovládacím prvkům ve View samotnou definici data triggerů. Zde potřebujeme reagovat na změnu na konkrétní hodnotu, což nám zajistí třída DataTrigger. Tato třída je definována v namespace Microsoft.Expression.Interactivity.Core z assembly Microsoft.Expression.Interactions.dll, která je součástí Microsoft Expression Blend 4 SDK.

xmlns:ei="clr-namespace:Microsoft.Expression.Interactivity.Core;assembly=Microsoft.Expression.Interactions"
<TextBox TabIndex="0" Height="28" Width="204" TextWrapping="Wrap" MaxLength="60" Text="{Binding Path=LoginName, Mode=TwoWay}">
    <i:Interaction.Triggers>
        <interactivity:KeyDownTrigger Keys="Enter">
            <interactivity:EventToCommand Command="{Binding LoginCommand}"/>
        </interactivity:KeyDownTrigger>
        <ei:DataTrigger Binding="{Binding SetFocusControlName}" Value="LoginName">
            <interactivity:SetFocusAction />
        </ei:DataTrigger>
    </i:Interaction.Triggers>
</TextBox>
<PasswordBox TabIndex="1" Height="28" Width="204" MaxLength="255" Password="{Binding Path=Password, Mode=TwoWay}">
    <i:Interaction.Triggers>
        <interactivity:KeyDownTrigger Keys="Enter">
            <interactivity:EventToCommand Command="{Binding LoginCommand}"/>
        </interactivity:KeyDownTrigger>
        <ei:DataTrigger Binding="{Binding SetFocusControlName}" Value="Password">
            <interactivity:SetFocusAction />
        </ei:DataTrigger>
    </i:Interaction.Triggers>
</PasswordBox>
<Button Content="OK" Width="93" Height="28" Margin="0,26,0,0" FontSize="12" Command="{Binding LoginErrorOKCommand}">
    <i:Interaction.Triggers>
        <ei:DataTrigger Binding="{Binding SetFocusControlName}" Value="LoginErrorOK">
            <interactivity:SetFocusAction />
        </ei:DataTrigger>
    </i:Interaction.Triggers>
</Button>

U třídy DataTrigger nastavujeme vlastnosti Binding, Value a případně Comparison. V našem případě nastavíme binding na vlastnost SetFocusControlName ViewModelu a název příslušného ovládacího prvku odpovídajícího hodnotě při volání metody SetFocusTo(). Tím máme tuto funkcionalitu u našeho formuláře doplněnou.

Pokud bychom potřebovali jiný Comparison než výchozí Equal vypadala by deklarace data triggeru např. takto:

<ei:DataTrigger Binding="{Binding TriggerWhenNotNullProperty}" Comparison="NotEqual" Value="{x:Null}">
    <!--...-->
</ei:DataTrigger>

Druhý typ data triggeru reaguje na libovolnou změnu bindované vlastnosti. Jedná se o třídu PropertyChangedTrigger, jejíž použití si ukážeme opět na příkladu.

Předpokládejme, že máme část formuláře zobrazující detail jednoho záznamu tabulky na více tabech pomoci prvku TabControl. K prvku TabControl doplníme akci triggeru, která bude provádět přepnutí na první tab, a trigger, který bude reagovat na změnu zobrazovaného záznamu (vlastnost SelectedItem).

/// <summary>
/// Set TabControl to first tab
/// </summary>
public class TabControlFirstTabAction : TriggerAction<TabControl>
{
    #region action methods
    /// <summary>
    /// Overrides Invoke
    /// </summary>
    protected override void Invoke(object parameter)
    {
        this.AssociatedObject.SelectedIndex = 0;
    }
    #endregion
}
<sdk:TabControl>
    <!--...-->
    <i:Interaction.Triggers>
        <ei:PropertyChangedTrigger Binding="{Binding SelectedItem}">
            <interactivity:TabControlFirstTabAction />
        </ei:PropertyChangedTrigger>
    </i:Interaction.Triggers>
</sdk:TabControl>

U třídy PropertyChangedTrigger se nastavuje pouze vlastnosti Binding.

Příště: Behaviors

 

hodnocení článku

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

 

Nový příspěvek

 

                       
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