Winform的combox的selected item在datasource设置为null时缓存,然后返回到原始列表
Winform's combox's selected item is cached when the datasource is set to null, then back to the original list
上下文如下:
ComboBox 绑定到 BindingSource。
BindingSource 然后绑定到 DataModel。
ComboBox 有一个对象列表的数据源。然而,此列表可能设置为
在某些条件下为 null,在其他条件下可能会恢复。
我的问题出在以下场景的最后一步:
- At the begining, ComboBox's dataSource is a List of 3 items (eg. item1, item2, item3)
- Now comboBox show item1 as the default selected item
- User pick item 3 on comboBox
- Now comboBox show item3 as the selected item
- ...some other event happened and cause comboBox's dataSource become null
- Now comboBox has no selected item (which selectedIndex is -1)
- ...some other event happened and cause comboBox's dataSource become the original list
8. Now comboBox's selected item is back to item3, but NOT item1
我想明白为什么会这样,因为我不希望数据源为空后保留先前的值。
这是我设置代码的方式,1 个组合框绑定到绑定源和数据模型,2 个按钮模拟组合框数据源的变化。
BindingModel model = new BindingModel();
public Form1()
{
InitializeComponent();
// BindingSource <--> model
cbxBindingSource.DataSource = typeof(BindingModel);
cbxBindingSource.Add(model);
// ComboxBox <-SelectedValue-> BindingSource <--> model.Result
comboBox.DataBindings.Add(new Binding("SelectedValue", cbxBindingSource, "Result", true, DataSourceUpdateMode.OnPropertyChanged));
}
// Simulate a re-usable list
List<BinderHelper<string>> list = new List<BinderHelper<string>>
{
new BinderHelper<string>("Item1", "1"),
new BinderHelper<string>("Item2", "2"),
new BinderHelper<string>("Item3", "3"),
new BinderHelper<string>("Item4", "4"),
};
private void button2_Click(object sender, EventArgs e)
{
// Simulate triggers that will result in a well-populated data-source
comboBox.DisplayMember = "DisplayMember";
comboBox.ValueMember = "ValueMember";
comboBox.DataSource = list;
}
private void button3_Click(object sender, EventArgs e)
{
// Simulate triggers that will result in a empty data-source
comboBox.DataSource = null;
}
private class BindingModel
{
// Simple model to bind to the selected value of the comboBox
public string Result { get; set; }
}
private class BinderHelper<T>
{
// Use for binding purpose
public string DisplayMember { get; set; }
public T ValueMember { get;set; }
public BinderHelper(string display, T value)
{
DisplayMember = display;
ValueMember = value;
}
}
但是,如果我创建一个新列表,每次都有新项目,那么问题就不会出现。
但是,在我的实际代码中这是不可能的,因为列表始终是相同的实例,只是将其放在数据源上的问题。
示例如下:
private void button2_Click(object sender, EventArgs e)
{
// Create new list everytime
List<BinderHelper<string>> list = new List<BinderHelper<string>>
{
new BinderHelper<string>("Item1", "1"),
new BinderHelper<string>("Item2", "2"),
new BinderHelper<string>("Item3", "3"),
new BinderHelper<string>("Item4", "4"),
};
// Simulate triggers that will result in a well-populated data-source
comboBox.DisplayMember = "DisplayMember";
comboBox.ValueMember = "ValueMember";
comboBox.DataSource = list;
}
我所做的解决方法是检查当前数据源:
如果当前数据源为空,而新数据源不是,
然后设置 SelectedIndex = 0
但除了更改数据源之外,我真的很想避免这种手动干预。
感谢任何对此有解决方案的人,谢谢!
根据 Ivan 的建议更新了工作代码:
public Form1()
{
InitializeComponent();
model = new BindingModel();
// BindingSource <--> model
cbxToModelBindingSource.DataSource = typeof (BindingModel);
cbxToModelBindingSource.Add(model);
// New dedicated source to provide the list in combox
// BindingSource <--> comboBox
listToCbxBindingSource = new BindingSource(components);
listToCbxBindingSource.DataSource = typeof (BinderHelper<string>);
comboBox.DataSource = listToCbxBindingSource;
// ComboxBox <--SelectedValue--> BindingSource <--> model.Result
comboBox.DataBindings.Add(new Binding("SelectedValue", cbxToModelBindingSource, "Result", true, DataSourceUpdateMode.OnPropertyChanged));
}
// Simulate triggers that will result in a well-populated data-source
private void button2_Click(object sender, EventArgs e)
{
listToCbxBindingSource.DataSource = list;
}
// Simulate triggers that will result in a empty data-source
private void button3_Click(object sender, EventArgs e)
{
listToCbxBindingSource.DataSource = null;
}
// Additional button to test two-way-bindings
private void button4_Click(object sender, EventArgs e)
{
model.Result = "4";
}
你可以简单地通过验证 item.count 来处理这个问题,所以如果等于零什么都不做
if (Combo1.item.count==0)
{
return;
}
将上面的代码保留在任何你想要的地方
如果有帮助请告诉我
所描述的行为是由 WinForms 数据绑定基础结构引起的,特别是数据源的 BindingContext
class. For each unique data source object it maintains a corresponding BindingManageBase
object, which along with other things provides Position
and Current
属性 - 它们通常没有的东西 - 考虑 IList
例如。对于列表类型数据源,它是 CurrencyManager
class 的一个实例,并且是在第一次为数据源对象请求绑定管理器时创建的。与您的问题相关的重要部分是没有删除已创建的绑定管理器的机制。可以把它想象成一个以数据源为键的字典,绑定管理器作为你第一次请求一个不存在的键时创建的值。
ComboBox
控件是在数据绑定它的列表部分时使用和修改 Position
属性 的控件之一。当您将列表分配给 DataSource
属性 时,它会将 selection 与 Position
同步,对于新创建的非空列表绑定管理器来说,它是 0
.然后,当您 select 组合框中的项目时, Position
会相应更新。当您将 DataSource
设置为 null 时,组合框项目将被清除,但具有最后设置位置的现有绑定管理器仍保留在 BindingContext
内。下次您将相同的列表设置为数据源时,它会获取具有最后设置位置的现有绑定管理器,因此会出现您所获得的行为。
要摆脱这种行为,您应该使用中间 BindingSource
组件。连同其他东西,它提供了它自己的 Position
、Current
property/management 和专用关联 CurrencyManager
,这允许它同时用作控件的常规数据源向他们隐藏实际数据源(特别是 BindingContext
)。
以下是我对解决方案的看法。添加另一个 BindingSource
组件(类似于您的 cbxBindingSource
),例如调用它 listBindingSource
并将组合框静态绑定到它:
public Form1()
{
InitializeComponent();
// Make properties of the expected object type available for binding
listBindingSource.DataSource = typeof(BinderHelper<string>);
comboBox.DisplayMember = "DisplayMember";
comboBox.ValueMember = "ValueMember";
comboBox.DataSource = listBindingSource;
// the rest ...
}
现在您只需操纵绑定源的 DataSource
属性 而不是绑定控件。
private void button2_Click(object sender, EventArgs e)
{
// Simulate triggers that will result in a well-populated data-source
listBindingSource.DataSource = list;
}
private void button3_Click(object sender, EventArgs e)
{
// Simulate triggers that will result in a empty data-source
listBindingSource.DataSource = null;
}
上下文如下:
ComboBox 绑定到 BindingSource。
BindingSource 然后绑定到 DataModel。
ComboBox 有一个对象列表的数据源。然而,此列表可能设置为 在某些条件下为 null,在其他条件下可能会恢复。
我的问题出在以下场景的最后一步:
- At the begining, ComboBox's dataSource is a List of 3 items (eg. item1, item2, item3)
- Now comboBox show item1 as the default selected item
- User pick item 3 on comboBox
- Now comboBox show item3 as the selected item
- ...some other event happened and cause comboBox's dataSource become null
- Now comboBox has no selected item (which selectedIndex is -1)
- ...some other event happened and cause comboBox's dataSource become the original list
8. Now comboBox's selected item is back to item3, but NOT item1
我想明白为什么会这样,因为我不希望数据源为空后保留先前的值。
这是我设置代码的方式,1 个组合框绑定到绑定源和数据模型,2 个按钮模拟组合框数据源的变化。
BindingModel model = new BindingModel();
public Form1()
{
InitializeComponent();
// BindingSource <--> model
cbxBindingSource.DataSource = typeof(BindingModel);
cbxBindingSource.Add(model);
// ComboxBox <-SelectedValue-> BindingSource <--> model.Result
comboBox.DataBindings.Add(new Binding("SelectedValue", cbxBindingSource, "Result", true, DataSourceUpdateMode.OnPropertyChanged));
}
// Simulate a re-usable list
List<BinderHelper<string>> list = new List<BinderHelper<string>>
{
new BinderHelper<string>("Item1", "1"),
new BinderHelper<string>("Item2", "2"),
new BinderHelper<string>("Item3", "3"),
new BinderHelper<string>("Item4", "4"),
};
private void button2_Click(object sender, EventArgs e)
{
// Simulate triggers that will result in a well-populated data-source
comboBox.DisplayMember = "DisplayMember";
comboBox.ValueMember = "ValueMember";
comboBox.DataSource = list;
}
private void button3_Click(object sender, EventArgs e)
{
// Simulate triggers that will result in a empty data-source
comboBox.DataSource = null;
}
private class BindingModel
{
// Simple model to bind to the selected value of the comboBox
public string Result { get; set; }
}
private class BinderHelper<T>
{
// Use for binding purpose
public string DisplayMember { get; set; }
public T ValueMember { get;set; }
public BinderHelper(string display, T value)
{
DisplayMember = display;
ValueMember = value;
}
}
但是,如果我创建一个新列表,每次都有新项目,那么问题就不会出现。 但是,在我的实际代码中这是不可能的,因为列表始终是相同的实例,只是将其放在数据源上的问题。
示例如下:
private void button2_Click(object sender, EventArgs e)
{
// Create new list everytime
List<BinderHelper<string>> list = new List<BinderHelper<string>>
{
new BinderHelper<string>("Item1", "1"),
new BinderHelper<string>("Item2", "2"),
new BinderHelper<string>("Item3", "3"),
new BinderHelper<string>("Item4", "4"),
};
// Simulate triggers that will result in a well-populated data-source
comboBox.DisplayMember = "DisplayMember";
comboBox.ValueMember = "ValueMember";
comboBox.DataSource = list;
}
我所做的解决方法是检查当前数据源:
如果当前数据源为空,而新数据源不是, 然后设置 SelectedIndex = 0
但除了更改数据源之外,我真的很想避免这种手动干预。
感谢任何对此有解决方案的人,谢谢!
根据 Ivan 的建议更新了工作代码:
public Form1()
{
InitializeComponent();
model = new BindingModel();
// BindingSource <--> model
cbxToModelBindingSource.DataSource = typeof (BindingModel);
cbxToModelBindingSource.Add(model);
// New dedicated source to provide the list in combox
// BindingSource <--> comboBox
listToCbxBindingSource = new BindingSource(components);
listToCbxBindingSource.DataSource = typeof (BinderHelper<string>);
comboBox.DataSource = listToCbxBindingSource;
// ComboxBox <--SelectedValue--> BindingSource <--> model.Result
comboBox.DataBindings.Add(new Binding("SelectedValue", cbxToModelBindingSource, "Result", true, DataSourceUpdateMode.OnPropertyChanged));
}
// Simulate triggers that will result in a well-populated data-source
private void button2_Click(object sender, EventArgs e)
{
listToCbxBindingSource.DataSource = list;
}
// Simulate triggers that will result in a empty data-source
private void button3_Click(object sender, EventArgs e)
{
listToCbxBindingSource.DataSource = null;
}
// Additional button to test two-way-bindings
private void button4_Click(object sender, EventArgs e)
{
model.Result = "4";
}
你可以简单地通过验证 item.count 来处理这个问题,所以如果等于零什么都不做
if (Combo1.item.count==0)
{
return;
}
将上面的代码保留在任何你想要的地方
如果有帮助请告诉我
所描述的行为是由 WinForms 数据绑定基础结构引起的,特别是数据源的 BindingContext
class. For each unique data source object it maintains a corresponding BindingManageBase
object, which along with other things provides Position
and Current
属性 - 它们通常没有的东西 - 考虑 IList
例如。对于列表类型数据源,它是 CurrencyManager
class 的一个实例,并且是在第一次为数据源对象请求绑定管理器时创建的。与您的问题相关的重要部分是没有删除已创建的绑定管理器的机制。可以把它想象成一个以数据源为键的字典,绑定管理器作为你第一次请求一个不存在的键时创建的值。
ComboBox
控件是在数据绑定它的列表部分时使用和修改 Position
属性 的控件之一。当您将列表分配给 DataSource
属性 时,它会将 selection 与 Position
同步,对于新创建的非空列表绑定管理器来说,它是 0
.然后,当您 select 组合框中的项目时, Position
会相应更新。当您将 DataSource
设置为 null 时,组合框项目将被清除,但具有最后设置位置的现有绑定管理器仍保留在 BindingContext
内。下次您将相同的列表设置为数据源时,它会获取具有最后设置位置的现有绑定管理器,因此会出现您所获得的行为。
要摆脱这种行为,您应该使用中间 BindingSource
组件。连同其他东西,它提供了它自己的 Position
、Current
property/management 和专用关联 CurrencyManager
,这允许它同时用作控件的常规数据源向他们隐藏实际数据源(特别是 BindingContext
)。
以下是我对解决方案的看法。添加另一个 BindingSource
组件(类似于您的 cbxBindingSource
),例如调用它 listBindingSource
并将组合框静态绑定到它:
public Form1()
{
InitializeComponent();
// Make properties of the expected object type available for binding
listBindingSource.DataSource = typeof(BinderHelper<string>);
comboBox.DisplayMember = "DisplayMember";
comboBox.ValueMember = "ValueMember";
comboBox.DataSource = listBindingSource;
// the rest ...
}
现在您只需操纵绑定源的 DataSource
属性 而不是绑定控件。
private void button2_Click(object sender, EventArgs e)
{
// Simulate triggers that will result in a well-populated data-source
listBindingSource.DataSource = list;
}
private void button3_Click(object sender, EventArgs e)
{
// Simulate triggers that will result in a empty data-source
listBindingSource.DataSource = null;
}