Zpět na domů

Vlastní WPF ovládací prvky: styly a attached properties

Článek popisuje metody vytváření vlastních WPF ovládacích prvků bez dědičnosti a UserControl. Jsou zváženy attached properties pro parametrické styly, implementace animovaných přepínačů prostřednictvím VisualStateManager a architektura systému témat.

Stylizace WPF ovládacích prvků: tlačítka a přepínače bez codebehind
Advertisement 728x90

# Vytváření vlastních WPF ovládacích prvků: stylizace tlačítek a přepínačů bez dědičnosti

Při vývoji WPF aplikací často vzniká potřeba přizpůsobit standardní ovládací prvky korporátnímu designu bez nákupu komerčních knihoven. Místo vytváření vlastních UserControl nebo dědění z základních tříd je efektivnější použít styly a připojené vlastnosti – to zachovává kompatibilitu s frameworkem a zjednodušuje údržbu.

Architektura systému témat v WPF

Pro řízení vzhledu aplikace se doporučuje centralizovat všechny zdroje ve složce Style. Každé téma (např. světlé a tmavé) je realizováno jako samostatná složka obsahující soubor barevné palety a hlavní slovník zdrojů. To umožňuje snadno přepínat témata za běhu nahrazením jediného ResourceDictionary v Application.Resources.

Implementace přepínání témat vypadá takto:

Google AdInline article slot
public void ChangeTheme(Theme theme)
{
    if (CurrentTheme == theme) return;

    CurrentTheme = theme;
    Uri themeSource = theme switch
    {
        Theme.Dark => new Uri("pack://application:,,,/CustomWpfControls;component/Style/DarkTheme/DarkTheme.xaml", UriKind.Absolute),
        Theme.Light => new Uri("pack://application:,,,/CustomWpfControls;component/Style/LightTheme/LightTheme.xaml", UriKind.Absolute),
        _ => throw new ArgumentOutOfRangeException(nameof(theme))
    };

    var dictionary = Resources.MergedDictionaries[0];
    dictionary.Clear();
    dictionary.Source = themeSource;
}

Tento přístup zaručuje, že všechny prvky uživatelského rozhraní okamžitě aktualizují svůj vzhled bez nutnosti přenačítat okno.

Stylizace tlačítek prostřednictvím připojených vlastností

Místo vytváření vlastní třídy RoundedButton je lepší definovat připojenou vlastnost CornerRadius ve statické třídě. To umožňuje nastavit poloměr zaoblení přímo v XAML bez porušení principu kompozice:

public static class RoundedButton
{
    public static readonly DependencyProperty CornerRadiusProperty = DependencyProperty.RegisterAttached(
        "CornerRadius",
        typeof(CornerRadius),
        typeof(RoundedButton),
        new FrameworkPropertyMetadata(new CornerRadius(0d), 
            FrameworkPropertyMetadataOptions.AffectsMeasure | 
            FrameworkPropertyMetadataOptions.AffectsRender));

    public static void SetCornerRadius(UIElement element, CornerRadius value) =>
        element.SetValue(CornerRadiusProperty, value);

    public static CornerRadius GetCornerRadius(UIElement element) =>
        (CornerRadius)element.GetValue(CornerRadiusProperty);
}

V šabloně stylu tlačítka se tato vlastnost používá prostřednictvím vazby na TemplatedParent:

Google AdInline article slot
<Border CornerRadius="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=(customWpfControls:RoundedButton.CornerRadius)}" ... />

Výhody tohoto řešení:

  • Není potřeba psát další code-behind kód
  • Zachovává se plná kompatibilita s Button
  • Možnost přepsání poloměru jak ve stylu, tak na úrovni instance
  • Podpora animací a triggerů stavů

Implementace ToggleButton s animovaným přepínačem

Vlastní přepínač napodobující iOS styl lze plně realizovat prostřednictvím ControlTemplate bez jediného řádku C#. Hlavní komponenty šablony:

  • Canvas pro přesné umístění prvků
  • DrawingImage pro ikony (háček a kružek)
  • Border s RenderTransform pro animované posunutí
  • VisualStateManager pro reakci na Checked/Unchecked

Animace posunu je realizována prostřednictvím DoubleAnimation uvnitř Storyboard:

Google AdInline article slot
<DoubleAnimation Duration="0:0:0.1"
                 To="20"
                 AccelerationRatio="0.2"
                 DecelerationRatio="0.7"
                 Storyboard.TargetName="Switcher"
                 Storyboard.TargetProperty="(RenderTransform).(TranslateTransform.X)"/>

Důležité je neuvažovat počáteční hodnotu (From), aby animace vždy začínala z aktuální pozice – to zabraňuje skokům při častém přepínání.

Doporučení pro organizaci stylů

Pro škálovatelnost projektu se řiďte těmito pravidly:

  • Základní styl (bez x:Key) by měl obsahovat pouze Template a obecné Settery
  • Konkrétní varianty (PrimaryButton, IconButton apod.) dědí prostřednictvím BasedOn
  • Všechny barvy vyčleňte do samostatného souboru (např. DarkColors.xaml)
  • Připojené vlastnosti seskupte podle funkčnosti do statických tříd

To usnadňuje refactoring a umožňuje rychle vytvářet nové typy ovládacích prvků na základě existujících šablon.

Co je důležité

  • Vyhněte se dědičnosti z Button/ToggleButton, pokud se nemění logika chování
  • Připojené vlastnosti jsou preferovaný způsob rozšíření funkcionality stylů
  • VisualStateManager zajišťuje deklarativní řízení stavů bez kódu
  • Centralizovaný systém témat zjednodušuje podporu více barevných schémat
  • Animace by měly používat relativní hodnoty (bez From), aby se zabránilo vizuálním artefaktům

— Editorial Team

Advertisement 728x90

Číst dál