C# 零基础到精通教程 - WPF 深度专题:自定义布局与性能优化

上一个专题我们学习了 WPF 的 3D 图形,掌握了如何在 3D 空间中创建和操作模型。这一深度专题将带你深入 WPF 的布局系统和性能优化技巧------这是从"能用"到"精通"的关键一步。
前置知识要求

  • 已完成 WPF 专题一、二、三

  • 熟悉标准布局容器(Grid、StackPanel 等)

  • 理解依赖属性和路由事件


5.1 WPF 布局系统原理

5.1.1 布局两阶段:Measure 和 Arrange

text

复制代码
┌─────────────────────────────────────────────────────────────────┐
│                      WPF 布局过程                                │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│   调用 InvalidateMeasure() / InvalidateArrange()                │
│                          │                                      │
│                          ▼                                      │
│   ┌─────────────────────────────────────────────────────────┐   │
│   │  阶段1: Measure (测量)                                   │   │
│   │  - 父元素询问子元素需要多大空间                            │   │
│   │  - 子元素返回 DesiredSize                                 │   │
│   │  - 递归向下传播                                           │   │
│   └─────────────────────────────────────────────────────────┘   │
│                          │                                      │
│                          ▼                                      │
│   ┌─────────────────────────────────────────────────────────┐   │
│   │  阶段2: Arrange (排列)                                   │   │
│   │  - 父元素根据测量结果分配空间                              │   │
│   │  - 子元素得到最终的 RenderSize                            │   │
│   │  - 递归向下传播                                           │   │
│   └─────────────────────────────────────────────────────────┘   │
│                          │                                      │
│                          ▼                                      │
│                     渲染到屏幕                                   │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

5.1.2 理解 MeasureOverride 和 ArrangeOverride

csharp

复制代码
public class CustomPanel : Panel
{
    // 测量阶段:计算所有子元素需要的空间
    protected override Size MeasureOverride(Size availableSize)
    {
        // availableSize: 父元素提供的可用空间(可能是无限大)
        
        // 1. 测量所有子元素
        foreach (UIElement child in Children)
        {
            // 通知子元素测量自己
            child.Measure(availableSize);
            
            // 子元素的期望大小:child.DesiredSize
        }
        
        // 2. 计算本容器需要的总大小
        double totalWidth = 0;
        double totalHeight = 0;
        // ... 布局算法
        
        // 3. 返回容器本身需要的大小
        return new Size(totalWidth, totalHeight);
    }
    
    // 排列阶段:分配最终位置和大小
    protected override Size ArrangeOverride(Size finalSize)
    {
        // finalSize: 父元素分配给本容器的实际大小
        
        // 1. 为每个子元素计算位置和大小
        foreach (UIElement child in Children)
        {
            Rect childRect = ComputeChildRect(child);
            
            // 2. 通知子元素排列
            child.Arrange(childRect);
        }
        
        // 3. 返回实际使用的尺寸
        return finalSize;
    }
}

5.2 自定义布局容器

5.2.1 瀑布流布局(Masonry Layout)

csharp

复制代码
public class MasonryPanel : Panel
{
    public static readonly DependencyProperty ColumnsProperty =
        DependencyProperty.Register(nameof(Columns), typeof(int), typeof(MasonryPanel),
            new FrameworkPropertyMetadata(3, FrameworkPropertyMetadataOptions.AffectsMeasure));
    
    public static readonly DependencyProperty SpacingProperty =
        DependencyProperty.Register(nameof(Spacing), typeof(double), typeof(MasonryPanel),
            new FrameworkPropertyMetadata(5.0, FrameworkPropertyMetadataOptions.AffectsMeasure));
    
    public int Columns
    {
        get => (int)GetValue(ColumnsProperty);
        set => SetValue(ColumnsProperty, value);
    }
    
    public double Spacing
    {
        get => (double)GetValue(SpacingProperty);
        set => SetValue(SpacingProperty, value);
    }
    
    protected override Size MeasureOverride(Size availableSize)
    {
        if (Children.Count == 0)
            return new Size(0, 0);
        
        double columnWidth = (availableSize.Width - (Columns - 1) * Spacing) / Columns;
        columnWidth = Math.Max(0, columnWidth);
        
        foreach (UIElement child in Children)
        {
            // 限制子元素测量的可用宽度
            var childAvailable = new Size(columnWidth, double.PositiveInfinity);
            child.Measure(childAvailable);
        }
        
        return base.MeasureOverride(availableSize);
    }
    
    protected override Size ArrangeOverride(Size finalSize)
    {
        if (Children.Count == 0)
            return finalSize;
        
        double columnWidth = (finalSize.Width - (Columns - 1) * Spacing) / Columns;
        columnWidth = Math.Max(0, columnWidth);
        
        // 记录每列当前高度
        double[] columnHeights = new double[Columns];
        double totalHeight = 0;
        
        // 创建临时列表存储每个子元素的信息
        var items = new List<(UIElement child, double height)>();
        
        for (int i = 0; i < Children.Count; i++)
        {
            var child = Children[i];
            double childHeight = child.DesiredSize.Height;
            items.Add((child, childHeight));
        }
        
        // 按高度降序排序(更好的瀑布流效果)
        items = items.OrderByDescending(x => x.height).ToList();
        
        // 布局每个子元素
        foreach (var (child, height) in items)
        {
            // 找到当前高度最小的列
            int shortestColumn = 0;
            for (int i = 1; i < Columns; i++)
            {
                if (columnHeights[i] < columnHeights[shortestColumn])
                    shortestColumn = i;
            }
            
            double x = shortestColumn * (columnWidth + Spacing);
            double y = columnHeights[shortestColumn];
            
            if (y > 0) y += Spacing;
            
            var rect = new Rect(x, y, columnWidth, height);
            child.Arrange(rect);
            
            columnHeights[shortestColumn] = y + height;
            totalHeight = Math.Max(totalHeight, columnHeights[shortestColumn]);
        }
        
        return new Size(finalSize.Width, totalHeight);
    }
}

xml

复制代码
<!-- 使用瀑布流布局 -->
<local:MasonryPanel Columns="3" Spacing="10">
    <Border Background="LightCoral" Height="150">
        <TextBlock Text="Item 1" VerticalAlignment="Center" HorizontalAlignment="Center"/>
    </Border>
    <Border Background="LightBlue" Height="200">
        <TextBlock Text="Item 2" VerticalAlignment="Center" HorizontalAlignment="Center"/>
    </Border>
    <Border Background="LightGreen" Height="120">
        <TextBlock Text="Item 3" VerticalAlignment="Center" HorizontalAlignment="Center"/>
    </Border>
    <Border Background="LightYellow" Height="180">
        <TextBlock Text="Item 4" VerticalAlignment="Center" HorizontalAlignment="Center"/>
    </Border>
    <Border Background="LightPink" Height="100">
        <TextBlock Text="Item 5" VerticalAlignment="Center" HorizontalAlignment="Center"/>
    </Border>
    <!-- 更多项目... -->
</local:MasonryPanel>

5.2.2 圆形布局(Radial Panel)

csharp

复制代码
public class RadialPanel : Panel
{
    public static readonly DependencyProperty RadiusProperty =
        DependencyProperty.Register(nameof(Radius), typeof(double), typeof(RadialPanel),
            new FrameworkPropertyMetadata(100.0, FrameworkPropertyMetadataOptions.AffectsArrange));
    
    public static readonly DependencyProperty StartAngleProperty =
        DependencyProperty.Register(nameof(StartAngle), typeof(double), typeof(RadialPanel),
            new FrameworkPropertyMetadata(0.0, FrameworkPropertyMetadataOptions.AffectsArrange));
    
    public double Radius
    {
        get => (double)GetValue(RadiusProperty);
        set => SetValue(RadiusProperty, value);
    }
    
    public double StartAngle
    {
        get => (double)GetValue(StartAngleProperty);
        set => SetValue(StartAngleProperty, value);
    }
    
    protected override Size MeasureOverride(Size availableSize)
    {
        foreach (UIElement child in Children)
        {
            child.Measure(availableSize);
        }
        
        // 容器大小 = 半径 * 2
        double size = (Radius + 50) * 2;
        return new Size(size, size);
    }
    
    protected override Size ArrangeOverride(Size finalSize)
    {
        int count = Children.Count;
        if (count == 0) return finalSize;
        
        // 计算每个项目之间的角度
        double angleStep = 360.0 / count;
        Point center = new Point(finalSize.Width / 2, finalSize.Height / 2);
        
        for (int i = 0; i < count; i++)
        {
            double angle = (StartAngle + i * angleStep) * Math.PI / 180;
            
            // 计算位置
            double x = center.X + Radius * Math.Cos(angle);
            double y = center.Y + Radius * Math.Sin(angle);
            
            UIElement child = Children[i];
            Size childSize = child.DesiredSize;
            
            // 调整位置使子元素中心对齐到计算点
            double left = x - childSize.Width / 2;
            double top = y - childSize.Height / 2;
            
            child.Arrange(new Rect(left, top, childSize.Width, childSize.Height));
        }
        
        return finalSize;
    }
}

xml

复制代码
<!-- 圆形菜单 -->
<local:RadialPanel Radius="150" StartAngle="0" Width="400" Height="400">
    <Button Content="首页" Width="60" Height="60" Style="{StaticResource CircleButton}"/>
    <Button Content="设置" Width="60" Height="60" Style="{StaticResource CircleButton}"/>
    <Button Content="消息" Width="60" Height="60" Style="{StaticResource CircleButton}"/>
    <Button Content="个人" Width="60" Height="60" Style="{StaticResource CircleButton}"/>
    <Button Content="帮助" Width="60" Height="60" Style="{StaticResource CircleButton}"/>
</local:RadialPanel>

5.2.3 响应式网格(Aspect Ratio Grid)

csharp

复制代码
public class AspectRatioGrid : Panel
{
    public static readonly DependencyProperty AspectRatioProperty =
        DependencyProperty.Register(nameof(AspectRatio), typeof(double), typeof(AspectRatioGrid),
            new FrameworkPropertyMetadata(1.0, FrameworkPropertyMetadataOptions.AffectsMeasure));
    
    public static readonly DependencyProperty ColumnsProperty =
        DependencyProperty.Register(nameof(Columns), typeof(int), typeof(AspectRatioGrid),
            new FrameworkPropertyMetadata(3, FrameworkPropertyMetadataOptions.AffectsMeasure));
    
    public double AspectRatio
    {
        get => (double)GetValue(AspectRatioProperty);
        set => SetValue(AspectRatioProperty, value);
    }
    
    public int Columns
    {
        get => (int)GetValue(ColumnsProperty);
        set => SetValue(ColumnsProperty, value);
    }
    
    protected override Size MeasureOverride(Size availableSize)
    {
        if (Children.Count == 0)
            return new Size(0, 0);
        
        double width = availableSize.Width;
        if (double.IsInfinity(width))
        {
            // 没有限制时,使用子元素的默认大小
            return base.MeasureOverride(availableSize);
        }
        
        double itemWidth = width / Columns;
        double itemHeight = itemWidth / AspectRatio;
        
        foreach (UIElement child in Children)
        {
            child.Measure(new Size(itemWidth, itemHeight));
        }
        
        int rows = (int)Math.Ceiling((double)Children.Count / Columns);
        double totalHeight = rows * itemHeight;
        
        return new Size(width, totalHeight);
    }
    
    protected override Size ArrangeOverride(Size finalSize)
    {
        if (Children.Count == 0)
            return finalSize;
        
        double itemWidth = finalSize.Width / Columns;
        double itemHeight = itemWidth / AspectRatio;
        
        for (int i = 0; i < Children.Count; i++)
        {
            int row = i / Columns;
            int col = i % Columns;
            
            double x = col * itemWidth;
            double y = row * itemHeight;
            
            Children[i].Arrange(new Rect(x, y, itemWidth, itemHeight));
        }
        
        int rows = (int)Math.Ceiling((double)Children.Count / Columns);
        double totalHeight = rows * itemHeight;
        
        return new Size(finalSize.Width, totalHeight);
    }
}

5.3 虚拟化技术

5.3.1 什么是虚拟化?

虚拟化 = 只创建/渲染可见区域的项,而不是全部项

text

复制代码
没有虚拟化(1000 项):
┌─────────────────────────────────────────┐
│ Item 1 (可见)    ← 创建                 │
│ Item 2 (可见)    ← 创建                 │
│ Item 3 (可见)    ← 创建                 │
│ Item 4 (可见)    ← 创建                 │
│ ...                                     │
│ Item 1000 (不可见) ← 仍然创建!性能差    │
└─────────────────────────────────────────┘

有虚拟化(1000 项):
┌─────────────────────────────────────────┐
│ Item 1 (可见)    ← 创建并渲染           │
│ Item 2 (可见)    ← 创建并渲染           │
│ Item 3 (可见)    ← 创建并渲染           │
│ Item 4 (可见)    ← 创建并渲染           │
│ Item 5-1000 (不可见) ← 不创建,滚动时创建│
└─────────────────────────────────────────┘

5.3.2 启用虚拟化

xml

复制代码
<!-- ListBox 默认支持虚拟化 -->
<ListBox x:Name="VirtualizedListBox" 
         VirtualizingPanel.IsVirtualizing="True"
         VirtualizingPanel.VirtualizationMode="Recycling"
         VirtualizingPanel.CacheLength="2,2"
         ScrollViewer.IsDeferredScrollingEnabled="True">
    <ListBox.ItemTemplate>
        <DataTemplate>
            <Border BorderBrush="Gray" BorderThickness="1" Margin="2" Padding="5">
                <StackPanel>
                    <TextBlock Text="{Binding Name}" FontWeight="Bold"/>
                    <TextBlock Text="{Binding Description}" Foreground="Gray"/>
                </StackPanel>
            </Border>
        </DataTemplate>
    </ListBox.ItemTemplate>
</ListBox>

5.3.3 DataGrid 虚拟化配置

xml

复制代码
<DataGrid x:Name="LargeDataGrid"
          VirtualizingPanel.IsVirtualizing="True"
          VirtualizingPanel.VirtualizationMode="Recycling"
          EnableColumnVirtualization="True"
          ScrollViewer.IsDeferredScrollingEnabled="False"
          EnableRowVirtualization="True">
    
    <!-- 性能优化设置 -->
    <DataGrid.Resources>
        <Style TargetType="DataGridCell">
            <Setter Property="BorderThickness" Value="0"/>
        </Style>
    </DataGrid.Resources>
    
</DataGrid>

5.3.4 自定义虚拟化面板

csharp

复制代码
public class VirtualizingTilePanel : VirtualizingPanel, IScrollInfo
{
    private Size _extent = new Size(0, 0);
    private Size _viewport = new Size(0, 0);
    private Point _offset = new Point(0, 0);
    private ScrollViewer _scrollOwner;
    
    private int _tileSize = 100;
    private int _columns = 3;
    
    public VirtualizingTilePanel()
    {
        this.CanHorizontallyScroll = false;
        this.CanVerticallyScroll = true;
    }
    
    public ScrollViewer ScrollOwner
    {
        get => _scrollOwner;
        set => _scrollOwner = value;
    }
    
    public bool CanHorizontallyScroll { get; set; }
    public bool CanVerticallyScroll { get; set; }
    
    public double ExtentHeight => _extent.Height;
    public double ExtentWidth => _extent.Width;
    public double ViewportHeight => _viewport.Height;
    public double ViewportWidth => _viewport.Width;
    public double HorizontalOffset => _offset.X;
    public double VerticalOffset => _offset.Y;
    
    protected override void OnItemsChanged(object sender, ItemsChangedEventArgs args)
    {
        base.OnItemsChanged(args);
        InvalidateMeasure();
    }
    
    protected override Size MeasureOverride(Size availableSize)
    {
        if (Items == null || Items.Count == 0)
            return new Size(0, 0);
        
        // 计算可视区域
        _viewport = availableSize;
        
        // 计算需要显示的行数
        int rows = (int)Math.Ceiling((double)Items.Count / _columns);
        _extent = new Size(availableSize.Width, rows * _tileSize);
        
        // 确定可见范围
        int firstVisibleRow = (int)(_offset.Y / _tileSize);
        int lastVisibleRow = (int)((_offset.Y + _viewport.Height) / _tileSize) + 1;
        
        int firstVisibleIndex = firstVisibleRow * _columns;
        int lastVisibleIndex = Math.Min(lastVisibleRow * _columns, Items.Count) - 1;
        
        // 生成/回收子元素
        IItemContainerGenerator generator = this.ItemContainerGenerator;
        GeneratorPosition startPos = generator.GeneratorPositionFromIndex(firstVisibleIndex);
        int childIndex = (startPos.Offset == 0) ? startPos.Index : startPos.Index + 1;
        
        using (generator.StartAt(startPos, GeneratorDirection.Forward, true))
        {
            for (int i = firstVisibleIndex; i <= lastVisibleIndex; i++)
            {
                bool newlyRealized;
                UIElement child = generator.GenerateNext(out newlyRealized) as UIElement;
                if (newlyRealized)
                {
                    if (childIndex >= InternalChildren.Count)
                        AddInternalChild(child);
                    else
                        InsertInternalChild(childIndex, child);
                    
                    generator.PrepareItemContainer(child);
                }
                
                child.Measure(new Size(_tileSize, _tileSize));
                
                childIndex++;
            }
        }
        
        // 清理不可见的子元素
        for (int i = InternalChildren.Count - 1; i >= childIndex; i--)
        {
            RemoveInternalChildRange(i, 1);
        }
        
        return availableSize;
    }
    
    protected override Size ArrangeOverride(Size finalSize)
    {
        for (int i = 0; i < InternalChildren.Count; i++)
        {
            UIElement child = InternalChildren[i];
            var itemIndex = Items.IndexOf(child);
            if (itemIndex >= 0)
            {
                int row = itemIndex / _columns;
                int col = itemIndex % _columns;
                
                double x = col * _tileSize;
                double y = row * _tileSize - _offset.Y;
                
                child.Arrange(new Rect(x, y, _tileSize, _tileSize));
            }
        }
        
        return finalSize;
    }
    
    public void LineUp()
    {
        SetVerticalOffset(_offset.Y - _tileSize);
    }
    
    public void LineDown()
    {
        SetVerticalOffset(_offset.Y + _tileSize);
    }
    
    public void PageUp()
    {
        SetVerticalOffset(_offset.Y - _viewport.Height);
    }
    
    public void PageDown()
    {
        SetVerticalOffset(_offset.Y + _viewport.Height);
    }
    
    public void MouseWheelUp()
    {
        SetVerticalOffset(_offset.Y - _tileSize * 3);
    }
    
    public void MouseWheelDown()
    {
        SetVerticalOffset(_offset.Y + _tileSize * 3);
    }
    
    public void SetHorizontalOffset(double offset) { }
    
    public void SetVerticalOffset(double offset)
    {
        offset = Math.Max(0, Math.Min(offset, _extent.Height - _viewport.Height));
        if (Math.Abs(_offset.Y - offset) < 0.01) return;
        
        _offset.Y = offset;
        InvalidateMeasure();
        InvalidateArrange();
        
        _scrollOwner?.InvalidateScrollInfo();
    }
}

5.4 性能优化技巧

5.4.1 使用 Freezable 冻结资源

csharp

复制代码
// 冻结不会改变的对象,提高性能
public static class ResourceOptimizer
{
    public static T FreezeIfPossible<T>(T resource) where T : Freezable
    {
        if (resource.CanFreeze && !resource.IsFrozen)
        {
            resource.Freeze();
        }
        return resource;
    }
}

// 使用示例
var brush = new SolidColorBrush(Colors.Red);
brush.Freeze();  // 冻结,提高性能

var transform = new TranslateTransform(10, 20);
transform.Freeze();

5.4.2 减少布局更新(使用 Dispatcher)

csharp

复制代码
public class ThrottledUpdate
{
    private DispatcherTimer _timer;
    private Action _updateAction;
    private bool _isUpdatePending;
    
    public ThrottledUpdate(Action updateAction, int delayMs = 16)
    {
        _updateAction = updateAction;
        _timer = new DispatcherTimer
        {
            Interval = TimeSpan.FromMilliseconds(delayMs)
        };
        _timer.Tick += (s, e) =>
        {
            if (_isUpdatePending)
            {
                _updateAction();
                _isUpdatePending = false;
                _timer.Stop();
            }
        };
    }
    
    public void RequestUpdate()
    {
        _isUpdatePending = true;
        _timer.Start();
    }
}

// 使用
var throttler = new ThrottledUpdate(() =>
{
    // 更新 UI 的操作
    MyListBox.ItemsSource = newData;
}, 50);

// 快速多次调用只会执行最后一次
throttler.RequestUpdate();
throttler.RequestUpdate();
throttler.RequestUpdate();  // 只会执行一次

5.4.3 使用 UI 虚拟化容器

xml

复制代码
<!-- 使用 VirtualizingStackPanel 替代普通 StackPanel -->
<ListBox ItemsSource="{Binding LargeCollection}">
    <ListBox.ItemsPanel>
        <ItemsPanelTemplate>
            <VirtualizingStackPanel VirtualizationMode="Recycling"/>
        </ItemsPanelTemplate>
    </ListBox.ItemsPanel>
</ListBox>

5.4.4 缓存容器大小

csharp

复制代码
public class CachedSizePanel : Panel
{
    private Dictionary<UIElement, Size> _cachedSizes = new Dictionary<UIElement, Size>();
    
    protected override Size MeasureOverride(Size availableSize)
    {
        foreach (UIElement child in Children)
        {
            if (_cachedSizes.TryGetValue(child, out Size cachedSize))
            {
                // 使用缓存的大小
                child.SetValue(WidthProperty, cachedSize.Width);
                child.SetValue(HeightProperty, cachedSize.Height);
            }
            else
            {
                child.Measure(availableSize);
                _cachedSizes[child] = child.DesiredSize;
            }
        }
        
        return base.MeasureOverride(availableSize);
    }
    
    public void InvalidateCache(UIElement child)
    {
        _cachedSizes.Remove(child);
        InvalidateMeasure();
    }
}

5.4.5 异步加载大数据

csharp

复制代码
public class AsyncDataLoader
{
    private readonly Dispatcher _dispatcher;
    private readonly int _batchSize = 100;
    
    public AsyncDataLoader()
    {
        _dispatcher = Dispatcher.CurrentDispatcher;
    }
    
    public async Task LoadLargeDataAsync<T>(
        IList<T> targetList,
        IEnumerable<T> sourceData,
        Action<T> onItemLoaded = null)
    {
        var items = sourceData.ToList();
        int total = items.Count;
        int loaded = 0;
        
        while (loaded < total)
        {
            int toLoad = Math.Min(_batchSize, total - loaded);
            var batch = items.Skip(loaded).Take(toLoad).ToList();
            
            // 使用 Invoke 将更新放到 UI 线程
            await _dispatcher.InvokeAsync(() =>
            {
                foreach (var item in batch)
                {
                    targetList.Add(item);
                    onItemLoaded?.Invoke(item);
                }
            });
            
            loaded += toLoad;
            
            // 让 UI 有机会处理其他事件
            await Task.Delay(10);
            
            // 更新进度
            OnProgressUpdated(loaded, total);
        }
    }
    
    public event Action<int, int> ProgressUpdated;
    protected virtual void OnProgressUpdated(int loaded, int total)
    {
        ProgressUpdated?.Invoke(loaded, total);
    }
}

5.5 内存优化

5.5.1 清理未使用的资源

csharp

复制代码
public class ResourceCleanup : IDisposable
{
    private List<IDisposable> _disposables = new List<IDisposable>();
    
    public void Register(IDisposable disposable)
    {
        _disposables.Add(disposable);
    }
    
    public void Dispose()
    {
        foreach (var disposable in _disposables)
        {
            disposable?.Dispose();
        }
        _disposables.Clear();
        
        // 强制垃圾回收(谨慎使用)
        GC.Collect();
        GC.WaitForPendingFinalizers();
        GC.Collect();
    }
}

// 使用示例
public partial class MainWindow : Window
{
    private ResourceCleanup _cleanup = new ResourceCleanup();
    
    public MainWindow()
    {
        InitializeComponent();
        
        // 注册需要清理的资源
        _cleanup.Register(new BitmapImage(new Uri("image.jpg")));
        
        this.Closed += (s, e) => _cleanup.Dispose();
    }
}

5.5.2 使用 WeakReference 避免内存泄漏

csharp

复制代码
public class EventWeakSubscriber<TEventArgs> where TEventArgs : EventArgs
{
    private WeakReference _target;
    private MethodInfo _method;
    
    public EventWeakSubscriber(EventHandler<TEventArgs> handler)
    {
        _target = new WeakReference(handler.Target);
        _method = handler.Method;
    }
    
    public void HandleEvent(object sender, TEventArgs e)
    {
        var target = _target.Target;
        if (target != null)
        {
            _method.Invoke(target, new object[] { sender, e });
        }
    }
}

// 扩展方法:弱订阅事件
public static class EventExtensions
{
    public static void AddWeakEventListener<TEventArgs>(
        this IEventSource source,
        string eventName,
        EventHandler<TEventArgs> handler) where TEventArgs : EventArgs
    {
        var weakSubscriber = new EventWeakSubscriber<TEventArgs>(handler);
        var eventInfo = source.GetType().GetEvent(eventName);
        var delegateType = eventInfo.EventHandlerType;
        var del = Delegate.CreateDelegate(delegateType, weakSubscriber, "HandleEvent");
        eventInfo.AddEventHandler(source, del);
    }
}

5.6 渲染性能

5.6.1 使用 BitmapCache 缓存复杂视觉对象

xml

复制代码
<!-- 对复杂但静态的内容使用缓存 -->
<Canvas>
    <Canvas.CacheMode>
        <BitmapCache RenderAtScale="1" EnableClearType="True"/>
    </Canvas.CacheMode>
    
    <!-- 大量复杂绘图 -->
    <Path ... />
    <Path ... />
    <!-- ... -->
</Canvas>

5.6.2 使用 DrawingVisual 进行高精度绘图

csharp

复制代码
public class FastDrawingPanel : FrameworkElement
{
    private DrawingVisual _visual;
    private bool _needsRedraw = true;
    
    public FastDrawingPanel()
    {
        _visual = new DrawingVisual();
        AddVisualChild(_visual);
    }
    
    protected override int VisualChildrenCount => 1;
    
    protected override Visual GetVisualChild(int index)
    {
        return _visual;
    }
    
    protected override void OnRenderSizeChanged(SizeChangedInfo sizeInfo)
    {
        base.OnRenderSizeChanged(sizeInfo);
        _needsRedraw = true;
        InvalidateVisual();
    }
    
    protected override void OnRender(DrawingContext drawingContext)
    {
        if (_needsRedraw)
        {
            UpdateDrawing();
            _needsRedraw = false;
        }
        drawingContext.DrawDrawing(_visual.Drawing);
    }
    
    private void UpdateDrawing()
    {
        using (var context = _visual.RenderOpen())
        {
            // 使用 DrawingContext 进行高性能绘图
            context.DrawRectangle(Brushes.White, null, new Rect(0, 0, ActualWidth, ActualHeight));
            
            // 绘制 10000 个圆
            var random = new Random();
            for (int i = 0; i < 10000; i++)
            {
                double x = random.NextDouble() * ActualWidth;
                double y = random.NextDouble() * ActualHeight;
                double r = random.NextDouble() * 5;
                context.DrawEllipse(Brushes.Blue, null, new Point(x, y), r, r);
            }
        }
    }
}

5.6.3 使用 WriteableBitmap 进行像素级操作

csharp

复制代码
public class FastBitmapRenderer : FrameworkElement
{
    private WriteableBitmap _bitmap;
    
    protected override void OnRender(DrawingContext drawingContext)
    {
        if (_bitmap == null)
        {
            _bitmap = new WriteableBitmap((int)ActualWidth, (int)ActualHeight, 96, 96, PixelFormats.Pbgra32, null);
            drawingContext.DrawImage(_bitmap, new Rect(0, 0, ActualWidth, ActualHeight));
        }
    }
    
    public void UpdatePixels()
    {
        if (_bitmap == null) return;
        
        // 锁定缓冲区
        _bitmap.Lock();
        
        unsafe
        {
            byte* buffer = (byte*)_bitmap.BackBuffer;
            int stride = _bitmap.BackBufferStride;
            int width = _bitmap.PixelWidth;
            int height = _bitmap.PixelHeight;
            
            // 直接操作像素
            for (int y = 0; y < height; y++)
            {
                for (int x = 0; x < width; x++)
                {
                    int index = y * stride + x * 4;
                    buffer[index] = (byte)(x % 255);     // B
                    buffer[index + 1] = (byte)(y % 255); // G
                    buffer[index + 2] = 255;              // R
                    buffer[index + 3] = 255;              // A
                }
            }
        }
        
        // 标记已修改
        _bitmap.AddDirtyRect(new Int32Rect(0, 0, _bitmap.PixelWidth, _bitmap.PixelHeight));
        _bitmap.Unlock();
        
        InvalidateVisual();
    }
}

5.7 常见性能陷阱

5.7.1 避免在 UI 线程执行耗时操作

csharp

复制代码
// ❌ 错误:阻塞 UI 线程
private void LoadButton_Click(object sender, RoutedEventArgs e)
{
    // 耗时操作会卡死 UI
    var data = DatabaseService.GetLargeData();  // 阻塞 3 秒
    ListBox.ItemsSource = data;
}

// ✅ 正确:使用异步
private async void LoadButton_Click(object sender, RoutedEventArgs e)
{
    // 显示加载指示器
    LoadingIndicator.Visibility = Visibility.Visible;
    
    try
    {
        // 异步操作不阻塞 UI
        var data = await DatabaseService.GetLargeDataAsync();
        ListBox.ItemsSource = data;
    }
    finally
    {
        LoadingIndicator.Visibility = Visibility.Collapsed;
    }
}

5.7.2 避免在布局中使用复杂转换

xml

复制代码
<!-- ❌ 不好:大量使用 LayoutTransform -->
<Button>
    <Button.LayoutTransform>
        <ScaleTransform ScaleX="2" ScaleY="2"/>
    </Button.LayoutTransform>
</Button>

<!-- ✅ 更好:使用 RenderTransform -->
<Button>
    <Button.RenderTransform>
        <ScaleTransform ScaleX="2" ScaleY="2"/>
    </Button.RenderTransform>
</Button>

5.7.3 减少视觉树深度

xml

复制代码
<!-- ❌ 不好:嵌套过深 -->
<Grid>
    <Border>
        <StackPanel>
            <Grid>
                <TextBlock Text="Hello"/>
            </Grid>
        </StackPanel>
    </Border>
</Grid>

<!-- ✅ 更好:扁平化 -->
<Grid>
    <TextBlock Text="Hello" Margin="10"/>
</Grid>
相关推荐
努力努力再努力wz1 小时前
【C++高阶数据结构系列】:跳表 SkipList 详解:多层索引、随机晋升与C++ 完整实现(附跳表实现的源码)
开发语言·数据结构·数据库·c++·redis·缓存·skiplist
更深兼春远1 小时前
scala基于IDEA部署
开发语言·scala·intellij-idea
AIFQuant1 小时前
贵金属投资 APP 开发:实时报价、图表、提醒与交易数据全链路
开发语言·前端·websocket·金融·web app
焚 城1 小时前
Winform双语实现
c#·winform
小七在进步1 小时前
C语言:编译与链接
c语言·开发语言
shuoshuohaohao1 小时前
《JavaScript》
开发语言·前端·javascript
ch.ju1 小时前
Java程序设计(第3版)第四章——私有属性
java·开发语言
Cloud_Shy6181 小时前
解读《Effective Python 3rd Edition》:从练气到老魔(第二章 Item 13 - 16)
c语言·开发语言·网络·笔记·python·编辑器
雪豹阿伟1 小时前
16.C# —— 委托,委托实例,多播委托,内置委托,泛型委托
c#·上位机