# 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:
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:
<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:
<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
Zatím žádné komentáře.