I realize this is an easy question, but despite searching I can't find anything specific towards my problem.
I have a gridview populated with 9 or so columns. I want to both change the column names and edit the number of visible columns. So instead of
| x | y | z |
2 6 7
I'd like
|new x|new z|
2 7
I realize that I can manually edit the column names and set them to visible or not, but is there a way to do something like: if (column = y) then (display column) and (column name = new y)?
Much appreciated.
You can handle the GridView.RowDataBound event to modify the columns when they are bound and apply any changes you want at that point.
you could do something like this inside the page load event, or grid load:
foreach(BoundField b in grid.Columns)
{
if(b.HeaderText == 'y')
{
b.HeaderText = "new y";
} else {
b.Visible = false;
}
}
Related
I've tried to display the values of properties from a list of class on the table of MS Word like this:
public void CreateTable<T>(IList<T> list)
{
if (list != null)
{
var props = typeof(T).GetProperties();
Range tableLoc = WdDoc.Range();
var table = WdDoc.Tables.Add(tableLoc, list.Count, props.Length);
for (int i = 0; i < props.Length; i++)
{
table.Columns[i + 1].Select();
if (props[i].PropertyType == typeof(double))
{
WdApp.Selection.Range.ParagraphFormat.Alignment = WdParagraphAlignment.wdAlignParagraphRight;
}
else
{
WdApp.Selection.Range.ParagraphFormat.Alignment = WdParagraphAlignment.wdAlignParagraphCenter;
}
}
//...
}
}
I tried to automatically set the text alignment of columns according to fill-in property type.
However, only the first row of each column can be set as the correct alignment I assign.
Other rows will always be set as the latest alignment of for loop.
Why did this happen? Did I miss any snippet?
Also, what are the differences between
WdApp.Selection.Paragraphs.Format.Alignment = WdParagraphAlignment.wdAlignParagraphCenter;
and
WdApp.Selection.Range.ParagraphFormat.Alignment = WdParagraphAlignment.wdAlignParagraphCenter;
Thanks!
The problem comes from using Selection.Range in this specific instance of working with the Word object model. When working with columns you need to use
WdApp.Selection.ParagraphFormat.Alignment
Background
Selection affects exactly what is selected - the entire column.
Selection.Range affects the Range of the selection. In the case of a Column, Selection.Range cannot be the entire column. A Range in Word must be a contiguous run of text, but a column is NOT contiguous. So Selection.Range returns only that part of the selection that is contiguous: the first cell (row). That explains why only the first row of the table is affected by your current code.
While a selected column appears to be contiguous this is just a convenience for the user. Under the covers, everything in the rows between the cells in one column and the next breaks up the text run.
If you're familiar with HTML think of how a table is defined in HTML. The way Word Open XML defines a table works on the same principle. The concept looks something like this:
<table><row><cell><cell></row><row><cell><cell><row></table>
The rows and their content are contiguous.
At first, I want to talk a little about normal binding (with standard property such as Text of a textbox). The sample grid here has only 2 rows (for simplicity). Suppose I have a table of 2 rows (myDataTable) with 2 columns (ID and Name), a DataGridView (myGrid) and a TextBox (myTextBox). Here is the code for binding data:
myGrid.DataSource = myDataTable;
myTextBox.DataBindings.Add("Text", myDataTable, "Name");
After binding data, when the selection changes in the grid, the info is updated automatically to the control TextBox, for example, the 2 rows are:
ID | Name
1 .NET
2 Java
At first, the selection in grid is at index 0, the Text of myTextBox is ".NET", moving the selection to next position (at index 1), the Text of myTextBox is "Java", moving again and again, forward and backward, it works OK as I expect. But now I have a control with a custom property called List, this is type of List and is readonly. I want to bind it to a column of table (for example, Name), I do the same binding rule, however add a little custom Parse to format the correct string before updating to the datasource (myDataTable) because my custom property is type of List while my Name column is type of string, here is the binding code:
Binding bind = new Binding("List", myDataTable, "Name"){
ControlUpdateMode = ControlUpdateMode.Never //Because my List property is readonly
};
//formating string data before updating to the datasource
bind.Parse += (s,e) => {
List<string> data = (List<string>) e.Value;
if(data.Count == 0) e.Value = DBNull.Value;
else e.Value = string.Join(",",data.ToArray());//format as comma separated string
};
myCustomControl.DataBindings.Add(bind);
In this case, suppose myDataTable currently has no data at column Name like this:
ID | Name
1 <DBNull.Value> <--- current index
2 <DBNull.Value>
After running the demo, the current selection index in the grid is 0, I try changing the value of myCustomControl property List (the Items, not the reference), for example, update it like this:
myCustomControl.List.Add(".NET");
myCustomControl.List.Add("Java");
Then, moving the selection in grid to the next position (index 1), the value ".NET,Java" is updated to the datasource in row 0 at column Name, like this:
ID | Name
1 .NET,Java
2 <----- current index
Now if I move the selection back to index 0, the value at column Name in row 1 is also updated to ".NET,Java" like this:
ID | Name
1 .NET,Java <----- current index
2 .NET,Java
Which is not what I want. I mean the value should be updated via control myCustomControl. Here is what I want:
ID | Name
1 .NET,Java <----- current index
2
I can understand that, at the time moving back from index 1 to index 0, the value of List property is still a List with 2 items (".NET" and "Java") and so after the moving, this is updated to the cell at column Name in row 1. I'm finding how to reset that value of List property after it's updated to the cell at column Name in row 0 so that when the selection is at index 1, it's already empty. I'v tried changing the Parse event handler to the following but no good shake:
bind.Parse += (s,e) => {
List<string> data = (List<string>) e.Value;
if(data.Count == 0) e.Value = DBNull.Value;
else e.Value = string.Join(",",data.ToArray());//format as comma separated string
//I think at here, the value has been already updated to the datasource
//and I can perform the reset
myCustomControl.List.Clear();
};
But it seems to Clear before the value is updated to the datasource and so there is no value udpated to the datasource (instead of ".NET,Java", it's a DBNull.Value).
Then I have also tried this:
bind.BindingComplete += (s,e) => {
if(e.BindingCompleteContext == BindingCompleteContext.DataSourceUpdate)
myCustomControl.List.Clear();
};
I thought, it should check if the data is updated to the datasource, the List can be clear. I have also tried some flag to mark as true before clearing and reset it to false after clearing, use this flag to control the flowing in bind.Parse but it did nothing.
Do you have any idea to solve this problem? Your help would be highly appreciated! Thank you.
I've found the solution myself. In fact I can't reset the List anyway, this will update the underlying datasource when switching between rows in the grid. The key idea here is initially set DataSourceUpdateMode to DataSourceUpdateMode.Never, then whenever the List is about to change, turn the DataSourceUpdateMode to DataSourceUpdateMode.OnPropertyChanged. In the Parse event handler, after the parsing is done, reset DataSourceUpdateMode to DataSourceUpdateMode.Never. And that works greatly. The underlying datasource is updated only when user changing the control's value (List) by typing or selecting ,...
Here is all the code:
Binding bind = new Binding("List", myDataTable, "Name"){
ControlUpdateMode = ControlUpdateMode.Never, //Because my List property is readonly
DataSourceUpdateMode = DataSourceUpdateMode.Never//This will be turned on when preparing to change the List's value
};
//formating string data before updating to the datasource
bind.Parse += (s,e) => {
List<string> data = (List<string>) e.Value;
if(data.Count == 0) e.Value = DBNull.Value;
else e.Value = string.Join(",",data.ToArray());//format as comma separated string
//At here reset the DataSourceUpdateMode to Never
//We can also do this in BindingComplete event handler with BindingCompleteContext = BindingCompleteContext.DataSourceUpdate
myCustomControl.DataBindings[0].DataSourceUpdateMode = DataSourceUpdateMode.Never;
};
myCustomControl.DataBindings.Add(bind);
myCustomControl has a method to update/populate the new items for the List property called UpdateList(), we have to set DataSourceUpdateMode to OnPropetyChanged at the very beginning of the method like this:
public void UpdateList(){
if(DataBindings.Count > 0) DataBindings[0].DataSourceUpdateMode = DataSourceUpdateMode.OnPropertyChanged;
//The remaining code for populating/updating new items goes below
....
}
And that's all, very clean. Hope this will help someone who will encounter the same situation with me.Thank you!
This question already has an answer here:
Closed 10 years ago.
Possible Duplicate:
Getting value on datagrid by columns
How to get the value in column id where index = 3 for example
This is WPF, datagrid
I assume that yr grid is bound to items/object so if you wanted to change values in that column or row you can simply use this in your grid click event, or what ever event you are using
MyObject rowObject = gridObjects.GetFocusedRow as MyObject;
if (rowObject != null)
{
}
It's kind of hard to understand your question, if you could add some more information I'm sure you will get a good answer.
What have you tried? And can you add some more code (and context) to the question?
Have you looked at MSDN? Here is one way to get cell value:
http://msdn.microsoft.com/en-us/library/z5c4a30a.aspx
private void GetCellValue(DataGrid myGrid){
DataGridCell myCell = new DataGridCell();
// Use and arbitrary cell.
myCell.RowNumber = 1;
myCell.ColumnNumber = 1;
Console.WriteLine(myGrid[myCell]);
}
Cheers
--Jocke
I've been writing an extension to grid view to add grouping rows to a gridview, by watching for when the desired item changes in the RowDataBoundEvent. So far it looks a little like this:
protected override void OnRowDataBound(GridViewRowEventArgs args)
{
if (args.Row.RowType == DataControlRowType.DataRow)
{
object dataitem = args.Row.DataItem;
string currentValue = dataitem.GetType().GetProperty(GroupingColumn).GetValue(dataitem, null).ToString();
if (string.IsNullOrEmpty(previousValue) || currentValue != previousValue)
{
int currentRowIndex = args.Row.RowIndex + rowsAdded;
int colspan = this.Columns.Count - 1;
Label label = new Label { Text = currentValue, CssClass = "rowLabel" };
TableCell cell = new TableCell { ColumnSpan = colspan, CssClass = "groupHeader" };
GridViewRow newRow = new GridViewRow(currentRowIndex, currentRowIndex, DataControlRowType.DataRow, DataControlRowState.Normal);
cell.Controls.Add(label);
newRow.Cells.Add(cell);
((Table)args.Row.Parent).Controls.AddAt(currentRowIndex, newRow);
previousValue = currentValue;
rowsAdded++;
}
}
base.OnRowDataBound(args);
}
The issue is that when I click a paging button, the paging event doesn't fire, and the extra rows added become empty rows. The page postbacks, and runs through page load etc, but never through the paging event. I have custom paging built into this extension as well, and the click event for my paging buttons doesn't fire either. It's not until the second click on the paging button that paging occurs.
I think this is happening because the paging row gets constructed before the rowdatabound event, during which paging buttons are created and their event handler's assigned. However, adding rows to the underlying table removes these handlers. Anyone have any idea how to ensure the custom paging buttons fire?
Edit 1: To clarify, adding to the data source then rebinding won't work, as there is no extra data to add. The effect I'm attempting to achieve is something like this:
|Group 1 |
|Item 1 |Item 2 |Item 3 |
|Item 4 |Item 5 |Item 6 |
|Group 2 |
|Item 7 |Item 8 |Item 9 |
Found a solution on an answer to a similar question. The trick is to add rows in the PrepareControlHierarchy event. Also, make sure your row type is "Header", otherwise there can be some nasty side effects with custom paging.
See Add Gridview Row AFTER Header
My issue is that I want to display data in a hierarchal structure as so:
Democrat
County Clerk
Candidate 1
Candidate 2
Magistrate
Candidate 1
Candidate 2
Candidate 3
But I'm retrieving the dataset like this:
Party | Office | Candidate
--------------------------------------------
Democrat | County Clerk | Candidate 1
Democrat | County Clerk | Candidate 2
Democrat | Magistrate | Candidate 1
Democrat | Magistrate | Candidate 2
Democrat | Magistrate | Candidate 3
I planned on using nested repeaters, but I need a distinct value of Party, and then distinct values of office name within that party in order to do it.
Are there any .NET functions to easily do what I'm attempting to? Would there be a better way of displaying the information other than repeaters?
Thanks in advance!
If you can modify the stored procedure, I would recommend a result set in the form of an adjacency list:
ID Name ParentID
1 Democrat NULL
2 County Clerk 1
3 Magistrate 1
4 Candidate 1 2
5 Candidate 2 2
6 Candidate 1 3
7 Candidate 2 3
8 Candidate 3 3
It reduces the amount of data retrieved, and allows you to program recursively against the parent-child relationships. You simply start at the root. This approach avoids the hassle of using nested Repeaters by directly building the string literal in a StringBuilder.
StringBuilder sb = new StringBuilder();
DataTable dt = new DataTable();
someDataAdapter.Fill(dt);
// find the roots:
DataRow[] roots = dt.Select("parentId is null");
sb.Append("<ul>");
foreach (DataRow child in roots)
{
WriteNode(child, sb);
}
sb.Append("</ul>");
// recursively write out each node
public static void WriteNode(DataRow row, StringBuilder sb) {
sb.Append("<li>");
sb.Append(row["Name"]);
// find children rows...
DataRow[] children = row.Table.Select("parentId = " + row["id"].ToString());
if (children.Length > 0)
{
sb.Append("<ul>");
foreach (DataRow child in children)
{
WriteNode(child, sb);
}
sb.Append("</ul>");
}
sb.Append("</li>");
}
I am not sure if you would be interested to use a third party control, but I had EXACTLY SAME requirement of yours and needed to display the data hierarchically, showing repeating values as single one with collapsible nodes (like trees). However, Tree controls are not data bound and still needs a fair bit of coding. But this style of Tree building based on non-normalized data can be easily done with DataBinding. The FlexGrid I have used was DataBound to the DataTable and just needed a few lines of code to display EXACTLY what you require, albeit based on a Third Party Control.
I have used the ComponentOne FlexGrid control and used its 'SubTotal' feature to generate the hierarchical data. I am pasting the code below, just in case you are interested to use the ComponentOne FlexGrid. You can download a Demo copy and check.
// Showing master policy in GROUPS
// -----------------------------------------------------------------------------------
// set tree mode to show settings in GROUPS
flxAvailableSettings.Tree.Style = TreeStyleFlags.Simple;
// show outline tree on column 1.
flxAvailableSettings.Tree.Column = 0;
flxAvailableSettings.Cols[0].Visible = true;
flxAvailableSettings.Cols[0].Width = 15;
flxAvailableSettings.AllowMerging = AllowMergingEnum.Nodes;
// subtotal on column 1, outline level 0
flxAvailableSettings.Subtotal(AggregateEnum.None, 0, 0, 0, "{0}");
// use owner draw to suppress repeating group names in Non-Node rows
flxAvailableSettings.DrawMode = DrawModeEnum.OwnerDraw;
flxAvailableSettings.OwnerDrawCell += new C1.Win.C1FlexGrid.OwnerDrawCellEventHandler(flxAvailableSettings_OwnerDrawCell);
// done, autosize columns to finish
flxAvailableSettings.AutoSizeCols();
// -----------------------------------------------------------------------------------
private void flxAvailableSettings_OwnerDrawCell(object sender, C1.Win.C1FlexGrid.OwnerDrawCellEventArgs e)
{
if (!flxAvailableSettings.Rows[e.Row].IsNode && flxAvailableSettings.Cols[e.Col].Name == "PolicyGroup")
e.Text = "";
}
The flxAvailableSettings.Subtotal(AggregateEnum.None, 0, 0, 0, "{0}") line generates the Tree Groups, though I needed to group on a single column, you can use groups on multiple columns. Just refer their docs example on Subtotalling.
Hope this helps.
A TreeView control would give you the format you want. However populating it from the database takes some effort. There is an article here about populating asp.net TreeViews from a database:
http://www.codeproject.com/KB/tree/TreeViewWithDatabase.aspx