Material Design In XAML Toolkit

repository·master·Indexed 12 days ago

https://github.com/materialdesigninxaml/materialdesigninxamltoolkit

A comprehensive theme and control library for WPF that implements Google's Material Design guidelines. It provides styles for standard WPF controls, specialized controls, a full icon pack, and palette configuration. The toolkit supports Material Design 2 and Material Design 3, and includes the MaterialDesignThemes.Motion project for implementing Material 3 spring-driven animations and motion primitives.

Tokens
4.8K
Snippets
15
Records
19
Agent score
95%

What's inside Material Design In XAML

  1. Using Material Motion Primitives in WPF

    master

    The MaterialDesignThemes.Motion project provides the building blocks for implementing Material Design 3 motion in WPF. Instead of a single AnimationTimeline, it exposes several primitives that allow you to build custom, WPF-friendly animations:

    • Spring Presets: Use MotionSchemeContext and MotionSchemes to access Material 3 spring presets (SpringMotionSpec) for spatial and effects motion.
    • Duration and Easing Tokens: Use MotionTokens to retrieve standard Material 3 durations and cubic bezier easing curves. These can be used directly in WPF Storyboard or DoubleAnimation definitions.
    • Animation Configuration: AnimationParameters, Repeatable, and Easing capture timing, easing curves, and repetition logic.
    • Advanced Spring Physics: For animations that require high fidelity beyond standard WPF easing functions, use SpringSimulation, SpringConstants, and SpringEstimation to drive per-frame spring-based motion.
  2. Use ShadowAssist.CacheMode for property inheritance

    master

    The Material Design in XAML toolkit provides the ShadowAssist.CacheMode attached property for scenarios where standard UIElement.CacheMode is insufficient.

    Because UIElement.CacheMode does not support property inheritance, you should use ShadowAssist.CacheMode when you need the caching behavior to be inherited down the visual tree. This is typically implemented by binding the CacheMode of a child element (like an AdornerDecorator) to the ShadowAssist.CacheMode value of its parent.

    <!-- Example of inheriting CacheMode via ShadowAssist -->
    <AdornerDecorator CacheMode="{Binding RelativeSource={RelativeSource Self}, Path=(wpf:ShadowAssist.CacheMode)}">
        <Ellipse x:Name="Thumb" ... />
    </AdornerDecorator>
  3. Increase rendering performance using UIElement.CacheMode

    master

    To improve performance for complex or time-consuming content, you can set the CacheMode property on any class inheriting from UIElement. By default, CacheMode is null, which ensures controls remain sharp and crisp but may require more rendering resources.

    Setting a BitmapCache allows the system to cache the rendered content. You can adjust the RenderAtScale property to balance sharpness and pixelation: increasing the value sharpens the control but may cause pixelation when the control is scaled down.

    <!-- This should decrease rendering time -->
    <ToggleButton>
        <ToggleButton.CacheMode>
            <BitmapCache 
                EnableClearType="True"
                RenderAtScale="1"
                SnapsToDevicePixels="True" />
        </ToggleButton.CacheMode>
    </ToggleButton>
  4. Implement spring-driven animation with SpringSimulation

    master

    When standard WPF easing functions are insufficient, you can use SpringSimulation to drive a per-frame spring animation.

    To implement this:

    1. Obtain a SpringMotionSpec from MotionSchemeContext to ensure your stiffness and damping ratios align with Material 3 guidelines.
    2. Initialize a SpringSimulation with the desired Stiffness and DampingRatio.
    3. Use SpringSimulation.UpdateValues within a rendering loop (such as CompositionTarget.Rendering) to calculate the next position and velocity based on elapsed time.
    4. Apply the resulting value to your target property.
    5. Stop the animation when the value and velocity have converged (e.g., when the difference from the final position and the velocity are both below a small threshold).
    // Example: Spring-driven translation on TranslateTransform.X
    using System;
    using System.Diagnostics; 
    using System.Windows;
    using System.Windows.Media;
    using MaterialDesignThemes.Motion;
    
    public partial class SpringSampleControl : UserControl, IDisposable
    {
        private readonly SpringAnimator _animator;
    
        public SpringSampleControl()
        {
            InitializeComponent();
            // 1. Get the Material 3 spring spec
            var spec = MotionSchemeContext.Current.RememberDefaultSpatialSpec();
            
            // 2. Initialize the animator with the spec
            _animator = new SpringAnimator(
                apply: value => Translate.X = value,
                springSpec: spec);
                
            Loaded += (_, _) => _animator.Start(from: -120, to: 0);
            Unloaded += (_, _) => Dispose();
        }
    
        public void Dispose() => _animator.Dispose();
    
        public TranslateTransform Translate { get; } = new();
    
        private sealed class SpringAnimator : IDisposable
        {
            private readonly SpringSimulation _simulation;
            private readonly Action<double> _apply;
            private readonly Stopwatch _stopwatch = new();
            private double _value;
            private double _velocity;
            private bool _isRunning;
    
            public SpringAnimator(Action<double> apply, SpringMotionSpec springSpec)
            {
                _apply = apply;
                _simulation = new SpringSimulation(finalPosition: 0f)
                {
                    Stiffness = (float)springSpec.Stiffness,
                    DampingRatio = (float)springSpec.DampingRatio,
                };
            }
    
            public void Start(double from, double to)
            {
                _simulation.FinalPosition = (float)to;
                _value = from;
                _velocity = 0;
                _stopwatch.Restart();
                if (_isRunning) return;
    
                CompositionTarget.Rendering += OnRendering;
                _isRunning = true;
            }
    
            private void OnRendering(object? sender, EventArgs e)
            {
                var elapsed = _stopwatch.Elapsed;
                _stopwatch.Restart();
                
                // 3. Update values per frame
                var next = _simulation.UpdateValues((float)_value, (float)_velocity, elapsed);
                _value = next.Value;
                _velocity = next.Velocity;
                _apply(_value);
    
                // 4. Check for convergence to stop
                if (Math.Abs(_value - _simulation.FinalPosition) < 0.5 &&
                    Math.Abs(_velocity) < 0.5)
                {
                    Stop();
                }
            }
    
            private void Stop()
            {
                if (!_isRunning) return;
                CompositionTarget.Rendering -= OnRendering;
                _isRunning = false;
            }
    
            public void Dispose() => Stop();
        }
    }
  5. Implement virtualization for large data sets

    master

    For controls displaying large amounts of data (e.g., ListView or DataGrid), enable virtualization to reduce memory usage and speed up scrolling. This ensures only the items currently in view are rendered.

    To optimize performance, use VirtualizingStackPanel.IsVirtualizing="True" and set the VirtualizationMode to Recycling to reuse containers instead of creating new ones.

    <ListView VirtualizingStackPanel.IsVirtualizing="True" 
              VirtualizingStackPanel.VirtualizationMode="Recycling" />
  6. Prefer StaticResource over DynamicResource for better performance

    master

    When referencing resources in XAML, use StaticResource instead of DynamicResource whenever possible. DynamicResource is reevaluated every time it is used, which can impact performance. Only use DynamicResource if the resource value must change at runtime.

    <!-- Use StaticResource instead of DynamicResource for better performance -->
    <Style x:Key="ButtonStyle" TargetType="Button" BasedOn="{StaticResource BaseButtonStyle}" />
  7. Reduce visual complexity to improve rendering

    master

    To lower CPU and GPU workload, reduce the number of visual elements the WPF engine must process. Key strategies include:

    • Minimizing Visual Layers: Consolidate overlapping elements to reduce the total number of layers per control.
    • Avoiding Overdraw: Arrange visuals to minimize redundant drawing where multiple elements overlap the same pixels.
    • Limiting Effects: Avoid using heavy effects like DropShadowEffect or BlurEffect in performance-critical areas of the UI.
  8. Use Dispatcher.BeginInvoke to prevent UI freezing

    master

    To prevent heavy computations or non-UI-intensive tasks from blocking the UI thread (which handles rendering and user interaction), use Dispatcher.BeginInvoke. This allows you to schedule tasks that interact with UI elements to be executed without freezing the application interface.

    // Execute this in the background without freezing the UI
    Dispatcher.BeginInvoke((Action)(() =>
    {
        // Update UI elements here
    }));
  9. Optimize data flow by avoiding complex bindings

    master

    Complex bindings, particularly those involving multiple levels of nesting or numerous converters, can degrade UI performance when dealing with large datasets. To optimize performance:

    1. Simplify bindings: Avoid deep nesting (e.g., User.Profile.Details.Name) where possible.
    2. Reduce Converters: Minimize the use of IValueConverter in high-frequency binding paths.
    3. Use ViewModels: Implement INotifyPropertyChanged on your ViewModels to manage data flow efficiently.
    <!-- Avoid multi-level bindings when possible -->
    <TextBlock Text="{Binding User.Name}" />
  10. Optimize image scaling with RenderOptions.BitmapScalingMode

    master

    You can adjust how images are scaled to balance visual quality and performance. Using LowQuality scaling mode improves performance during animations or when scaling is frequent, whereas HighQuality increases GPU workload.

    Set this property directly on an Image element in XAML.

    <Image Source="sample.png" RenderOptions.BitmapScalingMode="LowQuality" />
  11. Configure Material Design in App.xaml

    master

    After installation, you must configure your App.xaml to include the toolkit's resources. This involves defining the materialDesign XML namespace and merging the BundledTheme and default resource dictionaries.

    To target Material Design 3, use MaterialDesign3.Defaults.xaml in the ResourceDictionary.MergedDictionaries. For Material Design 2, use MaterialDesign2.Defaults.xaml.

    <Application 
      x:Class="Example.App"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
      StartupUri="MainWindow.xaml">
        <Application.Resources>
            <ResourceDictionary>
                <ResourceDictionary.MergedDictionaries>
                    <materialDesign:BundledTheme BaseTheme="Light" PrimaryColor="DeepPurple" SecondaryColor="Lime" />
    
                    <!-- Use MaterialDesign3.Defaults.xaml for Material Design 3 -->
                    <ResourceDictionary Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesign2.Defaults.xaml" /> 
                </ResourceDictionary.MergedDictionaries>
            </ResourceDictionary>
        </Application.Resources>
    </Application>