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

Binding Dictionary to ComboBox in C# .net winforms

这应该是一个重复的问题,但我发布它是因为 none 任何地方的答案都有效。

我有一个类型的字典:

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

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

我尝试了很多方法并搜索了 google 上的每个答案,但没有任何效果。这显然是您应该采用的方式:

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

执行此操作时出现运行时错误:(System.ArgumentException: '无法绑定到新的显示成员。(参数 'newDisplayMember')' )

当我尝试交换键和值时,出现同样的错误:(System.ArgumentException: '无法绑定到新的显示成员。(参数 'newDisplayMember')' )

当我尝试 key/value 的其他组合时,我得到了随机结果。有时它会显示整个 class 名称(无用),有时它会显示 ToString 表示(重载并完美运行,但启动后不更新),有时它什么都不显示或程序出错在运行期间。

然而,我尝试过的任何组合实际上都没有在加载和卸载模块时更新 BOX 内容(模块本身肯定 loading/unloading 并且工作正常)。

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

这是使用 .NET 核心 3.1 模块和 .NET 5.0 应用程序(需要模块才能工作,因为 Microsoft 5.0 exe 不能与 Microsoft 5.0 dll 一起工作)。

IModule 的重载 ToString 方法 returns Handle,它是一个字符串,用于命名模块,IE“ConsoleModule”,并按预期工作。除数据绑定外,其他一切正常。

其他人能否至少确认此数据绑定方法在 .NET 5.0 and/or 3.1 中确实有效?快速失去理智。

只要您有一系列相似的项目,并希望在 ComboBox 中显示,您需要告诉 ComboBox 应该使用 属性 项目中的哪一个来显示每个项目。你是对的,这是使用 ComboBox.DisplayMember

完成的

您的 Dictionary<IModule, AssemblyLoadContext> 实现了 IEnumerable<KeyValuePair<IModule, AssemblyLoadContext>,因此您可以将其视为一个键值对序列。每个 KeyValuePair 都有一个 IModule 类型的键和一个 AssemblyLoadContext 类型的值。

IModule 和 AssemblyLoadContext 有几个属性。您需要决定要显示其中的 属性 个。

I am trying to bind the names of the IModules (IModule.Handle)

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

comboBox1.DisplayMember = nameof(IModule.Handle);

如果你只需要显示,所以不需要更新,将你的原始序列转换成列表就足够了:

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

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

你可以做一个BindingList<KeyValuePair<IModule, AssemblyLoadContext>>,但这很难读,很难理解,很难单元测试,很难重用和维护。我的建议是为此创建一个特殊的 class。

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

class DisplayedModule
{
    public string DisplayText => this.Module.Handle;

    public IModule Module {get; set;}
    public AssemblyLoadContext AssemblyLoadContext{get; 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经常变化,或者是一个大列表,可以考虑Add / Remove原来的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。或者,如果您决定更改数据:不是字典,而是数据库中的序列