Here are my requirements:
I have a dropdown list and text box (appName and profile).
I want to take the values from the dropdown and text box and add them to a table (or a control like gridview that renders into a
table)
At some point I want to be able to loop through the table and submit the values to a db.
My problem:
The postback caused by the onClick even is casing the table to only show the last value entered, and doesn't retain any of the previous
values.
Notes:
I tried to work arond this using a datalist bound to a datagrid, but no luck.
Code:
protected void addAppButton_Click(object sender, EventArgs e)
{
DropDownList appList = (DropDownList)newEntryFV.FindControl("appList");
TextBox profileTextBox = (TextBox)newEntryFV.FindControl("profileTextBox");
addAppsToTable(appList.SelectedValue.ToString(), profileTextBox.Text.ToString());
}
private void addAppsToTable(string appName, string profileName)
{
Table appsTable = (Table)newEntryFV.FindControl("appTable");
TableRow newRow = new TableRow();
TableCell appNameCell = new TableCell();
TableCell profileCell = new TableCell();
appNameCell.Text = appName;
profileCell.Text = profileName;
newRow.Cells.Add(appNameCell);
newRow.Cells.Add(profileCell);
appsTable.Rows.Add(newRow);
}
Code that solved my problem:
[Serializable]
public class securityApps
{
public string secAppID { get; set; }
public string secAppName { get; set; }
public string secProfile { get; set; }
}
protected void Page_Load(object sender, EventArgs e)
{
BindApps();
}
protected void addAppButton_Click(object sender, EventArgs e)
{
DropDownList appList = (DropDownList)newEntryFV.FindControl("appList");
TextBox profileTextBox = (TextBox)newEntryFV.FindControl("profileTextBox");
addAppsToListVS(appList.SelectedValue.ToString(), appList.SelectedItem.Text.ToString(), profileTextBox.Text.ToString());
BindApps();
}
private void addAppsToListVS(string appID, string appName, string profile)
{
securityApps secApp = new securityApps();
secApp.secAppID = appID;
secApp.secAppName = appName;
secApp.secProfile = profile;
((List<securityApps>)ViewState["appsListVS"]).Add(secApp);
}
// Binds apps to Grid View
private void BindApps()
{
GridView appsListGV = (GridView)newEntryFV.FindControl("appsListGV");
if (ViewState["appsListVS"] != null)
{
appsListGV.DataSource = (List<securityApps>)ViewState["appsListVS"];
appsListGV.DataBind();
}
else
{
List<securityApps> appsListVS = new List<securityApps>();
ViewState["appsListVS"] = appsListVS;
}
}
How about storing a List of objects (they could even be simple key value pairs) in the ViewState. You can use that data as the DataSource for a GridView. I think that's the simplest way to go. If you need more details, let me know.
Edits-- Your solution above looks good-- I might just make it a little easier by setting up a property for your ViewState values..
List<securityApps> AppsListVS{
get
{
if(ViewState["AppListVS"] == null
this.AppListVS = new List(securityApps)();
return (List<securityApps>)ViewState["AppListVS"];
}
set
{
ViewState["AppListVS"] = value;
}
}
Related
We have a website using Webforms. We want to use a custom PageStatePersister which saves the viewstate into a redis cache (see code below). On page level we override the PageStatePersister property to use our PageStatePersister. All works fine so far, but when we have a control within another control, we encounter a few issues.
public class RedisPageStatePersister : PageStatePersister
{
const string ViewStateFieldName = "__VIEWSTATEKEY";
const string ViewStateKeyPrefix = "ViewState_";
public RedisPageStatePersister(Page page) : base(page)
{
}
public override void Load()
{
var key = Page.UniqueID;
// The cache key for this viewstate is stored in a hidden field, so grab it
string viewStateKey = Page.Request.Form[ViewStateFieldName] as string;
// Grab the viewstate data using the key to look it up
if (viewStateKey != null)
{
var database = RedisConnectorHelper.Connection.GetDatabase(1);
var viewstate = database.StringGet(viewStateKey);
if (!viewstate.IsNull)
{
Pair p = (Pair)StateFormatter.Deserialize(database.StringGet(viewStateKey));
ViewState = p.First;
ControlState = p.Second;
}
}
}
public override void Save()
{
string viewStateKey = Page.Request.Form[ViewStateFieldName] as string;
if (viewStateKey == null)
{
viewStateKey = ViewStateKeyPrefix + Guid.NewGuid().ToString();
}
// Put viewstate data on writer
var viewStateSerialized = StateFormatter.Serialize(new Pair(base.ViewState, base.ControlState));
// Store the viewstate's key in a hidden field, so on postback we can grab it from the cache
Page.ClientScript.RegisterHiddenField(ViewStateFieldName, viewStateKey);
// Get the Redis database
var database = RedisConnectorHelper.Connection.GetDatabase(1);
// Save viewstate to the database
database.StringSet(viewStateKey, viewStateSerialized, TimeSpan.FromMinutes(15));
}
}
For example:
When using ListViews. We have a ListView with DataKeys within another ListView. On postback, the datakeys are not restored for the nested ListView
<asp:ListView runat="server" ID="lvOuter" DataKeyNames="These,Keys,Work">
<ItemTemplate>
<asp:ListView runat="server" ID="lvInner" DataKeyNames="These,Keys,Dont,Work">
<ItemTemplate></ItemTemplate>
</asp:ListView>
</ItemTemplate>
</asp:ListView>
We use dynamic creates TabPanel with GridView within. The GridView within loose their datakeys values on postback
The problem only seems to encounter for DataKeys, the use of HiddenField for example is no problem.
I also tried to use the SessionPageStatePersister, since this is a built-in persister, but here too the problem occurs
Below a code snippet where dummy data is bound to a listview and its inner listview. The error occurs when clicking on the button and trying to retrieve the datakey values of the inner listview. The property "DataKeys" is an empty list, so a IndexOutOfRangeException is thrown.
public partial class InnerListViewTest : Page
{
public class Outer
{
public List<Inner> Inner { get; set; }
public string Key1 { get; set; }
public string Key2 { get; set; }
}
public class Inner
{
public string InnerKey1 { get; set; }
public string InnerKey2 { get; set; }
}
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
var data = new List<Outer>();
for (int i = 0; i < 5; i++)
{
data.Add(new Outer()
{
Inner = new List<Inner>()
{
new Inner() {
InnerKey1 = "InnerData1_" + i,
InnerKey2 = "InnerData2_" + i
},
new Inner() {
InnerKey1 = "InnerData3_" + i,
InnerKey2 = "InnerData4_" + i
},
new Inner() {
InnerKey1 = "InnerData5_" + i,
InnerKey2 = "InnerData6_" + i
}
},
Key1 = "Data1_" + i,
Key2 = "Data2_" + i
});
}
lvOuter.DataSource = data;
lvOuter.DataBind();
}
}
protected void lvOuter_ItemDataBound(object sender, ListViewItemEventArgs e)
{
var outerData = e.Item.DataItem as Outer;
var lvInner = e.Item.FindControl("lvInner") as ListView;
lvInner.DataSource = outerData.Inner;
lvInner.DataBind();
}
protected void Unnamed_Click(object sender, EventArgs e)
{
foreach (ListViewItem outerItem in lvOuter.Items)
{
var lvInner = outerItem.FindControl("lvInner") as ListView;
foreach (ListViewItem innerItem in lvInner.Items)
{
var innerKey1 = lvInner.DataKeys[innerItem.DataItemIndex]["InnerKey1"];
var innerKey2 = lvInner.DataKeys[innerItem.DataItemIndex]["InnerKey2"];
}
}
}
}
The problem is probably with the pagepersister, but i cant seem to find out why!
I already found my own answer! The PageStatePersister had to be enabled using a PageAdapter instead of overriding the property of the Page!
In my project I wanted to read data from a file and update the DataGridView to a simple List. I wanted this so that the list can be updated at run time and then upon save wanted the final content of the list to be updated to a file.
In most of the solutions seen on google search I came up with examples of how to use a DatagridView with Database connection used to update the DatagridView. for Insert, Update and Delete operations. Lot of suggestions for my answer included adding INotifyProperty and IBindingList based impelementations which are probably an overkill.
I only aim to post my solution which involves using Datagridview to update a list. The code snippet used here is part of a huge project where updating the data from Datagridview to the List and back was a very big challenge due to initial impelementation with Database, whose dependency needed to be removed.
At the point of posting this question, I have a solution to my problem. To arrive at this problem I have taken bits and pieces of suggestions from various previous questions. I am not looking for suggestions in comments. If anyone wants to show me better way to solve this problem, please post an answer with working code.
So after reading, you have a sequence of MyData objects. You want to display all MyData objects (or a subsection) in a DataGridView.
Operators may change the displayed values, add some new rows, or delete rows. Some columns may be readonly and can't be changed
After pressing the OK-button, you want to read all MyData objects from the DataGridView and Save them in a file.
Most of your work can be done using the forms designer.
Open your form class in the designer
Drag a DataGridView on this form
Drag a BindingSource on this form
Right click BindingSource and select properties
Click in the properties window in DataSource on the arrow on the right
If MyData is not visible there, select Add Project Data Source
In the new window select object
Select the added data source as DataSource of your BindingSource
In the properties of DataGridView, assign your bindingSource to the DataSource
And suddenly: your DataGridView has the columns of the public properties of MyData. And magically, the columns have the correct type. They are able to display the values. Readonly properties have readonly Columns, read-write properties are editable.
To display your data:
void FillDataGridView(IEnumerable<MyData> dataToDisplay)
{
this.bindingSource1.DataSource = new BindingList<MyData>(dataToDisplay.ToList();
}
To read all data after editing
IEnumerable<MyData> ReadDataGridView()
{
return this.bindingSource1.List.Cast<MyData>();
}
This enables the operator to add and delete rows and to edit the values.
If you don't want the operator to do this, adjust the DataGridView properties
If the displayed values of the columns are not to your liking, edit the column properties (different header text, different display format, different backgroundcolor etc)
Here is an example where I have used the DatagridView to perform Insert Update and Delete on a List (List of Class objects PersonState).
The DatagridView’s Datasource needs to contain a DataTable, to bridge this gap, I have used a function named ConvertToDatatable().
As my starting point I began with a project suggested on another link by Anup Kumar Sharma.
Using the following code:
using System;
using System.Collections.Generic;
using System.Data;
using System.Windows.Forms;
namespace InsertUpdateDelete {
public partial class Form1 : Form {
public class PersonState {
public string Name { get; set; }
public string State { get; set; }
}
public List<PersonState> listOfPersonState;
public Form1() {
InitializeComponent();
listOfPersonState = new List<PersonState>();
}
//Display Data in DataGridView
private void DisplayData() {
DataTable dt = new DataTable();
dt = ConvertToDatatable();
dataGridView1.DataSource = dt;
}
//Clear Data
private void ClearData() {
txt_Name.Text = "";
txt_State.Text = "";
}
public DataTable ConvertToDatatable() {
DataTable dt = new DataTable();
dt.Columns.Add("Name");
dt.Columns.Add("State");
foreach (var item in listOfPersonState) {
var row = dt.NewRow();
row["Name"] = item.Name;
row["State"] = item.State;
dt.Rows.Add(row);
}
return dt;
}
private void AddToList(string text1, string text2) {
listOfPersonState.Add(new PersonState { Name = text1, State = text2 });
}
private void UpdateToList(string text1, string text2) {
int index = dataGridView1.SelectedRows[0].Index;
listOfPersonState[index] = new PersonState { Name = text1, State = text2 };
}
private void DeleteToList() {
int index = dataGridView1.SelectedRows[0].Index;
listOfPersonState.RemoveAt(index);
}
private void btn_Insert_Click(object sender, EventArgs e) {
if (txt_Name.Text != "" && txt_State.Text != "") {
AddToList(txt_Name.Text, txt_State.Text);
//MessageBox.Show("Record Inserted Successfully");
DisplayData();
ClearData();
} else {
MessageBox.Show("Please Provide Details!");
}
}
private void btn_Update_Click(object sender, EventArgs e) {
if (txt_Name.Text != "" && txt_State.Text != "") {
if (dataGridView1.SelectedRows != null && dataGridView1.SelectedRows.Count > 0) {
UpdateToList(txt_Name.Text, txt_State.Text);
//MessageBox.Show("Record Updated Successfully");
DisplayData();
ClearData();
}
} else {
MessageBox.Show("Please Select Record to Update");
}
}
private void btn_Delete_Click(object sender, EventArgs e) {
if (dataGridView1.SelectedRows != null && dataGridView1.SelectedRows.Count > 0) {
DeleteToList();
//MessageBox.Show("Record Deleted Successfully!");
DisplayData();
ClearData();
} else {
MessageBox.Show("Please Select Record to Delete");
}
}
private void dataGridView1_RowHeaderMouseClick(object sender, DataGridViewCellMouseEventArgs e) {
FillInputControls(e.RowIndex);
}
private void dataGridView1_CellClick(object sender, DataGridViewCellEventArgs e) {
FillInputControls(e.RowIndex);
}
private void FillInputControls(int Index) {
if (Index > -1) {
txt_Name.Text = dataGridView1.Rows[Index].Cells[0].Value.ToString();
txt_State.Text = dataGridView1.Rows[Index].Cells[1].Value.ToString();
}
}
}
}
Learnings:
Observe that I have used propertis in the class.
Instead of using:
public class PersonState {
public string Name;
public string State;
}
I have used:
public class PersonState {
public string Name { get; set; }
public string State { get; set; }
}
For some reason, the former does not work when trying to assing values.
I have made the list of objects a class variable so that all
functions can access that list directly instead of it being passed
around as a parameter.
public List<PersonState> listOfPersonState;
I replaced the logic to Insert, Update and Delete to a DB, to Insert
Update and Delete to a List.
private void AddToList(string text1, string text2) {
listOfPersonState.Add(new PersonState { Name = text1, State = text2 });
}
private void UpdateToList(string text1, string text2) {
int index = dataGridView1.SelectedRows[0].Index;
listOfPersonState[index] = new PersonState { Name = text1, State = text2 };
}
private void DeleteToList() {
int index = dataGridView1.SelectedRows[0].Index;
listOfPersonState.RemoveAt(index);
}
Note: I am assigning the Grid directly from the List, so my Grid and my List always have the same index because I am using the Display function to ensure this on Insert, update and delete Button operations.
private void DisplayData() {
DataTable dt = new DataTable();
dt = ConvertToDatatable();
dataGridView1.DataSource = dt;
}
public DataTable ConvertToDatatable() {
DataTable dt = new DataTable();
dt.Columns.Add("Name");
dt.Columns.Add("State");
foreach (var item in listOfPersonState) {
var row = dt.NewRow();
row["Name"] = item.Name;
row["State"] = item.State;
dt.Rows.Add(row);
}
return dt;
}
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; }
}
How to setup a multi value dropdownlist from a list collection...
Datasource: Listcollection which contains ColorCode and Description...
how to I setup a dropdown with Colorcode-Description like ORG-orange...
then how to capture these selected value as colorcode only by removing description for update purpose...
Now I am doing like this...
ddl.datesource=list<datasetvalues> // ...contains (colorcode, description)
ddl.DataTextField = "ColorCode";
ddl.DataValueField = "ColorCode";
ddl.databind();
then selected value should be like this...
ddlcolor.Items.FindByValue((DataBinder.Eval(formView1.DataItem,
"colorCode").ToString())).Selected = true;
for update:
ClassA.Color= ddl.selectedvalue();
Now what I need change to in the above code to get the combination of both..otherwise i need have textbox for description which syncs with ddl..which is bit complex for my level of programming...thanks..
There are a couple of solutions as per my knowlege.
1) You can concatenate the text like : Code + "-" + Value, while preparing the list using a For/Foreach... loop
2) If it is permitted as per your project, you may also consider overriding the string inside the entity but the selected value will be with a hyphenated(with a - inbetween code & value) string, which you need to string split in the code behind.
public partial class _Default : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
if (!Page.IsPostBack)
{
List<CodeValue> colors = new List<CodeValue> {new CodeValue{Code="",Value="Select"},new CodeValue{Code="RD",Value="Red"},
new CodeValue{Code="BL",Value="Blue"}};
ddlColors.DataSource = colors;
ddlColors.DataBind();
}
}
protected void btnClick_Click(object sender, EventArgs e)
{
var item = ddlColors.SelectedValue;
var code = item.Split('-');
}
}
class CodeValue
{
public string Code { get; set; }
public string Value { get; set; }
public override string ToString()
{
return this.Code + "-" + this.Value;
}
}
Basically I'm using the ItemSource property of the datagrid to bind a generic list to my datagrid. However I'd really like to change the headings, I tried the following but I get a runtime exception:
dgtest.Columns[1].Header = "edited";
I used the AutoGeneratingColumn event and an Attribute to set my column names.
First create an attribute class...
public class ColumnNameAttribute : System.Attribute
{
public ColumnNameAttribute(string Name) { this.Name = Name; }
public string Name { get; set; }
}
Then I decorate my data class members with the new attribute...
public class Test
{
[ColumnName("User Name")]
public string Name { get; set; }
[ColumnName("User Id")]
public string UserID { get; set; }
}
Then I write my AutoGeneratingColumn event handler...
void dgPrimaryGrid_AutoGeneratingColumn(object sender, DataGridAutoGeneratingColumnEventArgs e)
{
var desc = e.PropertyDescriptor as PropertyDescriptor;
var att = desc.Attributes[typeof(ColumnNameAttribute)] as ColumnNameAttribute;
if(att != null)
{
e.Column.Header = att.Name;
}
}
... and attach it to my grid and test...
dgPrimaryGrid.AutoGeneratingColumn += dgPrimaryGrid_AutoGeneratingColumn;
var data = new object[]
{
new Test() { Name = "Joe", UserID = "1" }
};
dgPrimaryGrid.ItemsSource = data;
Here is what it looks like. Notice that the column names are not the property names (the default behavior).
This approach is a little more work, but it's nice to have the column heading defined at the same place as the bound column. You can reorder your columns without having to go to other places to fix c the column names.
You can change it on the ItemDataBound event:
public void yourDataGrid_OnItemDataBound(object s, DataGridItemEventArgs e)
{
if (e.Item.ItemType == ListItemType.Header)
{
// Change the cell index to the column index you want... I just used 0
e.Item.Cells[0].Text = "Text you want in header.";
}
}
If the grid is already bound you should be able to do:
yourDataGrid.Columns[0].Header = "Text you want in header.";
You are probably getting an error because you are trying to change the text before it is bound.
AutoGeneratedColumns event on wpf for change column name
datagrid1.AutoGeneratedColumns += datagrid1_AutoGeneratedColumns;
void datagrid1_AutoGeneratedColumns(object sender, EventArgs e)
{
datagrid1.Columns[0].Header = "New Column Name";
}
1) Switch off the automatic column generation and generate your data grid columns in the program code:
DataGridTextColumn TempColumn;
MyDataGrid.AutoGenerateColumns = false;
TempColumn = new DataGridTextColumn();
TempColumn.Header = "DisplayName0";
TempColumn.Binding = new Binding("BindingName0");
MyDataGrid.Columns.Add(TempColumn);
TempColumn = new DataGridTextColumn();
TempColumn.Header = "DisplayName1";
TempColumn.Binding = new Binding("BindingName1");
MyDataGrid.Columns.Add(TempColumn);
Then "BindigName0" is the internal binding name of column 0 and "DisplayName0" is the name that the user will see.
2) If you want to use the automatic column generation instead then the display names of the columns can be set in the "AutoGeneratingColumn" event:
MyDataGrid.AutoGeneratingColumn += MyDataGrid_AutoGeneratingColumn;
...
private void MyDataGrid_AutoGeneratingColumn(object sender,
DataGridAutoGeneratingColumnEventArgs e)
{
DataGridBoundColumn TempColumn;
string BindingName;
if (e.Column is DataGridBoundColumn)
{
TempColumn = e.Column as DataGridBoundColumn;
BindingName = (TempColumn.Binding as Binding).Path.Path;
if (BindingName == "BindingName0")
{
TempColumn.Header = "DisplayName0";
}
else if (BindingName == "BindingName1")
{
TempColumn.Header = "DisplayName1";
}
}
}