I want to display grid with multiple columns (like in RepositoryItemGridLookUpEdit) after click on cell in column, but if user don't want to pick item from grid, he can write something else (like in RepositoryItemComboBox). How to combine this two features?
//user can write, but only one column
RepositoryItemComboBox cbeMaterialy = new RepositoryItemComboBox();
DataTable dt = Getdt();
cbeMaterialy.Items.Clear();
foreach(DataRow item in dt.Rows)
{
cbeMaterialy.Items.Add(item);
}
gvView.Columns["ColumnName"].ColumnEdit = cbeMaterialy;
//user cannot write but multiple columns
RepositoryItemGridLookUpEdit editor = new RepositoryItemGridLookUpEdit();
editor.DataSource = dt;
column.ColumnEdit = editor;
//SOLUTION!
I mixed some answer from Devexpress team and came up with this:
public class Main()
{
//user can choose element from DB or write new value
RepositoryItemGridLookUpEdit riglue = new RepositoryItemGridLookUpEdit();
MyGridLookupDataSourceHelper.SetupGridLookUpEdit(riglue, GetMaterialyDataView(), "Kod", "Kod");
elementsEditGrid.gvView.Columns[ColumnName].ColumnEdit = riglue;
}
///////////////////
public class MyGridLookupDataSourceHelper
{
RepositoryItemGridLookUpEdit edit;
public MyGridLookupDataSourceHelper(RepositoryItemGridLookUpEdit edit, ITypedList dataSource, string displayMember, string valueMember)
{
this.edit = edit;
//enable writing into RepositoryItemGridLookUpEdit
edit.TextEditStyle = DevExpress.XtraEditors.Controls.TextEditStyles.Standard;
edit.DataSource = dataSource;
edit.DisplayMember = displayMember;
edit.ValueMember = valueMember;
edit.ProcessNewValue += edit_ProcessNewValue;
AddExistingValuesToDataSource();
}
public static void SetupGridLookUpEdit(RepositoryItemGridLookUpEdit edit, ITypedList dataSource, string displayMember, string valueMember)
{
new MyGridLookupDataSourceHelper(edit, dataSource, displayMember, valueMember);
}
//Add new values to temporary data source (not to DataBase!)
void edit_ProcessNewValue(object sender, DevExpress.XtraEditors.Controls.ProcessNewValueEventArgs e)
{
GridLookUpEdit lookUp = sender as GridLookUpEdit;
RepositoryItemGridLookUpEdit ri = lookUp.Properties;
DataTable dt = (ri.DataSource as DataView).Table;
DataRow row = dt.NewRow();
row[ri.DisplayMember] = e.DisplayValue;
row[ri.ValueMember] = e.DisplayValue;
dt.Rows.Add(row);
ri.View.RefreshData();
e.Handled = true;
//if user wants to add new values to database (data source)
//INSERT INTO DataSourceTable (ColumnName) VALUES (e.DisplayValue)
}
//Adds to temporary data source values already stored in RepositoryItemGridLookUpEdit (in case values aren't from DB)
void ProcessExistingValues(RepositoryItemGridLookUpEdit sender, object value)
{
RepositoryItemGridLookUpEdit ri = sender;
DataTable dt = (ri.DataSource as DataView).Table;
DataRow row = dt.NewRow();
row[ri.DisplayMember] = value;
row[ri.ValueMember] = value;
dt.Rows.Add(row);
ri.View.RefreshData();
}
private void AddExistingValuesToDataSource()
{
//SELECT from DB values that are already in riglue and add them to TEMPORARY DATA SOURCE (in case values aren't from DB)
//DataView dataView = SELECT ColumnName FROM table WHERE rowID = XXX
foreach (DataRow row in dataView.AsEnumerable())
{
string kod = (string)row["ColumnName"];
ProcessExistingValues(this.edit, kod);
}
}
}
You can set the RepositoryItemGridLookUpEdit.TextEditStyle property to Standard and you will be able to type. When a user types something different from what is contained in the lookup data source, the RepositoryItemGridLookUpEdit.ProcessNewValue event will be raised. In the event handler, you can add a new value to the data source to use it further.
RepositoryItemComboBox doesn't has DataSourse property, which allow to bind table with multiple column.
ComboBoxEdit Has DataSourse property, So you can bnd table with multiple column, OR create multiple column.
Related
I have a DataGridView in a C# WinForms project in which, when the user clicks on certain DGV cells, the cell changes to a DataGridViewComboBoxCell and the ComboBox is populated with some values for the user to select. Here's the form code for the DataGridView_Click event:
private void dgvCategories_Click(Object sender, DataGridViewCellEventArgs e)
{
if (e.ColumnIndex == 5 && !(dgvCategories.Rows[e.RowIndex].Cells[e.ColumnIndex].GetType().Name == "DataGridViewComboBoxCell"))
{
// Bind combobox to dgv and than bind new values datasource to combobox
DataGridViewComboBoxCell cboNewValueList = new DataGridViewComboBoxCell();
// Get fields to build New Value query
List<string> lsNewValuesResult = new List<string>();
string strCategory = dtCategories.Rows[e.RowIndex][1].ToString();
string strCompanyName = cboSelectCompany.Text;
string strQueryGetNewValuesValidationInfo = "SELECT validationdb, validationtable, validationfield, validationfield2, validationvalue2" +
" FROM masterfiles.categories" +
" WHERE category = #category";
//" WHERE category = '" + strCategory + "'";
// Pass validation info query to db and return list of New Values
db getListOfNewValues = new db();
lsNewValuesResult = getListOfNewValues.GetNewValuesList(strQueryGetNewValuesValidationInfo, strCategory, strCompanyName);
//Populate the combobox with the list of New Values
foreach (string strListItem in lsNewValuesResult)
{
cboNewValueList.Items.Add(strListItem);
}
//
dgvCategories[e.ColumnIndex, e.RowIndex] = cboNewValueList;
}
}
Here's the code in the db class that populates the ComboBox (this likely isn't necessary to include for the purposes of this question, but for the sake of completeness, I'm including it, in case is it relevant):
public List<string> GetNewValuesList(string strValidationInfoQuery, string strCategory, string strCompanyName)
{
List<string> lsValidationInfo = new List<string>();
List<string> lsNewValuesList = new List<string>();
using (NpgsqlConnection conn = new NpgsqlConnection(connString))
using (NpgsqlCommand cmd = new NpgsqlCommand(strValidationInfoQuery, conn))
{
cmd.Parameters.AddWithValue("category", strCategory);
conn.Open();
using (NpgsqlDataReader reader = cmd.ExecuteReader())
{
while (reader.Read())
{
int intReaderIndex;
for (intReaderIndex = 0; intReaderIndex <= reader.FieldCount - 1; intReaderIndex++)
{
// reader indexes 3 & 4 correspond to categories.validationfield2 and validationvalue2, which can be null
if (string.IsNullOrEmpty(reader[intReaderIndex].ToString()))
{
lsValidationInfo.Add("");
}
else
{
lsValidationInfo.Add(reader.GetString(intReaderIndex));
}
//Console.WriteLine("reader index " + intReaderIndex + ": " + reader.GetString(intReaderIndex));
}
}
}
}
string strValidationDb = lsValidationInfo[0];
string strValidationTable = lsValidationInfo[1];
string strValidationField = lsValidationInfo[2];
string strValidationField2 = lsValidationInfo[3];
string strValidationValue2 = lsValidationInfo[4];
string strQueryGetNewValues = "SELECT DISTINCT " + strValidationField +
" FROM " + strValidationDb + "." + strValidationTable +
" WHERE company_id = (SELECT id FROM company WHERE name = '" + strCompanyName + "')";
if (!string.IsNullOrEmpty(strValidationField2) && !string.IsNullOrEmpty(strValidationValue2)) strQueryGetNewValues += " AND " + strValidationField2 + " = '" + strValidationValue2 + "'";
strQueryGetNewValues += " ORDER BY " + strValidationField;
using (NpgsqlConnection conn = new NpgsqlConnection(connString))
using (NpgsqlCommand cmd = new NpgsqlCommand(strQueryGetNewValues, conn))
{
conn.Open();
using (NpgsqlDataReader reader = cmd.ExecuteReader())
{
while (reader.Read())
{
int intReaderIndex;
for (intReaderIndex = 0; intReaderIndex <= reader.FieldCount - 1; intReaderIndex++)
{
// reader indexes 3 & 4 correspond to categories.validationfield2 and validationvalue2, which can be null
if (string.IsNullOrEmpty(reader[intReaderIndex].ToString()))
{
lsNewValuesList.Add("");
}
else
{
lsNewValuesList.Add(reader.GetString(intReaderIndex));
}
Console.WriteLine("reader index " + intReaderIndex + ": " + reader.GetString(intReaderIndex));
}
}
}
}
return lsNewValuesList;
}
The combobox is getting populated, as I can access the items in lsNewValuesResult in the _Click method. The DGV Edit Mode is set to EditOnEnter. I tried EditOnKeystroke, but that didn't cause the combobox to expand on mouse click.
This is what the combobox looks like when the cell is clicked on and the CBO is populated and added to the DGV cell:
That's after I clicked each of the two cells.
[RESOLVED]
See my Answer below.
Unfortunately solving this revealed a new issue.
I'm about to publicly admit that I'm stupid:
For design and functionality reasons that are required for this project, I am manually setting the widths and names of the DGV's columns, and I also need the 2nd through 4th columns ReadOnly = true. Well, I inadvertently set the 5th column - the column that this question is about to ReadOnly = true as well.
Thank you all for your attempts at answering. This just serves to remind us how something so simple can cause a seemingly big issue and is so easy to overlook!
If I recognize your problem correctly, in my test app i add a DataGridView whit 6 column, EditMode = EditOnEnter
(Others need three time click to open dropdown, As far as I tried) and handle CellStateChanged envent.
private void dgvCategories_CellStateChanged(object sender, DataGridViewCellStateChangedEventArgs e)
{
if (e.StateChanged == DataGridViewElementStates.Selected)
{
DataGridViewCell cell = e.Cell;
int columnIndex = cell.ColumnIndex;
int rowIndex = cell.RowIndex;
//---IF CONDITIONS--
//columnIndex == 5
// Only cells in Columns[5]
//cell.Selected
// Because this event raised two time, first for last selected cell and once again
// for currently selected cell and we need only currently selected cell.
//cell.EditType.Name != "DataGridViewComboBoxEditingControl"
// If this cell "CellStateChanged" raised for second time, only other cell types allowed
// to edit, otherwise the current cell lost last selected item.
if (columnIndex == 5 && cell.Selected && cell.EditType.Name != "DataGridViewComboBoxEditingControl")
{
DataGridViewComboBoxCell cboNewValueList = new DataGridViewComboBoxCell();
//Add items to DataGridViewComboBoxCell for test, replace it with yours.
for (int i = 0; i < 10; i++)
cboNewValueList.Items.Add($"Item {i}");
dgvCategories[columnIndex, rowIndex] = cboNewValueList;
}
}
}
NOTE: user must click two time in a cell to open drop down menu.
Edit One: As Reza Aghaei suggest for single click in cell:
private void dgvCategories_CellClick(object sender, DataGridViewCellEventArgs e)
{
DataGridViewComboBoxEditingControl editingControl = dgvCategories.EditingControl as DataGridViewComboBoxEditingControl;
if (editingControl != null)
editingControl.DroppedDown = true;
}
You might need to turn AutoGenerateColumns off:
Also, it seems to take a three clicks for the dropdown to pop.
public Form1()
{
InitializeComponent();
dataGridView1.AutoGenerateColumns = false;
dataGridView1.DataSource = GetDataSource();
DataGridViewComboBoxColumn dgvcbc = new DataGridViewComboBoxColumn();
dgvcbc.Items.Add("R0C0");
dgvcbc.Items.Add("R1C0");
dgvcbc.Items.Add("R2C0");
dgvcbc.Items.Add("R3C0");
dgvcbc.DataPropertyName = "Col0";
dataGridView1.Columns.Add(dgvcbc);
}
DataTable GetDataSource()
{
var dtb = new DataTable();
dtb.Columns.Add("Col0", typeof(string));
dtb.Columns.Add("Col1", typeof(string));
dtb.Columns.Add("Col2", typeof(string));
dtb.Columns.Add("Col3", typeof(string));
dtb.Columns.Add("Col4", typeof(string));
dtb.Rows.Add("R0C0", "R0C1", "R0C2", "R0C3", "R0C4");
dtb.Rows.Add("R1C0", "R1C1", "R1C2", "R1C3", "R1C4");
dtb.Rows.Add("R2C0", "R2C1", "R2C2", "R2C3", "R2C4");
dtb.Rows.Add("R3C0", "R3C1", "R3C2", "R3C3", "R3C4");
return dtb;
}
Are you maybe getting an error which is not being shown for some reason?
If I use your code, DataGridViewComboBoxCell seems to be populated with values, but I get DataGridViewComboBoxCell value is not valid runtime error.
This test code is working fine for me:
private void dgvCategories_CellContentClick(object sender, DataGridViewCellEventArgs e)
{
DataGridViewComboBoxCell cboNewValueList = new DataGridViewComboBoxCell();
List<string> lsNewValuesResult = new List<string>();
lsNewValuesResult.Add("Value1");
lsNewValuesResult.Add("Value2");
lsNewValuesResult.Add("Value3");
foreach (string strListItem in lsNewValuesResult)
{
cboNewValueList.Items.Add(strListItem);
}
dgvCategories[e.ColumnIndex, e.RowIndex] = cboNewValueList;
// Added setting of initial value
cboNewValueList.Value = cboNewValueList.Items[0];
}
So maybe try setting the initial value for your DataGridViewComboBoxCell after you add it to the DataGridView.
You can consider the following facts about DataGridView:
If you set AutoGenerateColumns to false, then you need to add columns to Columns collection manually.
If you set AutoGenerateColumns to true, when you assign the data to DataSource, the control generates columns automatically for the data source. In this case, the control looks in list of columns of the data source and for each column if there's no column in the Columns collection of the control having the same DataPropertyName as data source's column name, it will add a column to Columns collection.
DataPropertyName of the datagridviews' columns determines the bound column of the data source.
You usually want to add DataGridViewXXXXColumn to columns collection rather than using a DataGridViewXXXXCell for a cell.
If you set EditMode to EditOnEnter, then if you click on dropdown button, one click is enough. If you click on cell content, two clicks is needed.
If you would like to make it single click even if you click on cell content, take a look at this post. (Note: I haven't used this is the example, it's a bit annoying.)
you can set DisplayStyle to Nothing, then it shows the column as a combo box, just in edit mode.
A basic example on using DataGridViewComboBoxColumn
I suppose you are going to show a list of Products having (Id, Name, Price, CategoryId) in a DataGridView and the CategoryId should come from a list of Categories having (Id, Name) and you are going to show CategoryId as a ComboBox.
In fact it's a basic and classic example of DataGridViewComboBoxColumn:
private void Form1_Load(object sender, EventArgs e) {
var categories = GetCategories();
var products = GetProducts();
var idColumn = new DataGridViewTextBoxColumn() {
Name = "Id", HeaderText = "Id", DataPropertyName = "Id"
};
var nameColumn = new DataGridViewTextBoxColumn() {
Name = "Name", HeaderText = "Name", DataPropertyName = "Name"
};
var priceColumn = new DataGridViewTextBoxColumn() {
Name = "Price", HeaderText = "Price", DataPropertyName = "Price"
};
var categoryIdColumn = new DataGridViewComboBoxColumn() {
Name = "CategoryId", HeaderText = "Category Id", DataPropertyName = "CategoryId",
DataSource = categories, DisplayMember = "Name", ValueMember = "Id",
DisplayStyle = DataGridViewComboBoxDisplayStyle.Nothing
};
dataGridView1.Columns.AddRange(idColumn, nameColumn, priceColumn, categoryIdColumn);
dataGridView1.EditMode = DataGridViewEditMode.EditOnEnter;
dataGridView1.AutoGenerateColumns = false;
dataGridView1.DataSource = products;
}
public DataTable GetProducts() {
var products = new DataTable();
products.Columns.Add("Id", typeof(int));
products.Columns.Add("Name", typeof(string));
products.Columns.Add("Price", typeof(int));
products.Columns.Add("CategoryId", typeof(int));
products.Rows.Add(1, "Product 1", 100, 1);
products.Rows.Add(2, "Product 2", 200, 2);
return products;
}
public DataTable GetCategories() {
var categories = new DataTable();
categories.Columns.Add("Id", typeof(int));
categories.Columns.Add("Name", typeof(string));
categories.Rows.Add(1, "Category 1");
categories.Rows.Add(2, "Category 2");
return categories;
}
Learn more
To learn more about DataGridView, take a look at DataGridView Control (Windows Forms). It contains links to some documentations and useful How To articles, including:
DataGridView Control Overview
Basic Column, Row, and Cell Features in the Windows Forms DataGridView Control
Basic Formatting and Styling in the Windows Forms DataGridView Control
Column Types in the Windows Forms DataGridView Control
I have a datagridview with a column of type string that display values for age ranges such as:
0-18
19-100
0-100
I also have a filter textbox that would need to filter on the age range
(dgv1.DataSource as DataTable).DefaultView.RowFilter =
string.Format("AgeRange LIKE '%{0}' OR AgeRange LIKE '{0}%'", textBoxFilter.Text);
The problem is that if the user enter a number like 18, the grid does not return row for 0-100
How can I get the datagrid to return both 0-18 and 0-100?
I do not think you will be able to do this using the “LIKE” comparator since the values you are looking for are “numeric”. To get the filter you are looking for, you will need a filter with “>=” and “<=” to see if the target age is in the range. It is unclear how the data is originally received, if the “age range” in each row is a string as shown, then I suggest a couple of different hacky ways. In addition, it is unclear what other columns would be in the grid.
One “hacky” approach, would be to make a method that returns a new DataTable with only the rows that fall into the given target range. To help in this endeavor, a second method that takes an int (target value we are looking for), and a DataRowView (The AgeRange we are comparing the “target” value to). This “AgeRange” will be in the rows first column. Here we simply take that string range (“0-18”) and the target value (“18”) to see if this target value IS in the range, then return true or false depending on the result. This can be done using the string.split method to split the “AgeRange” string and int.TryParse to convert the strings into numbers. Below is an example of this.
private bool TargetIsInRange(int target, DataRowView row) {
if (row.Row.ItemArray[0] != null) {
string cellValue = row.Row.ItemArray[0].ToString();
string[] splitArray = cellValue.Split('-');
int startValue;
int endValue;
if (!int.TryParse(splitArray[0], out startValue)) {
return false;
}
if (!int.TryParse(splitArray[1], out endValue)) {
return false;
}
if (target >= startValue && target <= endValue)
return true;
}
return false;
}
The method above should come in handy when looping through the grids rows to figure out which rows go into the new filter DataTable. Next, a method that does this looping through the grid and returns a filtered DataTable. For each row in the grid, we could call the above method and add the rows that return true.
private DataTable GetFilterTable() {
DataTable filterTable = ((DataTable)dgv1.DataSource).Clone();
dgv1.DataSource = gridTable;
int targetValue = -1;
if (int.TryParse(textBox1.Text, out targetValue)) {
foreach (DataGridViewRow row in dgv1.Rows) {
DataRowView dataRow = (DataRowView)row.DataBoundItem;
if (dataRow != null) {
if (TargetIsInRange(targetValue, dataRow)) {
filterTable.Rows.Add(dataRow.Row.ItemArray[0]);
}
}
}
}
return filterTable;
}
It is unclear where you are calling this filter, if you are filtering “strings” then as the user types the filter string in the text box the grid will filter with each character pressed by the user. This is nice with strings, however, in this case using “numbers”, I am guessing a button would be more appropriate. I guess this is something that you will have to decide. Putting all this together using a Button click event to signal when to filter the grid may look something like below
private DataTable gridTable;
public Form1() {
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e) {
gridTable = GetTable();
FillTable(gridTable);
dgv1.DataSource = gridTable;
textBox1.Text = "18";
}
private void FillTable(DataTable dt) {
dt.Rows.Add("0-18");
dt.Rows.Add("19-100");
dt.Rows.Add("0-100");
dt.Rows.Add("17-80");
dt.Rows.Add("18-80");
}
private DataTable GetTable() {
DataTable dt = new DataTable();
dt.Columns.Add("AgeRange", typeof(string));
return dt;
}
private void button1_Click(object sender, EventArgs e) {
if (textBox1.Text == "") {
dgv1.DataSource = gridTable;
return;
}
dgv1.DataSource = GetFilterTable();
}
Hacky Approach 2
The first approach works; however, I am guessing if there is a LOT of data and a LOT of filtering, this may become a performance issue. Therefore, in this approach, extra steps are taken in the beginning to take advantage of the DataTables RowFilter feature, as the posted code is doing. Obviously as stated previously, we will not use the “LIKE” comparator, instead the “<=” and “>=” operators are used.
In order to accomplish this, we MUST somehow turn the given string range “XX-XX” into two (2) ints. Then “add” these integers to the DataTable. Then it will be easy to filter the table using the RowFilter property and the less than and greater than operators. One problem is that it will require “extra” work on our part to set up the grid’s columns properly or these extra two columns of data will also display.
This can be done in the “designer” or manually in code. Without going into too much detail, it is useful to bear in mind that IF you assign a DataTable as a data source to the grid AND you set the grids AutoGenerateColumns property to false… THEN only the grid columns with DataPropertyName names that “match” one of the DataTable column names… will display. In this case, we only want the AgeRange column with the “XX-XX” strings to display, the other two new columns can remain hidden from the user. Setting up the grid column manually may look something like below, however you can do this in the designer. NOTE: the designer does not display an AutoGenerateColumns property, you have to do this in your code.
private void AddGridColumn() {
DataGridViewTextBoxColumn col = new DataGridViewTextBoxColumn();
col.Name = "AgeRange";
col.DataPropertyName = "AgeRange";
col.HeaderText = "Age Range";
dgv1.Columns.Add(col);
}
The important point is that the DataPropertyName MUST match the target column name in the DataTable, otherwise the column will not display.
Next is the construction of the new DataTable. This method is given the original DataTable. A new DataTable is created with three (3) columns, AgeRange-string (displayed), StartRange-int and EndRange-int. The start and end columns will not be displayed. Once this new table is constructed, a foreach loop is started through all the rows in the original table. The string digits from the original tables row are “parsed” into actual numbers and added to the new DataTable along with the original “range” string. This method could look something like below. A helper method is further below to help split the age range string and return a number.
private DataTable GetSplitTable(DataTable sourceTable) {
DataTable dt = new DataTable();
dt.Columns.Add("AgeRange", typeof(string));
dt.Columns.Add("StartRange", typeof(int));
dt.Columns.Add("EndRange", typeof(int));
foreach (DataRow row in sourceTable.Rows) {
int startValue = GetIntValue(row.ItemArray[0].ToString(), 0);
int endValue = GetIntValue(row.ItemArray[0].ToString(), 1);
dt.Rows.Add(row.ItemArray[0], startValue, endValue);
}
return dt;
}
private int GetIntValue(string rangeString, int index) {
string[] splitArray = rangeString.Split('-');
int value = 0;
int.TryParse(splitArray[index], out value);
return value;
}
Putting all this together may look like below. Note, the button click event checks to see if the text box is empty, and if it is, will remove the current filter if one is applied.
private DataTable gridTable;
private DataTable splitTable;
public Form1() {
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e) {
gridTable = GetTable();
FillTable(gridTable);
splitTable = GetSplitTable(gridTable);
AddGridColumn();
dgv1.AutoGenerateColumns = false;
dgv1.DataSource = splitTable;
textBox1.Text = "18";
}
private void FillTable(DataTable dt) {
dt.Rows.Add("0-18");
dt.Rows.Add("19-100");
dt.Rows.Add("0-100");
dt.Rows.Add("17-80");
dt.Rows.Add("15-75");
}
private DataTable GetTable() {
DataTable dt = new DataTable();
dt.Columns.Add("AgeRange", typeof(string));
return dt;
}
private void AddGridColumn() {
DataGridViewTextBoxColumn col = new DataGridViewTextBoxColumn();
col.Name = "AgeRange";
col.DataPropertyName = "AgeRange";
col.HeaderText = "Age Range";
dgv1.Columns.Add(col);
}
private void button1_Click(object sender, EventArgs e) {
string filterString = "";
DataView dv;
if (textBox1.Text != "") {
filterString = string.Format("StartRange <= {0} AND EndRange >= {0}", textBox1.Text);
}
dv = new DataView(splitTable);
dv.RowFilter = filterString;
dgv1.DataSource = dv;
}
This Code:
("AgeRange LIKE '%{0}' OR AgeRange LIKE '{0}%'", textBoxFilter.Text)
is a redundant with two AgeRange LIKE
If you want to search like textBoxFilter.Text you can try
("AgeRange LIKE '%{0}%'", textBoxFilter.Text)
Or
StringBuilder rowFilter = new StringBuilder();
rowFilter.Append("AgeRange Like '%" + textBoxFilter.Text + "%'");
(dgv1.DataSource as DataTable).DefaultView.RowFilter = rowFilter.ToString();
I'm new to WPF and I'm trying to set the value the DataGrid Column "DataGridNumericUpDownColumn" from a Database. I get the data as DataTable and bind it to the DataGrid.
That's the table I fetch my items from.
CREATE TABLE Items
(
ITEM varchar(64),
PART_NUMBER varchar(64),
TYPE varchar(64),
MANUFACTURER varchar(64),
PRICE decimal(32,2) NOT NULL,
PRIMARY KEY(ITEM, PART_NUMBER, TYPE, MANUFACTURER),
FOREIGN KEY (MANUFACTURER, TYPE)
REFERENCES ManufacturerType(MANUFACTURER,TYPE)
ON UPDATE CASCADE
ON DELETE CASCADE
);
C# Code
private void MetroWindow_Loaded(object sender, RoutedEventArgs e)
{
//////////////////////////////////////////////////////////////////////////////////////////
// ITEMS TABLE //
//////////////////////////////////////////////////////////////////////////////////////////
StringBuilder command = new StringBuilder();
command.Append("SELECT MIN(ii.IMAGE_PATH) AS IMAGE_PATH, MAX(i.ITEM) AS ITEM, i.PART_NUMBER AS PART_NUMBER, ");
command.Append("MAX(i.TYPE) AS TYPE, MAX(i.Manufacturer) AS MANUFACTURER, i.PRICE FROM ");
command.Append("Items i ");
command.Append("LEFT JOIN Item_Images ii ON i.PART_NUMBER = ii.PART_NUMBER ");
command.Append("GROUP BY i.PART_NUMBER, i.ITEM");
//SETUP COLUMNS FOR ITEMS TABLE
DataTable dt = mysql_database.queryDataFromDatabaseWithSQLCommand(command.ToString());
dgItems.Columns.Add(createImageColumn("IMAGE", new Binding("IMAGE_PATH"), new Size(32, 32)));
foreach (DataColumn dcol in dt.Columns)
{
if (dcol.ColumnName.ToUpper() == "PRICE")
{
dgItems.Columns.Add(createDataGridNumericUpDownColumn(dcol.ColumnName.ToUpper(), "C"));
}
else
{
DataGridTextColumn dgc = new DataGridTextColumn();
dgc.Header = dcol.ColumnName.ToUpper();
dgc.Binding = new Binding(dcol.ColumnName.ToUpper());
dgc.IsReadOnly = true;
dgItems.Columns.Add(dgc);
}
}
dgItems.ItemsSource = dt.DefaultView;
}
The code for creating the DataGridNumericUpDownColumn
public DataGridNumericUpDownColumn createDataGridNumericUpDownColumn(string header, string format)
{
DataGridNumericUpDownColumn dtemp = new DataGridNumericUpDownColumn();
dtemp.Minimum = 0.00;
dtemp.Binding = new Binding("{" + header + "}");
dtemp.StringFormat = format;
dtemp.Header = header;
return dtemp;
}
However when I run my application, the field with the numericupdown is empty.
The value will not be set from the DataTable.
The running tool:
This is what's stored in the database.
The data row in the database:
I'm out of ideas how to get this working. Any help is appreciated! Thanks!
I think I am overseeing something.
I dynamically generate a few ComboBoxes with this code (I do the same for other controls like TextBox, Label etc)
private ComboBox addControlsComboBox(string Id, string TBName, int point_X, int point_Y, int SizeWidth, DataTable DT)
{
ComboBox combobox = new ComboBox();
combobox.Text = TBName;
combobox.Location = new Point(point_X, point_Y);
combobox.Size = new Size(SizeWidth, 20);
combobox.Name = Id + TBName;
combobox.DataSource = DT;
combobox.DisplayMember = "key";
combobox.ValueMember = "value";
combobox.Enabled = true;
return combobox;
}
When I automatically want to set the selected value, for the controls all the values are set correct except for the ComboBox. Not 1 comboBox is updated but all the ComboBoxes.
I use a nested dictionary object to store all the values that i need to match.
See part of the used update Code
foreach (Control gb in GroupPanel.Controls)
{
foreach (Control childc in gb.Controls)
{
if (DataCollection[GroupNames].ContainsKey(childc.Name))
{
KeyName = childc.Name;
numberLessKeyName = SL.RemoveDigits(childc.Name);
TextValue = DataCollection[GroupNames][KeyName];
switch (NumberLessKeyName)
{
case "Name":
int IntTextValue = Convert.ToInt32(TextValue);
TextValue = IntTextValue.ToString("d2");
break;
}
switch (childc.GetType().ToString())
{
case "System.Windows.Forms.TextBox":
childc.Text = TextValue;
break;
case "System.Windows.Forms.ComboBox":
// Not Working
ComboBox combobox = (ComboBox)childc;
combobox.SelectedValue = TextValue;
//Also not Working
// --> childc.Text = TextValue;
break;
case "System.Windows.Forms.CheckBox":
CheckBox chChildc = (CheckBox)childc;
if (TextValue == "Yes")
{
chChildc.Checked = true;
}
break;
};
}
}
}
What I am doing wrong?
Can somebody help me please?
[EDIT 1]
Thanks to Karol
I added The Following Lines + interface ICloneable and it worked. Many Thanks.
DataTable DT = new DataTable();
DT = DTAttribute;
DataTable DTClone = (DataTable)DT.Clone();
For those searching [C# Object Clone Wars][1] link
[EDIT 2]
A other Idea is to use COPY (works also)
DataTable DT = new DataTable();
DT = DTAttribute;
DataTable DTClone = DT.Copy();
I think You bind all ComboBox same DataTable.
You must have diffrent instance of DataTable for each ComboBox. You can do this in two ways:
- Once again SELECT data from database.
- Use deep copy to make new instance of DataTable.
I created custom datatype classes that format the data the way I need it. The data I retrieve from the database comes as .NET base types, so I need to loop through the DataTable, convert each item into it's custom type, and put the converted item into a new table. I also copy the column names from the old table to the new one. The problem is, when I bind a GridView to my new table, it throws an exception:
HttpException:
The data source for GridView with id 'TestGrid' did not have any properties
or attributes from which to generate columns. Ensure that your data source
has content.
Stack Trace:
at System.Web.UI.WebControls.GridView.CreateAutoGeneratedColumns(PagedDataSource dataSource)
at System.Web.UI.WebControls.GridView.CreateColumns(PagedDataSource dataSource, Boolean useDataSource)
at System.Web.UI.WebControls.GridView.CreateChildControls(IEnumerable dataSource, Boolean dataBinding)
at System.Web.UI.WebControls.CompositeDataBoundControl.PerformDataBinding(IEnumerable data)
at System.Web.UI.WebControls.GridView.PerformDataBinding(IEnumerable data)
at System.Web.UI.WebControls.DataBoundControl.OnDataSourceViewSelectCallback(IEnumerable data)
at System.Web.UI.DataSourceView.Select(DataSourceSelectArguments arguments, DataSourceViewSelectCallback callback)
at System.Web.UI.WebControls.DataBoundControl.PerformSelect()
at System.Web.UI.WebControls.BaseDataBoundControl.DataBind()
at System.Web.UI.WebControls.GridView.DataBind()
What do I need to add to my DataTable to get autogeneratecolumns to work?
Edit: Here is the code for the DataTable:
// Populate first DataTable with data from database.
adapter = new DataAdapter("SELECT status, date_ordered, date_due FROM import_table", OpenConnection);
DataTable originalTable = new DataTable();
adapter.Fill(originalTable)
// Second DataTable for converted table data.
DataTable convertedTable = new DataTable();
// The list of custom datatypes to convert to.
Type[] newTypes = {typeof(TrackingStatus), typeof(Date), typeof(Date)};
// Set the ColumnName and DataType on each column of the new table.
for(int i = 0; i < originalTable.Columns.Count; i++)
{
convertedTable.Columns.Add();
convertedTable.Columns[i].ColumnName = originalTable.Columns[i].ColumnName;
if(newTypes.Length > i)
convertedTable.Columns[i].DataType = newTypes[i];
}
// Convert each item from the old table and add it to the new table.
foreach(DataRow oldRow in originalTable.Rows)
{
DataRow newRow = convertedTable.NewRow();
for(int i = 0; i < convertedTable.Columns.Count; i++)
{
if(newTypes.Length <= i)
newRow[i] = oldRow[i];
else if(newTypes[i] == typeof(Date))
newRow[i] = Date.FromObject(oldRow[i]);
else if(newTypes[i] == typeof(TrackingStatus))
newRow[i] = TrackingStatus.FromObject(oldRow[i]);
else if(newTypes[i] == typeof(EmailAddress))
newRow[i] = EmailAddress.FromObject(oldRow[i]);
}
convertedTable.Rows.Add(newRow);
}
// Bind the GridView.
displayGrid.DataSource = convertedTable;
displayGrid.DataBind();
Instead of creating a new datatable when you convert each to custom types, create a new List and bind the list to the datasource. e.g.
var list = new List<MyClass>();
var myType = //Convert your type
list.Add(myType);
grid.DataSource = list;
And ofcourse this all is just an idea of how to do it.
UPDATE:
Saw your code afterwards:
Try this:
Create a class called ImportRow
class ImportRow
{
private string m_Status = string.Empty;
private DateTime m_DateOrdered;
private DateTime m_DateDue;
public ImportRow() { }
public string Status
{
get { return m_Status; }
set { m_Status = value; }
}
public DateTime DateOrdered
{
get { return m_DateOrdered; }
set { m_DateOrdered = value; }
}
public DateTime DateDue
{
get { return m_DateDue; }
set { m_DateDue = value; }
}
}
Then use it as:
var importedData = new List<ImportRow>();
foreach (DataRow oldRow in originalTable.Rows)
{
var newRow = new ImportRow();
newRow.Status = oldRow["status"].ToString();
newRow.DateDue = Convert.ToDateTime(oldRow["date_due"].ToString());
newRow.DateOrdered = Convert.ToDateTime(oldRow["date_ordered"].ToString());
importedData.Add(newRow);
}
Then
displayGrid.DataSource = importedData;
displayGrid.DataBind();
Your custom type needs public properties like these:
public class Foo
{
public int Id;
public string Name;
}
or you need to use the DataTable itself as DataSource.