MVVM ve WPF a Silverlightu, část 6: Behaviors

Tomáš Holan       23. 4. 2011       WPF, Silverlight, Architektura, XML       6712 zobrazení

Nejprve začněme shrnutím tříd triggerů a akcí triggeru, se kterými jsme se setkali v minulé a předminulé časti tohoto seriálu. Dále k nim ale ještě přidáme poslední typ objektu z namespace System.Windows.Interactivity, kterým je Behavior, o kterém bude řeč dnes.

Následující tabulka ukazuje srovnání těchto tří typů objektů:

  Základní třída Parent XAML element Příklady tříd
Trigger TriggerBase<T> i:Interaction.Triggers i:EventTrigger
ei:PropertyChangedTrigger
ei:DataTrigger
KeyDownTrigger
TextChangedTrigger
DoubleClickTrigger
(pouze pro Silverlight)
TriggerAction TriggerAction<T> “element triggeru” i:InvokeCommandAction
EventToCommand
SetFocusAction
TabControlFirstTabAction
Behavior Behavior<T> i:Interaction.Behaviors ComboBoxDeleteBehavior
ComboBoxNullDeleteBehavior
SetLanguageBehavior
PopupBehavior

A nyní k samotnému objektu Behavior.

Objekty typu Behavior se také přidávají k ovládacím prvkům ve View podobně jako triggery, konkrétně se přidávají do XAML elementu i:Interaction.Behaviors. Behavior ale obsahuje v jedné třídě jak logiku pro vyvolávání akcí, tak i vlastní implementaci manipulující s daným prvkem (akce je resp. jsou inicializovány samotným ovládacím prvkem). Zjednodušeně tedy můžeme říci, že aplikováním Behavioru měníme chování daného controlu (odtud i název “Behavior”).

Na rozdíl od triggerů mechanizmus behavioru již neslouží k interakci mezi View s ViewModelem či naopak. Přesto je stále v MVVM využitelný, i když není samozřejmě vázaný pouze na použití v MVVM. Behavior umožňuje totiž implementovat a zapouzdřit obecnou funkcionalitu, kterou lze ve View opakovaně a pouze deklarativně používat. To opět snižuje nutnost codebehindu a těsné vazby mezi implementací funkcionality a konkrétním ovládacím prvkem, což je přesně v souladu s ostatními vlastnostmi MVVM.

Jak se behavior implementuje a používá si ukážeme na poměrně jednoduchém příkladu. Předpokládejme, že na formuláři s přehledem firem máme ComboBox pro výběr filtru s výčtem hodnot “Vše”, “Pouze plátci DPH”, “Pouze neplátci DPH”. V XAML to vypadá takto:

<ComboBox Margin="0,0,8,0" Width="155" Height="24" SelectedIndex="{Binding PlatceDPHSelectedIndex, Mode=TwoWay}">
    <ComboBoxItem>Vše</ComboBoxItem>
    <ComboBoxItem>Pouze plátci DPH</ComboBoxItem>
    <ComboBoxItem>Pouze neplátci DPH</ComboBoxItem>
    <i:Interaction.Triggers>
        <i:EventTrigger EventName="SelectionChanged">
            <interactivity:EventToCommand Command="{Binding RefreshCommand}"/>
        </i:EventTrigger>
    </i:Interaction.Triggers>
</ComboBox>

To co chceme udělat je doplnit funkcionalitu, že při stisku klávesy DELETE nebo BACKSPACE se tento prvek nastaví na první hodnotu tj. hodnotu “Vše”. Jak by se to udělalo v codebehind je asi jasné, jako behavior to ale nebude o moc těžší a navíc bez nutnosti pojmenování prvku.

Třídu našeho behavioru pojmenujeme ComboBoxDeleteBehavior.

/// <summary>
/// Set first item in ComboBox on delete
/// </summary>
public class ComboBoxDeleteBehavior : Behavior<ComboBox>
{
    #region action methods
    /// <summary>
    /// Overrides OnAttached
    /// </summary>
    protected override void OnAttached()
    {
        base.OnAttached();
        this.AssociatedObject.KeyDown += new KeyEventHandler(ComboBox_KeyDown);
    }

    /// <summary>
    /// Overrides OnDetaching
    /// </summary>
    protected override void OnDetaching()
    {
        this.AssociatedObject.KeyDown -= new KeyEventHandler(ComboBox_KeyDown);
        base.OnDetaching();
    }
    #endregion

    #region private member functions
    private void ComboBox_KeyDown(object sender, KeyEventArgs e)
    {
        if (e.Key == Key.Delete || e.Key == Key.Back)
        {
            this.AssociatedObject.SelectedIndex = 0;
        }
    }
    #endregion
}

Třída dědí ze základní generické třídy Behavior<T>, kde T je typ ovládacího prvku, pro který je behavior určený, tedy ComboBox v našem případě. Tohoto typu je pak dostupná vlastnost AssociatedObject, která bude vracet instanci controlu, pro konkrétní použití třídy.

Ve vlastní implementaci je třeba přepsat metody OnAttached a OnDetaching pro registraci a odregistraci příslušných událostí, v našem případě události KeyDown. V obsluze této události pak pro klávesy DELETE/BACKSPACE již jen provedeme, opět pomoci vlastnost AssociatedObject, vlastní manipulaci s naším prvkem tj. nastavení SelectedIndex na hodnotu 0.

Použití behavioru provedeme přímo v XAML našeho prvku:

<ComboBox Margin="0,0,8,0" Width="155" Height="24" SelectedIndex="{Binding PlatceDPHSelectedIndex, Mode=TwoWay}">
    <ComboBoxItem>Vše</ComboBoxItem>
    <ComboBoxItem>Pouze plátci DPH</ComboBoxItem>
    <ComboBoxItem>Pouze neplátci DPH</ComboBoxItem>
    <i:Interaction.Behaviors>
        <interactivity:ComboBoxDeleteBehavior/>
    </i:Interaction.Behaviors>
    <i:Interaction.Triggers>
        <i:EventTrigger EventName="SelectionChanged">
            <interactivity:EventToCommand Command="{Binding RefreshCommand}"/>
        </i:EventTrigger>
    </i:Interaction.Triggers>
</ComboBox>

Obdobně vypadá i implementace behavioru, který na stejné klávesy aktuálně vybranou položku ComboBoxu odstraní, což se může hodit pro výběr nepovinné položky.

/// <summary>
/// Clear ComboBox on delete
/// </summary>
public class ComboBoxNullDeleteBehavior : Behavior<ComboBox>
{
    #region action methods
    /// <summary>
    /// Overrides OnAttached
    /// </summary>
    protected override void OnAttached()
    {
        base.OnAttached();
        this.AssociatedObject.KeyDown += new KeyEventHandler(ComboBox_KeyDown);
    }

    /// <summary>
    /// Overrides OnDetaching
    /// </summary>
    protected override void OnDetaching()
    {
        this.AssociatedObject.KeyDown -= new KeyEventHandler(ComboBox_KeyDown);
        base.OnDetaching();
    }
    #endregion

    #region private member functions
    private void ComboBox_KeyDown(object sender, KeyEventArgs e)
    {
        if (e.Key == Key.Delete || e.Key == Key.Back)
        {
            this.AssociatedObject.SelectedIndex = -1;
        }
    }
    #endregion
}

A příklad použití tohoto behavioru u výběru měny z kurzovního lístku, pro případ kdy je vstupní měna jiná než měna výchozí:

<ComboBox x:Name="cboMena" Margin="0,0,6,0" Height="24" Width="60" ItemsSource="{Binding Kurzy}" ToolTipService.ToolTip="{Binding ElementName=cboMena, Path=SelectedItem.mn_Nazev}"
            IsEnabled="{Binding ReadOnly, Converter={StaticResource NegateConverter}}"
            DisplayMemberPath="mn_Kod" SelectedItem="{Binding MenaZ, Mode=TwoWay}" VerticalAlignment="Center">
    <i:Interaction.Behaviors>
        <interactivity:ComboBoxNullDeleteBehavior/>
    </i:Interaction.Behaviors>
    <i:Interaction.Triggers>
        <i:EventTrigger EventName="SelectionChanged">
            <i:InvokeCommandAction Command="{Binding NotifyHasChangesCommand}"/>
        </i:EventTrigger>
    </i:Interaction.Triggers>
</ComboBox>

Dalším příkladem behavioru může být třída SetLanguageBehavior pro nastavení správné kultury u prvků tooltip nebo popup. Jeho implementace i použití bylo popsáno v tomto dřívějším článku.

A poslední příklad je zablokování pravého tlačítka myši u prvku typu Popup, které bylo popsáno zde.

Tím tuto sérii prozatím přerušíme, v budoucnu se ale k tématu MVVM určitě zase někdy vrátíme.

 

hodnocení článku

0 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