I've searched all over here and Google and still can't find an answer to this. I'm playing around with Amazon's API and am making a simple Windows Form to try and display the data in a DataGridView. The GridView is generating 10 rows for the 10 results I am getting, but is not filling the rows with the actual data. They're just blank.
The code below is a method (GetResults) that return a DataTable. I didn't show all of it because there is a bunch of code above to get the data.
DataTable dt = new DataTable();
dt.Columns.Add("ASIN", typeof(string));
dt.Columns.Add("Title", typeof(string));
// write out the results
foreach (var item in response.Items[0].Item)
{
Product product = new Product(item.ASIN, item.ItemAttributes.Title);
Console.WriteLine(product.ASIN);
var dr = dt.NewRow();
dr["ASIN"] = product.ASIN;
dr["Title"] = product.Title;
dt.Rows.Add();
}
return dt;
}
private void btnSearch_Click(object sender, EventArgs e)
{
dgvProducts.AutoGenerateColumns = false;
dgvProducts.DataSource = GetReults();
}
I know it is getting the info because I am writing it to console and it is showing up correctly.
I also have this basic class for the Product:
public class Product
{
private string asin;
private string title;
public Product() { }
public Product(string newAsin, string newTitle)
{
this.asin = newAsin;
this.title = newTitle;
}
public string ASIN
{
get { return asin; }
set { asin = value; }
}
public string Title
{
get { return title; }
set { title = value; }
}
I've tried setting AutoGenerateColumns = false and setting the column data bindings myself, but that didn't do anything either.
You're adding an empty row to the table, not adding your newly created row.
Change
dt.Rows.Add();
To
dt.Rows.Add(dr);
Related
I am unable to make a hyperlink in the excel template. I'd like to display the value in the cell like this http://www.diamondschool/{personName}
I have read the official documentation on github. No luck at all
public FileInfo GenerateTemplate(long shoolId)
{
using (var excelPackage = new ExcelPackage(dataTemplate))
{
excelPackage.Workbook.Worksheets.Add("Sheet1");
var ws = excelPackage.Workbook.Worksheets[1];
var dataTable = InitializeDataTable();
var templateData = GetTemplateData(schoolId);
PopulateData(templateData, dataTable);
ws.Cells["A2"].LoadFromDataTable(dataTable, true);
}
// Since I am already loading the data from the data table I can't find a way to display the value as a hyperlink.
//http://www.diamondschool/{personName} so depending the value in the cell, display the personame as hyperlink to that address
//Person name as HyperLink
var namedStyle = ws.Workbook.Styles.CreateNamedStyle("HyperLink");
namedStyle.Style.Font.UnderLine = true;
namedStyle.Style.Font.Color.SetColor(Color.Blue);
ws.Cells["A2"].Value
ws.Cells["A2"].StyleName = "HyperLink";
excelPackage.Save();
}
return dataTemplate;
}
private static DataTable InitializeDataTable()
{
var dataTable = new DataTable();
dataTable.Columns.Add("Person Id", typeof(string));
dataTable.Columns.Add("Person Name", typeof(string));
return dataTable;
}
private static void PopulateData(IEnumerable<DataTemplateRow> data, DataTable dataTable)
{
foreach (var item in data)
{
var dataRow = dataTable.NewRow();
dataRow["Person Id"] = item.Id;
dataRow["Person Name"] = item.Name;
dataTable.Rows.Add(dataRow);
}
}
public IEnumerable<DataTemplateRow> GetTemplateData(long schoolId)
{
var personData = _schoolService.GetData(schoolId);
var result = personData.Data.Select(result => new DataTemplateRow
{
PersonId = result.Id,
PersonName = result.Name
});
return result;
}
public class DataTemplateRow
{
public long Id { get; set; }
public class Name { get; set; }
}
Since I am already loading the data from the data table I can't find a way to display the value as a hyperlink.
http://www.diamondschool/{personName} so depending the value in the cell, display the personame as hyperlink to that address
You need to format the Cell like this. Create a Hyperlink and set it with an Uri
ws.Cells[5, 5].Style.Font.UnderLine = true;
ws.Cells[5, 5].Style.Font.Bold = true;
ws.Cells[5, 5].Style.Font.Color.SetColor(Color.Blue);
ws.Cells[5, 5].Hyperlink = new Uri("https://www.google.nl");
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 have the below class as my DataModel:
public class InspectorOutput
{
string m_SymbolName;
// Each string is a column name with its value in double
List<KeyValuePair<string, double>> m_ListPrices = new List<KeyValuePair<string, double>>();
public string SymbolName
{
get
{
return m_SymbolName;
}
set
{
m_SymbolName = value;
}
}
public List<KeyValuePair<string, double>> ListPrices
{
get
{
return m_ListPrices;
}
set
{
m_ListPrices = value;
}
}
public void addResult(string strResultName, double nResult)
{
ListPrices.Add(new KeyValuePair<string, double>(strResultName, nResult));
}
}
In my xaml window data grid is defined as below:
<DataGrid x:Name="gridStockData">
</DataGrid>
Later on, in my mainwindow I have the below code:
private void runProfile(object sender, RoutedEventArgs e)
{
ObservableCollection<InspectorOutput> listOutput = null;
Profile.Profile objProfile = null;
Inspector.InspectorManager objInspectorManager = null;
try
{
// Some code here which makes a profile out of user input in objProfile
objInspectorManager = new Inspector.InspectorManager();
// Calculate data based on given profile
listOutput = objInspectorManager.startInspector(objProfile);
// Show calculated data
gridStockData.ItemsSource = listOutput;
}
catch (Exception ex)
{
Logger.getInstance().error(ex.getStackTrace());
}
}
The problem is as follows:
I have stock data for 10 companies.
Each company has a symbol name.
Calculated data for each symbol is stored in m_ListPrices where each key is a column name and each value is a cell value
Note: Columns are not known until run time(ie: Based on user's selected algorithm column names and numbers may vary).
I have a calculator class that runs user selected algorithms. Each algorithm has it's own output which stores it in above data model.
How could I possibly bind this DataModel to a DataGrid in WPF?
Currently above code gives me the following output:
This is how I got over the problem:
I create a DataTable as data source for my data grid which is filled with columns inside the dictionary.
DataTable tbl = new DataTable();
tbl.Columns.Add("Symbol Name", typeof(string));
// Add columns
foreach (InspectorOutput item in listScenarioOutput)
{
foreach (KeyValuePair<string, double> entry in item.ListPrices)
{
tbl.Columns.Add(entry.Key, typeof(double));
}
break;
}
for (int i = 0; i < listScenarioOutput.Count; i++)
{
DataRow row = tbl.NewRow();
row.SetField(0, listScenarioOutput[i].SymbolName);
int j = 0;
foreach (KeyValuePair<string, double> entry in listScenarioOutput[i].ListPrices)
{
row.SetField(++j, entry.Value);
}
tbl.Rows.Add(row);
}
gridStockData.ItemsSource = tbl.DefaultView;
How can you set default value for combobox provided you get the value from table in database. I am thinking comparing the value with column [2]/destinationColumn to see which value in the table should be selected as default. This is my code so far which is wrong. Suggestion or an example code will be much appreciated. Thank you in advance guys.
string sqlLookupColumn = "SELECT LookUpColumnID, SOURCE_NAME FROM TB_LOOKUP_COLUMN ORDER BY SOURCE_NAME ASC";
DataSet dsColumn = databaseManager.GetData(sqlLookupColumn);
DataGridViewComboBoxColumn dgvCboColumn = new DataGridViewComboBoxColumn();
dgvCboColumn.Name = "DESTINATION_NAME";
dataGridView1.Columns.Add(dgvCboColumn);
foreach (DataGridViewRow row in dataGridView1.Rows)
{
DataGridViewComboBoxCell cboDestinationColumns = (DataGridViewComboBoxCell)(row.Cells[3]);
cboDestinationColumns.DataSource = dsColumn.Tables[0];
string destinationColumn = row.Cells[2].Value.ToString();
cboDestinationColumns.DisplayMember = "SOURCE_NAME";
cboDestinationColumns.ValueMember = "LookUpColumnID";
if (destinationColumn == cboDestinationColumns.DisplayMember)
{
cboDestinationColumns.Selected = true;
}
}
Things that i can see wrong
1- Your loop on the GridView wont work, do the loop on the Dataset instead of the Gridview...
2- You are comparing destinationColumn with cboDestinationColumns.DisplayMember which = "SOURCE_NAME" and you want If destinationColumn = "InvoiceNo"
3- Add the to the combo items using for loop and .add method, and do your if statement their.
To add the items:
1st add this class
public class ComboboxItem
{
public string Text { get; set; }
public object Value { get; set; }
public override string ToString()
{
return Text;
}
}
Then loop on the Dataset
for(int i=0;i<ds.Tables[0].Rows.Count;i++)
{
DataRow dr = ds.Tables[0].Rows[i];
ComboboxItem tmp= new ComboboxItem();
tmp.Text = dr["SOURCE_NAME"];
tmp.Value = dr["LookUpColumnID"];
cb.Items.Add(tmp);
if(dr["InvoiceNo"].ToString() =="")//Your condition here to set selected
cb.SelectedIndex = i;
}
In a Windows Form application, I'm trying to create a DataGridView with two columns: one for the key given by an XML element and one for the value of said XML element. This is my code so far:
this.myData = new DataGridView();
((System.ComponentModel.ISupportInitialize)(myData)).BeginInit();
myData.Location = new System.Drawing.Point(12, 42);
myData.Name = "myData";
myData.Size = new System.Drawing.Size(1060, 585);
myData.TabIndex = 32;
foreach (XElement xElem in xInfoItems)
{
numItems++;
}
myData.Columns.Add(new DataGridViewTextBoxColumn());
myData.Columns.Add(new DataGridViewTextBoxColumn());
myData.Columns[0].Name = "Key";
myData.Columns[0].DataPropertyName = "key";
myData.Columns[1].Name = "Value";
myData.Columns[1].DataPropertyName = "value";
List<myRow> data = new List<myRow>();
foreach (XElement xElem in xInfoItems)
{
data.Add(new myRow(xElem.Attribute("key").Value, xElem.Value));
}
myData.DataSource = data;
myData.Refresh();
this.PerformLayout();
I have confirmed that all of the information in data is being loaded via foreach, so that part is working. My problem is that the grid displays, but nothing shows up on the grid. What am I doing wrong? I'm not very good with this data type so I apologize if it's something obvious.
UPDATE
I figured out that I hadn't set myData up properly in the Design view. After adding the myRow class, it worked perfectly. Thanks for the help!
The problem may lie in your myRow class. When I was trying to reproduce your code, I first defined "key" and "value" as public fields of the myRow class as so:
public class myRow {
public string key;
public string value;
public myRow( string Key, string Value )
{
key = Key;
value = Value;
}
}
This caused the bound rows to show up but the text was not in the cells. When I changed both of them to properties, the binding worked much better:
public class myRow{
private string _key;
public string key
{
get
{
return _key;
}
}
private string _value;
public string value
{
get
{
return _value;
}
}
public myRow( string Key, string Value )
{
_key = Key;
_value = Value;
}
}
Probably the modifications I made on your code can help. (I just have focused on the part where you create the columns and add the rows, using a DataTable).
this.myData = new DataGridView();
((System.ComponentModel.ISupportInitialize)(myData)).BeginInit();
myData.Location = new System.Drawing.Point(12, 42);
myData.Name = "myData";
myData.Size = new System.Drawing.Size(1060, 585);
myData.TabIndex = 32;
foreach (XElement xElem in xInfoItems)
{
numItems++;
}
// Here we create a DataTable with two columns.
DataTable table = new DataTable();
table.Columns.Add("Key", typeof(string));
table.Columns.Add("Value", typeof(string));
foreach (XElement xElem in xInfoItems)
{
//Here we add rows to table
table.Rows.Add(xElem.Attribute("key").Value, xElem.Value);
}
myData.DataSource = table;
myData.Refresh();
this.PerformLayout();