在 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 中确实有效吗?迅速失去理智。
【问题讨论】:
您的意思是您将 BindingSource 的 DataSource 设置为一个空 Dictionary,您尝试将 Items 添加到 Dictionary 并且您没有看到内部 List 更改? -- 请注意,字典是一个复杂对象,要将其用作数据源,它会转换为List<KeyValuePairs<TKey, TValue>>
。此列表不支持列表更改事件。 -- 如果更改 Dictionary 的内容,则必须重置 BindingSource,将 ComboBox 的 DataSource 设置为null
,设置DisplayMember
(第一)、ValueMember
(第二)和DataSource
(最后)再次。
或者选择一个不是动态字典的数据源。
不能直接将 Dictionary 设置为 ComboBox 的 DataSource。你必须设置[ComboBox].DataSource = [Dictionary].ToList();
。这同样适用于 BindingSource,它在内部创建一个 BindingList。类中包含 IModule
和 AssemblyLoadContext
类型属性的 List<class>
可能更容易处理。
是的,这当然有效。 Dictionary 已填充,并使用 BindingSource 作为 mediator 设置为 ComboBox 的 DataSource(DisplayMember
和 ValueMember
应设置在 DataSource
之前,但这是另一回事)。 -- 如果您不立即填写字典(因此,它是空的)和/或您尝试在之后添加 KeyValuePairs,则会出现不同的情况。在这种情况下,您可以使用类型初始化 BindingSource(例如,[BindingSource] = new BindingSource(typeof(Dictionary<IModule, AssemblyLoadContext>), null);)
- 如果需要 - 并在添加一些项目后将其重置。
是的,它实际上绑定到BindingList<KeyValuePair<TKey, TValue>>
。如果之后将项目添加到 Dictionary 中,则不会发生任何事情:ComboBox 内容保持不变。 -- 请注意,您可以在[BindingSource].List
属性中添加/删除项目(类型为KeyValuePair<TKey, TValue>
):这会将项目添加到组合框,但不会影响字典。
【参考方案1】:
当您有一系列相似的项目时,您想在 ComboBox 中显示这些项目,您需要告诉 ComboBox 应该使用项目的哪个属性来显示每个项目。你是对的,这是使用ComboBox.DisplayMember
完成的
您的Dictionary<IModule, AssemblyLoadContext>
实现了IEnumerable<KeyValuePair<IModule, AssemblyLoadContext>
,因此您可以将其视为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<T>
。见BindingList。
你可以创建一个BindingList<KeyValuePair<IModule, AssemblyLoadContext>>
,但这很难阅读、难以理解、难以进行单元测试、难以重用和维护。我的建议是为此创建一个特殊的类。
我不知道 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的主要内容,如果未能解决你的问题,请参考以下文章
在 WinForms 中将 Listbox 绑定到 List<object>
在 WinForm 中将 List<T> 绑定到 DataGridView