Hi coders I have yet another question involving data binding in winforms. I set up a test applications where I have a bindinglist composed of structs called CustomerInfo. I have bound a listbox control to this list and spun a thread to add CustomerInfo items to the bindinglist.
namespace dataBindingSample {
public partial class Form1 : Form {
public BindingList<CustomerInfo> stringList = new BindingList<CustomerInfo>();
public Thread testThread;
public Form1() {
InitializeComponent();
stringList.AllowNew = true;
stringList.RaiseListChangedEvents = true;
listBox1.DataSource = stringList;
testThread = new Thread(new ThreadStart(hh_net_retask_request_func));
testThread.Priority = ThreadPriority.Normal;
}
private void hh_net_retask_request_func() {
int counter = 1;
while (true) {
CustomerInfo cust = new CustomerInfo();
cust.Name = "Customer "+ counter.ToString();
this.Invoke((MethodInvoker)delegate {
stringList.Add(cust);
});
counter++;
Thread.Sleep(1000);
}
}
private void Form1_Load(object sender, EventArgs e) {
testThread.Start();
}
}
public struct CustomerInfo {
public string Name {
set {
name = value;
}
get {
return name;
}
}
private string name;
}
}
What I see in the list box is the name of the struct dataBindingSample.CustomerInfo as opposed to the property of the struct. I was under the impression that non complex binding took the first available property.
Please educate me as to what I am doing wrong.
Thanks,
You'll need to either add an override of ToString() to your CustomerInfo class that returns what you'd like displyed in your list box, or set listBox1.DisplayMemer = "Name" before setting the DataSource.
Related
I have a DataGridView whose DataSource is a DataTable with five columns. If I attempt to access a column's ReadOnly property, like so:
datagridview.Columns[1].ReadOnly = true;
It throws a NullReferenceExcpetion.
I understand this is due to how the framework manages its auto generated columns, as noted by the answer to this question.
My question is: How do I make a column(s) readonly when the data source is auto generated?
Can't really say why it's not working, but a simple test with this code:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
dataGridView1.AutoGenerateColumns = true;
dataGridView1.DataSource = GenerateData();
dataGridView1.Columns[0].ReadOnly = true;
}
private List<DataSourceTest> GenerateData()
{
return new List<DataSourceTest>()
{
new DataSourceTest(1, "A"),
new DataSourceTest(2, "B"),
new DataSourceTest(3, "C"),
new DataSourceTest(4, "D"),
new DataSourceTest(5, "E"),
new DataSourceTest(6, "F"),
};
}
}
public class DataSourceTest
{
public DataSourceTest(int id, string name) { ID = id; Name = name; }
public int ID { get; set; }
public string Name { get; set; }
}
and making the gridview EditMode set to EditOnEnter so we can easily check if it's readonly or not, shows that it does the job well.
But if you still have issues, the best bet is to use an event, and the closest event for your question is the DataBindingComplete that will fire after the binding is done, so on that time, you will have full access to all your columns as they already bind to the gridview object.
double click on the event in the GridView control and add your readonly setter:
private void dataGridView1_DataBindingComplete(
object sender, DataGridViewBindingCompleteEventArgs e)
{
dataGridView1.Columns[0].ReadOnly = true;
}
In true TEK fashion, I figured out a solution to my own question:
To do this, you need to make use of the ColumnAdded event
datagridview.ColumnAdded += dataGridView_ColumnAdded;
Then in the event, you can check a column by name:
private void dataGridView_ColumnAdded(object sender, DataGridViewColumnEventArgs e)
{
if (e.Column is DataGridViewColumn)
{
DataGridViewColumn column = e.Column as DataGridViewColumn;
column.ReadOnly = true;
if (column.Name == "first_name")
{
column.ReadOnly = false;
}
}
}
Make column read-only when column has been generated
private void Form1_Load(object sender, EventArgs e)
{
List<Student> allStudent = new List<Student>();
for (int i = 0; i < 10; i++)
{
allStudent.Add(new Student { Name = "Student" + i, Roll = i + 1 });
}
dataGridView1.AutoGenerateColumns = true;
dataGridView1.DataSource = allStudent;
//Edited to show column count
MessageBox.Show("Column count is " + dataGridView1.Columns.Count);
foreach (DataGridViewColumn column in dataGridView1.Columns)
{
column.ReadOnly = true;
}
}
public partial class Student
{
public string Name { get; set; }
public int Roll { get; set; }
}
let me describe the situation. Winforms C#
I have xml file with data. I load this data to an user defined class object using Deserialize.
Based on this object with data, I build [in Form] UI: many tabPages of custom controls (textBox, 2 buttons in groupBox). I can also save this user defined class object using Serialize to XML file.
Question:
When I update textBox.Text in Form UI in custom control I do not know how to keep connection with the object with data (Layout layout) and save the updated object with data to XML. The change of text happens only in user custom control TextBox. I want to update data from UI in data object (layout) and then simply save with Serialization.
user class:
public class Layout
{
public string type;
public List<TabPage> TabPageList;
public Layout()
{
this.TabPageList = new List<TabPage>();
}
}
public class TabPage
{
public string text;
public List<ActionGroup> ActionGroupList;
public TabPage()
{
this.ActionGroupList = new List<ActionGroup>();
}
}
public class ActionGroup
{
public string type;
public string text;
public string sourceLocal;
public string sourceRemote;
public ActionGroup()
{
this.type = string.Empty;
this.text = string.Empty;
this.sourceLocal = string.Empty;
this.sourceRemote = string.Empty;
}
}
Custom control:
public partial class ViewActionGroup : UserControl
{
public string type;
public string text;
public string sourceLocal;
public string sourceRemote;
public bool isRemote;
public bool isDone;
public ViewActionGroup()
{
this.type = string.Empty;
this.text = string.Empty;
this.sourceLocal = string.Empty;
this.sourceRemote = string.Empty;
this.isRemote = false;
this.isDone = false;
InitializeComponent();
}
public ViewActionGroup(ActionGroup actionGroup)
{
this.type = actionGroup.type;
this.text = actionGroup.text;
this.sourceLocal = actionGroup.sourceLocal;
this.sourceRemote = actionGroup.sourceRemote;
this.isRemote = false;
this.isDone = false;
InitializeComponent();
groupBox1.Text = text;
button1.Text = type;
button1.Click += new EventHandler(Button_Click);
textBox1.Text = sourceLocal;
textBox1.TextChanged += new EventHandler(textBox1_TextChanged);
}
public void ChangeToRemote()
{
isRemote = true;
textBox1.Text = this.sourceRemote;
}
public void ChangeToLocal()
{
isRemote = false;
textBox1.Text = this.sourceLocal;
}
private void textBox1_TextChanged(object sender, EventArgs e)
{
if (this.isRemote)
{
this.sourceRemote = textBox1.Text;
}
else
{
this.sourceLocal = textBox1.Text;
}
}
Creating UI where I loose connection between UI and data object:
private void CreateLayout(Layout layout)
{
this.Text = layout.type;
TabControl tabControl = new TabControl();
tabControl.Dock = DockStyle.Fill;
int tabCount = 0;
foreach (TabPage tabpage in layout.TabPageList)
{
int actionCount = 0;
tabControl.TabPages.Add(tabpage.text);
foreach (ActionGroup actionGroup in tabpage.ActionGroupList)
{
ViewActionGroup view = new ViewActionGroup(actionGroup);
view.Location = new Point(0, actionCount * view.Height);
tabControl.TabPages[tabCount].Controls.Add(view);
tabControl.TabPages[tabCount].AutoScroll = true;
tabControl.TabPages[tabCount].AutoScrollMinSize = new System.Drawing.Size(tabControl.Width/2,tabControl.Height);
actionCount++;
}
tabCount++;
this.panelMain.Controls.Add(tabControl);
}
}
There are two common ways:
One is a routine WriteDataIntoControls and another ReadDataFromControls where you transfer the data to and from your visible controls manually (advantage: highest degree of control). In this case you'd have to read your object from your XML source, deserialize it into your business object and create all visible controls together with their value. On saving you'd have to transfer all values into your business object and serizalize it after this.
The second is DataBinding (advantage: highest degree of automation). Read here: https://msdn.microsoft.com/en-us/library/ef2xyb33%28v=vs.110%29.aspx?f=255&MSPPError=-2147217396
You can bind to simple values as well as to lists (including navigation) or complex objects.
You find a tutorial here: http://www.codeproject.com/Articles/24656/A-Detailed-Data-Binding-Tutorial
#Shnugo Thank You for your feedback. The tutorial you posted did not help because it is too hard for me but Data Binding topic gave me some clue.
Here easy tutorial in VB actually but simple. It helped me to do it quickly in C#.
https://www.youtube.com/watch?v=jqLQ2K9YY2A
C# solution
class MyObject
{
string name;
public MyObject()
{ }
public string Name
{
get { return name;}
set { name = value; }
}
}
public partial class Form1 : Form
{
MyObject obj = new MyObject();
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
obj.Name = "Lucas";
textBox1.DataBindings.Add("Text", obj, "Name", true, DataSourceUpdateMode.OnPropertyChanged);
}
private void button1_Click(object sender, EventArgs e)
{
label1.Text = obj.Name;
}
}
I have a problem with arrays which are made from class; actually I can make an array from a class and in the first form I set the data in may array, but when I switch to my second form and create an object from my class, I find my array empty so I can't use the information witch I had entered into my array.
public partial class Form1 : Form
{
string _name;
string _department;
int _id;
int _count;
int counter = 0;
Mediator m = new Mediator();
Employee ee = new Employee();
public Form1()
{
InitializeComponent();
}
private void Add_to_Array_Click(object sender, EventArgs e)
{
_name = txtName.Text;
_department = txtDepartment.Text;
_id = int.Parse(txtID.Text);
_count = counter;
m.Set_Value(_name, _department, _id,_count);
counter++;
Exchange();
//-----------------------------------
txtDepartment.Text = "";
txtID.Text = "";
txtName.Text = "";
}
private void Search_Click(object sender, EventArgs e)
{
listBox1.Items.Add(m.array[0].E_Name);
listBox1.Items.Add(m.array[0].E_Department);
listBox1.Items.Add(m.array[0].E_ID.ToString());
//---------------------------------------------------
listBox1.Items.Add(m.array[1].E_Name);
listBox1.Items.Add(m.array[1].E_Department);
listBox1.Items.Add(m.array[1].E_ID.ToString());
}
private void button1_Click(object sender, EventArgs e)
{
Form2 f2 = new Form2();
f2.Show();
}
public partial class Form2 : Form
{
Mediator M;
public Form2()
{
InitializeComponent();
}
private void Show2_Click(object sender, EventArgs e)
{
listBox1.Items.Add(M.array[0].E_Name);
listBox1.Items.Add(M.array[0].E_Department);
listBox1.Items.Add(M.array[0].E_ID.ToString());
//---------------------------------------------------
listBox1.Items.Add(M.array[1].E_Name);
listBox1.Items.Add(M.array[1].E_Department);
listBox1.Items.Add(M.array[1].E_ID.ToString());
}
class Employee
{
string Name;
string Department;
int ID;
//**************************
public string E_Name
{
get { return Name; }
set { Name = value; }
}
public string E_Department
{
get { return Department; }
set { Department = value; }
}
public int E_ID
{
get { return ID; }
set { ID = value; }
}
}
class Mediator
{
public Employee[] array = new Employee[5];
public void Set_Value(string name,string department,int id,int count)
{
array[count] = new Employee();
array[count].E_Name = name;
array[count].E_Department = department;
array[count].E_ID = id;
}
}
I would strongly suggest that you don't make Mediator m static.
IMHO this is a hack and you don't want to get in the habit of effectively making global variables everywhere, it will come back to bite you as you progress through your career.
The better solution is to pass Form2 only the data it needs to work on.
I'm guessing Form2 only needs to display the list of Employees, so you should create a Property in Form2 as follows:
public Employee[] Employees {get; set;}
[If you need more than just the list of employees then you should rather have a property for the Mediator - public Mediator Mediator {get; set;} ]
You then need to send this data to the second form just before you show it:
Form2 f2 = new Form2();
f2.Employees = m.array;
f2.Show();
Because the data is sent by reference any changes you make in form2 will reflect in the objects stored in form1.
Another option is to structure your program using the MVC pattern. It's a little bit more advanced, but it's a great pattern to keep your winForms apps neat and tidy. If you are building a wizard style app then I would strongly suggest this approach.
As an aside note you can also clean up the code quite a bit by using a list instead of an array to store the employee data - this will also make the code more adaptable to larger numbers of employees in the future.
You can also use C#s automatic properties to reduce the amount of code you need to write. So your code can look like this this instead:
public partial class Form1 : Form
{
private Mediator _mediator = new Mediator();
public Form1()
{
InitializeComponent();
}
private void Add_to_Array_Click(object sender, EventArgs e)
{
var newEmployee = new Employee
{
Name = txtName.Text,
Department = txtDepartment.Text,
ID = int.Parse(txtID.Text)
};
_mediator.Employees.Add(newEmployee);
Exchange();
//-----------------------------------
txtDepartment.Text = "";
txtID.Text = "";
txtName.Text = "";
}
private void Search_Click(object sender, EventArgs e)
{
foreach (var employee in _mediator.Employees)
{
listBox1.Items.Add(employee.Name);
listBox1.Items.Add(employee.Department);
listBox1.Items.Add(employee.ID.ToString());
}
}
private void button1_Click(object sender, EventArgs e)
{
Form2 f2 = new Form2();
f2.Employees = _mediator.Employees;
f2.Show();
}
}
public partial class Form2 : Form
{
public List<Employee> Employees { get; set; }
public Form2()
{
InitializeComponent();
}
private void Show2_Click(object sender, EventArgs e)
{
foreach (var employee in Employees)
{
listBox1.Items.Add(employee.Name);
listBox1.Items.Add(employee.Department);
listBox1.Items.Add(employee.ID.ToString());
}
}
}
class Employee
{
public string Name { get; set; }
public string Department { get; set; }
public int ID { get; set; }
}
class Mediator
{
public List<Employee> Employees { get; private set; }
public Mediator()
{
Employees = new List<Employee>();
}
}
You need an instance of Form1 in Form2 to use the array you've created.
You have two options;
First one, you can store an instance of Form1 in a common class so you can reach it from Form2.
Second option; you need to pass reference of Form1 to your Form2 instance.
A quick fix for your problem would be to add the static keyword in your array definition. this will make it stateless and therefor there won't be a new (instance of the) array being created each time you access it.
You should also consider placing this array in some kind of a static class that will be in charge of handling this kind of global variables.
As for your edits, you have at least 2 options here: the first one is making the Mediator class and the array inside static; or create a property in your second form like public string [] SomeArray {get; set;} and pass the array to it after declaring the ... = new Form2(); and before calling the .Open()
I created a ListBoxItem where I have a property Name and override ToString() to give back name. That works nicely when I add new items.
But now I need to force the ListBox to update the labels when I change the name of my ship. I thought Refresh or Update would do that but that doesn't work.
I might be missing something very easy here.
public class ShipListBoxItem
{
public ListBox Parent { get; set; }
public ShipType Ship { get; set; }
public ShipListBoxItem()
{
Ship = new ShipType();
}
public ShipListBoxItem(ShipType st)
{
Ship = st;
}
public override string ToString()
{
return Ship.Name;
}
public void UpdateListBox()
{
Parent.Refresh(); //My problem is here. Update doesn't work either.
}
public static ShipListBoxItem AddToListBox(ListBox lb, ShipType ship)
{
ShipListBoxItem li = new ShipListBoxItem(ship);
li.Parent = lb;
lb.Items.Add(li);
return li;
}
}
If you use a List<T> as the DataSource for the listbox it is pretty easy to have changes to items show up. It also means there is no real reason to have a special class for adding a ShipListBoxItem to a ListBox, your basic Ship class may work:
class ShipItem
{
public enum ShipTypes { BattleShip, Carrier, Destroyer, Submarine, Frigate };
public ShipTypes Ship { get; set; }
public string Name { get; set; }
public ShipItem(string n, ShipTypes st)
{
Name = n;
Ship = st;
}
public override string ToString()
{
return String.Format("{0}: {1}", Ship.ToString(), Name);
}
}
The form related stuff:
private void Form1_Load(object sender, EventArgs e)
{
// add some ships
Ships = new List<ShipItem>();
Ships.Add(new ShipItem("USS Missouri", ShipTypes.BattleShip));
Ships.Add(new ShipItem("USS Ronald Reagan", ShipTypes.Carrier));
lb.DataSource = Ships;
}
private void button1_Click(object sender, EventArgs e)
{
// change a ship name
lb.DataSource = null; // suspend binding
this.Ships[0].Name = "USS Iowa";
lb.DataSource = Ships; // rebind
lb.Refresh();
}
As an alternative, you can also tell the Listbox to use a specific property for the display using DisplayMember:
lb.DataSource = Ships;
lb.DisplayMember = "Name";
This would use the Name property in the listbox instead of the ToString method. If your list is changing a lot, use a BindingList instead. It will allow changes to the list show up in the ListBox as you add them without toggling the DataSource.
Try this
ListBox.RefreshItems()
msdn
EDIT: You can use an extended class like this:
public class FooLisBox : System.Windows.Forms.ListBox
{
public void RefreshAllItems()
{
RefreshItems();
}
}
private void button1_Click(object sender, EventArgs e)
{
(listBox1.Items[0] as ShipListBoxItem).Ship.Name = "AAAA";
listBox1.RefreshAllItems();
}
I managed to solve my problem.
Mostly, thanks Jose M.
I ran into a problem however. RefreshItems() triggers OnSelectedIndexChanged()
so my overridden class looks like this
public class MyListBox : ListBox
{
public bool DoEvents{ get; set; } // Made it public so in the future I can block event triggering externally
public MyListBox()
{
DoEvents = true;
}
public void RefreshAllItems()
{
SuspendLayout();
DoEvents = false;
base.RefreshItems(); // this triggers OnSelectedIndexChanged as it selects the selected item again
DoEvents = true;
ResumeLayout();
}
// I only use this event but you can add all events you need to block
protected override void OnSelectedIndexChanged(EventArgs e)
{
if (DoEvents)
base.OnSelectedIndexChanged(e);
}
}
I have found many answered questions on here explaining how to do this when the objects are created as part of the data source but my list box is just displaying "SharePointXMLBuilder.Farm" (Namespace.class) and not the selected DisplayName?
I dont know what I am doing wrong can anyone help please.
I have a list box with a data source as a databinding control and I am adding my created objects(Farm) to the databinding(farmListBindingSource) which all works fine, I just cant get the list to show the property I want it to.
Form: (loads another form takes input and returns to create object from Farm class)
private void CreateNewFarm_Click(object sender, EventArgs e)
{
FarmInput input = new FarmInput();
input.ShowDialog();
Farm nFarm = new Farm();
nFarm.location = input.inputLocation.ToString();
nFarm.identifier = input.inputType.ToString();
nFarm.environment = input.inputEnvironment.ToString();
this.farmListBindingSource.Add(nFarm);
this.testReturnTextBox.Text = nFarm.friendlyName;
}
private void MainForm_Load(object sender, EventArgs e)
{
this.FarmListBox.DisplayMember = "friendlyName";
this.testReturnTextBox.Text = "Form Loaded....";
}
Class:
namespace SharePointXMLBuilder
{
class Farm
{
private string farmLocation;
private string farmIdentifier;
private string farmEnvironment;
private string farmFriendlyName;
//private List<Server> farmServers;
//properties
public string friendlyName
{
get { return farmFriendlyName; }
set { farmFriendlyName = value; }
}
public string location
{
get { return farmLocation;}
set { farmLocation = value; this.buildFriendlyName(); }
}
public string identifier
{
get { return farmIdentifier; }
set { farmIdentifier = value; this.buildFriendlyName(); }
}
public string environment
{
get { return farmEnvironment; }
set { farmEnvironment = value; this.buildFriendlyName(); }
}
//constructor
public Farm()
{
}
//methods
public void AddServer(string s)
{
Server nServer = new Server(s);
// farmServers.Add(nServer);
}
public void buildFriendlyName()
{
this.friendlyName = this.location + " " + this.identifier + " " + this.environment;
}
}
}
Maybe you are not calling this function: buildFriendlyName() for each object in the list prior to binding?
In your buildfriendlyname() method set your private string farmFriendlyName insted of setting the property value friendlyname
Ok, so I tried to manually add DisplayMember in the designer and it wouldn't hold the value, as soon as I removed the DataSource it allowed DisplayMember to be populated so I changed the CreateNewFarm_Click to add the object to farmListBox.Items and when I re-ran the code it was fine.
It appears that you cannot use DisplayMember if you are pulling the objects from a DataSource.