I have a custom combobox control that is supposed to show a list of webcams available.
The code is fairly tiny.
using System;
using System.ComponentModel;
using System.Data;
using System.Linq;
using System.Windows.Forms;
using DirectShowLib;
namespace CameraSelectionCB
{
public partial class CameraComboBox : ComboBox
{
protected BindingList<string> Names;
protected DsDevice[] Devices;
public CameraComboBox()
{
InitializeComponent();
Devices = DsDevice.GetDevicesOfCat(FilterCategory.VideoInputDevice);
Names = new BindingList<string>(Devices.Select(d => d.Name).ToList());
this.DataSource = Names;
this.DropDownStyle = ComboBoxStyle.DropDownList;
}
}
}
However, I ran into a couple of bugs.
First, whenever I place an instance of this combobox, designer generates the following code:
this.cameraComboBox1.DataSource = ((object)(resources.GetObject("cameraComboBox1.DataSource")));
this.cameraComboBox1.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
this.cameraComboBox1.Items.AddRange(new object[] {
"HP Webcam"});
Which results in an exception during runtime, since Items should not be modified when DataSource is set. This happens even if I don't touch Items property in the designer.
"HP Webcam" is the only camera present on my computer at the time.
How can I suppress this behaviour?
When you drop your control on a form the constructor code and any loading code will run. Any code in there that changes a property value will be executed in designtime and therefore will be written in the designer.cs of the form you dropped your control on.
When programming controls you should always keep that in mind.
I solved this by adding a property that I can use to check if the code executed in designtime or runtime.
protected bool IsInDesignMode
{
get { return DesignMode || LicenseManager.UsageMode == LicenseUsageMode.Designtime; }
}
protected BindingList<string> Names;
protected DsDevice[] Devices;
public CameraComboBox()
{
InitializeComponent();
if (InDesignMode == false)
{
// only do this at runtime, never at designtime...
Devices = DsDevice.GetDevicesOfCat(FilterCategory.VideoInputDevice);
Names = new BindingList<string>(Devices.Select(d => d.Name).ToList());
this.DataSource = Names;
}
this.DropDownStyle = ComboBoxStyle.DropDownList;
}
Now the binding will only happen at runtime
Dont forget to remove the generated code in the Designer.cs file when you try this
The problem is that the binding in constructor is being run by the designer. You could try moving it to the Initialise or Loaded events
Related
If you run the following code without debugging (or with "Enable Just My Code" active), it will seem to work. But if you start it with debugger and with "Enable Just My Code" turned off, you will get this exception (which is then swallowed by library code):
System.ArgumentException occurred
HResult=-2147024809
Message=Complex DataBinding accepts as a data source either an IList or an IListSource.
Source=System.Windows.Forms
StackTrace:
at System.Windows.Forms.ListControl.set_DataSource(Object value)
InnerException:
Here's a minimal version of my code:
using System;
using System.Collections;
using System.Collections.Generic;
using System.Windows.Forms;
static class Program {
[STAThread]
static void Main() {
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new MainForm(new MainModel()));
}
}
public class MainModel {
public IList Options { get; } = new List<string> { "Foo", "Bar" };
}
class MainForm : Form {
private System.ComponentModel.IContainer components;
private ComboBox comboBox;
private BindingSource bindingSource;
private ErrorProvider errorProvider;
public MainForm(MainModel mainModel) {
InitializeComponent();
bindingSource.DataSource = mainModel;
}
private void InitializeComponent() {
components = new System.ComponentModel.Container();
bindingSource = new BindingSource(components);
errorProvider = new ErrorProvider(components);
((System.ComponentModel.ISupportInitialize) bindingSource).BeginInit();
((System.ComponentModel.ISupportInitialize) errorProvider).BeginInit();
SuspendLayout();
bindingSource.DataSource = typeof(MainModel);
comboBox = new ComboBox();
comboBox.DataBindings.Add(new Binding("DataSource", bindingSource, "Options", true));
comboBox.DropDownStyle = ComboBoxStyle.DropDownList;
Controls.Add(comboBox);
errorProvider.ContainerControl = this;
errorProvider.DataSource = bindingSource;
((System.ComponentModel.ISupportInitialize) bindingSource).EndInit();
((System.ComponentModel.ISupportInitialize) errorProvider).EndInit();
ResumeLayout(false);
}
}
The problem appears to be with the data binding. If I comment out the line comboBox.DataBindings.Add(new Binding("DataSource", bindingSource, "Options", true));, the exception does not occur.
I found many references to this exception online, but it seems that in all those cases, the problem was that the data source was not an IList (as stated by the exception message). In this case, however, Options is an IList. So I'm at a loss to explain the exception.
I noticed that the exception does not occur if I remove the ErrorProvider. But I can't figure out why that is; and I need the error provider in my actual program.
I'm working in Visual Studio 2015, targeting .NET 4.6.
It looks like you are declaring your DataBinding while your bindingSource is still inside a BeginInit - EndInit block. Try moving that line to after the EndInit line, or in the OnLoad override instead:
protected override void OnLoad(EventArgs e) {
base.OnLoad(e);
comboBox.DataBindings.Add(new Binding("DataSource", bindingSource, "Options", true));
}
Adding a Binding for DataSource property is wrong idea.
To set the DataSource you should assign something to DataSource rather than adding a Binding. Adding a Binding makes sense for SelectedValue for example, but not for DataSource.
Your code should be:
bindingSource.DataSource = typeof(MainModel);
bindingSource.DataMember = "Options";
comboBox = new ComboBox();
comboBox.DataSource = bindingSource;
comboBox.DropDownStyle = ComboBoxStyle.DropDownList;
Controls.Add(comboBox);
Then you will not receive any error.
Note: If for any reason you are just curious about how to avoid the error in your example, just set this.Visible = true or call this.Show() exactly after InitializeComponent to force control handles created and make the data-binding start working. This fix is not required for my code.
Why data-binding to DataSource property is not a good idea?
If you are going to bind DataSource property to Optins property of MainModel it means you are going to update Options property using the combo box! Also it means the MainModel class should implement INotifyPropertyChanged to notify the combo boxes about changes in Options property!(Keep in mind the options property is an IList) That's totally wrong idea!
So how can I inform the ComboBox about changes in the data source?
If you want to notify the ComboBox about change in the data-source, the data-source list should implement IBindingList. BindingList<T> is an example of the implementation. So it's enough to set comboBox.DataSource = someDataSource; rather than data-binding to DataSource.
Long story short.
I have an UWP UI which contains a GridView and does not use Xaml. I'd like to display fully code-behind constructed items. No Xaml templates.
I have figured out that the GridView's ChoosingItemContainer event will allow me to create the GridViewItem instances programmatically and even possibly reuse them.
However the custom UI of the items is not actually displayed.
I have noticed that when scrolling a large amount of data the content appears very briefly and then it disappears. I'm guessing that the GridViewItem's Content is being overwritten by some kind of default template. Is there a way to disable this machinery?
More generally speaking, is there a known way to use GridView + Items without Xaml at all?
UPDATE:
Here is a minimal code sample that demonstrates the problem.
Place the CustomGridView somewhere in your UI.
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Windows.UI;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Data;
using Windows.UI.Xaml.Media;
namespace MyApp
{
// Some kind of data object
public class MyData
{
public string MyProperty;
}
// A custom GridViewItem
public class MyGridViewItem : GridViewItem
{
private TextBox mTextBox;
public MyGridViewItem()
{
mTextBox = new TextBox();
mTextBox.Width = 100;
mTextBox.Height = 100;
Content = mTextBox;
// Make the items visible at all: use red background
Background = new SolidColorBrush(Color.FromArgb(255,255,0,0));
}
public void SetData(MyData d)
{
mTextBox.Text = d.MyProperty;
// Content seems to be always reset to the data object itself.
Content = mTextBox;
// With the following line the contents appear briefly while the view is scrolling.
// Without this line the contents don't appear at all
Template = null;
}
}
// Custom grid. No Xaml.
public class CustomGridView : GridView
{
public CustomGridView()
{
this.ChoosingItemContainer += CustomGridView_ChoosingItemContainer;
// Create some data to show.
CollectionViewSource s = new CollectionViewSource();
ObservableCollection<MyData> oc = new ObservableCollection<MyData>();
for(int i = 0;i < 10000;i++)
{
MyData d = new MyData();
d.MyProperty = i.ToString();
oc.Add(d);
}
s.Source = oc;
ItemsSource = s.View;
}
private void CustomGridView_ChoosingItemContainer(ListViewBase sender,ChoosingItemContainerEventArgs args)
{
// Unchecked cast, but for the sake of simplicity let's assume it always works.
MyData d = (MyData)args.Item;
MyGridViewItem it = null;
if((args.ItemContainer != null) && (args.ItemContainer.GetType() == typeof(MyGridViewItem)))
it = (MyGridViewItem)args.ItemContainer;
else
it = new MyGridViewItem();
it.SetData(d);
args.ItemContainer = it;
args.IsContainerPrepared = true;
// This should probably go elsewhere, but for simplicity it's here :)
((ItemsWrapGrid)ItemsPanelRoot).ItemWidth = 100;
((ItemsWrapGrid)ItemsPanelRoot).ItemHeight = 100;
}
}
}
For you can custom UI by new a style to set.
You can set the resource in xaml,and give it a key ,like "res".
And you can get GridView.Style=(Style)Resources["res"]; in code.
To use style in xaml is a good way.But you can set style in code see:
Style style = new Style(typeof (GridView));
style.Setters.Add(new Setter(GridView.Property, xx));
GridView.Style=style ;
The style can be set the GridView and Items just like the xaml.
You have no say that what ui you want that I cant give you a code.
How to bind a ListBox or a CheckedListBox with a List<> of programmer defined object? How to edit the data after the binding? Through which object could I modify the data? How to make the listbox view updating when the data change? How to retrieve the check state of an item's checkbox when the user click on it for a CheckedListBox?
I wanted to share the few knowledge I have because I saw many threads on this subject but none was showing clearly how to deal with data binding. Don't misunderstand my attempt, I only want to show a lightened approach among others.
Feel free to comment. Hope that will help!
In the following, 'ListBox' is different from 'listbox' which is the generalized term used for ListBox and CheckedListBox.
Firstly, the bound is done in the testForm_Load event handler. One must tell which object is to be bound and what property/field to be shown in the listbox.
((ListBox)m_checkedListBox).DataSource = m_underList;
((ListBox)m_checkedListBox).DisplayMember = "PropToDisplay";
As I understand the process, the bound listbox behave like a read-only unidirectional (datasource => listbox) mirror view. We can only affect the listbox and retrieve its data by the way of handling the underlying data object or the events mechanism of the control (forget Items & co.).
For CheckListBox,we can retrieve the checked items by adding an ItemCheckEventHandler method to the ItemCheck event and then store it into a property's/field's object programmer-defined.
m_checkedListBox.ItemCheck += new ItemCheckEventHandler(this.ItemCheck);
But we can't define the state of an internally checked item in the datasource (underlying list) to be shown as checked in the CheckListBox. The CheckListBox is seemingly not designed to behave that way.
Then, one may add or remove Userclass object at will through the underlying list or call any method you want. Just don't care about the listbox.
m_underList.Add(new UserClass("Another example", 0, true));
Finally, the view refreshing of the listbox. I saw many article talking about setting DataSource to null and then re-assigning it back to its previous object. I looked for a better way to do it (better or prettier?). The method below do it pretty simply.
void refreshView(ListBox lb, object dataSource);
References:
CheckedListBox checked list item property binding to field in a class
[.net] ListBox.DataSource not updating?
For complementary information about retrieving user selection from the listbox, visit MSDN ListControl Class Examples
Below is the entire code of a simple example of a CheckedListBox being bound, filled by data and cleared. Note for simplicity that I started from the principle that either the listbox nor the data list are sorted.
UserClass.cs: Class used to store displayed/check states/hidden data
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace TestForm
{
class UserClass
{
private int m_underProp;
// ctors
public UserClass(string prop2Disp, int anotherObj, bool isChecked = false)
{
PropToDisplay = prop2Disp;
IsChecked = isChecked;
AnotherProp = anotherObj;
}
public UserClass()
{
PropToDisplay = string.Empty;
IsChecked = false;
AnotherProp = 0;
}
// Property to be displayed in the listbox
public string PropToDisplay
{
get;
set;
}
// For CheckedListBox only!
// Property used to store the check state of a listbox
// item when a user select it by clicking on his checkbox
public bool IsChecked
{
get;
set;
}
// Anything you want
public int AnotherProp
{
get
{
return m_underProp;
}
set
{
m_underProp = value;
// todo, processing...
}
}
// For monitoring
public string ShowVarState()
{
StringBuilder str = new StringBuilder();
str.AppendFormat("- PropToDisplay: {0}", PropToDisplay);
str.AppendLine();
str.AppendFormat("- IsChecked: {0}", IsChecked);
str.AppendLine();
str.AppendFormat("- AnotherProp: {0}", AnotherProp.ToString());
return str.ToString();
}
}
}
TestForm.Designer.cs: Design of the form
namespace TestForm
{
partial class testForm
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.m_checkedListBox = new System.Windows.Forms.CheckedListBox();
this.m_toggle = new System.Windows.Forms.Button();
this.SuspendLayout();
//
// m_checkedListBox
//
this.m_checkedListBox.CheckOnClick = true;
this.m_checkedListBox.FormattingEnabled = true;
this.m_checkedListBox.Location = new System.Drawing.Point(13, 13);
this.m_checkedListBox.Name = "m_checkedListBox";
this.m_checkedListBox.Size = new System.Drawing.Size(171, 109);
this.m_checkedListBox.TabIndex = 0;
//
// m_toggle
//
this.m_toggle.Location = new System.Drawing.Point(190, 53);
this.m_toggle.Name = "m_toggle";
this.m_toggle.Size = new System.Drawing.Size(75, 23);
this.m_toggle.TabIndex = 1;
this.m_toggle.Text = "Fill";
this.m_toggle.UseVisualStyleBackColor = true;
this.m_toggle.Click += new System.EventHandler(this.m_toggle_Click);
//
// testForm
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(275, 135);
this.Controls.Add(this.m_toggle);
this.Controls.Add(this.m_checkedListBox);
this.Name = "testForm";
this.Text = "Form";
this.Load += new System.EventHandler(this.testForm_Load);
this.ResumeLayout(false);
}
#endregion
private System.Windows.Forms.CheckedListBox m_checkedListBox;
private System.Windows.Forms.Button m_toggle;
}
}
TestForm.cs: Behavior of the form
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Diagnostics;
using System.IO;
namespace TestForm
{
public partial class testForm : Form
{
// a List which will contain our external data. Named as the underlying list
private List<UserClass> m_underList;
public testForm()
{
InitializeComponent();
m_underList = new List<UserClass> (3);
}
private void testForm_Load(object sender, EventArgs e)
{
// Bind the CheckedListBox with the List
// The DataSource property is hidden so cast the object back to ListBox
((ListBox)m_checkedListBox).DataSource = m_underList;
// Tell which property/field to display in the CheckedListBox
// The DisplayMember property is hidden so cast the object back to ListBox
((ListBox)m_checkedListBox).DisplayMember = "PropToDisplay";
/*
* The CheckedListBox is now in "read-only" mode, that means you can't add/remove/edit
* items from the listbox itself or edit the check states. You can't access
* the underlying list through the listbox. Considers it as a unidirectionnal mirror
* of the underlying list. The internal check state is disabled too, however
* the ItemCheck event is still raised...
*/
// Manually set the ItemCheck event to set user defined objects
m_checkedListBox.ItemCheck += new ItemCheckEventHandler(this.ItemCheck);
}
private void ItemCheck(object sender, ItemCheckEventArgs evnt)
{
if (sender == m_checkedListBox)
{
if (!m_checkedListBox.Sorted)
{
// Set internal object's flag to remember the checkbox state
m_underList[evnt.Index].IsChecked = (evnt.NewValue != CheckState.Unchecked);
// Monitoring
Debug.WriteLine(m_underList[evnt.Index].ShowVarState());
}
else
{
// If sorted... DIY
}
}
}
private void m_toggle_Click(object sender, EventArgs e)
{
if (sender == m_toggle)
{
if (m_toggle.Text == "Fill")
{
// Fill the checkedListBox with some data
// Populate the list with external data.
m_underList.Add(new UserClass("See? It works!", 42));
m_underList.Add(new UserClass("Another example", 0, true));
m_underList.Add(new UserClass("...", -7));
m_toggle.Text = "Clear";
}
else
{
// Empty the checkedListBox
m_underList.Clear();
m_toggle.Text = "Fill";
}
// Refresh view
// Remember CheckedListBox inherit from ListBox
refreshView(m_checkedListBox, m_underList);
}
}
// Magic piece of code which refresh the listbox view
void refreshView(ListBox lb, object dataSource)
{
CurrencyManager cm = (CurrencyManager)lb.BindingContext[dataSource];
cm.Refresh();
}
}
}
Here is the views of the form. The first image is the form when loaded and the second one is when the "Fill" button is clicked. One may note that the second item in the listbox is not checked despite the IsChecked property set to true when added to the datasource.
This should be relatively easy. I do not know why I am struggling with it, but Google cannot help me find an example of what I want to do.
I am building a navigation/menu out a simple nested structure of ULs with LIs. The parent object that is placed on the page is called NavController. It has a collection of NavItems.
So I pretty much let the NavController handle building the base UI, creating the CSS, and JavaScript. It also creates the base UL for the top most level of LIs. From my research and understanding the UI creation should be done in the CreateChildControls method.
Here is my code:
protected override void CreateChildControls()
{
Controls.Clear();
this.Page.RegisterClientJavaScript("~/Resources/Script/SideNav.js");
_ClientObject = this.GetClientJavaScriptObject("SideNav", this.ClientID);
Controls.Add(_BaseContainer);
HtmlGenericControl innerContents = this.BuildBaseContainer();
this.BuildList(innerContents);
_ClientObject.AddParameter("BaseContainerID", _BaseContainer.ClientID, true);
_ClientObject.AddParameter("ImgId", _SideBarTabImg.ClientID, true);
_ClientObject.AddParameter("SideBarContentsId", _SideBarContents.ClientID, true);
base.CreateChildControls();
}
The BuildList method actually builds the top level LIs. I am unsure how this method is called, but I can see that it is. Inside the BuildList function I add to the innerContents's Controls collection the NavItem objects.
First off, is this correct? Placing another custom server control inside of of a UL (with the caveat that the custom control is basically rendering an LI; more on this later)?
The CreateChildControls is never being called for the NavItem. After looking around on the Internet it seemed like it was because I was never calling EnsureChildControls. This confuses me because It will only fire when the ChildControlsCreated property is false. Once it is run this property becomes true. If I have a Text and Href on the NavItem and EnsureChildControls is called when either of them are set then how will the other's value be placed into any sub control?
Just to clog up some screen space here is the code that I have so far for the NavItem:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Text;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.HtmlControls;
namespace AthletesCafe.Web.WebControls.SideNav
{
[ToolboxData("<{0}:NavItem runat=server></{0}:NavItem>")]
public class NavItem : ExtendedWebControl
{
private string _Text;
private string _Value;
private IList<NavItem> _ChildItems = new List<NavItem>();
private HtmlGenericControl _ListItem = new HtmlGenericControl("LI");
private HtmlAnchor _Link = new HtmlAnchor();
private HtmlGenericControl _SubList = new HtmlGenericControl("UL");
public string Text
{
get
{
EnsureChildControls();
return _Text;
}
set
{
EnsureChildControls();
_Text = value;
_Link.InnerText = _Text;
}
}
public string Value
{
get
{
EnsureChildControls();
return _Value;
}
set
{
EnsureChildControls();
_Value = value;
_Link.HRef = _Value;
}
}
protected override void CreateChildControls()
{
Controls.Clear();
Controls.Add(_ListItem);
_ListItem.Controls.Add(_Link);
base.CreateChildControls();
}
public NavItem AddChildItem(string text, string url)
{
EnsureChildControls();
NavItem newItem = new NavItem();
newItem.Text = text;
newItem.Value = url;
_ChildItems.Add(newItem);
this.BuildSubList();
return newItem;
}
private void BuildSubList()
{
_SubList.Controls.Clear();
if (_ChildItems.Count > 0)
{
if (!_ListItem.Controls.Contains(_SubList))
_ListItem.Controls.Add(_SubList);
foreach (NavItem item in _ChildItems)
_SubList.Controls.Add(item);
}
}
protected override void Render(HtmlTextWriter writer)
{
RenderContents(writer);
}
protected override void RenderContents(HtmlTextWriter writer)
{
_ListItem.RenderControl(writer);
//base.RenderContents(writer);
}
public override string ToString()
{
EnsureChildControls();
return _Text + " - " + _Value;
}
}
}
I don't understand why I can't just set the value and text in their public properties and then use some method or manner to place it into the controls before render.
Please, please some point me to the correct way to do this. I have a bunch of these little controls that I need to make that basically do this same approach. I'm going to start a rudimentary toolbar soon and this information would help!
EDIT - Placing in code that shows the list building for clarification:
HtmlGenericControl list = new HtmlGenericControl("UL");
innerContents.Controls.Add(list);
foreach (NavItem item in _Items)
{
list.Controls.Add(item);
}
Once you override CreateChildControls(), you basically become responsible for making sure that your nested controls get created properly.
Your approach seems pretty standard, however, for your simple properties, I do not think you need to be calling EnsureChildControls(). If you picked this up from the MSDN docs on the function, take another look - the "Text" property example that they give is retrieving the text value from one of its child controls, which means that the property accessor needs to make sure the child controls exist.
You cannot guarantee when someone might access your control, which is why you would put in EnsureChildControl() calls before any access to a control in your Controls collection. In a normal page lifecycle, CreateChildControls() will get called when ASP.net gets around to adding your composite control to the page. This may not happen until PreRender on the first page load if nobody touches your control and it doesn't databind or anything.
On subsequent postbacks, CreateChildControls will be called during ViewState processing, or any code might touch off a composite control to start creating it's child controls. FindControl() will do it automatically, for example - and this could happen in OnInit().
Hope that helps. I think you're on the right track.
We have a custom collection of objects that we bind to a listbox control. When an item is added to the list the item appears in the listbox, however when one selects the item the currency manager position will not go to the position. Instead the currency manager position stays at the existing position. The listbox item is high lighted as long as the mouse is press however the cm never changes position.
If I copy one of the collection objects the listbox operates properly.
One additional note the collection also has collections within it, not sure if this would be an issue.
I found the issue, after spending way too much time....
This issue was related to one of the propertys of the item(custom class) in the collection which was bound to a date picker control. The constructor for the class never set the value to a default value.
This caused an issue with the currency manager not allowing the position to change as the specific property (bound to the date picker) was not valid.
Me bad! I know better!
You might need to post some code; the following (with two lists tied together only by the CM) shows that it works fine... so to find the bug we might need some code.
using System;
using System.ComponentModel;
using System.Windows.Forms;
static class Program
{
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
BindingList<Foo> foos = new BindingList<Foo>();
foos.Add(new Foo("abc"));
foos.Add(new Foo("def"));
ListBox lb1 = new ListBox(), lb2 = new ListBox();
lb1.DataSource = lb2.DataSource = foos;
lb1.DisplayMember = lb2.DisplayMember = "Bar";
lb1.Dock = DockStyle.Left;
lb2.Dock = DockStyle.Right;
Button b = new Button();
b.Text = "Add";
b.Dock = DockStyle.Top;
b.Click += delegate
{
foos.Add(new Foo("new item"));
};
Form form = new Form();
form.Controls.Add(lb1);
form.Controls.Add(lb2);
form.Controls.Add(b);
Application.Run(form);
}
}
class Foo
{
public Foo(string bar) {this.Bar = bar;}
private string bar;
public string Bar {
get {return bar;}
set {bar = value;}
}
}
Collections don't have a sense of "current item". Perhaps your custom collection does, but the ListBox is not using that. It has its own "current item" index into the collection. You need to handle SelectedIndexChanged events to keep them in sync.