C# WPF - change cell color dynamiclly - c#

I'm creating from c# Data-grid that contain Data-Table and fill it with data,then I have refresh button that when i'm clicking it i want to see the cells that their value has been change in another color(red for example).
I'm pretty new in WPF so I don't really understand how to do it from the XML and I'm creating the tables from the code so I try to do it from the code too.
tried everything and the cell background is not changing.
Thank's for everyone that will try to help :)
example of the code for creating the DataTable:
string TID =selectedTab.Header.ToString().Split('~')[1]; // (TableID, Lvl)
List<Tuple<string,string>> FieldList = API.getFieldsByTableID(TID); // {(Field_name,size in bits),...}
DataGrid dg = new DataGrid();
DataTable dt = new DataTable();
string[] TableLevel = splitTID(TID); //TableLevel[0]=Table ;TableLevel[1]=Level;
string TableDump = API.GetRegs(TableLevel[0], TableLevel[1]);// Getting debug dump from simics
#endregion
#region *Fields_row*
foreach (var item in FieldList) // First line ,name of fields.
{
dc = new DataColumn(item.Item1, typeof(string));
dt.Columns.Add(dc);
}
#endregion
TableDump = TableDump.Split(':')[1]; // split to get just the dump
int x = 0;
int DumpLen = TableDump.Length; // dump length
int EntrySize = int.Parse(API.GetEntrySize(TID)); // return entry size
int NumOfBytes = round_bits_2_chars_amount(EntrySize);
int count = 0;
while (x < DumpLen)
{
count++;
String str_Entry = BE_to_LE(TableDump.Substring(x, NumOfBytes));
ulong Entry = ulong.Parse(str_Entry, System.Globalization.NumberStyles.HexNumber);
DataRow dr = dt.NewRow();
int row = 0;
dr[row++] = count;
foreach (var item in FieldList)
{
int FieldLen = int.Parse(item.Item2);
ulong Mask =(ulong) ((1 << FieldLen) - 1);
ulong Value = Entry & Mask;
Entry = Entry >> FieldLen;
if (Properties.Settings.Default.IsHexadecimal)
{
dr[row] = "0x" + Value.ToString("X");
}
else
{
dr[row] =Value.ToString();
}
row += 1;
/* if (int.Parse(item.Item2) > DumpLen - x)
{
x = DumpLen + 1;
break;
}
string FieldDump =TableDump.Substring(x,int.Parse(item.Item2));
x +=int.Parse(item.Item2);
dr[row] = long.Parse(FieldDump,System.Globalization.NumberStyles.HexNumber);
row +=1;*/
}
dt.Rows.Add(dr);
x += EntrySize;
}
dg.ItemsSource = new DataView(dt);
selectedTab.Content = dg;
}
}

so after a lot of checking i found the solution.
with the use of :
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Media;
namespace Nagasaki
{
public static class Datagrid
{
public static DataGridRow GetSelectedRow(this DataGrid grid)
{
return (DataGridRow)grid.ItemContainerGenerator.ContainerFromItem(grid.SelectedItem);
}
public static DataGridRow GetRow(this DataGrid grid, int index)
{
DataGridRow row = (DataGridRow)grid.ItemContainerGenerator.ContainerFromIndex(index);
if (row == null)
{
// May be virtualized, bring into view and try again.
grid.UpdateLayout();
grid.ScrollIntoView(grid.Items[index]);
row = (DataGridRow)grid.ItemContainerGenerator.ContainerFromIndex(index);
}
return row;
}
public static DataGridCell GetCell(this DataGrid grid, DataGridRow row, int column)
{
if (row != null)
{
DataGridCellsPresenter presenter = GetVisualChild<DataGridCellsPresenter>(row);
if (presenter == null)
{
grid.ScrollIntoView(row, grid.Columns[column]);
presenter = GetVisualChild<DataGridCellsPresenter>(row);
}
DataGridCell cell = (DataGridCell)presenter.ItemContainerGenerator.ContainerFromIndex(column);
return cell;
}
return null;
}
public static T GetVisualChild<T>(Visual parent) where T : Visual
{
T child = default(T);
int numVisuals = VisualTreeHelper.GetChildrenCount(parent);
for (int i = 0; i < numVisuals; i++)
{
Visual v = (Visual)VisualTreeHelper.GetChild(parent, i);
child = v as T;
if (child == null)
{
child = GetVisualChild<T>(v);
}
if (child != null)
{
break;
}
}
return child;
}
public static DataGridCell GetCell(this DataGrid grid, int row, int column)
{
DataGridRow rowContainer = GetRow(grid, row);
return GetCell(grid, rowContainer, column);
}
}
}
I was able to get to specific cell and like this i can change the background color of the cell.

Related

Set ComboBox in Specific Cell in DataGridView

I've seen multiple threads about combobox's in DataGridView rows/columns but nothing really focused on just one cell. I'm trying to loop through all the cells in the DataGridView and when it recognizes a Parameter that is supposed to have the options On/Off, it loads a ComboBox in.
The code I've been working on is:
dataGridView1.DataSource = typeof(Parameter);
dataGridView1.DataSource = _paramList;
foreach (DataGridViewRow row in dataGridView1.Rows)
{
foreach (DataGridViewCell ce in row.Cells)
{
foreach (Information i in _information)
{
if (ce.Value.ToString() == "Light")
{
DataGridViewComboBoxCell c = new DataGridViewComboBoxCell();
c.Items.Add("On");
c.Items.Add("Off");
dataGridView1.Rows[ce.RowIndex].Cells[ce.ColumnIndex] = c;
}
}
}
}
It throws an error "Collection was modified; enumeration operation may not execute." which I'm guessing has to do with it populating the cell with a combobox and trying to continue the foreach statement? Any ideas/change to fix this?
Thanks!
Foreach loops handle enumerators for you, they get this error (enumeration cannot continue, collection modified) quite easily. You can get around this by using for loops. I tested the below (tried with foreach first - no dice), and we seem to be good.
Also of note - I had to set the actual cell value to null before setting the new item. Without this, I got an error "value of datagridcomboboxcell is not valid" (or something similar).
Edit: I left out your third nested loop. It's not necessary for this proof of concept. This simply adds the combobox to 100% of cells.
Including all of my testing code for reproduction:
using System;
using System.Data;
using System.Windows.Forms;
namespace WindowsFormsApp5
{
public partial class Form1 : Form
{
private DataGridView oDg;
public Form1()
{
InitializeComponent();
CreateGrid();
this.Shown += Form1_Shown;
}
private void Form1_Shown(object sender, EventArgs e)
{
TestIt();
}
private void TestIt()
{
//works
for (int i = 0;i < oDg.RowCount; i++)
{
for (int j = 0;j< oDg.ColumnCount; j++)
{
oDg.Rows[i].Cells[j].Value = null; //this is important.
DataGridViewComboBoxCell c = new DataGridViewComboBoxCell();
c.Items.Add("On");
c.Items.Add("Off");
oDg.Rows[i].Cells[j] = c;
}
}
//does not work
//foreach (DataGridViewRow row in oDg.Rows)
//{
// foreach (DataGridViewCell ce in row.Cells)
// {
// oDg.Rows[ce.RowIndex].Cells[ce.ColumnIndex].Value = null;
// DataGridViewComboBoxCell c = new DataGridViewComboBoxCell();
// c.Items.Add("On");
// c.Items.Add("Off");
// oDg.Rows[ce.RowIndex].Cells[ce.ColumnIndex] = c;
// }
//}
}
private void CreateGrid()
{
oDg = new DataGridView();
oDg.Width = 800;
oDg.Height = 800;
oDg.DataSource = CreateDataSource();
this.Controls.Add(oDg);
}
private DataTable CreateDataSource()
{
DataTable oDt = new DataTable();
for(int i = 0; i < 8; i++)
{
DataColumn col = new DataColumn(i.ToString(),typeof (String));
oDt.Columns.Add(col);
}
for (int i = 0; i < 99; i++)
{
DataRow rw = oDt.NewRow();
for (int j = 0;j < oDt.Columns.Count; j++)
{
rw[j] = j.ToString();
}
oDt.Rows.Add(rw);
}
return oDt;
}
}
}

WPF validation run-time generated forms

I'm creating a WPF application which generates a form based on a model to edit it. I use reflection to go through all properties of the model to create inputfields for the properties. The GenerateForm method iterates through the properties and uses the SimpleInputFactory to generate input fields. I want to validate the input of the generated fields, but all validation methods require that you know what you are going to validate (either it's using generics or you have to specify it on the binding in the XAML). I want to validate the input based on attributes in the models. Is there any existing way of doing this? I could just make it myself, but if there is some existing way it would help.
Thanks in advance.
public static Grid GenerateForm(List<object> basisgegevensModels, AddOrEdit addOrEdit)
{
if (basisgegevensModels.Count <= 0)
return null;
Grid formGrid = new Grid();
formGrid.Margin = new Thickness(20,20,20,20);
formGrid.HorizontalAlignment = HorizontalAlignment.Stretch;
AddColumnToGrid(formGrid, GridUnitType.Star, 1);
AddColumnToGrid(formGrid, GridUnitType.Star, 3);
AddColumnToGrid(formGrid, GridUnitType.Star, 1);
AddColumnToGrid(formGrid, GridUnitType.Star, 3);
AddRowToGrid(formGrid, GridUnitType.Auto, 0);
var propertyInfos = new List<PropertyInfo>();
foreach (var propertyInfo in basisgegevensModels[0].GetType().GetProperties(BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.Instance))
{
var visibleAttribute = propertyInfo.GetCustomAttributes(typeof(Visible), false).Cast<Visible>().FirstOrDefault();
if (visibleAttribute == null || visibleAttribute.IsVisible)
propertyInfos.Add(propertyInfo);
}
int column = 0;
int row = 0;
foreach (var property in propertyInfos)
{
if (row >= Math.Ceiling((decimal)propertyInfos.Count / 2) && row != 0 && column != 2)
{
column = 2;
row = 0;
}
var displayNameAttribute = basisgegevensModels[0].GetType().GetProperty(property.Name).GetCustomAttributes(typeof(DisplayNameAttribute), false)
.Cast<DisplayNameAttribute>().FirstOrDefault();
string displayName;
if (displayNameAttribute != null)
displayName = displayNameAttribute.DisplayName;
else
displayName = property.Name;
bool isEditAllowed = true;
if (addOrEdit == AddOrEdit.Edit)
{
var editAllowed =
basisgegevensModels[0].GetType()
.GetProperty(property.Name)
.GetCustomAttributes(typeof (EditAllowed), false)
.Cast<EditAllowed>()
.FirstOrDefault();
if (editAllowed != null)
isEditAllowed = editAllowed.IsEditAllowed;
}
//add label for inputfield
TextBlock label = SimpleInputFieldFactory.CreateTextBlock(displayName, column, row);
label.VerticalAlignment = VerticalAlignment.Center;
formGrid.Children.Add(label);
column++;
//add input field
formGrid.Children.Add(SimpleInputFieldFactory.CreateInputField(basisgegevensModels, property, isEditAllowed, column, row, 300, HorizontalAlignment.Left));
column--;
row++;
if (column == 0)
{
AddRowToGrid(formGrid, GridUnitType.Auto, 0);
}
}
return formGrid;
}
SimpleInputFieldFactory Class:
public class SimpleInputFieldFactory
{
public static Control CreateInputField(List<object> basisgegevensModels, PropertyInfo property, bool editAllowed, int column, int row, double inputFieldWidth, HorizontalAlignment inputFieldHorAlignment)
{
Control inputField = null;
var triggers = new List<System.Windows.Interactivity.EventTrigger>();
var multiBinding = new MultiBinding();
multiBinding.NotifyOnSourceUpdated = true;
multiBinding.Mode = BindingMode.TwoWay;
multiBinding.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
foreach (var basisgegevensModel in basisgegevensModels)
{
Binding binding = new Binding(property.Name)
{
UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged,
Source = basisgegevensModel,
Mode = BindingMode.TwoWay
};
multiBinding.Bindings.Add(binding);
}
//add inputfield
if (property.PropertyType == typeof(string) || property.PropertyType == typeof(int))
{
string valueAsString = "";
if (property.GetValue(basisgegevensModels[0]) != null)
valueAsString = property.GetValue(basisgegevensModels[0]).ToString();
inputField = CreateTextBox(valueAsString, column, row);
triggers.Add(new System.Windows.Interactivity.EventTrigger("EditValueChanged"));
}
else if (property.PropertyType == typeof(bool))
{
bool valueAsBool = false;
if (property.GetValue(basisgegevensModels[0]) != null)
valueAsBool = (bool)property.GetValue(basisgegevensModels[0]);
inputField = CreateCheckBox(valueAsBool, column, row);
triggers.Add(new System.Windows.Interactivity.EventTrigger("EditValueChanged"));
}
else if (property.PropertyType.BaseType == typeof(Enum))
{
int valueAsInt = 0;
if (property.GetValue(basisgegevensModels[0]) != null)
valueAsInt = (int)property.GetValue(basisgegevensModels[0]);
inputField = CreateDropDown(property.PropertyType, valueAsInt, column, row);
triggers.Add(new System.Windows.Interactivity.EventTrigger("EditValueChanged"));
((ComboBoxEdit)inputField).SelectedIndex = valueAsInt;
((ComboBoxEdit)inputField).IsTextEditable = false;
}
//add general settings, bindings and triggers
if (inputField != null)
{
inputField.Width = inputFieldWidth;
inputField.HorizontalAlignment = inputFieldHorAlignment;
inputField.Margin = new Thickness(5);
inputField.IsEnabled = editAllowed;
var multiEditAllowedAttribute = property.GetCustomAttributes(typeof(MultiEditAllowed), false)
.Cast<MultiEditAllowed>().FirstOrDefault();
//only add binding and trigger if 1 entity is selected OR multiedit is allowed
if (basisgegevensModels.Count == 1 || multiEditAllowedAttribute == null || multiEditAllowedAttribute.IsMultiEditAllowed)
{
multiBinding.Converter = new MultiEditValueConverter();
inputField.SetBinding(BaseEdit.EditValueProperty, multiBinding);
foreach (var trigger in triggers)
{
var action = new ActionMessage();
action.MethodName = "InputChanged";
trigger.Actions.Add(action);
Interaction.GetTriggers(inputField).Add(trigger);
}
}
else
{
inputField.IsEnabled = false;
}
return inputField;
}
return null;
}
public static List<string> GetEnumList(Type enumType)
{
if (!enumType.IsEnum)
{
return new List<string>();
}
return Enum.GetNames(enumType).ToList();
}
public static TextBlock CreateTextBlock(string text, int column, int row)
{
TextBlock textBlock = new TextBlock();
textBlock.Text = text;
Grid.SetColumn(textBlock, column);
Grid.SetRow(textBlock, row);
return textBlock;
}
private static TextEditBase CreateTextBox(string text, int column, int row)
{
TextEdit textBox = new TextEdit();
textBox.Text = text;
Grid.SetColumn(textBox, column);
Grid.SetRow(textBox, row);
return textBox;
}
private static CheckEdit CreateCheckBox(bool isChecked, int column, int row)
{
CheckEdit checkBox = new CheckEdit();
checkBox.IsChecked = isChecked;
Grid.SetColumn(checkBox, column);
Grid.SetRow(checkBox, row);
return checkBox;
}
private static ComboBoxEdit CreateDropDown(Type enumType, int value, int column, int row)
{
ComboBoxEdit dropDown = new ComboBoxEdit();
foreach (var enumValue in GetEnumList(enumType))
{
dropDown.Items.Add(enumValue);
}
dropDown.SelectedIndex = value;
Grid.SetColumn(dropDown, column);
Grid.SetRow(dropDown, row);
return dropDown;
}
}
Yes, you can use the System.ComponentModel.DataAnnotations for validation.
Documentation for the base namespace : MSDN: System.ComponentModel.DataAnnotations
Examples include RequiredAttribute and RangeAttribute.
Microsoft also provide an excellent example of how to provide validation feedback in realtime to the user in WPF using the ErrorTemplate and Binding in the following example: MSDN: Validation in MVVM using Data Annotations
I've also developed a small framework for my own purposes which incorporates these techniques - basically a base class where you need to decorate your VM with ValidationAttribute derived attributes and use the appropriate Binding and WPF takes care of the rest. GitHub: ValidatingBaseViewModel

EPPlus - Read Excel Table

Using EPPlus, I want to read an excel table, then store all the contents from each column into its corresponding List. I want it to recognize the table's heading and categorize the contents based on that.
For example, if my excel table is as below:
Id Name Gender
1 John Male
2 Maria Female
3 Daniel Unknown
I want the data to store in List<ExcelData> where
public class ExcelData
{
public string Id { get; set; }
public string Name { get; set; }
public string Gender { get; set; }
}
So that I can call out the contents using the heading name. For example, when I do this:
foreach (var data in ThatList)
{
Console.WriteLine(data.Id + data.Name + data.Gender);
}
It will give me this output:
1JohnMale
2MariaFemale
3DanielUnknown
This is really all I got:
var package = new ExcelPackage(new FileInfo(#"C:\ExcelFile.xlsx"));
ExcelWorksheet sheet = package.Workbook.Worksheets[1];
var table = sheet.Tables.First();
table.Columns.Something //I guess I can use this to do what I want
Please help :(
I have spent long hours searching for sample code regarding this so that I can learn from it but to no avail. I also understand ExcelToLinQ is managed to do that but it can't recognize table.
Not sure why but none of the above solution work for me.
So sharing what worked:
public void readXLS(string FilePath)
{
FileInfo existingFile = new FileInfo(FilePath);
using (ExcelPackage package = new ExcelPackage(existingFile))
{
//get the first worksheet in the workbook
ExcelWorksheet worksheet = package.Workbook.Worksheets[1];
int colCount = worksheet.Dimension.End.Column; //get Column Count
int rowCount = worksheet.Dimension.End.Row; //get row count
for (int row = 1; row <= rowCount; row++)
{
for (int col = 1; col <= colCount; col++)
{
Console.WriteLine(" Row:" + row + " column:" + col + " Value:" + worksheet.Cells[row, col].Value?.ToString().Trim());
}
}
}
}
There is no native but what if you use what I put in this post:
How to parse excel rows back to types using EPPlus
If you want to point it at a table only it will need to be modified. Something like this should do it:
public static IEnumerable<T> ConvertTableToObjects<T>(this ExcelTable table) where T : new()
{
//DateTime Conversion
var convertDateTime = new Func<double, DateTime>(excelDate =>
{
if (excelDate < 1)
throw new ArgumentException("Excel dates cannot be smaller than 0.");
var dateOfReference = new DateTime(1900, 1, 1);
if (excelDate > 60d)
excelDate = excelDate - 2;
else
excelDate = excelDate - 1;
return dateOfReference.AddDays(excelDate);
});
//Get the properties of T
var tprops = (new T())
.GetType()
.GetProperties()
.ToList();
//Get the cells based on the table address
var start = table.Address.Start;
var end = table.Address.End;
var cells = new List<ExcelRangeBase>();
//Have to use for loops insteadof worksheet.Cells to protect against empties
for (var r = start.Row; r <= end.Row; r++)
for (var c = start.Column; c <= end.Column; c++)
cells.Add(table.WorkSheet.Cells[r, c]);
var groups = cells
.GroupBy(cell => cell.Start.Row)
.ToList();
//Assume the second row represents column data types (big assumption!)
var types = groups
.Skip(1)
.First()
.Select(rcell => rcell.Value.GetType())
.ToList();
//Assume first row has the column names
var colnames = groups
.First()
.Select((hcell, idx) => new { Name = hcell.Value.ToString(), index = idx })
.Where(o => tprops.Select(p => p.Name).Contains(o.Name))
.ToList();
//Everything after the header is data
var rowvalues = groups
.Skip(1) //Exclude header
.Select(cg => cg.Select(c => c.Value).ToList());
//Create the collection container
var collection = rowvalues
.Select(row =>
{
var tnew = new T();
colnames.ForEach(colname =>
{
//This is the real wrinkle to using reflection - Excel stores all numbers as double including int
var val = row[colname.index];
var type = types[colname.index];
var prop = tprops.First(p => p.Name == colname.Name);
//If it is numeric it is a double since that is how excel stores all numbers
if (type == typeof(double))
{
if (!string.IsNullOrWhiteSpace(val?.ToString()))
{
//Unbox it
var unboxedVal = (double)val;
//FAR FROM A COMPLETE LIST!!!
if (prop.PropertyType == typeof(Int32))
prop.SetValue(tnew, (int)unboxedVal);
else if (prop.PropertyType == typeof(double))
prop.SetValue(tnew, unboxedVal);
else if (prop.PropertyType == typeof(DateTime))
prop.SetValue(tnew, convertDateTime(unboxedVal));
else
throw new NotImplementedException(String.Format("Type '{0}' not implemented yet!", prop.PropertyType.Name));
}
}
else
{
//Its a string
prop.SetValue(tnew, val);
}
});
return tnew;
});
//Send it back
return collection;
}
Here is a test method:
[TestMethod]
public void Table_To_Object_Test()
{
//Create a test file
var fi = new FileInfo(#"c:\temp\Table_To_Object.xlsx");
using (var package = new ExcelPackage(fi))
{
var workbook = package.Workbook;
var worksheet = workbook.Worksheets.First();
var ThatList = worksheet.Tables.First().ConvertTableToObjects<ExcelData>();
foreach (var data in ThatList)
{
Console.WriteLine(data.Id + data.Name + data.Gender);
}
package.Save();
}
}
Gave this in the console:
1JohnMale
2MariaFemale
3DanielUnknown
Just be careful if you Id field is an number or string in excel since the class is expecting a string.
This is my working version. Note that the resolvers code is not shown but are a spin on my implementation which allows columns to be resolved even though they are named slightly differently in each worksheet.
public static IEnumerable<T> ToArray<T>(this ExcelWorksheet worksheet, List<PropertyNameResolver> resolvers) where T : new()
{
// List of all the column names
var header = worksheet.Cells.GroupBy(cell => cell.Start.Row).First();
// Get the properties from the type your are populating
var properties = typeof(T).GetProperties().ToList();
var start = worksheet.Dimension.Start;
var end = worksheet.Dimension.End;
// Resulting list
var list = new List<T>();
// Iterate the rows starting at row 2 (ie start.Row + 1)
for (int row = start.Row + 1; row <= end.Row; row++)
{
var instance = new T();
for (int col = start.Column; col <= end.Column; col++)
{
object value = worksheet.Cells[row, col].Text;
// Get the column name zero based (ie col -1)
var column = (string)header.Skip(col - 1).First().Value;
// Gets the corresponding property to set
var property = properties.Property(resolvers, column);
try
{
var propertyName = property.PropertyType.IsGenericType
? property.PropertyType.GetGenericArguments().First().FullName
: property.PropertyType.FullName;
// Implement setter code as needed.
switch (propertyName)
{
case "System.String":
property.SetValue(instance, Convert.ToString(value));
break;
case "System.Int32":
property.SetValue(instance, Convert.ToInt32(value));
break;
case "System.DateTime":
if (DateTime.TryParse((string) value, out var date))
{
property.SetValue(instance, date);
}
property.SetValue(instance, FromExcelSerialDate(Convert.ToInt32(value)));
break;
case "System.Boolean":
property.SetValue(instance, (int)value == 1);
break;
}
}
catch (Exception e)
{
// instance property is empty because there was a problem.
}
}
list.Add(instance);
}
return list;
}
// Utility function taken from the above post's inline function.
public static DateTime FromExcelSerialDate(int excelDate)
{
if (excelDate < 1)
throw new ArgumentException("Excel dates cannot be smaller than 0.");
var dateOfReference = new DateTime(1900, 1, 1);
if (excelDate > 60d)
excelDate = excelDate - 2;
else
excelDate = excelDate - 1;
return dateOfReference.AddDays(excelDate);
}
Below code will read excel data into a datatable, which is converted to list of datarows.
if (FileUpload1.HasFile)
{
if (Path.GetExtension(FileUpload1.FileName) == ".xlsx")
{
Stream fs = FileUpload1.FileContent;
ExcelPackage package = new ExcelPackage(fs);
DataTable dt = new DataTable();
dt= package.ToDataTable();
List<DataRow> listOfRows = new List<DataRow>();
listOfRows = dt.AsEnumerable().ToList();
}
}
using OfficeOpenXml;
using System.Data;
using System.Linq;
public static class ExcelPackageExtensions
{
public static DataTable ToDataTable(this ExcelPackage package)
{
ExcelWorksheet workSheet = package.Workbook.Worksheets.First();
DataTable table = new DataTable();
foreach (var firstRowCell in workSheet.Cells[1, 1, 1, workSheet.Dimension.End.Column])
{
table.Columns.Add(firstRowCell.Text);
}
for (var rowNumber = 2; rowNumber <= workSheet.Dimension.End.Row; rowNumber++)
{
var row = workSheet.Cells[rowNumber, 1, rowNumber, workSheet.Dimension.End.Column];
var newRow = table.NewRow();
foreach (var cell in row)
{
newRow[cell.Start.Column - 1] = cell.Text;
}
table.Rows.Add(newRow);
}
return table;
}
}
Yet another way to do it.
I used Ernie S solution but it didn't work if I had empty cells in the first data row (it wasn't able to guess the data type from it).
So instead of getting a data type from Excel table I get it from the T parameter class properties using reflection.
/// <summary>
/// Converts table to list of T objects
/// </summary>
/// <typeparam name="T">The type to return</typeparam>
/// <param name="table">Data source</param>
/// <returns>List of T objects</returns>
public static IEnumerable<T> ConvertToObjects<T>(this ExcelTable table) where T : new()
{
ExcelCellAddress start = table.Address.Start;
ExcelCellAddress end = table.Address.End;
List<ExcelRange> cells = new();
for (int r = start.Row; r <= end.Row; r++)
for (int c = start.Column; c <= end.Column; c++)
cells.Add(table.WorkSheet.Cells[r, c]);
List<IGrouping<int, ExcelRange>> allRows = cells
.GroupBy(cell => cell.Start.Row)
.OrderBy(cell => cell.Key)
.ToList();
IEnumerable<PropertyInfo> typeProperties = typeof(T).GetProperties();
IGrouping<int, ExcelRangeBase> header = allRows.First();
Dictionary<PropertyInfo, int> columns = new();
foreach (ExcelRangeBase col in header)
{
string propName = col.GetValue<string>();
PropertyInfo propInfo = typeProperties.FirstOrDefault(x => x.Name.Equals(propName));
if (propInfo != null)
{
columns.Add(propInfo, col.Start.Column);
}
}
IEnumerable<IGrouping<int, ExcelRangeBase>> rows = allRows.Skip(1);
List<T> objects = new();
foreach (IGrouping<int, ExcelRangeBase> row in rows)
{
T obj = new();
foreach (KeyValuePair<PropertyInfo, int> colInfo in columns)
{
ExcelRangeBase col = row.First(x => x.Start.Column == colInfo.Value);
if (col.Value == null)
continue;
object value = Convert.ChangeType(col.Value, Nullable.GetUnderlyingType(colInfo.Key.PropertyType) ?? colInfo.Key.PropertyType);
colInfo.Key.SetValue(obj, value);
}
objects.Add(obj);
}
return objects;
}
I have got an error on the first answer so I have changed some code line.
Please try my new code, it's working for me.
using OfficeOpenXml;
using OfficeOpenXml.Table;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
public static class ImportExcelReader
{
public static List<T> ImportExcelToList<T>(this ExcelWorksheet worksheet) where T : new()
{
//DateTime Conversion
Func<double, DateTime> convertDateTime = new Func<double, DateTime>(excelDate =>
{
if (excelDate < 1)
{
throw new ArgumentException("Excel dates cannot be smaller than 0.");
}
DateTime dateOfReference = new DateTime(1900, 1, 1);
if (excelDate > 60d)
{
excelDate = excelDate - 2;
}
else
{
excelDate = excelDate - 1;
}
return dateOfReference.AddDays(excelDate);
});
ExcelTable table = null;
if (worksheet.Tables.Any())
{
table = worksheet.Tables.FirstOrDefault();
}
else
{
table = worksheet.Tables.Add(worksheet.Dimension, "tbl" + ShortGuid.NewGuid().ToString());
ExcelAddressBase newaddy = new ExcelAddressBase(table.Address.Start.Row, table.Address.Start.Column, table.Address.End.Row + 1, table.Address.End.Column);
//Edit the raw XML by searching for all references to the old address
table.TableXml.InnerXml = table.TableXml.InnerXml.Replace(table.Address.ToString(), newaddy.ToString());
}
//Get the cells based on the table address
List<IGrouping<int, ExcelRangeBase>> groups = table.WorkSheet.Cells[table.Address.Start.Row, table.Address.Start.Column, table.Address.End.Row, table.Address.End.Column]
.GroupBy(cell => cell.Start.Row)
.ToList();
//Assume the second row represents column data types (big assumption!)
List<Type> types = groups.Skip(1).FirstOrDefault().Select(rcell => rcell.Value.GetType()).ToList();
//Get the properties of T
List<PropertyInfo> modelProperties = new T().GetType().GetProperties().ToList();
//Assume first row has the column names
var colnames = groups.FirstOrDefault()
.Select((hcell, idx) => new
{
Name = hcell.Value.ToString(),
index = idx
})
.Where(o => modelProperties.Select(p => p.Name).Contains(o.Name))
.ToList();
//Everything after the header is data
List<List<object>> rowvalues = groups
.Skip(1) //Exclude header
.Select(cg => cg.Select(c => c.Value).ToList()).ToList();
//Create the collection container
List<T> collection = new List<T>();
foreach (List<object> row in rowvalues)
{
T tnew = new T();
foreach (var colname in colnames)
{
//This is the real wrinkle to using reflection - Excel stores all numbers as double including int
object val = row[colname.index];
Type type = types[colname.index];
PropertyInfo prop = modelProperties.FirstOrDefault(p => p.Name == colname.Name);
//If it is numeric it is a double since that is how excel stores all numbers
if (type == typeof(double))
{
//Unbox it
double unboxedVal = (double)val;
//FAR FROM A COMPLETE LIST!!!
if (prop.PropertyType == typeof(int))
{
prop.SetValue(tnew, (int)unboxedVal);
}
else if (prop.PropertyType == typeof(double))
{
prop.SetValue(tnew, unboxedVal);
}
else if (prop.PropertyType == typeof(DateTime))
{
prop.SetValue(tnew, convertDateTime(unboxedVal));
}
else if (prop.PropertyType == typeof(string))
{
prop.SetValue(tnew, val.ToString());
}
else
{
throw new NotImplementedException(string.Format("Type '{0}' not implemented yet!", prop.PropertyType.Name));
}
}
else
{
//Its a string
prop.SetValue(tnew, val);
}
}
collection.Add(tnew);
}
return collection;
}
}
How to call this function? please view below code;
private List<FundraiserStudentListModel> GetStudentsFromExcel(HttpPostedFileBase file)
{
List<FundraiserStudentListModel> list = new List<FundraiserStudentListModel>();
if (file != null)
{
try
{
using (ExcelPackage package = new ExcelPackage(file.InputStream))
{
ExcelWorkbook workbook = package.Workbook;
if (workbook != null)
{
ExcelWorksheet worksheet = workbook.Worksheets.FirstOrDefault();
if (worksheet != null)
{
list = worksheet.ImportExcelToList<FundraiserStudentListModel>();
}
}
}
}
catch (Exception err)
{
//save error log
}
}
return list;
}
FundraiserStudentListModel here:
public class FundraiserStudentListModel
{
public string Name { get; set; }
public string Email { get; set; }
public string Phone { get; set; }
}
Working solution with validate email,mobile number
public class ExcelProcessing
{
public List<ExcelUserData> ReadExcel()
{
string path = Config.folderPath + #"\MemberUploadFormat.xlsx";
using (var excelPack = new ExcelPackage())
{
//Load excel stream
using (var stream = File.OpenRead(path))
{
excelPack.Load(stream);
}
//Lets Deal with first worksheet.(You may iterate here if dealing with multiple sheets)
var ws = excelPack.Workbook.Worksheets[0];
List<ExcelUserData> userList = new List<ExcelUserData>();
int colCount = ws.Dimension.End.Column; //get Column Count
int rowCount = ws.Dimension.End.Row;
for (int row = 2; row <= rowCount; row++) // start from to 2 omit header
{
bool IsValid = true;
ExcelUserData _user = new ExcelUserData();
for (int col = 1; col <= colCount; col++)
{
if (col == 1)
{
_user.FirstName = ws.Cells[row, col].Value?.ToString().Trim();
if (string.IsNullOrEmpty(_user.FirstName))
{
_user.ErrorMessage += "Enter FirstName <br/>";
IsValid = false;
}
}
else if (col == 2)
{
_user.Email = ws.Cells[row, col].Value?.ToString().Trim();
if (string.IsNullOrEmpty(_user.Email))
{
_user.ErrorMessage += "Enter Email <br/>";
IsValid = false;
}
else if (!IsValidEmail(_user.Email))
{
_user.ErrorMessage += "Invalid Email Address <br/>";
IsValid = false;
}
}
else if (col ==3)
{
_user.MobileNo = ws.Cells[row, col].Value?.ToString().Trim();
if (string.IsNullOrEmpty(_user.MobileNo))
{
_user.ErrorMessage += "Enter Mobile No <br/>";
IsValid = false;
}
else if (_user.MobileNo.Length != 10)
{
_user.ErrorMessage += "Invalid Mobile No <br/>";
IsValid = false;
}
}
else if (col == 4)
{
_user.IsAdmin = ws.Cells[row, col].Value?.ToString().Trim();
if (string.IsNullOrEmpty(_user.IsAdmin))
{
_user.IsAdmin = "0";
}
}
_user.IsValid = IsValid;
}
userList.Add(_user);
}
return userList;
}
}
public static bool IsValidEmail(string email)
{
Regex regex = new Regex(#"^([a-zA-Z0-9_\-\.]+)#((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.)|(([a-zA-Z0-9\-]+\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})(\]?)$",
RegexOptions.CultureInvariant | RegexOptions.Singleline);
return regex.IsMatch(email);
}
}
With this code you won't get an error because a cell is null. it will also cast the data type according to the properties in your class!
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.OleDb;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using OfficeOpenXml;
public static class ReadExcel
{
public static List<T> ReadExcelToList<T>(this ExcelWorksheet worksheet) where T : new()
{
List<T> collection = new List<T>();
try
{
DataTable dt = new DataTable();
foreach (var firstRowCell in new T().GetType().GetProperties().ToList())
{
//Add table colums with properties of T
dt.Columns.Add(firstRowCell.Name);
}
for (int rowNum = 2; rowNum <= worksheet.Dimension.End.Row; rowNum++)
{
var wsRow = worksheet.Cells[rowNum, 1, rowNum, worksheet.Dimension.End.Column];
DataRow row = dt.Rows.Add();
foreach (var cell in wsRow)
{
row[cell.Start.Column - 1] = cell.Text;
}
}
//Get the colums of table
var columnNames = dt.Columns.Cast<DataColumn>().Select(c => c.ColumnName).ToList();
//Get the properties of T
List<PropertyInfo> properties = new T().GetType().GetProperties().ToList();
collection = dt.AsEnumerable().Select(row =>
{
T item = Activator.CreateInstance<T>();
foreach (var pro in properties)
{
if (columnNames.Contains(pro.Name) || columnNames.Contains(pro.Name.ToUpper()))
{
PropertyInfo pI = item.GetType().GetProperty(pro.Name);
pro.SetValue(item, (row[pro.Name] == DBNull.Value) ? null : Convert.ChangeType(row[pro.Name], (Nullable.GetUnderlyingType(pI.PropertyType) == null) ? pI.PropertyType : Type.GetType(pI.PropertyType.GenericTypeArguments[0].FullName)));
}
}
return item;
}).ToList();
}
catch (Exception ex)
{
//Save error log
}
return collection;
}
}
How to call this function? please view below code;
public List<Users> GetStudentsFromExcel(HttpPostedFileBase file)
{
List<Users> list = new List<Users>();
if (file != null)
{
try
{
using (ExcelPackage package = new ExcelPackage(file.InputStream))
{
ExcelWorkbook workbook = package.Workbook;
if (workbook != null)
{
ExcelWorksheet worksheet = workbook.Worksheets.FirstOrDefault();
if (worksheet != null)
{
list = worksheet.ReadExcelToList<Users>();
//Your code
}
}
}
}
catch (Exception ex)
{
//Save error log
}
}
return list;
}
public class Users
{
public string Code { get; set; }
public string Name { get; set; }
public string Email { get; set; }
public string Phone { get; set; }
public string Address { get; set; }
public DateTime CreatedAt { get; set; }
}
Hope to help someone!

WPF Control DataGrid Cell Color Change

I come to write my first WPF Control. It contains a DataGrid filled with data. Change the font of some of the cells based by function that I call in the main form.Its here I really hit rock bottom.Anyone has ideas?
=========================================================================
I Looked in the topic above and still can't get my head around how to make it function that is dynamically called from the control in the main form. Here is the Code I am working on so far.
public void PaintCell(int row, int column)
{
DataGridRow rowContainer = GetRow(row);
if (rowContainer != null)
{
DataGridCellsPresenter presenter = GetVisualChild<DataGridCellsPresenter>(rowContainer);
DataGridCell cell = (DataGridCell)presenter.ItemContainerGenerator.ContainerFromIndex(column);
if (cell == null)
{
CalendarGridView.ScrollIntoView(rowContainer, CalendarGridView.Columns[column]);
cell = (DataGridCell)presenter.ItemContainerGenerator.ContainerFromIndex(column);
// I want to the text in the cell in red
}
}
}
private DataGridRow GetRow(int index)
{
DataGridRow row = (DataGridRow)CalendarGridView.ItemContainerGenerator.ContainerFromIndex(index);
if (row == null)
{
CalendarGridView.UpdateLayout();
CalendarGridView.ScrollIntoView(CalendarGridView.Items[index]);
row = (DataGridRow)CalendarGridView.ItemContainerGenerator.ContainerFromIndex(index);
}
return row;
}
public static T GetVisualChild<T>(Visual parent) where T : Visual
{
T child = default(T);
int numVisuals = VisualTreeHelper.GetChildrenCount(parent);
for (int i = 0; i < numVisuals; i++)
{
Visual v = (Visual)VisualTreeHelper.GetChild(parent, i);
child = v as T;
if (child == null)
{
child = GetVisualChild<T>(v);
}
if (child != null)
{
break;
}
}
return child;
}
}
Don't said is the best idea since it's my very first experience with WPF.

how to get and set the value of a cell in WPF DataGrid by providing columnidnex and row index?

Can you please tell , how to get and set the value of a cell in WPF DataGrid by providing columnidnex and row index
Thanks
This is not as easy as it should be in WPF, it is all about selecting visual children (the WPF 'Visual' object type). This blog post explains quite well how to accomplish it. Google it and you will probably find a lot more.
// Getting Data from Grid Cell
string cellContent = ((TextBox)(GetCell(3).Content)).Text; //As I want the value of 3 column
public DataGridCell GetCell(int column)
{
DataGridRow rowContainer = GetRow();
if (rowContainer != null)
{
DataGridCellsPresenter presenter = GetVisualChild<DataGridCellsPresenter>(rowContainer);
// Try to get the cell but it may possibly be virtualized.
DataGridCell cell = (DataGridCell)presenter.ItemContainerGenerator.ContainerFromIndex(column);
if (cell == null)
{
// Now try to bring into view and retreive the cell.
customDataGrid.UCdataGridView.ScrollIntoView(rowContainer, customDataGrid.UCdataGridView.Columns[column]);
cell = (DataGridCell)presenter.ItemContainerGenerator.ContainerFromIndex(column);
}
return cell;
}
return null;
}
public DataGridRow GetRow()
{
DataGridRow row = (DataGridRow)customDataGrid.UCdataGridView.ItemContainerGenerator.ContainerFromIndex(_currentRowIndex);
if (row == null)
{
// May be virtualized, bring into view and try again.
customDataGrid.UCdataGridView.UpdateLayout();
customDataGrid.UCdataGridView.ScrollIntoView(customDataGrid.UCdataGridView.Items[_currentRowIndex]);
row = (DataGridRow)customDataGrid.UCdataGridView.ItemContainerGenerator.ContainerFromIndex(_currentRowIndex);
}
return row;
}
public static T GetVisualChild<T>(Visual parent) where T : Visual
{
T child = default(T);
int numVisuals = VisualTreeHelper.GetChildrenCount(parent);
for (int i = 0; i < numVisuals; i++)
{
Visual v = (Visual)VisualTreeHelper.GetChild(parent, i);
child = v as T;
if (child == null)
{
child = GetVisualChild<T>(v);
}
if (child != null)
{
break;
}
}
return child;
}
// Setting Data to Grid Cell
if (GetCell(3).Content is TextBlock) // if grid cell is not editable
{
((TextBlock)(GetCell(3).Content)).Text = "sometext";
}
else // TextBox - if grid cell is editable
{
((TextBox)(GetCell(3).Content)).Text = "sometext";
}

Categories