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()
Related
I'm coding at C# and I'm trying to make an OOP representation of a list of topics. I tried countless approaches but I still not being able to reach the desired result.
I want to make a method later that will output it like:
1) Text
1.1) Text
2) Text
2.1) Text
2.2) Text
2.2.1) Text
2.2.2) Text
2.3) Text
3) Text
3.1) Text
When needed to get a single topic, I would like to create a method calling my object like:
private string GetSingleTopic()
{
return $"{Topic.Numerator}) {Topic.Text}"
}
EXAMPLES
Example 1
I would be able to instantiate the object such as:
var variable = new TopicObject
{
"TitleA",
"TitleB",
"TitleC"
}
/* --- OUTPUT ---
1) TitleA
2) TitleB
3) TitleC
--- OUTPUT --- */
Example 2
Be able to instantiate the object such as:
var variable = new TopicObject
{
"TitleA",
"TitleB",
"TitleC":
{
"TitleD":
{
"TitleE"
},
"TitleF":
{
"TitleG",
"TitleH"
}
}
}
/* --- OUTPUT ---
1) TitleA
2) TitleB
3) TitleC
3.1) TitleD
3.1.2) TitleE
3.2) TitleF
3.2.1) TitleG
3.2.2) TitleH
--- OUTPUT --- */
My Approach
This, was one of my many approaches. I couldn't use it because I can't initialize the inner topic List in the way i mentioned, like an hierarchy.
But the structure is pretty similar to what I want to achieve so I decided to put here as an example.
public abstract class TopicBase
{
public List<Topic> Topics { get; set; } // optional
protected TopicBase() { Topics = new List<Topic>(); }
protected TopicBase(List<Topic> topics) { Topics = topics; }
public TopicBase AddTopic(string topicText)
{
var test = new Topic(topicText);
Topics.Add(test);
return this;
}
}
public class Topic
{
public Topic(string text)
{
Numerator++;
Text = text;
}
public int Numerator { get; }
public string Text { get; }
}
public class TopicLevel1 : TopicBase { }
public class TopicLevel2 : TopicBase { }
public class TopicLevel3 : TopicBase { }
Let's start by defining a data structure that can hold the topics:
public class Topics<T> : List<Topic<T>> { }
public class Topic<T> : List<Topic<T>>
{
public T Value { get; private set; }
public Topic(T value, params Topic<T>[] children)
{
this.Value = value;
if (children != null)
this.AddRange(children);
}
}
That allows us to write this code:
var topics = new Topics<string>()
{
new Topic<string>("TitleA"),
new Topic<string>("TitleB"),
new Topic<string>("TitleC",
new Topic<string>("TitleD",
new Topic<string>("TitleE")),
new Topic<string>("TitleF",
new Topic<string>("TitleF"),
new Topic<string>("TitleH")))
};
That matches your "Example 2" data.
To output the result we add two ToOutput methods.
To Topics<T>:
public IEnumerable<string> ToOutput(Func<T, string> format)
=> this.SelectMany((t, n) => t.ToOutput(0, $"{n + 1}", format));
To Topic<T>:
public IEnumerable<string> ToOutput(int depth, string prefix, Func<T, string> format)
{
yield return $"{new string(' ', depth)}{prefix}) {format(this.Value)}";
foreach (var child in this.SelectMany((t, n) => t.ToOutput(depth + 1, $"{prefix}.{n + 1}", format)))
{
yield return child;
}
}
Now I can run this code:
foreach (var line in topics.ToOutput(x => x))
{
Console.WriteLine(line);
}
That gives me:
1) TitleA
2) TitleB
3) TitleC
3.1) TitleD
3.1.1) TitleE
3.2) TitleF
3.2.1) TitleF
3.2.2) TitleH
If the goal is to have some structure that will help with the output of the topic hierarchy, you already have it (and may even be able to simplify it more).
For example, here's an almost-minimal Topic to get what you want:
public class Topic
{
public string Title { get; set; }
public List<Topic> SubTopics { get; private set; } = new();
public Topic() : this("DocRoot") { }
public Topic(string title) => Title = title;
public void AddTopics(List<Topic> subTopics) => SubTopics.AddRange(subTopics);
public void AddTopics(params Topic[] subTopics) => SubTopics.AddRange(subTopics);
public override string ToString() => Title;
}
That is, you have a Topic that can have SubTopics (aka children) and that's all you need.
With that, we can build your second example:
var firstLevelTopics = new List<Topic>();
for (var c = 'A'; c < 'D'; ++c)
{
firstLevelTopics.Add(new Topic(c.ToString()));
}
var cTopic = firstLevelTopics.Last();
cTopic.AddTopics(
new Topic
{
Title = "D",
SubTopics = { new Topic("E") }
},
new Topic
{
Title = "F",
SubTopics = { new Topic("G"), new Topic("H") }
});
Now, imagine if we had a function to print the hierarchy from the list of top-level topics. I'm leaving the final detail for yourself in case this is homework.
PrintTopics(firstLevelTopics);
static void PrintTopics(List<Topic> topics, string prefix = "")
{
// For the simple case, we can just loop and print...
for (var i = 0; i < topics.Count; ++i)
{
var topic = topics[i];
var level = i + 1;
Console.WriteLine($"{prefix}{level}) {topic}");
// ...but, if we want to print the children, we need more.
// Make a recursive call to print the SubTopics
// PrintTopics(<What goes here?>);
}
}
UML is attached. I want to create a readonly property of pre which is an array of string. When I create an object in the main and try to set name and pre it is showing me an error.
UML
using System;
class Unit
{
private string _name;
private string[] _pre;
public Unit(string name, string[] pre)
{
_name = name;
_pre = new string[2];
}
public string Name { get { return _name; } }
public string[] Pre { get { return _pre; } }
}
class Program
{
public static void DisplayInfo(Unit[] _u)
{
for (int i = 0; i < 2; i++)
{
Console.WriteLine(_u[i].Name + _u[i].Pre);
}
}
static void Main(string[] args)
{
Unit[] unitarraytest = new Unit[2];
unitarraytest[0] = new Unit("test 1", "test 3");
unitarraytest[1] = new Unit("test 2", "test 4");
DisplayInfo(unitarraytest);
}
}
Your example makes little sense. You Unit constructor takes a parameter for "Pre", but immediately throws it away and allocates a new empty string array instead. It should probably be written like
class Unit
{
public Unit(string name, string[] pre)
{
Name = name;
Pre = pre;
}
public string Name { get;}
public string[] Pre { get;}
}
When creating Unit objects you actually need to create an array for the "Pre" parameter. Like new Unit("Name", new []{"pre1", "pre2"});
And when outputting the strings you need to access the individual strings in the array, or combine them to a larger string, for example like Console.WriteLine(_u[i].Name + string.Join(" , ", _u[i].Pre));
I have a .json file and a custom class.
I am taking this .json file and putting it in a dynamic variable, so that I can access specific points in the file at run time. See below code
private static dynamic elements = null;
public static dynamic Elements { get { return elements; } }
static Settings()
{
elements = JObject.Parse(Common.GetFile("Elements.json"));
}
In the below function, I am using the dynamic variable above in order to identify smaller "chunks" of the .json file. [See Below]
public void Login(string pUserName, string pPassword)
{
dynamic _module = Settings.Elements.Login;
ElementObject _userName = _module.UserName.ToObject<ElementObject>();
ElementObject _password = _module.Password.ToObject<ElementObject>();
ElementObject _loginBTN = _module.LoginButton.ToObject<ElementObject>();
_userName.OnSendKeys(pUserName);
_password.OnSendKeys(pPassword);
_loginBTN.OnClick();
}
The issue, is that ElementObject.cs has a constructor that requires the public properties to be populated via the .json script. However, when stepping through debugging, the public properties arn't getting set until after the variable declaration. [See images below]
public class ElementObject
{
public string ClassName;
public string CssSelector;
public string Id;
public string LinkText;
public string Name;
public string PartialLinkText;
public string TagName;
public string XPath;
private int index = 0;
private string finalName = "";
private string finalClassName = "";
public ElementObject()
{
var _b = new string[] { nameof(ClassName), nameof(CssSelector), nameof(Id), nameof(LinkText), nameof(Name), nameof(PartialLinkText), nameof(TagName), nameof(XPath) };
var _a = new string[] { ClassName, CssSelector, Id, LinkText, Name, PartialLinkText, TagName, XPath };
index = Array.IndexOf(_a, _a.FirstOrDefault(s => !string.IsNullOrEmpty(s)));
finalName = _a[index];
finalClassName = _b[index];
}
}
In the picture below, you can see that I am properly getting the json data.
In the below picture, by the time we get to the constructor, none of the values are being populated
In the below picture, you can see that after we stepped out of the constructor, the properties were applied, but the constructor didn't see it applied.
I created a work around, after investigation what I wanted doesn't seem to work.
Here is my work around. [See Code Below].
public ElementObject() { }
public static ElementObject Create(dynamic pSrcObj)
{
ElementObject obj = pSrcObj.ToObject<ElementObject>();
obj.Init();
return obj;
}
public void Init()
{
var _b = new string[] { nameof(ClassName), nameof(CssSelector), nameof(Id), nameof(LinkText), nameof(Name), nameof(PartialLinkText), nameof(TagName), nameof(XPath) };
var _a = new string[] { ClassName, CssSelector, Id, LinkText, Name, PartialLinkText, TagName, XPath };
index = Array.IndexOf(_a, _a.FirstOrDefault(s => !string.IsNullOrEmpty(s)));
finalName = _a[index];
finalClassName = _b[index];
}
In order for me now to create the object, i create it like this;
ElementObject _userName = ElementObject.Create(_module.UserName);
I have the following property Class:
public class Ctas
{
private string _CodAgrup;
public string CodAgrup
{
get { return _CodAgrup; }
set { _CodAgrup = value; }
}
private string _NumCta;
public string NumCta
{
get { return _NumCta; }
set { _NumCta = value; }
}
private string _Desc;
public string Desc
{
get { return _Desc; }
set { _Desc = value; }
}
private string _subctade;
public string SubCtaDe
{
get { return _subctade; }
set { _subctade = value; }
}
private string _Nivel;
public string Nivel
{
get { return _Nivel; }
set { _Nivel = value; }
}
private string _Natur;
public string Natur
{
get { return _Natur; }
set { _Natur = value; }
}
public override string ToString()
{
return "CodAgrup = " + CodAgrup + ", NumCta = " + NumCta + ", Desc = " + Desc + ", SubCtaDe = " + SubCtaDe + ", Nivel = " + Nivel + ", Natur = " + Natur;
}
#endregion
}
and I have Create an XML from these properties, so first I have to fill the properties, then i got the next method i want to use to fill the properties, first question is, is it correct the way Im using to fill the properties?
Then I should retreive the data and write it on an XML file so I convert properties data into a list and then just write them as atributes but when i Debug, I get that the list is empty, Why is that? what could be the best way to do it?
//Insert n data on properties
static void cuenta(string codagroup, string numcta, string desc, string subctade, string nivel, string natur)
{
Ctas cuentas = new Ctas();
int x = 0;
while (cuentas.CodAgrup != null)
{
cuentas.CodAgrup.Insert(x, "codagroup");
cuentas.NumCta.Insert(x, "numcta");
cuentas.Desc.Insert(x, "desc");
cuentas.SubCtaDe.Insert(x,"subctade");
cuentas.Nivel.Insert(x, "nivel");
cuentas.Natur.Insert(x, "natur");
x = x + 1;
}
}
//Converting propierties data into list
List<string> coda = cuentas.CodAgrup.GetType().GetProperties().Select(p => p.Name).ToList();
List<string> ncta = cuentas.NumCta.GetType().GetProperties().Select(p => p.Name).ToList();
List<string> desc = cuentas.Desc.GetType().GetProperties().Select(p => p.Name).ToList();
List<string> subdes = cuentas.SubCtaDe.GetType().GetProperties().Select(p => p.Name).ToList();
List<string> nivel = cuentas.Nivel.GetType().GetProperties().Select(p => p.Name).ToList();
List<string> natur = cuentas.Natur.GetType().GetProperties().Select(p => p.Name).ToList();
//Create XML from data in list´s
for (int i = 0; i < coda.Count; i++)
{
xmlWriter.WriteAttributeString("CodAgrup", coda[i]);
xmlWriter.WriteAttributeString("NumCta", ncta[i]);
xmlWriter.WriteAttributeString("Desc", desc[i]);
//write the atribute when property data exists.
if (cuentas.SubCtaDe != null)
{
xmlWriter.WriteAttributeString("SubCtaDe", subdes[i]);
}
xmlWriter.WriteAttributeString("Nivel", nivel[i]);
xmlWriter.WriteAttributeString("Natur", natur[i]);
xmlWriter.WriteEndElement();
}
Your code is confusing, but if I understand it right, here is the first error I see:
Ctas cuentas = new Ctas();
int x = 0;
while (cuentas.CodAgrup != null) // cuentas.CodAgrup is null from the beginning!
{
cuentas.CodAgrup.Insert(x, "codagroup");
cuentas.NumCta.Insert(x, "numcta");
cuentas.Desc.Insert(x, "desc");
cuentas.SubCtaDe.Insert(x,"subctade");
cuentas.Nivel.Insert(x, "nivel");
cuentas.Natur.Insert(x, "natur");
x = x + 1;
}
Since you are looking at a brand-new Ctas object, and there is no code to initialize the CodAgrup property, it will have the default value of null, so the code never enters the while loop.
Even if it DID, I suspect it would be an endless loop, because you're Inserting a literal value into a string property, and there is no condition I see where cuentas.CodAgrup will ever be null.
As for your XML generation, why not just use the built in XmlSerializer class? Even if you require a specific format, there are attributes that let you customize the XML that is generated.
The goal here is that after inputing csv file, a magic tool would output c# class with the fields from csv. Let's look at example.
Input myFile.csv:
Year,Make,Model
1997,Ford,E350
2000,Mercury,Cougar
Output myFile.cs
public class myFile
{
public string Year;
public string Make;
public string Model;
}
So, the only thing I would need to fix is the types of properties. After that I would use this class with FileHelpers to read csv file. Later it would be mapped to EntityFramework class (using AutoMapper) and saved to database.
Actually, https://csv2entity.codeplex.com/ looks like is doing what I need, but it just doesn't work - I installed it and nothing changed in my Visual studio, no new template appeared. The project is totally dead. Opened source code and ... decided maybe I'll just ask this question in stackoverflow :)
FileHelpers has only a simple wizard, which allows you to manually add fields. But I have 50 fields and this is not the last time I will need to do it, so automated solution is preferred here.
I believe this problem is solved many times before, any help?
Thank you Bedford, I took your code and added three things:
It removes symbols invalid for property names. For example "Order No." will become "OrderNo" property.
Ability to add property and class attributes. In my case I need [DelimitedRecord(",")] and [FieldOptional()], because I'm using FileHelpers.
Some columns don't have names, so it generates names itself. Naming convention is Column10, Column11 and so on.
Final code:
public class CsvToClass
{
public static string CSharpClassCodeFromCsvFile(string filePath, string delimiter = ",",
string classAttribute = "", string propertyAttribute = "")
{
if (string.IsNullOrWhiteSpace(propertyAttribute) == false)
propertyAttribute += "\n\t";
if (string.IsNullOrWhiteSpace(propertyAttribute) == false)
classAttribute += "\n";
string[] lines = File.ReadAllLines(filePath);
string[] columnNames = lines.First().Split(',').Select(str => str.Trim()).ToArray();
string[] data = lines.Skip(1).ToArray();
string className = Path.GetFileNameWithoutExtension(filePath);
// use StringBuilder for better performance
string code = String.Format("{0}public class {1} {{ \n", classAttribute, className);
for (int columnIndex = 0; columnIndex < columnNames.Length; columnIndex++)
{
var columnName = Regex.Replace(columnNames[columnIndex], #"[\s\.]", string.Empty, RegexOptions.IgnoreCase);
if (string.IsNullOrEmpty(columnName))
columnName = "Column" + (columnIndex + 1);
code += "\t" + GetVariableDeclaration(data, columnIndex, columnName, propertyAttribute) + "\n\n";
}
code += "}\n";
return code;
}
public static string GetVariableDeclaration(string[] data, int columnIndex, string columnName, string attribute = null)
{
string[] columnValues = data.Select(line => line.Split(',')[columnIndex].Trim()).ToArray();
string typeAsString;
if (AllDateTimeValues(columnValues))
{
typeAsString = "DateTime";
}
else if (AllIntValues(columnValues))
{
typeAsString = "int";
}
else if (AllDoubleValues(columnValues))
{
typeAsString = "double";
}
else
{
typeAsString = "string";
}
string declaration = String.Format("{0}public {1} {2} {{ get; set; }}", attribute, typeAsString, columnName);
return declaration;
}
public static bool AllDoubleValues(string[] values)
{
double d;
return values.All(val => double.TryParse(val, out d));
}
public static bool AllIntValues(string[] values)
{
int d;
return values.All(val => int.TryParse(val, out d));
}
public static bool AllDateTimeValues(string[] values)
{
DateTime d;
return values.All(val => DateTime.TryParse(val, out d));
}
// add other types if you need...
}
Usage example:
class Program
{
static void Main(string[] args)
{
var cSharpClass = CsvToClass.CSharpClassCodeFromCsvFile(#"YourFilePath.csv", ",", "[DelimitedRecord(\",\")]", "[FieldOptional()]");
File.WriteAllText(#"OutPutPath.cs", cSharpClass);
}
}
There is a link to full code and working example https://github.com/povilaspanavas/CsvToCSharpClass
You can generate the class code with a little C# app which checks all the values for each column. You can determine which is the narrowest type each one fits:
public static string CSharpClassCodeFromCsvFile(string filePath)
{
string[] lines = File.ReadAllLines(filePath);
string[] columnNames = lines.First().Split(',').Select(str => str.Trim()).ToArray();
string[] data = lines.Skip(1).ToArray();
string className = Path.GetFileNameWithoutExtension(filePath);
// use StringBuilder for better performance
string code = String.Format("public class {0} {{ \n", className);
for (int columnIndex = 0; columnIndex < columnNames.Length; columnIndex++)
{
code += "\t" + GetVariableDeclaration(data, columnIndex, columnNames[columnIndex]) + "\n";
}
code += "}\n";
return code;
}
public static string GetVariableDeclaration(string[] data, int columnIndex, string columnName)
{
string[] columnValues = data.Select(line => line.Split(',')[columnIndex].Trim()).ToArray();
string typeAsString;
if (AllDateTimeValues(columnValues))
{
typeAsString = "DateTime";
}
else if (AllIntValues(columnValues))
{
typeAsString = "int";
}
else if (AllDoubleValues(columnValues))
{
typeAsString = "double";
}
else
{
typeAsString = "string";
}
string declaration = String.Format("public {0} {1} {{ get; set; }}", typeAsString, columnName);
return declaration;
}
public static bool AllDoubleValues(string[] values)
{
double d;
return values.All(val => double.TryParse(val, out d));
}
public static bool AllIntValues(string[] values)
{
int d;
return values.All(val => int.TryParse(val, out d));
}
public static bool AllDateTimeValues(string[] values)
{
DateTime d;
return values.All(val => DateTime.TryParse(val, out d));
}
// add other types if you need...
You can create a command line application from this which can be used in an automated solution.
You can create the dynamic model class from CSV using dynamic in C#. Override TryGetMember of the custom DynamicObject class and use Indexers.
A useful link:
C# Linq to CSV Dynamic Object runtime column name
csv2entity has moved to:
https://github.com/juwikuang/csv2entity
The installation guide is the readme.md file.