[奇淫巧技] WPF篇 (长期更新)

文章目录

本文介绍了在WPF开发中遇到的坑,长期更新

界面居中

xml 复制代码
WindowStartupLocation = WindowStartupLocation.CenterScreen;

配置管理器

返回的是一个String类型,configuration就是配置的意思,Manager是管理的意思,AppSetting对应配置文件.config里面的标签

在*.cs文件里面定义path,意思就是把在配置文件里面配置好的Path的值赋给path。

csharp 复制代码
public static string path = ConfigurationManager.AppSettings["Path"];

在*.config文件里面配置以下代码

xml 复制代码
<appSettings>
<add key="Path" value="D:\Project\FaceControl\Skins\Diamond\DiamondBlue.ssk" />
</appSettings>

代码规范

一般一些涉及到界面修改变化的代码可以不用放到xaml.cs里面,防止xaml.cs内对界面操作代码过冗杂,可以通过binding放到对应viewmodel里

遇到的问题

Loaded 两次的问题

csharp 复制代码
// 第一时间移除
// 或者UnLoaded
// 可以使用布尔标志进行操作 IsLoaded
public class MyClass : Window
{
    public MyClass()
    {
        Loaded += MyLoadedRoutedEventHandler;
    }

    void MyLoadedRoutedEventHandler(Object sender, RoutedEventArgs e)
    {
        Loaded -= MyLoadedRoutedEventHandler;
        /// ...
    }
};

全局捕获异常

csharp 复制代码
AppDomain.CurrentDomain.UnhandledException += AppDomainUnhandledException;

Current.DispatcherUnhandledException += CurrentApplication_DispatcherUnhandledException;

Dispatcher.CurrentDispatcher.UnhandledException += CurrentDispatcher_UnhandledException;

AppDomain.CurrentDomain.UnhandledException

AppDomain.CurrentDomain.UnhandledException 事件在 应用程序域 层面捕获所有未被处理的异常,无论这些异常发生在 UI 线程还是后台线程。当一个异常未被任何 try-catch 块捕获,并且传播到其所在线程的顶层时,这个事件就会触发。

  • 作用范围: 整个应用程序,包括所有线程(UI 和后台)。
  • 线程: 跨线程。它能捕获后台线程(如 TaskThread)中发生的未处理异常。
  • 用途: 这是一个 最后的防线。通常用于记录异常信息并优雅地关闭应用程序,防止程序崩溃。

Current.DispatcherUnhandledException

Current.DispatcherUnhandledException 事件在 WPF 应用 层面捕获所有未被处理的异常,但仅限于 UI 线程 。它是 System.Windows.Application 类的一部分。当 UI 线程中的代码抛出一个未被捕获的异常时,这个事件就会触发。

  • 作用范围: 整个 WPF 应用程序,但仅限于 UI 线程。
  • 线程: UI 线程。它 不会 捕获后台线程的异常。
  • 用途: 主要用于处理 UI 相关的异常。你可以在这里显示一个友好的错误信息给用户,或者记录异常然后决定是否继续运行程序。

Dispatcher.CurrentDispatcher.UnhandledException

Dispatcher.CurrentDispatcher.UnhandledException 事件在 特定线程的调度器 层面捕获未处理的异常。每个线程都有一个 Dispatcher 对象,它负责管理该线程的消息队列和工作项。这个事件只处理在其 关联线程 上发生的未捕获异常。

  • 作用范围: 仅限于其关联的特定线程。
  • 线程: 单个线程,通常是 UI 线程(因为 CurrentDispatcher 多数情况下指的是主 UI 线程的调度器)。
  • 用途: 当你需要为 某个特定的 UI 线程 (比如一个独立的、由 Dispatcher 管理的辅助 UI 线程)处理异常时,它会非常有用。

总结与比较

这三个事件共同构成了 WPF 异常处理的层次结构,从特定线程到整个应用程序。

事件 作用范围 线程 触发时机
AppDomain.CurrentDomain.UnhandledException 整个应用域 所有线程(UI 和后台) 最后一个捕获点,程序即将崩溃
Current.DispatcherUnhandledException 整个 WPF 应用 仅 UI 线程 UI 线程发生未捕获异常
Dispatcher.CurrentDispatcher.UnhandledException 特定线程 仅其关联线程 线程调度器发生未捕获异常

在实际开发中,通常会同时订阅这三个事件,以确保在任何情况下都能捕获并处理异常,提高应用的健壮性。

未响应

WPF 程序出现**"未响应"(Not Responding)状态**,通常是由于主 UI 线程被阻塞导致的。WPF 应用程序有一个称为 UI 线程 的单一线程,它负责处理所有用户界面相关的任务,包括绘制界面、响应鼠标点击和键盘输入、处理事件等。

当这个 UI 线程被长时间占用,无法处理 Windows 的消息队列时,操作系统就会判定程序进入"未响应"状态,并在标题栏显示"(未响应)"

1. 耗时操作

这是最常见的原因。当你在 UI 线程上执行一个需要很长时间才能完成的任务时,例如:

  1. 文件读写:加载或保存大文件。
  2. 网络请求:同步下载大文件或等待网络 API 响应。
  3. 复杂计算:进行大量的数学运算、图像处理或数据处理。
  4. 数据库操作:执行复杂的查询或批量插入/更新。

这些操作会"冻结"UI 线程,导致界面无法更新,按钮无法点击,鼠标光标也无法改变。

2. 死锁

当两个或多个线程互相等待对方释放资源时,就会发生死锁。虽然这通常涉及多个线程,但如果 UI 线程是其中一个被卡住的线程,程序就会进入未响应状态。

3. 无限循环或长时间的同步等待

无限循环:在 UI 线程上执行一个没有退出条件的 while 循环。

同步等待:使用 Task.Wait()、.Result 或 GetAwaiter().GetResult() 来同步等待一个异步任务完成。这会立即阻塞 UI 线程,直到任务完成。

推荐使用以下方案避免出现未响应

  1. 使用 async/await (推荐)

  2. 使用 Task.Run

  3. 使用 BackgroundWorker

UCEERR_RENDERTHREADFAILURE

错误分析:UCEERR_RENDERTHREADFAILURE

这个错误的核心是:UCEERR_RENDERTHREADFAILURE,其对应的 HRESULT 码是 0x88980406。

1. 错误含义

UCEERR_RENDERTHREADFAILURE:这是 Unmanaged Code Exception Error - Render Thread Failure 的缩写。它明确指出 WPF 的渲染线程(Render Thread)发生了致命错误。

WPF 架构: WPF 应用程序有两个主要线程:

UI 线程 (或 Dispatcher 线程): 处理用户输入、控件逻辑和数据绑定。

渲染线程: 这是一个独立的、高优先级的线程,负责将视觉树转换为屏幕上的像素。它通过 DUCE (DirectX Unmanaged Code) 与 DirectX/GPU 进行通信。

COMException (HRESULT:0x88980406): 这个异常表明渲染线程在与图形硬件(通过 DirectX 或 DWM/Desktop Window Manager)通信时遇到了问题,导致底层图形系统崩溃。

2. 堆栈跟踪分析

堆栈跟踪清晰地指向了问题的发生位置:

在 System.Windows.Media.Composition.DUCE.Channel.SyncFlush()

在 System.Windows.Interop.HwndTarget.UpdateWindowSettings(...)

DUCE.Channel.SyncFlush(): 这是应用程序的 UI 线程试图将渲染指令(例如更新窗口内容、应用动画等)同步刷新到渲染线程时发生的。

结论: 当 UI 线程尝试与渲染线程同步时,渲染线程已经崩溃或处于无效状态,导致 UI 线程接收到这个致命的异常。

3. 常见原因

这个错误通常不是由您的 C# 应用程序代码直接逻辑错误引起的,而是与运行时环境、图形硬件或驱动程序有关。常见的原因包括:

图形驱动程序问题(最常见):驱动程序陈旧、损坏或与操作系统/WPF 版本不兼容。

硬件加速冲突:应用程序可能试图执行某些图形操作,但 GPU 或 DirectX 环境无法支持。

WPF 宿主环境问题:如果应用程序运行在远程桌面、虚拟化环境或存在多个 GPU 的机器上,可能会出现图形初始化或渲染上下文丢失的问题。

底层图形操作压力:频繁且复杂的动画、渲染变换或大量几何图形更新,可能会压垮渲染线程。

解决建议

要解决这个错误,您需要从底层图形环境和应用配置两个方面入手:

  1. 环境与驱动程序检查 (用户侧)

更新/重装显卡驱动: 确保用户运行的是最新的官方显卡驱动程序。

检查 DirectX: 确保系统上的 DirectX 组件是健康且最新的。

系统补丁: 确保操作系统已安装所有最新的更新和补丁。

  1. 代码和配置调整 (开发者侧)

关闭硬件加速(作为测试):在应用程序启动时,可以通过设置渲染层级来禁用硬件加速,强制使用软件渲染。如果错误消失,说明问题确实出在硬件或驱动上。

// 在 App.xaml.cs 或启动代码中添加

csharp 复制代码
RenderOptions.ProcessRenderMode = RenderMode.SoftwareOnly;

检查资源清理:确保所有自定义的 DrawingVisual 或复杂的图形资源在使用完毕后都得到了妥善清理,避免资源泄漏导致渲染线程内存不足或混乱。

使用 Dispatcher.Invoke:确保所有对 UI 元素的修改都严格在 UI 线程上进行。虽然此错误通常是渲染线程的问题,但错误的线程操作也可能间接触发图形子系统的崩溃。

复制代码
[E] 20251013 08:05:56.963 [0001] An unknown exception was received. UCEERR_RENDERTHREADFAILURE (异常来自 HRESULT:0x88980406) {"ClassName":"System.Runtime.InteropServices.COMException","Message":"UCEERR_RENDERTHREADFAILURE (异常来自 HRESULT:0x88980406)","Data":{"System.Object":null},"InnerException":null,"HelpURL":null,"StackTraceString":"   在 System.Windows.Media.Composition.DUCE.Channel.SyncFlush()\r\n   在 System.Windows.Interop.HwndTarget.UpdateWindowSettings(Boolean enableRenderTarget, Nullable`1 channelSet)\r\n   在 System.Windows.Interop.HwndTarget.UpdateWindowPos(IntPtr lParam)\r\n   在 System.Windows.Interop.HwndTarget.HandleMessage(WindowMessage msg, IntPtr wparam, IntPtr lparam)\r\n   在 System.Windows.Interop.HwndSource.HwndTargetFilterMessage(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam, Boolean& handled)\r\n   在 MS.Win32.HwndWrapper.WndProc(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam, Boolean& handled)\r\n   在 MS.Win32.HwndSubclass.DispatcherCallbackOperation(Object o)\r\n   在 System.Windows.Threading.ExceptionWrapper.InternalRealCall(Delegate callback, Object args, Int32 numArgs)\r\n   在 System.Windows.Threading.ExceptionWrapper.TryCatchWhen(Object source, Delegate callback, Object args, Int32 numArgs, Delegate catchHandler)","RemoteStackTraceString":null,"RemoteStackIndex":0,"ExceptionMethod":"8\nSyncFlush\nPresentationCore, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35\nSystem.Windows.Media.Composition.DUCE+Channel\nVoid SyncFlush()","HResult":-2003303418,"Source":"PresentationCore","WatsonBuckets":null}[App.HandleException.0]
[I] 20251013 08:05:56.970 [0001] UserMessageBox Message: An unknown exception was received. UCEERR_RENDERTHREADFAILURE (异常来自 HRESULT:0x88980406)[MessageBoxServiceImpl+<>c__DisplayClass27_0+<<Show>b__0>d.MoveNext.0]

核心错误是:UCEERR_RENDERTHREADFAILURE

是 Unmanaged Code Exception Error - Render Thread Failure 的缩写。是WPF的渲染线程发生了致命错误。可能是底层图形操作压力:频繁且复杂的动画、渲染变换或大量几何图形更新,可能会压垮渲染线程。

可以理解成WPF渲染线程的时间跟不上上自动化脚本的速度。

怀疑有两点

渲染指令队列溢出: 自动化测试可能在极短的时间内触发大量的 UI 变化、元素重定位、复杂的布局计算或动画,会导致UI线程向渲染线程发送的指令队列(DUCE.Channel)堆积,

当指令过多,或者渲染线程处理不过来时,可能导致渲染线程因内存或句柄耗尽而崩溃。

内存泄漏/句柄泄漏: 如果应用程序在快速执行测试用例时有小的、未被及时释放的图形资源,快速迭代会迅速将其放大,最终压垮渲染线程。

自动化工具介入操作 UI 元素。这种外部干预可能在 UI 线程和渲染线程之间创造不稳定的同步点。

集合已修改;可能无法执行枚举操作

遇到 "集合已修改;可能无法执行枚举操作" (Collection was modified; enumeration operation may not execute) 这个错误,通常是因为你在使用 foreach 循环遍历一个集合的同时,又在循环内部修改了这个集合。

在 WPF 中,这在处理 UI 绑定时尤为常见。比如,你有一个 ObservableCollection 绑定到 ListBox,然后在遍历这个 ObservableCollection 时又试图添加或删除元素,就会触发这个异常。

为什么会发生这个错误?

foreach 循环在开始遍历时,会创建一个迭代器来跟踪集合的当前状态。如果你在循环体内修改了集合,比如添加或删除了元素,这个状态就会变得不一致,迭代器无法继续安全地执行,因此会抛出异常。

解决方法

1. 使用 for 循环

如果你需要修改集合,可以改用 for 循环,并从集合的末尾向前遍历。这样,即使你删除了元素,索引也不会受到影响。

// 假设 myList 是你要操作的集合

csharp 复制代码
for (int i = myList.Count - 1; i >= 0; i--)
{
    // 在这里安全地删除元素
    if (myList[i].SomeCondition)
    {
        myList.RemoveAt(i);
    }
}

2. 先复制集合,再遍历

你可以先创建一个集合的副本,然后在副本上进行遍历,这样就可以安全地修改原始集合。

// 假设 myList 是你要操作的集合

csharp 复制代码
var listCopy = myList.ToList();

foreach (var item in listCopy)
{
    // 在这里可以安全地修改原始集合 myList
    if (item.SomeCondition)
    {
        myList.Remove(item);
    }
}

3. 将待修改的操作记录下来,在循环结束后统一执行

如果你的逻辑比较复杂,需要添加或删除多个元素,可以先将需要修改的元素记录到另一个临时集合中,然后在 foreach 循环结束后,再根据临时集合的内容来操作原始集合。

csharp 复制代码
var itemsToRemove = new List<MyObject>();

foreach (var item in myList)
{
    if (item.SomeCondition)
    {
        itemsToRemove.Add(item);
    }
}

foreach (var item in itemsToRemove)
{
    myList.Remove(item);
}

总结一下,遇到这个错误时,核心思想就是:不要在遍历集合的同时修改它。

解决WPF界面卡死等待问题:三种高效处理耗时操作的方法!

当WPF界面操作中存在耗时的后台处理时,为了避免界面卡死等待问题,可以采用以下解决方法:

1.使用异步操作

优点:

  • 提高应用的响应性
  • 不会阻塞UI线程

步骤:

  1. 将耗时操作封装在Task.Run中。
  2. 使用async/await确保异步执行。
csharp 复制代码
private async void Button_Click(object sender, RoutedEventArgs e)
{
    // UI线程不被阻塞
    await Task.Run(() =>
    {
        // 耗时操作
    });

    // 更新UI或执行其他UI相关操作
}

2.使用后台线程

优点:

  • 简单易实现
  • 适用于一些简单的耗时任务

步骤:

  1. 使用Thread创建后台线程执行耗时操作。
  2. 利用Dispatcher更新UI。
csharp 复制代码
private void Button_Click(object sender, RoutedEventArgs e)
{
    Thread thread = new Thread(() =>
    {
        // 耗时操作

        // 更新UI
        this.Dispatcher.Invoke(() =>
        {
            // 更新UI或执行其他UI相关操作
        });
    });

    // 启动后台线程
    thread.Start();
}

3.使用BackgroundWorker

优点:

  • 专为UI线程设计
  • 提供了进度报告事件

步骤:

  1. 创建BackgroundWorker实例,处理耗时操作。
  2. 利用RunWorkerCompleted事件更新UI。
csharp 复制代码
private BackgroundWorker worker;

private void InitializeBackgroundWorker()
{
    worker = new BackgroundWorker();
    worker.DoWork += Worker_DoWork;
    worker.RunWorkerCompleted += Worker_RunWorkerCompleted;
}

private void Worker_DoWork(object sender, DoWorkEventArgs e)
{
    // 耗时操作
}

private void Worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
    // 更新UI或执行其他UI相关操作
}

选择适当的方法取决于项目的需求和复杂性。异步操作通常是最为灵活和强大的解决方案,但在一些情况下,使用后台线程或BackgroundWorker可能更为简单和直观。

相关推荐
月明长歌3 小时前
【码道初阶】【Leetcode94&144&145】二叉树的前中后序遍历(非递归版):显式调用栈的优雅实现
java·数据结构·windows·算法·leetcode·二叉树
李斯维3 小时前
安装 WSL 最好的方式
linux·windows
Psycho_MrZhang3 小时前
Airflow简介和架构
架构·wpf
ITHAOGE153 小时前
下载 | Win11 官方精简版,系统占用空间极少!(12月更新、Win 11 IoT物联网 LTSC版、适合老电脑安装使用)
windows·科技·物联网·microsoft·微软·电脑
c#上位机4 小时前
halcon窗口显示带有箭头的直线
计算机视觉·c#·halcon
秦苒&4 小时前
【C语言指针四】数组指针变量、二维数组传参本质、函数指针变量、函数指针数组
c语言·开发语言·c++·c#
秋雨雁南飞4 小时前
C# 字符串占位
开发语言·c#
寰天柚子4 小时前
DotNetBar全面解析:.NET WinForms开发的高效UI控件库
ui·.net
追逐时光者4 小时前
精选 8 个 .NET 开发实用的类库,效率提升利器!
后端·.net