在 C# .net winforms 中将字典绑定到 ComboBox

Posted

技术标签:

【中文标题】在 C# .net winforms 中将字典绑定到 ComboBox【英文标题】:Binding Dictionary to ComboBox in C# .net winforms 【发布时间】:2021-10-03 12:43:47 【问题描述】:

这应该是一个重复的问题,但我发布它是因为任何地方的答案都不起作用。

我有一本类型的字典:

private Dictionary<IModule, AssemblyLoadContext> ModuleList = new Dictionary<IModule, AssemblyLoadContext>();

我正在尝试将 IModule 的名称(IModule.Handle,用于实现 IModule 的所有内容)绑定到组合框。

我尝试了很多方法,并在谷歌上搜索了所有答案,但没有任何效果。这显然是你应该这样做的方式:

comboBox1.DataSource = new BindingSource(ModuleList, null);
comboBox1.DisplayMember = "Value";
comboBox1.ValueMember = "Key";

当我这样做时,我得到一个 RUNTIME 错误:(System.ArgumentException:'无法绑定到新的显示成员。(参数'newDisplayMember')' )

当我尝试交换键和值时,我得到同样的错误:(System.ArgumentException: 'Cannot bind to the new display member. (Parameter 'newDisplayMember')' )

当我尝试其他键/值组合时,我得到随机结果。有时它会显示整个类名(没有帮助),有时它会显示 ToString 表示(重载并且工作正常,除了启动后不更新),有时它什么也不显示或程序在运行时出错。

但是,当模块被加载和卸载时,我尝试过的任何组合都没有真正让 BOX 内容更新(模块本身肯定在加载/卸载并且工作正常)。

这应该在很多年前就可以工作了,我只能想象微软在他们的一次更新中破坏了一些东西,因为预期的方法对我不起作用。

这是使用 .NET core 3.1 模块和 .NET 5.0 应用程序(需要模块工作,因为 microsoft 5.0 exe 不适用于 microsoft 5.0 dll)。

IModule 的重载 ToString 方法返回 Handle,它是一个命名模块的字符串,即 IE“ConsoleModule”,并按预期工作。除了数据绑定之外,其他一切都在工作。

其他人至少可以确认此数据绑定方法在 .NET 5.0 和/或 3.1 中确实有效吗?迅速失去理智。

【问题讨论】:

您的意思是您将 Bi​​ndingSource 的 DataSource 设置为一个空 Dictionary,您尝试将 Items 添加到 Dictionary 并且您没有看到内部 List 更改? -- 请注意,字典是一个复杂对象,要将其用作数据源,它会转换为List&lt;KeyValuePairs&lt;TKey, TValue&gt;&gt;。此列表不支持列表更改事件。 -- 如果更改 Dictionary 的内容,则必须重置 BindingSource,将 ComboBox 的 DataSource 设置为null,设置DisplayMember(第一)、ValueMember(第二)和DataSource(最后)再次。 或者选择一个不是动态字典的数据源。 不能直接将 Dictionary 设置为 ComboBox 的 DataSource。你必须设置[ComboBox].DataSource = [Dictionary].ToList();。这同样适用于 BindingSource,它在内部创建一个 BindingList。类中包含 IModuleAssemblyLoadContext 类型属性的 List&lt;class&gt; 可能更容易处理。 是的,这当然有效。 Dictionary 已填充,并使用 BindingSource 作为 mediator 设置为 ComboBox 的 DataSource(DisplayMemberValueMember 应设置在 DataSource 之前,但这是另一回事)。 -- 如果您立即填写字典(因此,它是空的)和/或您尝试在之后添加 KeyValuePairs,则会出现不同的情况。在这种情况下,您可以使用类型初始化 BindingSource(例如,[BindingSource] = new BindingSource(typeof(Dictionary&lt;IModule, AssemblyLoadContext&gt;), null);) - 如果需要 - 并在添加一些项目后将其重置。 是的,它实际上绑定到BindingList&lt;KeyValuePair&lt;TKey, TValue&gt;&gt;。如果之后将项目添加到 Dictionary 中,则不会发生任何事情:ComboBox 内容保持不变。 -- 请注意,您可以在[BindingSource].List 属性中添加/删除项目(类型为KeyValuePair&lt;TKey, TValue&gt;):这会将项目添加到组合框,但不会影响字典。 【参考方案1】:

当您有一系列相似的项目时,您想在 ComboBox 中显示这些项目,您需要告诉 ComboBox 应该使用项目的哪个属性来显示每个项目。你是对的,这是使用ComboBox.DisplayMember完成的

您的Dictionary&lt;IModule, AssemblyLoadContext&gt; 实现了IEnumerable&lt;KeyValuePair&lt;IModule, AssemblyLoadContext&gt;,因此您可以将其视为KeyValuePairs 的序列。每个 KeyValuePair 都有一个 IModule 类型的 Key 和一个 AssemblyLoadContext 类型的 Value。

IModule 和 AssemblyLoadContext 有几个属性。您需要决定要显示它们的哪个属性。

我正在尝试绑定 IModule (IModule.Handle) 的名称

我猜每个IModule都有一个属性Handle,你想在ComboBox中显示这个Handle。

comboBox1.DisplayMember = nameof(IModule.Handle);

如果您只需要显示,而不需要更新,则将原始序列转换为列表就足够了:

Dictionary<IModule, AssemblyLoadContext> myData = ...
comboBox.DataSource = myData.ToList();

但是,如果要更新显示的数据,则需要一个实现 IBindingList 的对象,例如(惊喜!)BindingList&lt;T&gt;。见BindingList。

你可以创建一个BindingList&lt;KeyValuePair&lt;IModule, AssemblyLoadContext&gt;&gt;,但这很难阅读、难以理解、难以进行单元测试、难以重用和维护。我的建议是为此创建一个特殊的类。

我不知道 IModule 中有什么,所以你必须找到一个合适的类名。我会坚持:

class DisplayedModule

    public string DisplayText => this.Module.Handle;

    public IModule Module get; set;
    public AssemblyLoadContext AssemblyLoadContextget; set;

在表单的构造函数中:

public MyForm()

    InitializeComponent();

    this.ComboBox1.DisplayMember = nameof(DisplayedModule.DisplayText);

这样,如果你想改变需要显示的文本,你所要做的就是改变属性DisplayText。

public BindingList<DisplayedModule> DisplayedItems

    get => (BindingList<DisplayedModule>)this.comboBox1.DataSource;
    set => this.comboBox1.DataSource = value;

您需要程序来获取初始数据:

private Dictionary<IModule, AssemblyLoadContext> GetOriginalData() ... // out of scope of this question

private IEnumerable<DisplayedModule> OriginalDataToDisplay =>
    this.GetOriginalData().Select(keyValuePair => new DisplayedModule
    
        Module = keyValuePair.Key,
        AssemblyLoadcontext =  keyValuePair.Value;
    );

我已将其放在单独的程序中,以使其非常灵活。易于理解,易于单元测试,易于更改和维护。例如,如果您的原始数据不在字典中,而是在列表、数组或数据库中,则只需更改一个过程。

最初填充组合框现在是单行:

private ShowInitialComboBoxData()

    this.DisplayedItems = new BindingList<DisplayedModule>
        (this.OriginalDataToDisplay.ToList());


private void OnFormLoad(object sender, ...)

    this.ShowInitialComboBoxData();
    ... // other inits during load form

如果操作员在列表中添加/删除元素,则绑定列表会自动更新。如果发生什么事情,之后你知道字典已经改变,你可以简单地改变 bindingList 对于不经常改变的小列表,我会做一个完整的新的 BindingList。如果 List 经常变化,或者列表很大,可以考虑添加/删除原来的 BindingList。

private void AddDisplayedModule(DisplayedModule module)

    this.DisplayedItems.Add(module);


private void RemoveDisplayedMOdule(DisplayedModule module)

    this.DisplayedItems.Remove(module);


private void ModuleAddedToDictionary(IModule module, AssemblyLoadContext assembly)

    this.AddDisplayedModule(new DisplayedModule
    
        Module = module,
        AssemblyLoadContext = assembly,
    )

如果操作员进行了一些更改,并表明他完成了对组合框的编辑,例如按下“立即应用”按钮,您可以简单地获取编辑后的数据:

private void ButtonApplyNowClicked(object sender, ...)

    // get the edited data from the combobox and convert to a Dictionary:
    Dictionary<IModule, AssemblyLoadContext> editedData = this.DisplayedItems
        .ToDictionary(displayedItem => displayedItem.Module,               // Key
                      displayedItem => displayedItem.AssemblyLoadContext); // Value;
    this.ProcesEditedData(editedData);

访问组合框的选定项

DisplayedModule SelectedModule => (DisplayedModule)this.comboBox1.SelectedItem;

结论

通过将数据与其显示方式分开,如果您决定更改视图,更改将是最小的:将 Combobox 更改为 ListBox,甚至是 DataGridView。或者,如果您决定更改数据:不是字典,而是数据库中的序列

【讨论】:

我打算将此标记为答案,但 comboBox1.DisplayMember = nameof(IModule.Handle);出于某种原因,正在显示类信息而不是句柄字符串。任何想法为什么?我没有时间尝试您列为当前实现的所有内容(手动更新组合框)现在工作正常,但我希望它像我应该做的那样由语言管理。问题是程序严重依赖句柄准确才能运行,并从组合框中获取卸载句柄名称。

以上是关于在 C# .net winforms 中将字典绑定到 ComboBox的主要内容,如果未能解决你的问题,请参考以下文章

c# winform项目中,如何使用字典代码?

在 WinForms 中将 Listbox 绑定到 List<object>

在 WinForm 中将 List<T> 绑定到 DataGridView

在 C# winforms 中将 RDLC 报告适合 A4 页面

如何通过拖放在 Winform 中将元文件转换为图像

c# winform DataGridView添加一行,添加数据后,保存到数据库