I have a user control that contains a GridView. I pass an IEnumerable data source object to the user control instance. How can I use a ForEach to loop through the data source in the user control code behind to display the columns I want from the data source?
So far, I have the following code in the code behind of the user control:
public IEnumerable<object> DataSource { get; set; }
protected void Page_Load(object sender, EventArgs e)
{
this.GridView1.DataSource = DataSource;
foreach (var item in DataSource)
{
//The line below gives an error - what's the correct way to do this?
this.GridView1.Columns.Add(new BoundColumn() {DataField = "What to put here", HeaderText = "What to put here"; }
}
this.GridView1.DataBind();
}
You should not loop on all items of the DataSource, you are looping vertically on all records while you want to loop only once, horizontally, on all columns. Since you know the list of properties of the object contained in the DataSource you can do this statically and not even in a foreach. If you really want to have it dynamic then you could use Reflection and loop on all public fields of the first object available in your IEnumerable.
Edit: to find all public fields of an object via reflection see here:
How can I find all the public fields of an object in C#?
but this only applies if you want to make it generic, if you already know that your object contains some fields like Name, Address and Email, for example, you do not need it.
Related
So I am using Linq-to-sql to communicate with my sql server and i want to populate a asp.net dropdown list with data from my database. The problem is I want the text value that is visible to the user to bind to a relation-property.
I have a list of linq-to-sql objects and each of those objects has a one-to-many relation child and parent property. The parentproperty is simply called "parent" which is also a linq-to-sql object that contains a name property of type string. The problem is I want to bind DataTextField to the parent.name property.
The code below is from my code-behind file which does not work at the moment where DropDownList is my dropdownlist and Table.GetAll() returns a list of linq-to-sql objects:
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
Bind_Drop_Down();
}
}
protected void Bind_Drop_Down()
{
List<linq-to-sql-class-name> objects = Table.GetAll();
if (objects != null)
{
DropDownList.DataSource = objects;
DropDownList.DataTextField = "parent.name";
DropDownList.DataValueField = "id";
DropDownList.DataBind();
}
}
I think this can be solved by putting the raw query to my database right above this code and make it so that it creates a new object with new parameters but I do not want this. I want to keep the raw queries in my Table class which should only return proper linq-to-sql objects.
I get the following error message when executing the above code where linq-to-sql-class-name is a placeholder for the actual name:
DataBinding: 'linq-to-sql-class-name' does not contain a property with the name 'parent.name'
My question is: can this be done?
EDIT:
I added some code to verify the property actually exists, I tried printing the parent.name property of each object in the list and it actually printed it as expected.
protected void Bind_Drop_Down()
{
List<linq-to-sql-class-name> objects = Table.GetAll();
if (objects != null)
{
foreach(linq-to-sql-class-name oneObject in objects)
{
System.Diagnostics.Debug.WriteLine(oneObject.parent.name);
}
DropDownList.DataSource = objects;
DropDownList.DataTextField = "parent.name";
DropDownList.DataValueField = "id";
DropDownList.DataBind();
}
}
It printed this in the debug console:
name1 lastname1
name2 lastname2
...etc
...etc
Ofcourse I found a suitable solution right after I posted this question: https://stackoverflow.com/a/5721761/3266294
I'm writing a WPF application connected to a local Access database. In one of the application screens, one table data (named Service) is shown in individual textboxes, like a form, and the user can navigate through records, create new ones, delete, edit or search. Everything is done on the same table.
After a intensive research on how to navigate through records displayed in textboxes, I ended up using a DataSet and a CollectionView.
public partial class Entries : Window
{
AgendaDataSet agendaDataSet = new AgendaDataSet();
AgendaDataSetTableAdapters.ServiceTableAdapter serviceAdapter = new AgendaDataSetTableAdapters.ServiceTableAdapter();
CollectionView workSheetView;
private void Window_Loaded(object sender, RoutedEventArgs e)
{
this.serviceAdapter.FillByDateAsc(agendaDataSet.Service);
this.DataContext = agendaDataSet.Service;
this.workSheetView = (CollectionView)CollectionViewSource.GetDefaultView(agendaDataSet.Service);
this.workSheetView.MoveCurrentToLast();
}
I got record navigation working using the CollectionView methods MoveCurrentToFirst(), MoveCurrentToNext(), etc. I also can create new records, edit and delete.
This is the method I use to create a new record:
private void btnNovo_Click(object sender, RoutedEventArgs e)
{
dynamic row = this.agendaDataSet.Service.NewMainRow();
this.agendaDataSet.Service.AddMainRow(row);
this.workSheetView.MoveCurrentToLast();
}
My problem is with record searching. I have a button that, when the user presses it, it asks for the PatientName he is searching. Then, the data about that Patient must appear on the various textboxes, ready to be consulted, edited or deleted.
Through the CollectionView, I only found the method GetItemAt() that gets a record based on it's row index. Since I am working with an Access database, I can't use the predicate ROW_NUMBER. And I don't think this approach would be the best.
So, how can I get an item based on it's ID, or PatientName, or any other field, and pass it as a row to the CollectionView?
Probably you don't need to get an item based on its ID or PatientName property.
Suppose that the user looks for "Andrew" as PatientName. Your code finds that the second row of your DataTable (called "Service") is the one the user is looking for.
You can use a simple static method to look for a DataRowView, something like this:
private static DataRowView FindDataRowView(DataView dataView, DataRow dataRow)
{
foreach (DataRowView dataRowView in dataView)
{
if (dataRowView.Row == dataRow)
{
return dataRowView;
}
}
return null;
}
and then you can select the object in your CollectionView:
collectionView.MoveCurrentTo(FindDataRowView(agendaDataSet.Service.DefaultView,
agendaDataSet.Service.Rows[2]));
Of course you can find the real DataRow index by using a foreach cycle or the Select method of DataTable.
I have a gridview with boundfields inside the grid. I'm trying to get the values of the boundfields when OnRowUpdating is fired. But when I try to read the new values the result is always empty.
This is the instruction I'm using :
protected void MyGridView_OnRowUpdating(object sender, GridViewUpdateEventArgs e)
{
string id = MyGridView.Rows[e.RowIndex].Cells[3].Text;
DataBind();
}
Maybe I'm wrong, since I haven't used the GridView for a while, but shouldn't you be accessing the NewValues collection of e?
That's the beauty of the GridView: it keeps track of old vs. new values, and conveniently makes them available to you without you having to fish around to find them.
string id = (string) e.NewValues["whatever"];
TextBox sampleTextBox=((TextBox)MyGridView.Rows[e.RowIndex].FindControl("CostTextBox"));
string data=sampleTextBox.Text;
try this this, where CostTextBox is the id of the control you have given for the control in the grid.
I am having trouble getting my datasource linked to my repeater through this code
protected void Page_Load(object sender, EventArgs e)
{
//HiddenField used as a placholder
HiddenField username = list.FindControl("username") as HiddenField;
//list is a DataList containing all of the user names
list.DataSource = Membership.GetAllUsers();
list.DataBind();
//Creates a string for each user name that is bound to the datalist
String user = username.Value;
//profilelist is a repeater containing all of the profile information
//Gets the profile of every member that is bound to the DataList
//Repeater is used to display tables of profile information for every user on
// the site in a single webform
profilelist.DataSource = Profile.GetProfile(user);
profilelist.DataBind();
}
I am getting the error message
An invalid data source is being used for profilelist. A valid data source must implement either IListSource or IEnumerable.
Well the reason why it will not work is because Profile.GetProfile returns ProfileCommon. As the error states the type you set profilelist.Datasource equal to, must be IListSource or IEnumerable.
I would suggest not using a repeater since you don't have actual repeating data to display.
EDIT
I think this is what you want to do.
IEnumerable<ProfileCommon> myProfileList = new IEnumerable<ProfileCommon>();
foreach(var user in userlist)
{
myProfileList.Add(Profile.GetProfile(user));
}
profilelist.datasource = myProfileList;
Your going about this wrong. As Etch said, a repeater is for lists of things. GetProfile doesn't return a list.
You're better off just putting your controls in a panel and assigning them in the "list" controls ondatabinding event.
In other words, you don't need a repeater here.
I forgot to post this up but for anyone that needs to do something similar here is the code behind that works
protected void Page_Load(object sender, EventArgs e)
{
List<MembershipUserCollection> usernamelist = new List<MembershipUserCollection>();
usernamelist.Add(Membership.GetAllUsers());
List<ProfileCommon> myProfileList = new List<ProfileCommon>();
foreach (MembershipUser user in usernamelist[0])
{
string username = user.ToString();
myProfileList.Add(Profile.GetProfile(username));
Label emailLabel = profilelist.FindControl("EmailLabel") as Label;
}
}
At the moment this is displaying about 15 user names and providing an ability to link to each of theses users respective profiles.
I have a windows forms application containing a datagridview control. The datagridview is populated by the contents of an xml file. At the moment, all of the columns are displayed as datagridviewtextboxcolumns. I want to select one that is populated by a particular xml tag and display it's content in a datagridviewcomboboxcolumn along with 2 other options.
EXAMPLE:
<SMS>
<Number>+447931663542</Number>
<DateTime>2009-07-12T17:00:02</DateTime>
<Message>YES</Message>
<FollowedUpBy>Unassigned</FollowedUpBy>
<Outcome>Resolved</Outcome>
</SMS>
The OUTCOME tag is the column that I would like to be displayed as a comboboxcolumn in the datagridview. If for example the tag is empty and contains no data, then I want to display nothing, but have the comboboxcolumn populated with 3 possible options to choose from (Unresolved, Resolved, Pending). If however the tag contains data, I want that particular item to be displayed in the comboboxcolumn, and have the other two options available to be selected.
Help in achieving this would be appreciated greatly!
Regards,
EDIT:
Currently I use this code:
colOutcome = new DataGridViewComboBoxColumn();
colOutcome.HeaderText = "Outcome";
colOutcome.Width = 90;
colOutcome.Items.AddRange("Resolved", "Unresolved", "Pending");
this.dataGridView1.Columns.Insert(1, colOutcome);
this.dataGridView1.Columns[1].Name = "OutcomeColumn";
This code above populates the combobox. THE PROBLEM IS: When The xml document populates the datagridview, the outcome column just appears as a textbox column, containing the data inbetween the outcome tags in the xml file. My point is, how can i get the datagridview to realise when it reads the outcome column that it needs to be changed into a combobox column and then display the data that way, along with the other potentially selectable options in the combobox?! Currently the datagridview gets populated with all columns as textboxcolumns containing the data, as well as a seperate combobox column which is not what I want. I need the application to merge the outcome column and its data with the code above.
Any ideas?
Updated Answer
You could pass in the XML document to a function that will loop through each node and determine whether it should be a ComboBox one or not i.e. if the name is "Outcome".
private void CreateColumns(XmlDocument doc)
{
foreach (...) // loop through each node in xml document
{
if (node.Name == "Outcome")
{
var items = new List<string>() { "Resolved", "Unresolved", "Pending" };
this.dataGridView1.Columns.Add(CreateComboBoxColumn(node.Name, items));
}
else
{
this.dataGridView1.Columns.Add(String.Format("col{0}", node.Name), node.Name);
}
}
}
Then your code for creating the Outcome column would be:
private DataGridViewComboBoxColumn CreateComboBoxColumn(string colHeaderText, List<string> items)
{
var colOutcome = new DataGridViewComboBoxColumn();
colOutcome.HeaderText = colHeaderText;
colOutcome.Width = 90;
colOutcome.Items.AddRange(items.ToArray());
colOutcome.Name = String.Format("col{0}", colHeaderText);
return colOutcome;
}
You would then just call CreateColumns on the form load event and pass in your XML. You should only need to create the columns once.
My advice would be to have a similar function that will find all the SMS elements and add a new row populating it with the information in each node.
public void MyForm_Load(object sender, EventArgs e)
{
var doc = new XmlDocument(filename);
CreateColumns(doc);
CreateRows(doc);
}
Hope that helps.
Answer #2 for me, based on the updated question.
The problem you are experiencing is with the AutoGeneratedColumns functionality of the DataGridView. You will need to create your columns manually before databinding. This can be done at design-time or run-time. I prefer design-time because it gives you a bit more direction with the look/feel of the grid but either way works.
You will need to disable the AutoGeneratedColumns property of the grid:
private void Form1_Load(object sender, EventArgs e)
{
// Define your columns at run-time here if that's what you prefer
this.dataGridView1.AutoGeneratedColumns = false;
this.dataGridView1.DataSource = myDataSource;
}
I'm not sitting in front of VS so this might not compile but should give you direction.
You need to either pre-populate the ResolvedColumn with the 3-4 possible values at design-time or assign it to another datasource at runtime. If you chose the design-time approach, simply open the DataGridView "Edit Columns" dialog, find the ResolvedColumn, go to Items, and add your values ("", "Unresolved", "Pending", "Resolved"). The empty value might help the ComboBox to render if there is the possiblity of rendering the grid with SMS records that have no Outcome.
To bind the possible options at runtime do something like this:
private List<string> _outcomeDataSource;
private void Form1_Load(object sender, EventArgs e)
{
_outcomeDataSource = new List<string>;
_outcomeDataSource.Add("");
_outcomeDataSource.Add("Unresolved");
_outcomeDataSource.Add("Pending");
_outcomeDataSource.Add("Resolved");
ResolvedColumn.DataSource = _outcomeDataSource;
ResolvedColumn.PropertyName = "Outcome";
}