Is there more efficient way to build HTML table than the one I'm trying on right now?
I'm getting an object and it has some list of entities in it. So I need to pass through each of them and build first a cell and then add it to an row and finally adding it in table.
The thing I'm trying on is totally messy, kind of works, but it has too much of redundant code.
public static string CreateNotificationMailMessage(NotificationMailMessage mailMessageObject)
{
var table = new HtmlTable();
var mailMessage = new StringBuilder();
string html;
if (mailMessageObject.InvalidCompanies.Any())
{
HtmlTableRow row;
HtmlTableCell cell;
foreach (var invalidCompany in mailMessageObject.InvalidCompanies)
{
row = new HtmlTableRow();
cell = new HtmlTableCell();
cell.InnerText = invalidCompany.BusinessName;
row.Cells.Add(cell);
cell.InnerText = invalidCompany.SwiftBIC;
row.Cells.Add(cell);
cell.InnerText = invalidCompany.IBAN;
row.Cells.Add(cell);
table.Rows.Add(row);
}
}
using (var sw = new StringWriter())
{
table.RenderControl(new HtmlTextWriter(sw));
html = sw.ToString();
}
mailMessage.AppendFormat(html);
return mailMessage.ToString();
}
At the end I want to return text version of created HTML table.
The problem is that I have much more properties than those 3 (BusinessName, SwiftBIC and IBAN) and plus I have one more list of objects inside of mailMessageObject, so the code would be terrible.
Anybody has an idea how to solve this in simpler and cleaner way?
I would just like to supplement Steve Harris' answer with a class library that is a little more built out. His answer is a totally elegant solution that made a windows service I was creating not have to reference System.Web for no good reason!
Classes Defined:
public static class Html
{
public class Table : HtmlBase, IDisposable
{
public Table(StringBuilder sb, string classAttributes = "", string id = "") : base(sb)
{
Append("<table");
AddOptionalAttributes(classAttributes, id);
}
public void StartHead(string classAttributes = "", string id = "")
{
Append("<thead");
AddOptionalAttributes(classAttributes, id);
}
public void EndHead()
{
Append("</thead>");
}
public void StartFoot(string classAttributes = "", string id = "")
{
Append("<tfoot");
AddOptionalAttributes(classAttributes, id);
}
public void EndFoot()
{
Append("</tfoot>");
}
public void StartBody(string classAttributes = "", string id = "")
{
Append("<tbody");
AddOptionalAttributes(classAttributes, id);
}
public void EndBody()
{
Append("</tbody>");
}
public void Dispose()
{
Append("</table>");
}
public Row AddRow(string classAttributes = "", string id = "")
{
return new Row(GetBuilder(), classAttributes, id);
}
}
public class Row : HtmlBase, IDisposable
{
public Row(StringBuilder sb, string classAttributes = "", string id = "") : base(sb)
{
Append("<tr");
AddOptionalAttributes(classAttributes, id);
}
public void Dispose()
{
Append("</tr>");
}
public void AddCell(string innerText, string classAttributes = "", string id = "", string colSpan = "")
{
Append("<td");
AddOptionalAttributes(classAttributes, id, colSpan);
Append(innerText);
Append("</td>");
}
}
public abstract class HtmlBase
{
private StringBuilder _sb;
protected HtmlBase(StringBuilder sb)
{
_sb = sb;
}
public StringBuilder GetBuilder()
{
return _sb;
}
protected void Append(string toAppend)
{
_sb.Append(toAppend);
}
protected void AddOptionalAttributes(string className = "", string id = "", string colSpan = "")
{
if (!id.IsNullOrEmpty())
{
_sb.Append($" id=\"{id}\"");
}
if (!className.IsNullOrEmpty())
{
_sb.Append($" class=\"{className}\"");
}
if (!colSpan.IsNullOrEmpty())
{
_sb.Append($" colspan=\"{colSpan}\"");
}
_sb.Append(">");
}
}
}
Usage:
StringBuilder sb = new StringBuilder();
using (Html.Table table = new Html.Table(sb, id: "some-id"))
{
table.StartHead();
using (var thead = table.AddRow())
{
thead.AddCell("Category Description");
thead.AddCell("Item Description");
thead.AddCell("Due Date");
thead.AddCell("Amount Budgeted");
thead.AddCell("Amount Remaining");
}
table.EndHead();
table.StartBody();
foreach (var alert in alertsForUser)
{
using (var tr = table.AddRow(classAttributes: "someattributes"))
{
tr.AddCell(alert.ExtendedInfo.CategoryDescription);
tr.AddCell(alert.ExtendedInfo.ItemDescription);
tr.AddCell(alert.ExtendedInfo.DueDate.ToShortDateString());
tr.AddCell(alert.ExtendedInfo.AmountBudgeted.ToString("C"));
tr.AddCell(alert.ExtendedInfo.ItemRemaining.ToString("C"));
}
}
table.EndBody();
}
return sb.ToString();
As I've recently come to play with creating IDisposable classes, I think this would be both efficient for this specific task, and much easier to read:
Create some very simple classes
/// <summary>
/// https://stackoverflow.com/a/36476600/2343
/// </summary>
public class Table : IDisposable
{
private StringBuilder _sb;
public Table(StringBuilder sb, string id = "default", string classValue="")
{
_sb = sb;
_sb.Append($"<table id=\"{id}\" class=\"{classValue}\">\n");
}
public void Dispose()
{
_sb.Append("</table>");
}
public Row AddRow()
{
return new Row(_sb);
}
public Row AddHeaderRow()
{
return new Row(_sb, true);
}
public void StartTableBody()
{
_sb.Append("<tbody>");
}
public void EndTableBody()
{
_sb.Append("</tbody>");
}
}
public class Row : IDisposable
{
private StringBuilder _sb;
private bool _isHeader;
public Row(StringBuilder sb, bool isHeader = false)
{
_sb = sb;
_isHeader = isHeader;
if (_isHeader)
{
_sb.Append("<thead>\n");
}
_sb.Append("\t<tr>\n");
}
public void Dispose()
{
_sb.Append("\t</tr>\n");
if (_isHeader)
{
_sb.Append("</thead>\n");
}
}
public void AddCell(string innerText)
{
_sb.Append("\t\t<td>\n");
_sb.Append("\t\t\t"+innerText);
_sb.Append("\t\t</td>\n");
}
}
}
Then you can define your table using:
StringBuilder sb = new StringBuilder();
using (Html.Table table = new Html.Table(sb))
{
foreach (var invalidCompany in mailMessageObject.InvalidCompanies)
{
using (Html.Row row = table.AddRow())
{
row.AddCell(invalidCompany.BusinessName);
row.AddCell(invalidCompany.SwiftBIC);
row.AddCell(invalidCompany.IBAN);
}
}
}
string finishedTable = sb.ToString();
It is a decent approach, and just 'what it takes' to output something as complicated as HTML - unless you want to do it using plain strings (which is just as messy, if not worse).
One improvement: do not use the same cell object multiple times, you run the risk of getting incorrect output. Improved code:
row.Cells.Add(new HtmlTableCell { InnerText = invalidCompany.BusinessName });
row.Cells.Add(new HtmlTableCell { InnerText = invalidCompany.SwiftBIC });
row.Cells.Add(new HtmlTableCell { InnerText = invalidCompany.IBAN });
Of course you can also create your own helpers for creating cells, for creating a row full of cells, etc. There are also good libraries for this, e.g. see https://www.nuget.org/packages/HtmlTags/.
I think maybe you can add a function to get all the properties from your object. And then just iterate over them. Also you can create a list of properties that need to be displayed in your message.
private static PropertyInfo[] GetProperties(object obj)
{
return obj.GetType().GetProperties();
}
// -------
foreach (var invalidCompany in mailMessageObject.InvalidCompanies)
{
var properties = GetProperties(invalidCompany);
foreach (var p in properties)
{
string name = p.Name;
if(propertiesThatNeedToBeDisplayed.Contains(name)
{
cell.InnerText = p.GetValue(invalidCompany, null);
row.Cells.Add(cell);
table.Rows.Add(row);
}
}
}
Related
I want to use the Builder pattern and use that by method chaining.
This is my class:
public class SQLGenerator : ISQLGenerator
{
private readonly SQLGeneratorModel generatorModel;
public SQLGenerator()
{
generatorModel = new SQLGeneratorModel();
}
public ISQLGenerator From(string TableName)
{
generatorModel.TabelName = string.Format("FROM {0}", TableName);
return this;
}
public ISQLGenerator Select(List<string> SelectItems)
{
StringBuilder select = new StringBuilder();
var lastItem = SelectItems.Last();
foreach (var item in SelectItems)
{
if (!item.Equals(lastItem))
{
select.Append(item).Append(" , ");
}
else
{
select.Append(item);
}
}
generatorModel.Select =string.Format("SELECT {0}", select.ToString());
return this;
}
This is how this class is used:
SQLGenerator generator = new SQLGenerator();
List<string> select = new List<string>();
select.Add("id");
select.Add("Name");
var str = generator.Select(select).From("AppUsers");
Console.WriteLine(str);
Now I expect a string to be returned like this: SELECT ID , NAME FROM APPUSER but it shows me this result in the console:
SqlPagingGenerator.Pattern.SQLGenerator
How can I show the string result?
The reason for your error is that you are returning an object from the method not a string.
All you have to do is to override the ToString method of your class to return the string you want.
public class SQLGenerator : ISQLGenerator
{
....
public override string ToString()
{
return $"{generatorModel.TableName} {generatorModel.Select}";
}
}
Good day,
I try that when I connect two documents, the numbered lists are reset. As in the example below.
I use the Xceed.Words.NET DocX libary.
In this Sample, the Template is :
Test Header
List:
1) <-This should be one
And the Result is:
Test Header
List:
1) <-This should be one
Test Header
List:
2) <-This should be one
Test Header
List:
3) <-This should be one
With the following code I am able to create the lists with similar formatting, but the formatting does not match 100%. Does anyone have any idea which way to try otherwise?
Note the number of existing paragraphs and call the function to reset the list after inserting the document.
/// <summary>
/// Insert complete WordFile
/// </summary>
/// <param name="wordFile"></param>
public void InsertWordTemplate(IWordFile wordFile, bool InsertPageBreak)
{
if (wordFile != null && wordFile.IsFileOk)
{
int pargraphCount = Document.Paragraphs.Count - 1;
// NotNeeded for this Problem wordFile.RemoveStyles();
// NotNeeded for this Problem RemoveHeaderAndFooter(wordFile);
Document.InsertDocument(wordFile.Document);
// NotNeeded for this Problem ReplaceSectionBreak(InsertPageBreak, pargraphCount);
ResetNumberedList(pargraphCount);
logger.Info("Word file inserted: " + wordFile.File.FullName);
}
else
{
logger.Warn("Word file is not okay - will not be inserted: " + wordFile?.File?.FullName);
}
}
In the Word document, three different names are used in a list, only from the 4th level is worked with a level. For other Word templates they are called different.
private void ResetNumberedList(int pargraphCount)
{
string styleName1 = "ListNumbers";
string styleName2 = "PIgeordneteListe2Ebene";
string styleName3 = "PIgeordneteListe3Ebene";
NumberedListReset numberedListReset = new NumberedListReset(Document, styleName1, styleName2, styleName3);
bool OnlyFirstFoundList = true;
numberedListReset.Reset(pargraphCount, OnlyFirstFoundList);
}
Below is the helper class with which I try to reset the numbering. I do this by myself
I notice the formatting of the individual list items, create new lists, fill them with the old values, set the styles correctly again and then insert everything into old place.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Linq;
using Xceed.Document.NET;
using Xceed.Words.NET;
namespace PIB.Report.DataWriter.WordExport
{
public class NumberedListReset
{
private readonly DocX _Document;
private readonly string _StyleName1;
private readonly string _StyleName2;
private readonly string _StyleName3;
public NumberedListReset(DocX Document, string StyleName1, string StyleName2, string StyleName3)
{
_Document = Document;
_StyleName1 = StyleName1;
_StyleName2 = StyleName2;
_StyleName3 = StyleName3;
}
public void Reset(int StartParagraphNumber, bool OnlyFirstFinding)
{
for (int i = StartParagraphNumber; i < _Document.Paragraphs.Count; i++)
{
var paragraph = _Document.Paragraphs[i];
if (paragraph.IsListItem == true && paragraph.ListItemType == ListItemType.Numbered && paragraph.StyleName == _StyleName1)
{
//int? numId = GetNumId(paragraph);
//if (numId != -1)
//{
//}
ResetFoundList(ref i);
if (OnlyFirstFinding == true)
{
break;
}
}
}
}
private void ResetFoundList(ref int ParagraphCounter)
{
List<ParagraphMemorize> ParagraphMemorizes = CreateMemorizeListItems(ParagraphCounter);
if (ParagraphMemorizes.Count != 0)
{
RemoveOldParagraphsFromDocument(ParagraphMemorizes);
List numberedList = CreateNewDocumentList();
FillDocumentList(ParagraphMemorizes, numberedList);
List<Paragraph> actualListData = numberedList.Items;
ResetSyleNames(ParagraphMemorizes, actualListData);
InsertNewParagraphsToDocument(ParagraphCounter, actualListData);
ParagraphCounter += ParagraphMemorizes.Count;
}
}
private List<ParagraphMemorize> CreateMemorizeListItems(int ParagraphCounter)
{
List<ParagraphMemorize> ParagraphMemorizes = new List<ParagraphMemorize>();
for (int ii = ParagraphCounter; ii < _Document.Paragraphs.Count; ii++)
{
var paragraph = _Document.Paragraphs[ii];
if (!NameIsKnown(paragraph.StyleName))
{
break;
}
ParagraphMemorize paragraphMemorize = new ParagraphMemorize(paragraph);
paragraphMemorize.ListLevel = GetListLevel(paragraph);
ParagraphMemorizes.Add(paragraphMemorize);
}
return ParagraphMemorizes;
}
private void RemoveOldParagraphsFromDocument(List<ParagraphMemorize> ParagraphMemorizes)
{
ParagraphMemorizes.ForEach(m => _Document.RemoveParagraph(m.Paragraph));
}
private List CreateNewDocumentList()
{
return _Document.AddList(startNumber: 1);
}
private void FillDocumentList(List<ParagraphMemorize> ParagraphMemorizes, List numberedList)
{
for (var ii = 0; ii < ParagraphMemorizes.Count; ii++)
{
//numberedList.AddItem(ParagraphMemorizes[ii].Paragraph); //Raised an Error
ParagraphMemorize paragraphMemorize = ParagraphMemorizes[ii];
int listLevel = GetListLevel(paragraphMemorize);
_Document.AddListItem(numberedList, paragraphMemorize.Text, listLevel);
}
}
private static void ResetSyleNames(List<ParagraphMemorize> ParagraphMemorizes, List<Paragraph> actualListData)
{
for (int ii = 0; ii < actualListData.Count; ii++)
{
actualListData[ii].StyleName = ParagraphMemorizes[ii].StyleName;
}
}
private void InsertNewParagraphsToDocument(int i, List<Paragraph> actualListData)
{
Paragraph paragraph = _Document.Paragraphs[i];
for (int ii = 0; ii < actualListData.Count; ii++)
{
paragraph.InsertParagraphBeforeSelf(actualListData[ii]);
}
}
private bool NameIsKnown(string Name)
{
return Name == _StyleName1 | Name == _StyleName2 | Name == _StyleName3;
}
private int GetListLevel(ParagraphMemorize paragraphMemorize)
{
if (paragraphMemorize.StyleName == _StyleName1)
{
return 0;
}
else if (paragraphMemorize.StyleName == _StyleName2)
{
return 1;
}
else if (paragraphMemorize.StyleName == _StyleName3)
{
return (int)paragraphMemorize.ListLevel;
}
else
{
return 0;
}
}
private int? GetNumId(Paragraph paragraph)
{
var numIds = paragraph.ParagraphNumberProperties.Descendants().Where(e => e.Name.LocalName.Equals("numId"));
foreach (var numId in numIds)
{
XNamespace nsW = Namespace.WordNamespace;
var values = numId.Attributes(XName.Get("val", nsW.ToString()));
foreach (var value in values)
{
int resultId = 0;
int.TryParse(value.Value, out resultId);
return resultId;
}
}
return null;
}
private int? GetListLevel(Paragraph paragraph)
{
var numIds = paragraph.ParagraphNumberProperties.Descendants().Where(e => e.Name.LocalName.Equals("ilvl"));
foreach (var numId in numIds)
{
XNamespace nsW = Namespace.WordNamespace;
var values = numId.Attributes(XName.Get("val", nsW.ToString()));
foreach (var value in values)
{
int resultId = 0;
int.TryParse(value.Value, out resultId);
return resultId;
}
}
return null;
}
private class ParagraphMemorize
{
public Paragraph Paragraph { get; set; }
public string Text { get; set; }
public string StyleName { get; set; }
public int? ListLevel { get; set; }
public ParagraphMemorize(Paragraph Paragraph)
{
this.Paragraph = Paragraph;
this.Text = Paragraph.Text;
this.StyleName = Paragraph.StyleName;
}
}
}
}
I am relatively new to C# (WinForms), and had a question regarding combo boxes. I have a combo box of Reviewer objects (it is a custom class with an overridden ToString method) and am currently attempting to go through all the checked items and use them to generate a setup file.
Here is how the combo box is populated (populated on form load). Parameters is just a collection of linked lists and parsing code.
for (int i = 0; i < parameters.GetUsers().Count; i++)
{
UserList.Items.Add(parameters.GetUsersArray()[i], parameters.GetUsersArray()[i].isSelected());
}
Here is how I am trying to read it. setup is a StringBuilder. The problem is that GetID is not defined. Does the add function above cast the Reviewer object to a Object object? It looks a little funny since it creates a file fed into a Perl script. A sample desired output line looks like this: inspector0 => "chg0306",
for (int i = 0; i < UserList.CheckedItems.Count; i++)
{
setup.AppendLine("inspector" + i.ToString() + " => \t \"" +
UserList.CheckedItems[i].GetID() + "\",");
}
Here is the users class: (Sample User is ID = aaa0000 name: Bob Joe)
public class Reviewer
{
private string name;
private string id;
private bool selected;
public Reviewer(string newName, string newID, bool newSelected)
{
name = newName;
id = newID;
selected = newSelected;
}
public string GetName()
{
return name;
}
public override string ToString()
{
//string retVal = new string(' ', id.Length + name.Length + 1);
string retVal = id + '\t' + name;
return retVal;
}
public string GetID()
{
return id;
}
public bool isSelected()
{
return selected;
}
}
For posterity, here is the Parameters class:
public class ParameterLists
{
public ParameterLists()
{
projects = new LinkedList<string>();
reviewers = new LinkedList<Reviewer>();
}
public enum FileContents {
PROJECT_LIST,
USERS_LIST,
}
public LinkedList<Reviewer> GetUsers()
{
return reviewers;
}
public LinkedList<string> GetProjects()
{
return projects;
}
public Reviewer[] GetUsersArray()
{
Reviewer[] userArray = new Reviewer[reviewers.Count];
reviewers.CopyTo(userArray, 0);
return userArray;
}
public string[] GetProjectsArray()
{
String[] projectArray = new String[projects.Count];
projects.CopyTo(projectArray, 0);
return projectArray;
}
public void LoadParameters(string fileName)
{
//Reads the parameters from the input file.
}
private void CreateDefaultFile(string fileName)
{
// Create the file from the defaultfile , if it exists.
// Otherwise create a blank default file.
}
private LinkedList <string> projects;
private LinkedList <Reviewer> reviewers;
}
I am probably missing something simple, coming from embedded C++. Any help would be appreciated.
You have to cast that object:
((Reviewer)UserList.CheckedItems[i]).GetID()
While exploring Roslyn I put together a small app that should include a trace statement as the first statement in every method found in a Visual Studio Solution. My code is buggy and is only updating the first method.
The line that is not working as expected is flagged with a “TODO” comment. Please, advise.
I also welcome style recommendations that would create a more streamlined/readable solution.
Thanks in advance.
...
private void TraceBtn_Click(object sender, RoutedEventArgs e) {
var myWorkSpace = new MyWorkspace("...Visual Studio 2012\Projects\Tests.sln");
myWorkSpace.InjectTrace();
myWorkSpace.ApplyChanges();
}
...
using System;
using System.Linq;
using Roslyn.Compilers;
using Roslyn.Compilers.CSharp;
using Roslyn.Services;
namespace InjectTrace
{
public class MyWorkspace
{
private string solutionFile;
public string SolutionFile {
get { return solutionFile; }
set {
if (string.IsNullOrEmpty(value)) throw new Exception("Invalid Solution File");
solutionFile = value;
}
}
private IWorkspace loadedWorkSpace;
public IWorkspace LoadedWorkSpace { get { return loadedWorkSpace; } }
public ISolution CurrentSolution { get; private set; }
public IProject CurrentProject { get; private set; }
public IDocument CurrentDocument { get; private set; }
public ISolution NewSolution { get; private set; }
public MyWorkspace(string solutionFile) {
this.SolutionFile = solutionFile;
this.loadedWorkSpace = Workspace.LoadSolution(SolutionFile);
}
public void InjectTrace()
{
int projectCtr = 0;
int documentsCtr = 0;
int transformedMembers = 0;
int transformedClasses = 0;
this.CurrentSolution = this.LoadedWorkSpace.CurrentSolution;
this.NewSolution = this.CurrentSolution;
//For Each Project...
foreach (var projectId in LoadedWorkSpace.CurrentSolution.ProjectIds)
{
CurrentProject = NewSolution.GetProject(projectId);
//..for each Document in the Project..
foreach (var docId in CurrentProject.DocumentIds)
{
CurrentDocument = NewSolution.GetDocument(docId);
var docRoot = CurrentDocument.GetSyntaxRoot();
var newDocRoot = docRoot;
var classes = docRoot.DescendantNodes().OfType<ClassDeclarationSyntax>();
IDocument newDocument = null;
//..for each Class in the Document..
foreach (var #class in classes) {
var methods = #class.Members.OfType<MethodDeclarationSyntax>();
//..for each Member in the Class..
foreach (var currMethod in methods) {
//..insert a Trace Statement
var newMethod = InsertTrace(currMethod);
transformedMembers++;
//TODO: PROBLEM IS HERE
newDocRoot = newDocRoot.ReplaceNode(currMethod, newMethod);
}
if (transformedMembers != 0) {
newDocument = CurrentDocument.UpdateSyntaxRoot(newDocRoot);
transformedMembers = 0;
transformedClasses++;
}
}
if (transformedClasses != 0) {
NewSolution = NewSolution.UpdateDocument(newDocument);
transformedClasses = 0;
}
documentsCtr++;
}
projectCtr++;
if (projectCtr > 2) return;
}
}
public MethodDeclarationSyntax InsertTrace(MethodDeclarationSyntax currMethod) {
var traceText =
#"System.Diagnostics.Trace.WriteLine(""Tracing: '" + currMethod.Ancestors().OfType<NamespaceDeclarationSyntax>().Single().Name + "." + currMethod.Identifier.ValueText + "'\");";
var traceStatement = Syntax.ParseStatement(traceText);
var bodyStatementsWithTrace = currMethod.Body.Statements.Insert(0, traceStatement);
var newBody = currMethod.Body.Update(Syntax.Token(SyntaxKind.OpenBraceToken), bodyStatementsWithTrace,
Syntax.Token(SyntaxKind.CloseBraceToken));
var newMethod = currMethod.ReplaceNode(currMethod.Body, newBody);
return newMethod;
}
public void ApplyChanges() {
LoadedWorkSpace.ApplyChanges(CurrentSolution, NewSolution);
}
}
}
The root problem of you code is that newDocRoot = newDocRoot.ReplaceNode(currMethod, newMethod); somehow rebuilds newDocRoot internal representation of code so next currMethod elements won't be find in it and next ReplaceNode calls will do nothing. It is a situation similar to modifying a collection within its foreach loop.
The solution is to gather all necessary changes and apply them at once with ReplaceNodes method. And this in fact naturally leads to simplification of code, because we do not need to trace all those counters. We simply store all needed transformation and apply them for whole document at once.
Working code after changes:
public void InjectTrace()
{
this.CurrentSolution = this.LoadedWorkSpace.CurrentSolution;
this.NewSolution = this.CurrentSolution;
//For Each Project...
foreach (var projectId in LoadedWorkSpace.CurrentSolution.ProjectIds)
{
CurrentProject = NewSolution.GetProject(projectId);
//..for each Document in the Project..
foreach (var docId in CurrentProject.DocumentIds)
{
var dict = new Dictionary<CommonSyntaxNode, CommonSyntaxNode>();
CurrentDocument = NewSolution.GetDocument(docId);
var docRoot = CurrentDocument.GetSyntaxRoot();
var classes = docRoot.DescendantNodes().OfType<ClassDeclarationSyntax>();
//..for each Class in the Document..
foreach (var #class in classes)
{
var methods = #class.Members.OfType<MethodDeclarationSyntax>();
//..for each Member in the Class..
foreach (var currMethod in methods)
{
//..insert a Trace Statement
dict.Add(currMethod, InsertTrace(currMethod));
}
}
if (dict.Any())
{
var newDocRoot = docRoot.ReplaceNodes(dict.Keys, (n1, n2) => dict[n1]);
var newDocument = CurrentDocument.UpdateSyntaxRoot(newDocRoot);
NewSolution = NewSolution.UpdateDocument(newDocument);
}
}
}
}
I am making a program to check how many courses I have left in my univercity.
I load info from a csv put it's data to a dataset and the display it with a datagrid.
I want some columns(lab and theory) to have checkbox cells so that I can check the courses I passed and then save them back to the csv to load them again later (when I pass somethnig :P).
But I have problem converting those (string) columns to checkboxes as I am not so experienced in c#
That's my code :
string delimiter = ";";
string tablename = "paTable";
filename = "aname";
DataSet dataset = new DataSet();
StreamReader sr = new StreamReader(filename);
DataGridViewColumn column = new DataGridViewColumn();
dataset.Tables.Add(tablename);
dataset.Tables[tablename].Columns.Add("A/A");
dataset.Tables[tablename].Columns.Add("Course");
dataset.Tables[tablename].Columns.Add("Semester");
dataset.Tables[tablename].Columns.Add("Theory");
dataset.Tables[tablename].Columns.Add("Lab");
dataset.Tables[tablename].Columns.Add("Passed");
string alldata = sr.ReadLine();
while (sr.Peek() != -1)
{
alldata = sr.ReadLine();
string[] rows;
rows = alldata.Split("\r".ToCharArray());
foreach (string r in rows)
{
string[] items = r.Split(delimiter.ToCharArray());
dataset.Tables[tablename].Rows.Add(items);
}
this.dataGridView1.DataSource = dataset.Tables[0].DefaultView;
}
Any help would be appreciated !
If you manually created and added the columns to your DataGridView, I think you could keep your DataSet-loading process as is.
Turn off AutoGenerateColumns and add them manually:
dataGridView1.AutoGenerateColumns = false;
dataGridView1.Columns.Add(new DataGridViewTextBoxColumn { Name="Course", ... } );
dataGridView1.Columns.Add(new DataGridViewCheckBoxColumn { Name="Passed", ... } );
...
The grid view should populate after you bind it with your DataView like you're doing:
dataGridView1.DataSource = dataset.Tables[0].DefaultView;
EDIT
Because it relies on strong types and proper binding, a perhaps better alternative would be to create a class to represent a CSV record (types assumed). If this class implements the INotifyPropertyChanged interface the values can be edited in the DataGridView and reflected in the class with minimal effort.
public class CsvDataRow : INotifyPropertyChanged
{
private bool _aa;
private string _course;
private int _semester;
private double _theory;
private double _lab;
private bool _passed;
public bool AA { get { return _aa; } set { if (value == _aa) return; _aa = value; NotifyPropertyChanged("AA"); } }
public string Course { get { return _course; } set { if (value == _course) return; _course = value; NotifyPropertyChanged("Course"); } }
public int Semester { get { return _semester; } set { if (value == _semester) return; _semester = value; NotifyPropertyChanged("Semester"); } }
public double Theory { get { return _theory; } set { if (value == _theory) return; _theory = value; NotifyPropertyChanged("Theory"); } }
public double Lab { get { return _lab; } set { if (value == _lab) return; _lab = value; NotifyPropertyChanged("Lab"); } }
public bool Passed { get { return _passed; } set { if (value == _passed) return; _passed = value; NotifyPropertyChanged("Passed"); } }
char _delimiter;
// static factory method creates object from CSV row
public static CsvDataRow Create(string row, char delimiter)
{
return new CsvDataRow(row, delimiter);
}
// private constructor initializes property values
private CsvDataRow(string row, char delimiter)
{
_delimiter = delimiter;
var values = row.Split(_delimiter);
AA = (values[0].ToString().Equals("1"));
Course = Convert.ToString(values[1]);
Semester = Convert.ToInt32(values[2]);
Theory = Convert.ToDouble(values[3]);
Lab = Convert.ToDouble(values[4]);
Passed = (values[5].ToString().Equals("1"));
}
// a method to convert back into a CSV row
public string ToCsvString()
{
var values = new string[] { (AA ? 1 : 0).ToString(), Course, Semester.ToString(), Theory.ToString(), Lab.ToString(), (Passed ? 1: 0).ToString() };
return string.Join(_delimiter.ToString(), values);
}
// INotifyPropertyChanged interface requires this event
public event PropertyChangedEventHandler PropertyChanged;
// helper method to raise PropertyChanged event
private void NotifyPropertyChanged(string name)
{
if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
The CSV file itself could be represented by a class:
public class CsvDataSource
{
public char Delimiter { get; private set; }
public CsvDataSource()
: this(',')
{ }
public string[] Columns { get; private set; }
public ObservableCollection<CsvDataRow> Rows { get; private set; }
public CsvDataSource(char delimiter)
{
Delimiter = delimiter;
}
public void LoadCsv(string csvFileName)
{
string header;
string data;
using (var reader = new StreamReader(csvFileName))
{
header = reader.ReadLine(); // assumes 1st row is column headers
data = reader.ReadToEnd();
}
Columns = header.Split(Delimiter);
var rows = Regex.Split(data, Environment.NewLine);
if (!rows.Select(row => row.Split(Delimiter)).All(row => row.Length == Columns.Length)) throw new FormatException("Inconsistent data format.");
Rows = new ObservableCollection<CsvDataRow>(rows.Select(row => CsvDataRow.Create(row, Delimiter)));
}
}
The DataGridView can then be bound to the Rows property of a CsvDataSource instance, so the form's code can look like this:
public partial class Form1 : Form
{
private CsvDataSource _data;
private ObservableCollection<CsvDataRow> _rows;
public Form1()
{
InitializeComponent();
}
public void LoadCsv(CsvDataSource data)
{
_data = data;
_rows = _data.Rows;
dataGridView1.DataSource = _rows;
}
public void SaveCsv(string path)
{
using (var writer = new StreamWriter(path))
{
writer.WriteLine(string.Join(_data.Delimiter.ToString(), _data.Columns));
foreach (var row in _rows)
{
writer.WriteLine(row.ToCsvString());
}
}
}
}
Of course you'd have a button calling the SaveCsv method, and you'll want another button to add/remove rows to/from your DataGridView. Also you'll want to wrap file I/O operations in a try/cath block.
This is a quickly summed-up implementation that does require a bit of additional work to be fully functional, but it gives an idea, and works out of the box.
This way, the "defining the columns" part is completely taken care of in the CsvDataRow class which provides a strong type for each record. No DataSet, no DataColumn, no DataGridViewColumn either. Just the data, and some data binding.
You can "convert" a textboxcolumn to a checkbox like this:
dgv.Columns.RemoveAt(<NumberColumn2Remove>);
DataGridViewCheckBoxColumn chk = new DataGridViewCheckBoxColumn;
chk.HeaderText = "<HeaderText>";
chk.Name = "<WhatEver>";
chk.DataPropertyName = "<DataField2Bind2>";
dgv.Columns.Insert(<NumberColumn2Insert>, chk);
You need a DataGridViewCheckBoxColumn:
So you need to replace the
DataGridViewColumn column = new DataGridViewColumn();
with the DataGridViewCheckBoxColumn. Have a read this.