在 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。或者,如果您决定更改数据:不是字典,而是数据库中的序列
这应该是一个重复的问题,但我发布它是因为 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。或者,如果您决定更改数据:不是字典,而是数据库中的序列