上一个专题我们学习了 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>