C# - DataGridView can't add row? - c#

I have a simple C# Windows Forms application which should display a DataGridView. As DataBinding I used an Object (selected a class called Car) and this is what it looks like:
class Car
{
public string color { get; set ; }
public int maxspeed { get; set; }
public Car (string color, int maxspeed) {
this.color = color;
this.maxspeed = maxspeed;
}
}
However, when I set the DataGridView property AllowUserToAddRows to true, there is still no little * which allows me to add rows.
Someone suggested to set carBindingSource.AllowAdd to true, however, when I do that, I get a MissingMethodException which says my constructor could not be found.

Your Car class needs to have a parameterless constructor and your datasource needs be something like a BindingList
Change the Car class to this:
class Car
{
public string color { get; set ; }
public int maxspeed { get; set; }
public Car() {
}
public Car (string color, int maxspeed) {
this.color = color;
this.maxspeed = maxspeed;
}
}
And then bind something like this:
BindingList<Car> carList = new BindingList<Car>();
dataGridView1.DataSource = carList;
You can also use a BindingSource for this, you don't have to but BindingSource gives some extra functionality that can sometimes be necessary.
If for some reason you absolutely cannot add the parameterless constructor then you can handle the adding new event of the binding source and call the Car parameterised constructor:
Setting up the binding:
BindingList<Car> carList = new BindingList<Car>();
BindingSource bs = new BindingSource();
bs.DataSource = carList;
bs.AddingNew +=
new AddingNewEventHandler(bindingSource_AddingNew);
bs.AllowNew = true;
dataGridView1.DataSource = bs;
And the handler code:
void bindingSource_AddingNew(object sender, AddingNewEventArgs e)
{
e.NewObject = new Car("",0);
}

You need to add AddingNew event handler:
public partial class Form1 : Form
{
private BindingSource carBindingSource = new BindingSource();
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
dataGridView1.DataSource = carBindingSource;
this.carBindingSource.AddingNew +=
new AddingNewEventHandler(carBindingSource_AddingNew);
carBindingSource.AllowNew = true;
}
void carBindingSource_AddingNew(object sender, AddingNewEventArgs e)
{
e.NewObject = new Car();
}
}

I recently discovered that if you implement your own binding list using IBindingList you MUST return true in your SupportsChangeNotification in addition to AllowNew.
The MSDN article for DataGridView.AllowUserToAddRows only specifies the following remark however:
If the DataGridView is bound to data, the user is allowed to add rows if both this property and the data source's IBindingList.AllowNew property are set to true.

Related

ComboBox items not updating when underlying ObservableCollection changes

I have a ComboBox and an ObservableCollection set as DataSource for that ComboBox.
When I programmatically add/remove items from the observable collection, nothing changes in the ComboBox.
What am I doing wrong?
Part 2: tried to put a BindingSource as a proxy for ObservableCollection. When programmatically added/removed items from ObservableCollection, no event like ListChanged or similar fired.
How can I make a ComboBox automatically update its list when underlying collection changes?
public Form1()
{
InitializeComponent();
comboBox1.DataSource = new ObservableCollection<MyItem>(
new []
{
new MyItem() { Name = "AAA"},
new MyItem() { Name = "BBB"},
});
}
private void Button3_Click(object sender, EventArgs e)
{
// Nothing changes in the ComboBox when I add a new item to ObservableCollection
((ObservableCollection<MyItem>)(comboBox1.DataSource))
.Add(new MyItem() { Name = Guid.NewGuid().ToString()});
}
}
public class MyItem
{
public string Name { get; set; }
}
It helps to wrap a list in a BindingList<T>. Here a little test code:
public partial class Form1 : Form
{
private readonly List<string> _coll = new List<string> { "aaaaa", "bbbbb", "ccccc" };
private readonly BindingList<string> _blist;
private readonly Random _rand = new Random();
private const string Templ = "mcvnoqei4yutladfffvtymoiaro875b247ytmlarkfhsdmptiuo58y1toye";
public Form1()
{
InitializeComponent();
_blist = new BindingList<string>(_coll);
comboBox1.DataSource = _blist;
}
private void AddButton_Click(object sender, EventArgs e)
{
int i = _rand.Next(Templ.Length - 5);
string s = Templ.Substring(i, 5);
_blist.Add(s);
}
}
Note that you have to make the changes (Add, Remove etc.) to the BindingList. The BindingSource works the same way.

How to create user control for displaying collection of other user controls in WinForms?

I need to create a user control MyTypeListControl to display collection of objects of type MyType using a user controls MyTypeDisplayControl instance for each of those objects.
So that I could
add instance of MyTypeListControl to my WinForm, then
load collection of MyType and
assign it to MyTypeListControl's DataSource.
In the result it should generate and show appropriate count of MyTypeDisplayControl instances in MyTypeListControl's instance.
In case if I needed to show list of properties - equivalent would be DataGrid with specific fields from MyType assigned to specific DataGrid's columns, but I want to view each MyType item as a user control - with more power for visual representation and functionality than DataGrid provides for it's rows.
Is that even possible?
I found this SO resource how to create My collection type, but this is only small part of the problem solution...
It is quite easy (if you know how) and doesn't take so much effort as you might think in the first place (at least for a simple implementation that handles collection of less then 100 items).
So at first lets create a MyType:
public class MyType
{
public static MyType Empty = new MyType(String.Empty, DateTime.MinValue);
public MyType(string myName, DateTime myBirthday)
{
MyName = myName;
MyBirthday = myBirthday;
}
public DateTime MyBirthday { get; private set; }
public string MyName { get; private set; }
}
At next we need a MyTypeControl:
public partial class MyTypeControl : UserControl
{
private MyType _MyType;
private Label labelBirthday;
private Label labelName;
private Label labelSeparator;
public MyTypeControl()
{
InitializeComponent();
}
public event EventHandler MyTypeChanged;
public MyType MyType
{
get { return _MyType; }
set
{
if (_MyType == value)
return;
_MyType = value ?? MyType.Empty;
OnMyTypeChanged(EventArgs.Empty);
}
}
protected virtual void OnMyTypeChanged(EventArgs eventArgs)
{
UpdateVisualization();
RaiseEvent(MyTypeChanged, eventArgs);
}
protected void UpdateVisualization()
{
SuspendLayout();
labelName.Text = _MyType.MyName;
labelBirthday.Text = _MyType.MyBirthday.ToString("F");
labelBirthday.Visible = _MyType.MyBirthday != DateTime.MinValue;
ResumeLayout();
}
private void InitializeComponent()
{
labelName = new Label();
labelBirthday = new Label();
labelSeparator = new Label();
SuspendLayout();
labelName.Dock = DockStyle.Top;
labelName.Location = new Point(0, 0);
labelName.TextAlign = ContentAlignment.MiddleCenter;
labelBirthday.Dock = DockStyle.Top;
labelBirthday.TextAlign = ContentAlignment.MiddleCenter;
labelSeparator.BorderStyle = BorderStyle.Fixed3D;
labelSeparator.Dock = DockStyle.Top;
labelSeparator.Size = new Size(150, 2);
Controls.Add(labelSeparator);
Controls.Add(labelBirthday);
Controls.Add(labelName);
MinimumSize = new Size(0, 48);
Name = "MyTypeControl";
Size = new Size(150, 48);
ResumeLayout(false);
}
private void RaiseEvent(EventHandler eventHandler, EventArgs eventArgs)
{
var temp = eventHandler;
if (temp != null)
temp(this, eventArgs);
}
}
Then comes our magically list control:
public class MyTypeListControl : UserControl
{
private ObservableCollection<MyType> _Items;
public MyTypeListControl()
{
AutoScroll = true;
_Items = new ObservableCollection<MyType>();
_Items.CollectionChanged += OnItemsCollectionChanged;
}
public Collection<MyType> Items
{
get { return _Items; }
}
private void OnItemsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
UpdateVisualization();
}
private void UpdateVisualization()
{
SuspendLayout();
Controls.Clear();
foreach (var item in _Items)
{
var control = new MyTypeControl { MyType = item, Dock = DockStyle.Top };
Controls.Add(control);
Controls.SetChildIndex(control, 0);
}
ResumeLayout();
}
}
And now simply create the list control in your form or parent control and fill it with some meaningful values:
myTypeListControl.Items.Add(new MyType("Adam", DateTime.UtcNow.Add(-TimeSpan.FromDays(365 * 40))));
myTypeListControl.Items.Add(new MyType("Eva", DateTime.UtcNow.Add(-TimeSpan.FromDays(365 * 38))));

binding to a combobox getting different results when using using SelectedValue

I have bound to my combobox this simple class:
public class Company
{
public Guid CorporationId { set; get; }
public Guid TokenId { set; get; }
public string Name { set; get; }
}
And this is my binding:
private void FillCompaniesComboBox()
{
_doneLoadingComboBox = false;
comboBox_Companies.Items.Clear();
if (CurrentSettings.AllCompanies.Count == 0)
{
return;
}
bindingSource1.DataSource = CurrentSettings.AllCompanies;
comboBox_Companies.DataSource = bindingSource1.DataSource;
comboBox_Companies.DisplayMember = "Name";
comboBox_Companies.ValueMember = "CorporationId";
comboBox_Companies.SelectedIndex = 1;
_doneLoadingComboBox = true;
}
When I attempt to get the value of the selected item, I'm getting different results. Here is the code I am using to get my value:
private void comboBox_Companies_SelectedIndexChanged(object sender, EventArgs e)
{
if (!_doneLoadingComboBox && comboBox_Companies.SelectedIndex == -1)
{
return;
}
var value = (Company)comboBox_Companies.SelectedValue;
Console.WriteLine("Value: " + value.CorporationId);
}
Here is what is happening:
This one works at intended:
And this is were it is causing an issue:
Am I not retrieving the data correctly? I need the Company information that it is bound to.
Okay so here's what you need to do...
Assuming that your CurrentSettings.AllCompanies is an IList<Company> that you've already populated with data, here's what your code should look like:
public class ComboBoxItem {
// your class
private Company Comp;
}
private readonly BindingSource _bsSelectedCompany = new BindingSource();
private readonly ComboBoxItem _comboBoxItem = new ComboBoxItem();
// your main form method
public MainForm() {
// initialization code...
InitializeComponent();
// prevents errors in case your data binding objects are empty
ResetComboBox(comboBox1);
comboBox1.DataBindings.Add(new Binding(
"SelectedItem",
_bsSelectedCompany,
"Comp",
false,
DataSourceUpdateMode.OnPropertyChanged
));
comboBox1.DataSource = CurrentSettings.AllCompanies;
comboBox1.DisplayMember = "Name";
}
// simple method for resetting a given combo box to a default state
private static void ResetComboBox(ComboBox comboBox) {
comboBox.Items.Clear();
comboBox.Items.Add("Select a method...");
comboBox.SelectedItem = comboBox.Items[0];
}
By doing this, you're able to just use _comboBoxItem to safely get the information about your selected item without having to potentially Invoke it (in the case of accessing it on a separate thread).

Passing value from one form to a textbox in another form

Well I know this question has been asked a couple of times but none of the solutions worked for me. I simply want to pass a value from one form to a textbox in a different form.
On the first form I have a data grid when double-clicked on it obtains a value from the datagrid column.
public partial class AvailableRooms : Form
{
private void DCRoom(object sender, DataGridViewCellMouseEventArgs e)
{
var roomnum = dgRooms.Rows[e.RowIndex].Cells["iRoomNum"].Value.ToString();
RoomBooking rb = new RoomBooking();//The second form
rb.roomnumber = roomnum;
rb.Show();
}
}
On the second form I have set the properties of the textbox
public partial class RoomBooking : Form
{
public RoomBooking()
{
StartPosition = FormStartPosition.CenterScreen;
InitializeComponent();
}
public string roomnumber
{
get { return txtRoomNum.Text; }
set {txtRoomNum.Text = value;}
}
}
Thanks in advance for the help?
You have to find the control to edit it as it does not belong to the class.
private void DCRoom(object sender, DataGridViewCellMouseEventArgs e)
{
//new value
var roomnum = dgRooms.Rows[e.RowIndex].Cells["iRoomNum"].Value.ToString();
//The second form
RoomBooking rb = new RoomBooking(this);
//The textbox
TextBox roomnumber = (TextBox)rb.Controls.Find("roomnumber", true)[0];
//set the value of the textbox
roomnumber.Text = roomnum;
//show second form
rb.Show();
}
I would define the RoomBookingclass like this:
public partial class RoomBooking : Form
{
public RoomBooking() // WinForms Designer requires a public parameterless constructor
{
InitializeComponent();
StartPosition = FormStartPosition.CenterScreen;
}
public RoomBooking(string roomNumber) : this() // Constructor chaining
{
RoomNumber = roomNumber;
txtRoomNum.Text = RoomNumber;
}
public string RoomNumber { get; set; }
}
Then:
public partial class AvailableRooms : Form
{
private void DCRoom(object sender, DataGridViewCellMouseEventArgs e)
{
var roomNumber = dgRooms.Rows[e.RowIndex].Cells["iRoomNum"].Value.ToString();
var roomBooking = new RoomBooking(roomNumber);
roomBooking.Show();
}
}
Hope this helps.

c# DataBinding to string property on a Generic

I'm trying to Databind to my custom dictionary class. In formLoad, I can bind to Car.Desc but I cannot bind to RatesCache.Desc.
They are both public string properties.
What am I missing?
Thanks!
System.ArgumentException was unhandled
Message="Cannot bind to the property or column Desc on the DataSource.\r\nParameter name: dataMember"
Source="System.Windows.Forms"
ParamName="dataMember"
public class RatesCache : Dictionary<int, Rate>
{
public string Desc { get; set; }
}
public class Car
{
public string Desc { get; set; }
}
static Car car = new Car();
static RatesCache rc = new RatesCache();
private void Form1_Load(object sender, EventArgs e)
{
rc.Desc = "hello too";
car.Desc = "Im a car";
textBox1.DataBindings.Add("Text", rc, "Desc");
}
private void Form1_Load(object sender, EventArgs e)
{
rc.Desc = "hello too";
car.Desc = "Im a car";
textBox1.DataBindings.Add("Text", rc, "Desc");
textBox1.TextChanged .TextChanged += _textBox1_TextChanged;
}
private void _messagesReceviedLabel_TextChanged(object sender, EventArgs e)
{
_textBox1.Text = rc.Desc.ToString();
}
public class RatesCache : Dictionary<int, Rate>
{
public string Desc { get; set; }
public override string ToString()
{
return Desc;
}
}
My guess is that because your class is inheriting from a Dictionary which is a Collection, it throws off the DataBinding for the textbox. Windows Forms has it's own way of dealing with databinding to a collection different then when binding directly to a property of a class. Not much of an answer, I know, but I don't think there's really a way around it. My suggestion would be to either not directly inherit from Dictionary; rather keep an internal Dictionary, and expose methods as needed. OR, don't databind the texbox directly. Rather, raise an event whenever your "Desc" property changes in your RatesCache class, and then in your form listen to that event. When it changes, update your textbox.

Categories