1. WPF 基础入门:从零构建第一个应用
1.1 开发环境准备与项目创建
工欲善其事,必先利其器。在开始 WPF 开发前,我们需要准备合适的开发环境。Visual Studio 是微软官方推荐的 IDE,目前最新版本是 Visual Studio 2022。安装时需要注意勾选以下工作负载:
- ".NET 桌面开发"(包含 WPF 开发所需的核心组件)
- "通用 Windows 平台开发"(可选,如需开发 UWP 应用)
- ".NET Core 跨平台开发"(如需跨平台支持)
创建新项目时,会面临 .NET Framework 和 .NET Core/.NET 5+ 的选择。我强烈建议选择 .NET 6 或更高版本,原因有三:
- 跨平台支持:.NET Core 可以在 Windows、Linux 和 macOS 上运行
- 性能优化:.NET Core 在启动速度和内存占用上有显著提升
- 长期支持:微软已将开发重心转移到 .NET Core/.NET 5+
创建项目时,搜索"WPF"模板,选择"WPF 应用程序"(注意不要选成"WPF 类库")。项目命名建议遵循 Pascal 命名法,如"WpfDemoApp"。
1.2 项目结构解析
创建完成后,VS 会生成几个核心文件:
App.xaml:应用程序入口点,相当于 WinForms 中的 Program.csMainWindow.xaml:默认主窗口的界面定义文件MainWindow.xaml.cs:主窗口的后台代码文件
这里有个新手容易忽略的细节:在 .NET Core 项目中,App.xaml 默认不包含 StartupUri 属性,这意味着你需要手动指定启动窗口。有两种处理方式:
- 在
App.xaml中添加:
xml复制<Application x:Class="WpfDemo.App"
StartupUri="MainWindow.xaml">
- 或者在
App.xaml.cs中重写OnStartup方法:
csharp复制protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
new MainWindow().Show();
}
我个人更推荐第二种方式,因为它提供了更大的灵活性,可以在显示窗口前执行一些初始化操作。
1.3 XAML 基础与常用控件
XAML (eXtensible Application Markup Language) 是 WPF 的界面描述语言,它采用 XML 语法,具有以下特点:
- 声明式编程:通过标记语言描述界面,而非过程式代码
- 树形结构:控件以层次结构组织,形成可视化树
- 数据绑定:支持强大的数据绑定机制
让我们创建一个简单的用户界面,包含几个基本控件:
xml复制<Window x:Class="WpfDemo.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="控件演示" Height="450" Width="800">
<StackPanel Margin="10">
<!-- 文本显示 -->
<TextBlock Text="欢迎使用WPF" FontSize="20" Foreground="Blue"/>
<!-- 文本输入 -->
<TextBox x:Name="InputBox" Margin="0,10"
TextChanged="InputBox_TextChanged"/>
<!-- 按钮 -->
<Button Content="点击我" HorizontalAlignment="Left"
Click="Button_Click"/>
<!-- 复选框 -->
<CheckBox Content="我同意条款" Margin="0,10"/>
<!-- 单选按钮 -->
<StackPanel Orientation="Horizontal">
<RadioButton Content="选项1" GroupName="Options" IsChecked="True"/>
<RadioButton Content="选项2" GroupName="Options" Margin="10,0"/>
</StackPanel>
<!-- 进度条 -->
<ProgressBar Value="30" Height="20" Margin="0,10"/>
</StackPanel>
</Window>
对应的后台代码:
csharp复制private void Button_Click(object sender, RoutedEventArgs e)
{
MessageBox.Show("按钮被点击了!");
}
private void InputBox_TextChanged(object sender, TextChangedEventArgs e)
{
// 可以在这里处理文本变化逻辑
}
1.4 布局系统详解
WPF 的布局系统是其强大功能之一,它通过布局面板自动管理控件的尺寸和位置。常用的布局面板有:
- Grid:表格布局,最灵活的面板
xml复制<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/> <!-- 根据内容自动调整高度 -->
<RowDefinition Height="*"/> <!-- 占用剩余空间 -->
<RowDefinition Height="2*"/> <!-- 高度是上一行的2倍 -->
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Label Grid.Row="0" Grid.Column="0" Content="用户名:"/>
<TextBox Grid.Row="0" Grid.Column="1" Margin="5"/>
<Label Grid.Row="1" Grid.Column="0" Content="密码:"/>
<PasswordBox Grid.Row="1" Grid.Column="1" Margin="5"/>
</Grid>
- StackPanel:线性堆叠控件
xml复制<StackPanel Orientation="Vertical">
<Button Content="按钮1"/>
<Button Content="按钮2"/>
<Button Content="按钮3"/>
</StackPanel>
- DockPanel:控件可以停靠在面板的边缘
xml复制<DockPanel LastChildFill="True">
<Menu DockPanel.Dock="Top">...</Menu>
<StatusBar DockPanel.Dock="Bottom">...</StatusBar>
<ToolBar DockPanel.Dock="Left">...</ToolBar>
<ContentControl/> <!-- 填充剩余空间 -->
</DockPanel>
- WrapPanel:自动换行布局
xml复制<WrapPanel>
<Button Content="按钮1" Width="100" Margin="5"/>
<Button Content="按钮2" Width="100" Margin="5"/>
<!-- 当空间不足时会自动换行 -->
</WrapPanel>
- Canvas:绝对定位(慎用)
xml复制<Canvas>
<Button Canvas.Left="50" Canvas.Top="30" Content="绝对定位"/>
</Canvas>
重要提示:在实际项目中,应尽量避免使用 Canvas 进行绝对定位,这会导致界面无法适应不同分辨率和DPI设置。优先考虑使用 Grid 和 StackPanel 等自适应布局。
1.5 数据绑定基础
数据绑定是 WPF 的核心特性,它实现了视图(View)和数据(Data)的自动同步。基本绑定语法:
xml复制<TextBlock Text="{Binding Path=UserName}"/>
这表示 TextBlock 的 Text 属性绑定到当前 DataContext 的 UserName 属性。
绑定模式有三种:
- OneWay:源→目标单向绑定
- TwoWay:双向绑定(默认用于可编辑控件如 TextBox)
- OneTime:一次性绑定(只在初始化时更新)
示例:创建一个简单的数据绑定
首先定义数据类:
csharp复制public class Person : INotifyPropertyChanged
{
private string _name;
public string Name
{
get => _name;
set
{
_name = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
然后在 XAML 中使用:
xml复制<StackPanel>
<TextBox Text="{Binding Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Margin="5"/>
<TextBlock Text="{Binding Name}" Margin="5"/>
</StackPanel>
后台代码设置 DataContext:
csharp复制public MainWindow()
{
InitializeComponent();
DataContext = new Person { Name = "张三" };
}
这样,当在 TextBox 中输入内容时,TextBlock 会实时更新,这就是双向绑定的威力。
2. ModernUI 实践:打造现代化界面
2.1 ModernUI 设计理念
ModernUI(又称 Fluent Design)是微软推出的现代化设计语言,包含五大核心元素:
- 光感(Light):通过阴影和层次感创造深度
- 深度(Depth):利用视差和分层创造立体感
- 动效(Motion):流畅的动画过渡
- 材质(Material):半透明和模糊效果
- 缩放(Scale):自适应不同设备尺寸
在 WPF 中实现 ModernUI 风格,最常用的库是 ModernWpfUI(原 MahApps.Metro 的进化版),它提供了:
- 现代化的窗口样式(圆角、亚克力效果)
- 丰富的主题支持(亮色/暗色)
- 符合 Fluent Design 的控件样式
- 内置动画和过渡效果
2.2 集成 ModernWpfUI
安装步骤
- 通过 NuGet 安装 ModernWpfUI 包:
powershell复制Install-Package ModernWpfUI
- 修改 App.xaml 引入主题资源:
xml复制<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="pack://application:,,,/ModernWpf;component/Theme/ThemeResources.xaml"/>
<ResourceDictionary Source="pack://application:,,,/ModernWpf;component/Theme/ThemeManager.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
- 启用现代窗口样式:
xml复制<Window x:Class="WpfDemo.MainWindow"
xmlns:ui="http://schemas.modernwpf.com/2019"
ui:WindowHelper.UseModernWindowStyle="True">
<!-- 窗口内容 -->
</Window>
主题切换
ModernWpfUI 支持亮色和暗色主题,可以通过代码动态切换:
csharp复制// 切换到亮色主题
ThemeManager.Current.ApplicationTheme = ApplicationTheme.Light;
// 切换到暗色主题
ThemeManager.Current.ApplicationTheme = ApplicationTheme.Dark;
// 跟随系统主题
ThemeManager.Current.ApplicationTheme = null;
也可以在 XAML 中为特定元素设置主题:
xml复制<StackPanel ui:ThemeManager.RequestedTheme="Dark">
<!-- 这个面板及其子元素将使用暗色主题 -->
</StackPanel>
2.3 ModernUI 控件样式
ModernWpfUI 为常见控件提供了现代化的样式,例如:
- 按钮:
xml复制<Button Content="主要按钮" Style="{StaticResource AccentButtonStyle}"/>
<Button Content="标准按钮" Style="{StaticResource DefaultButtonStyle}"/>
- 文本框:
xml复制<TextBox ui:ControlHelper.PlaceholderText="请输入用户名"/>
- 进度控件:
xml复制<ProgressRing IsActive="True" Width="40" Height="40"/>
<ProgressBar Value="50" Maximum="100" IsIndeterminate="False"/>
- 导航视图(类似 UWP 的 NavigationView):
xml复制<ui:NavigationView PaneDisplayMode="LeftCompact">
<ui:NavigationView.MenuItems>
<ui:NavigationViewItem Content="首页" Icon="Home"/>
<ui:NavigationViewItem Content="设置" Icon="Setting"/>
</ui:NavigationView.MenuItems>
<Frame x:Name="MainFrame"/>
</ui:NavigationView>
2.4 亚克力效果与阴影
ModernWpfUI 支持亚克力(Acrylic)材质效果,可以为元素添加半透明模糊背景:
xml复制<ui:AcrylicBrush TintColor="White" TintOpacity="0.8"
BackgroundSource="HostBackdrop" NoiseOpacity="0.03">
<!-- 应用亚克力效果的控件 -->
</ui:AcrylicBrush>
添加阴影效果:
xml复制<Border ui:ThemeShadowHelper.IsEnabled="True"
CornerRadius="4" Background="{StaticResource SystemControlBackgroundAltHighBrush}">
<!-- 内容 -->
</Border>
2.5 动画与过渡
ModernUI 强调流畅的动画体验。ModernWpfUI 提供了几种内置动画:
- 内容过渡动画:
xml复制<ui:TransitionFrame x:Name="MainFrame">
<ui:TransitionFrame.Transitions>
<ui:TransitionCollection>
<ui:NavigationThemeTransition/>
</ui:TransitionCollection>
</ui:TransitionFrame.Transitions>
</ui:TransitionFrame>
- 元素入场动画:
xml复制<StackPanel>
<ui:ElementAnimator.Animation>
<ui:AnimationCollection>
<ui:OpacityAnimation From="0" To="1" Duration="0:0:0.3"/>
<ui:TranslationAnimation From="0,20,0" To="0,0,0" Duration="0:0:0.5"/>
</ui:AnimationCollection>
</ui:ElementAnimator.Animation>
<!-- 内容 -->
</StackPanel>
3. MVVM 架构与 Prism 框架
3.1 MVVM 设计模式详解
MVVM(Model-View-ViewModel)是 WPF 推荐的架构模式,它将应用程序分为三层:
- Model:数据模型和业务逻辑
- View:用户界面(XAML)
- ViewModel:连接 View 和 Model 的桥梁
MVVM 的核心优势:
- 关注点分离:界面逻辑与业务逻辑解耦
- 可测试性:ViewModel 不依赖 UI,易于单元测试
- 数据绑定:通过绑定自动同步视图和状态
3.2 实现基础 MVVM
3.2.1 ViewModel 基类
创建一个实现 INotifyPropertyChanged 的基类:
csharp复制public abstract class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
protected bool SetField<T>(ref T field, T value, [CallerMemberName] string propertyName = null)
{
if (EqualityComparer<T>.Default.Equals(field, value)) return false;
field = value;
OnPropertyChanged(propertyName);
return true;
}
}
3.2.2 命令实现
WPF 的命令通过 ICommand 接口实现:
csharp复制public class RelayCommand : ICommand
{
private readonly Action _execute;
private readonly Func<bool> _canExecute;
public RelayCommand(Action execute, Func<bool> canExecute = null)
{
_execute = execute ?? throw new ArgumentNullException(nameof(execute));
_canExecute = canExecute;
}
public bool CanExecute(object parameter) => _canExecute?.Invoke() ?? true;
public void Execute(object parameter) => _execute();
public event EventHandler CanExecuteChanged
{
add => CommandManager.RequerySuggested += value;
remove => CommandManager.RequerySuggested -= value;
}
}
3.2.3 完整 ViewModel 示例
csharp复制public class MainViewModel : ViewModelBase
{
private string _userName;
public string UserName
{
get => _userName;
set => SetField(ref _userName, value);
}
private string _status;
public string Status
{
get => _status;
private set => SetField(ref _status, value);
}
public ICommand LoginCommand { get; }
public MainViewModel()
{
LoginCommand = new RelayCommand(Login, CanLogin);
}
private bool CanLogin() => !string.IsNullOrWhiteSpace(UserName);
private void Login()
{
Status = $"欢迎, {UserName}!";
}
}
对应的 View:
xml复制<Window.DataContext>
<local:MainViewModel/>
</Window.DataContext>
<StackPanel Margin="10">
<TextBox Text="{Binding UserName, UpdateSourceTrigger=PropertyChanged}"
Margin="0,5"/>
<Button Content="登录" Command="{Binding LoginCommand}"
Margin="0,5" HorizontalAlignment="Left"/>
<TextBlock Text="{Binding Status}" Margin="0,5"/>
</StackPanel>
3.3 Prism 框架深入
Prism 是一个功能强大的 MVVM 框架,提供了以下核心功能:
- 依赖注入:基于 DryIoc 或 Unity
- 导航系统:区域导航和视图切换
- 模块化:将应用拆分为多个模块
- 事件聚合:组件间松耦合通信
- 对话框服务:标准化对话框交互
3.3.1 安装与配置
- 安装 Prism.DryIoc 包:
powershell复制Install-Package Prism.DryIoc
- 修改 App.xaml:
xml复制<prism:PrismApplication x:Class="WpfDemo.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:prism="http://prismlibrary.com/">
</prism:PrismApplication>
- 修改 App.xaml.cs:
csharp复制public partial class App : PrismApplication
{
protected override Window CreateShell()
{
return Container.Resolve<MainWindow>();
}
protected override void RegisterTypes(IContainerRegistry containerRegistry)
{
// 注册类型
containerRegistry.Register<IService, MyService>();
// 注册视图
containerRegistry.RegisterForNavigation<ViewA>();
containerRegistry.RegisterForNavigation<ViewB>();
}
}
3.3.2 导航系统
Prism 的导航系统基于"区域"概念:
- 定义区域:
xml复制<ContentControl prism:RegionManager.RegionName="MainRegion"/>
- 导航到视图:
csharp复制_regionManager.RequestNavigate("MainRegion", "ViewA");
- 带参数导航:
csharp复制var parameters = new NavigationParameters();
parameters.Add("id", 123);
_regionManager.RequestNavigate("MainRegion", "ViewA", parameters);
- 在目标视图接收参数:
csharp复制public override void OnNavigatedTo(NavigationContext navigationContext)
{
var id = navigationContext.Parameters.GetValue<int>("id");
// 使用参数
}
3.3.3 对话框服务
Prism 提供了标准化的对话框服务:
- 创建对话框视图:
xml复制<UserControl x:Class="WpfDemo.Views.NotificationDialog"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<TextBlock Text="{Binding Message}" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</UserControl>
- 创建对话框 ViewModel:
csharp复制public class NotificationDialogViewModel : BindableBase, IDialogAware
{
public string Title => "通知";
private string _message;
public string Message
{
get => _message;
set => SetProperty(ref _message, value);
}
public event Action<IDialogResult> RequestClose;
public bool CanCloseDialog() => true;
public void OnDialogClosed() { }
public void OnDialogOpened(IDialogParameters parameters)
{
Message = parameters.GetValue<string>("message");
}
}
- 注册对话框:
csharp复制containerRegistry.RegisterDialog<NotificationDialog, NotificationDialogViewModel>();
- 显示对话框:
csharp复制_dialogService.ShowDialog("NotificationDialog", new DialogParameters
{
{ "message", "操作成功!" }
}, r =>
{
// 对话框关闭后的回调
});
3.3.4 事件聚合器
事件聚合器实现组件间松耦合通信:
- 定义事件:
csharp复制public class UserLoggedInEvent : PubSubEvent<string> { }
- 发布事件:
csharp复制_eventAggregator.GetEvent<UserLoggedInEvent>().Publish(userName);
- 订阅事件:
csharp复制_eventAggregator.GetEvent<UserLoggedInEvent>().Subscribe(UserLoggedInHandler);
private void UserLoggedInHandler(string userName)
{
// 处理事件
}
- 取消订阅(重要!避免内存泄漏):
csharp复制_eventAggregator.GetEvent<UserLoggedInEvent>().Unsubscribe(UserLoggedInHandler);
3.4 高级 MVVM 技巧
3.4.1 验证与错误处理
实现 IDataErrorInfo 或 INotifyDataErrorInfo 进行数据验证:
csharp复制public class LoginViewModel : BindableBase, INotifyDataErrorInfo
{
private readonly Dictionary<string, List<string>> _errors = new();
private string _userName;
public string UserName
{
get => _userName;
set
{
SetProperty(ref _userName, value);
ValidateUserName();
}
}
private void ValidateUserName()
{
ClearErrors(nameof(UserName));
if (string.IsNullOrWhiteSpace(UserName))
{
AddError(nameof(UserName), "用户名不能为空");
}
else if (UserName.Length < 3)
{
AddError(nameof(UserName), "用户名至少3个字符");
}
}
private void AddError(string propertyName, string error)
{
if (!_errors.ContainsKey(propertyName))
_errors[propertyName] = new List<string>();
if (!_errors[propertyName].Contains(error))
{
_errors[propertyName].Add(error);
OnErrorsChanged(propertyName);
}
}
private void ClearErrors(string propertyName)
{
if (_errors.Remove(propertyName))
{
OnErrorsChanged(propertyName);
}
}
public bool HasErrors => _errors.Any();
public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
public IEnumerable GetErrors(string propertyName)
{
return _errors.TryGetValue(propertyName, out var errors) ? errors : null;
}
protected virtual void OnErrorsChanged(string propertyName)
{
ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs(propertyName));
}
}
在 XAML 中显示验证错误:
xml复制<TextBox Text="{Binding UserName, ValidatesOnNotifyDataErrors=True}"/>
<TextBlock Text="{Binding (Validation.Errors)[0].ErrorContent, ElementName=UserNameTextBox}"
Foreground="Red"/>
3.4.2 异步命令
Prism 提供了 AsyncCommand 处理异步操作:
csharp复制public class AsyncViewModel : BindableBase
{
public IAsyncCommand LoadDataCommand { get; }
private bool _isLoading;
public bool IsLoading
{
get => _isLoading;
set => SetProperty(ref _isLoading, value);
}
public AsyncViewModel()
{
LoadDataCommand = new AsyncCommand(LoadDataAsync);
}
private async Task LoadDataAsync()
{
try
{
IsLoading = true;
await Task.Delay(2000); // 模拟耗时操作
// 加载数据...
}
finally
{
IsLoading = false;
}
}
}
3.4.3 视图模型定位器
Prism 的 ViewModelLocator 自动将视图与视图模型关联:
xml复制<Window x:Class="WpfDemo.Views.MainWindow"
prism:ViewModelLocator.AutoWireViewModel="True">
<!-- 内容 -->
</Window>
命名约定:
- 视图:WpfDemo.Views.MainWindow
- 视图模型:WpfDemo.ViewModels.MainWindowViewModel
4. 实战技巧与性能优化
4.1 数据绑定最佳实践
-
绑定模式选择:
- 只读数据:OneWay
- 可编辑数据:TwoWay
- 一次性数据:OneTime
-
更新时机控制:
- TextBox 默认在失去焦点时更新源(UpdateSourceTrigger=LostFocus)
- 对于实时搜索等场景,使用 PropertyChanged:
xml复制<TextBox Text="{Binding SearchText, UpdateSourceTrigger=PropertyChanged}"/> -
绑定验证:
- 使用 IDataErrorInfo 或 INotifyDataErrors
- 设置 ValidatesOnDataErrors=True
-
绑定性能优化:
- 避免过度绑定(如绑定到大型集合)
- 对静态数据使用 x:Static
- 考虑使用 Binding.Delay 属性
4.2 集合绑定与虚拟化
处理大型集合时的优化技巧:
- 使用 ObservableCollection:
csharp复制public ObservableCollection<Item> Items { get; } = new();
- 启用UI虚拟化:
xml复制<ListBox VirtualizingStackPanel.IsVirtualizing="True"
VirtualizingStackPanel.VirtualizationMode="Recycling"/>
- 分页加载:
csharp复制public async Task LoadMoreItemsAsync()
{
var newItems = await _service.GetItemsAsync(_pageNumber, _pageSize);
foreach (var item in newItems)
{
Items.Add(item);
}
_pageNumber++;
}
- 数据虚拟化:
csharp复制public class VirtualizingCollection<T> : IList<T>, INotifyCollectionChanged
{
// 实现按需加载数据
}
4.3 资源管理与样式
-
资源字典组织:
- 按功能/模块拆分资源
- 使用 MergedDictionaries 合并资源
-
主题支持:
xml复制<ResourceDictionary>
<ResourceDictionary.ThemeDictionaries>
<ResourceDictionary x:Key="Light">
<Color x:Key="BackgroundColor">White</Color>
</ResourceDictionary>
<ResourceDictionary x:Key="Dark">
<Color x:Key="BackgroundColor">Black</Color>
</ResourceDictionary>
</ResourceDictionary.ThemeDictionaries>
</ResourceDictionary>
- 动态资源与静态资源:
- StaticResource:一次性查找,性能更好
- DynamicResource:可动态更新,适合主题切换
4.4 调试与性能分析
- 绑定调试:
xml复制<Window xmlns:diag="clr-namespace:System.Diagnostics;assembly=WindowsBase">
<TextBlock Text="{Binding Path, diag:PresentationTraceSources.TraceLevel=High}"/>
</Window>
-
性能分析工具:
- Visual Studio 性能探查器
- WPF Performance Suite
- PerfView
-
常见性能问题:
- 布局循环(Measure/Arrange)
- 过度可视化树遍历
- 未虚拟化的长列表
- 频繁的垃圾回收
4.5 部署与更新
-
发布方式:
- ClickOnce(简单但功能有限)
- MSI 安装包(使用 WiX Toolset)
- Squirrel.Windows(支持自动更新)
-
自动更新实现:
csharp复制using var mgr = new UpdateManager("https://your-update-server.com");
var updateInfo = await mgr.CheckForUpdate();
if (updateInfo.ReleasesToApply.Any())
{
await mgr.DownloadReleases(updateInfo.ReleasesToApply);
await mgr.ApplyReleases(updateInfo);
UpdateManager.RestartApp();
}
- 单文件发布:
xml复制<PropertyGroup>
<PublishSingleFile>true</PublishSingleFile>
<SelfContained>true</SelfContained>
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
</PropertyGroup>
5. 常见问题与解决方案
5.1 内存泄漏排查
WPF 常见内存泄漏原因:
-
事件未注销:
- 特别是静态事件或长生命周期对象的事件
- 解决方案:实现 IDisposable 并在 Dispose 中注销事件
-
绑定未清除:
- 特别是绑定到静态属性或单例对象
- 解决方案:在 Unloaded 事件中清除绑定
-
资源未释放:
- 图像、媒体等资源
- 解决方案:明确调用 Dispose 或设置 Source=null
5.2 跨线程访问
WPF 的 UI 元素只能由创建它们的线程访问:
csharp复制// 错误方式 - 会抛出异常
Task.Run(() =>
{
textBox.Text = "更新文本";
});
// 正确方式
await Task.Run(() => DoWork());
textBox.Text = "更新文本";
// 或者使用 Dispatcher
Dispatcher.Invoke(() => textBox.Text = "更新文本");
// 在 ViewModel 中
Application.Current.Dispatcher.Invoke(() => Status = "完成");
5.3 DPI 与缩放问题
确保应用支持高DPI:
- 在 app.manifest 中启用高DPI感知:
xml复制<application xmlns="urn:schemas-microsoft-com:asm.v3">
<windowsSettings>
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true</dpiAware>
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness>
</windowsSettings>
</application>
-
使用 ViewBox 或布局面板而非固定尺寸
-
测试不同DPI设置下的表现
5.4 数据模板选择
根据数据类型自动选择模板:
xml复制<ContentControl Content="{Binding CurrentItem}">
<ContentControl.Resources>
<DataTemplate DataType="{x:Type local:TextItem}">
<TextBlock Text="{Binding Content}"/>
</DataTemplate>
<DataTemplate DataType="{x:Type local:ImageItem}">
<Image Source="{Binding ImageUrl}"/>
</DataTemplate>
</ContentControl.Resources>
</ContentControl>
5.5 命令参数传递
复杂命令参数传递技巧:
- 使用 CommandParameter:
xml复制<Button Command="{Binding EditCommand}"
CommandParameter="{Binding SelectedItem}"/>
- 使用交互行为(需要 System.Windows.Interactivity):
xml复制<Button>
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<i:InvokeCommandAction Command="{Binding EditCommand}"
CommandParameter="{Binding SelectedItem}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
- 使用 Prism 的 InvokeCommandAction:
xml复制<Button prism:Click.Command="{Binding EditCommand}"
prism:Click.CommandParameter="{Binding SelectedItem}"/>
6. 项目结构与团队协作
6.1 解决方案组织
推荐的项目结构:
code复制WpfApp.sln
├── WpfApp.Core (类库)
│ ├── Models
│ ├── Services
│ ├── Utilities
│ └── ViewModels
├── WpfApp.Infrastructure (类库)
│ ├── Data
│ ├── Logging
│ └── Networking
├── WpfApp.Modules (可选)
│ ├── ModuleA
│ └── ModuleB
└── WpfApp (WPF应用程序)
├── Assets
├── Converters
├── Views
└── App.xaml
6.2 模块化开发
使用 Prism 的模块化功能:
- 创建模块项目
- 实现 IModule 接口:
csharp复制public class ModuleA : IModule
{
public void OnInitialized(IContainerProvider containerProvider)
{
// 模块初始化
}
public void RegisterTypes(IContainerRegistry containerRegistry)
{
// 注册类型
containerRegistry.RegisterForNavigation<ViewA>();
}
}
- 在 App.xaml.cs 中配置模块目录:
csharp复制protected override void ConfigureModuleCatalog(IModuleCatalog moduleCatalog)
{
moduleCatalog.AddModule<ModuleA>();
// 或从目录加载
moduleCatalog.AddModule(new ModuleInfo
{
ModuleName = "ModuleB",
ModuleType = typeof(ModuleB).AssemblyQualifiedName,
InitializationMode = InitializationMode.OnDemand
});
}
6.3 代码共享策略
-
共享 ViewModel:
- 将 ViewModel 放在核心类库中
- 使用接口抽象平台相关功能
-
共享资源:
- 创建共享资源字典
- 使用 MergedDictionaries 引用
-
条件编译:
csharp复制#if WPF
// WPF 特定代码
#elif XAMARIN
// Xamarin 特定代码
#endif
6.4 测试策略
-
单元测试:
- 测试 ViewModel 逻辑
- 使用 Moq 等框架模拟依赖
-
UI 测试:
- 使用 TestStack.White 或 Appium
- 测试关键用户流程
-
集成测试:
- 测试模块间交互
- 测试数据访问层
7. 进阶主题与扩展
7.1 自定义控件开发
创建自定义控件的步骤:
- 继承 Control 类:
csharp复制public class CustomControl : Control
{
static CustomControl()
{
DefaultStyleKeyProperty.OverrideMetadata(
typeof(CustomControl),
new FrameworkPropertyMetadata(typeof(CustomControl)));
}
}
- 定义默认样式(在 Themes/Generic.xaml 中):
xml复制<Style TargetType="{x:Type local:CustomControl}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:CustomControl}">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<TextBlock Text="{TemplateBinding Text}"
HorizontalAlignment="Center"
VerticalAlignment="Center"/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
- 添加依赖属性:
csharp复制public static readonly DependencyProperty TextProperty =
DependencyProperty.Register(
"Text",
typeof(string),
typeof(CustomControl),
new PropertyMetadata("Default Text"));
public string Text
{
get => (string)GetValue(TextProperty);
set => SetValue(TextProperty, value);
}
7.2 渲染技术与性能
-
视觉层优化:
- 使用 DrawingVisual 轻量级绘制
- 考虑使用 WriteableBitmap 直接操作像素
-
硬件加速:
- 启用 GPU 加速
- 优化 RenderOptions 设置
-
缓存策略:
xml复制<Image RenderOptions.BitmapScalingMode="HighQuality"
CacheMode="BitmapCache"/>
7.3 与 Win32 互操作
通过 P/Invoke 调用 Win32 API:
csharp复制[DllImport("user32.dll")]
public static extern IntPtr GetDesktopWindow();
// 在 WPF 中嵌入 Win32 控件
HwndHost host = new MyHwndHost();
wpfPanel.Children.Add(host);
7.4 多语言支持
- 使用资源文件 (.resx)
- 实现动态语言切换:
csharp复制public class LocalizationManager : IN