如何使用简单注入器注册 Windows 窗体

2024-01-12

背景

我正在构建一个 winforms 应用程序,其中使用 IoC 容器 (SimpleInjector) 来注册我的类型。在我的应用程序中,大多数屏幕(即表单)在任何给定时间都只有一个实例。

Problem

对于在任何给定时间只需要一个实例的表单,我可以将它们注册为单例:

container.Register<IHomeView, HomeView>(Lifestyle.Singleton);

这允许我使用容器来跟踪所有表单。然而,在这种情况下,当表单关闭时,它将被释放(表单实现 IDisposable)。如果应用程序尝试使用容器再次打开该表单,则该表单的容器实例将被释放,并引发异常。

Question

处理这个问题的正确方法是什么?我目前看到两种解决方案:

  1. 对于每个表单,覆盖表单关闭而不是隐藏表单,而不是实际关闭它。我真的不喜欢这个主意。我觉得我宁愿每次都关闭表单并从新的/新鲜的表单开始。
  2. 以短暂的生活方式而非单身人士的身份注册表格。在这种情况下,容器实际上更像是一个工厂。我遇到两个问题:a)我失去了通过容器跟踪表单的能力,b)容器在验证过程中抛出异常,表示一次性类型不应注册为瞬态(我不明白为什么会这样) )。这些问题都适用于我同时需要多个实例的表单。

我可以通过在验证期间抑制诊断警告来解决问题 b)。

registration = container.GetRegistration(typeof(ILoginView)).Registration;
registration.SuppressDiagnosticWarning(
    DiagnosticType.DisposableTransientComponent,
    "Winforms registration supression.");

这里采取的正确方法是什么?我错过了什么吗?


理想情况下,您希望将表单注册为Singleton。然而,根据我的经验,这将导致难以调试的错误,特别是当您使用BindingSource https://msdn.microsoft.com/en-us/library/system.windows.forms.bindingsource.datasource(v=vs.110).aspx用于将您的数据绑定到任何内容。

使用中的第二个问题Singleton因为生活方式是,如果您的应用程序使用无模式窗口,该窗口将抛出一个ObjectDisposedException第二次打开时,因为 Windows 窗体应用程序框架将在第一次关闭时处理该窗体,而简单注入器应负责该操作。因此,如果注册为 Singleton,Simple Injector 将创建一个(而且恰好是一个)实例。如果其他人(例如您的应用程序、Windows 窗体框架)将处置该对象,则不会重新创建该对象。

最简单的解决方案也很容易理解,是将您的表单注册为Transient。是的,您需要抑制诊断警告。根据该诊断警告的原因文档 https://simpleinjector.org/diadt:

一个实现的组件IDisposable通常需要确定性清理,但简单注入器不会隐式跟踪和处置使用瞬态生活方式注册的组件。

简单注入器无法处置瞬态组件,因为它无法确定何时应处置对象。然而,这意味着通过调用以模式方式打开的表单.ShowDialog()永远不会被处置!而且由于 Windows 窗体应用程序通常会运行很长时间,甚至可能是一周或一个月,因此这最终会导致'Win32异常' https://msdn.microsoft.com/en-us/library/system.componentmodel.win32exception(v=vs.110).aspx并显示一条消息:“创建窗口句柄时出错”。这本质上意味着您耗尽了计算机的所有资源。

因此,处理表格很重要。尽管简单注射器能够完成这项工作,但如果您使用Scope https://simpleinjector.org/lifestyles#scoped,这对于 Windows Forms 来说不是那么容易实现的。因此,您自己必须负责处理已使用显示的封闭表格ShowDialog().

根据您的具体用例,有多种方法可以实现FormOpener or NavigationService。一种方法是:

public interface IFormOpener
{
    void ShowModelessForm<TForm>() where TForm : Form;
    DialogResult ShowModalForm<TForm>() where TForm : Form;
}

public class FormOpener : IFormOpener
{
    private readonly Container container;
    private readonly Dictionary<Type, Form> openedForms;

    public FormOpener(Container container)
    {
        this.container = container;
        this.openedForms = new Dictionary<Type, Form>();
    }

    public void ShowModelessForm<TForm>() where TForm : Form
    {
        Form form;
        if (this.openedForms.ContainsKey(typeof(TForm)))
        {
            // a form can be held open in the background, somewhat like 
            // singleton behavior, and reopened/reshown this way
            // when a form is 'closed' using form.Hide()   
            form = this.openedForms[typeof(TForm)];
        }
        else
        {
            form = this.GetForm<TForm>();
            this.openedForms.Add(form.GetType(), form);
            // the form will be closed and disposed when form.Closed is called
            // Remove it from the cached instances so it can be recreated
            form.Closed += (s, e) => this.openedForms.Remove(form.GetType());
        }

        form.Show();
    }

    public DialogResult ShowModalForm<TForm>() where TForm : Form
    {
        using (var form = this.GetForm<TForm>())
        {
            return form.ShowDialog();
        }
    }

    private Form GetForm<TForm>() where TForm : Form
    {
        return this.container.GetInstance<TForm>();
    }
}

该类必须注册为Singleton:

container.RegisterSingleton<IFormOpener, FormOpener>();

并且可以通过将此服务注入到应用程序的根表单中来使用:

public partial class RootForm : Form
{
    private readonly IFormOpener formOpener;

    public RootForm(IFormOpener formOpener)
    {
        this.formOpener = formOpener;
        this.InitializeComponent();
    }

    private void ShowCustomers_Click(object sender, EventArgs e)
    {
        this.formOpener.ShowModelessForm<AllCustomersForm>();
    }

    private void EditCustomer_Click(object sender, EventArgs e)
    {
        var result = this.formOpener.ShowModalForm<EditCustomerForm>();
        // do something with result
    }
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

如何使用简单注入器注册 Windows 窗体 的相关文章

随机推荐

  • 从 swift 调用 Objective-C typedef 块

    我正在尝试从 swift 调用一个方法 该方法是用 Objective C 编写的单例 头文件中的块 typedef void VPersonResultBlock Person person NSError error void askF
  • 为什么 Swift 中的协议比类更好? [关闭]

    Closed 这个问题是基于意见的 help closed questions 目前不接受答案 通过观看Apple提供的视频教程 似乎swift是面向协议的编程语言 并且Apple鼓励程序员使用协议而不是类 但从我个人的角度来看 我认为协议
  • Magento 购物车 API 不显示价格

    我正在尝试使用 Magento Enterprise 1 10 XML RPC API 来处理 Magento 安装之外的购物车 目录功能 我遇到的问题是当我添加到购物车时 我可以很好地连接到 API 端点 登录并检索数据 以下是我用来发现
  • 更新 Google App Engine 中的模型架构?

    谷歌建议一次将一个条目更改为默认值 http code google com appengine articles update schema html http code google com appengine articles upd
  • 为什么 WinForms/WPF 控件不在内部使用 Invoke?

    我明白为什么 GUI 控件具有线程关联性 But 为什么控件不在其方法和属性中内部使用调用 现在你必须做这样的事情才能更新TextBox value this Invoke new MethodInvoker delegate textBo
  • Amazon SSE-S3 密钥轮换如何工作?

    我正在尝试围绕 Amazon 的服务器端加密选项进行思考 以便在我的应用程序上传文件时开始要求 S3 加密我的静态数据 到目前为止 AWS 管理的加密密钥选项听起来像是我正在寻找的 Model C https media amazonweb
  • Room 的“onDelete = CASCADE”在迁移期间不起作用

    我有以下表格 Entity tableName users class Users PrimaryKey autoGenerate true var id Long null NonNull var name String null Ent
  • 如何使用 PlistBuddy 将多个条目添加到 plist 字典中

    In my Info plist我想修改 shell 上的 Plist 文件 如下所示
  • Python跨平台监听按键?

    我需要在 python 终端程序中监听某些按键 而不用暂停执行raw input 我见过人们使用一些特定于 Windows 的方式来监听击键 并且我见过人们使用像 tkinter 和 pygame 这样的大型模块 我想避免这些模块 是否有一
  • 如何完整遍历未知深度的复杂字典?

    导入自JSON可以得到非常复杂和嵌套的结构 例如 u body u declarations u id u name u i u type u Identifier u init u type u Literal u value 2 u t
  • 如何转到 Chrome 开发者工具中的特定文件?

    我正在开发一个采用重型前端方法的 Web 应用程序 通过使用 Dojo 和 AMD 方式 我目前的测试屏幕可以轻松加载一百多个不同的 javascript 文件 当我想要调试任何特定问题 或者验证是否看到特定文件的旧版本时 我发现很难在 C
  • 使用 loginPopup 时 Microsoft 身份验证反应“hash_empty_error”

    我无法理解在尝试使用 Micrsoft Authentication 库 for React PWA 对用户进行身份验证时遇到的错误 我需要帮助理解为什么尝试使用 loginPopup 方法登录用户时失败 在某些情况下 身份验证按预期工作
  • WordPress 小部件更新需要标签的项目的实例

    最近 我意识到在开发主题中的侧边栏时 我没有充分利用 WordPress 中的小部件 因此我花了几天时间研究如何正确开发它们 在查看了大量教程后 我发现其中一些关于自定义构建小部件的教程已经过时了 我确实看到了应该在哪里使用该构造 func
  • 如何在 Swift 中反转数组?

    如何以相反的顺序打印以下数组 var toDoListReverse Take out garbage Pay bills Cross off finished items 这是 swift 3 的代码 let array IOS A IO
  • Android toast.makeText 上下文错误

    我在位置侦听器内调用 toast Maketext 时遇到问题 上下文不可用 我做错了什么 private LocationListener ll new LocationListener public void onLocationCha
  • 用于显示 PHP 警告和错误的 Sublime Text 扩展

    我最近安装了 Sublime Text 2 还在适应中 我已经安装了 Package Control 和大部分 PHP 扩展 我希望有一个能够强调语法错误的工具 就像 Netbeans 那样 有没有一个包可以做到这一点 Use Sublim
  • Macports 选择默认的 Python 解释器来执行脚本? [关闭]

    Closed 这个问题是无关 help closed questions 目前不接受答案 I used python select 它似乎没有改变执行脚本的默认 python 例如django admin py runserver 前面没有
  • MSDTC - 需要多少个端口

    我有一个使用 MSDTC 的相当大的应用程序 我应该打开多少个端口 有什么办法可以确定吗 EDIT I know what我需要打开的端口 我不知道how many I need 当我们必须进行这种调试时 这篇文章特别有用 如何解决 MS
  • Spring MockRestServiceServer 处理多个异步请求

    我有一个协调器 Spring Boot 服务 它向外部服务发出几个异步休息请求 我想模拟这些服务的响应 我的代码是 mockServer expect requestTo http localhost retrieveBook book1
  • 如何使用简单注入器注册 Windows 窗体

    背景 我正在构建一个 winforms 应用程序 其中使用 IoC 容器 SimpleInjector 来注册我的类型 在我的应用程序中 大多数屏幕 即表单 在任何给定时间都只有一个实例 Problem 对于在任何给定时间只需要一个实例的表