File I/O with Windows Forms - list

I need to create a program with Windows forms. I made a bit of code in c++...and Windows forms in c++/cli at the same time. Now I'm trying to adapt the c++ code from the forms, but I'm having some problems with the file, it's completely different from c++.
I have 2 forms. The first is for registration (it should register every student in a file). The second is for modifying students data with a given surname for example.
In registration.cpp I have created a list of objects but when I write I use streamwriter, but I guess there isnt any relationship with my list.
So my problems are:
How can I WRITE my data list into a file?
How can I MODIFY that data?
Now I post some code, but it's in italian :D as I am from italy (sorry for my mistakes.)
//.cpp of the registration
class studente
{
private:
string cognome;
string nome;
public:
studente(){
cognome="";
nome="";
};
~studente(){};
void set(string str1,string str2){
cognome=str1;
nome=str2;
}
class primo_anno:public studente
{
private:
int voto_diploma;
public:
primo_anno(){
cognome="";
nome="";
voto_diploma='0';
};
~primo_anno(){};
void set(string str1,string str2, int mark){ voto_diploma=mark; };
void stampa(){//I KNOW ITS NOT USEFUL HERE..BUT IN C++ I USED THAT
f<<"\ncognome: "<<cognome<<"\n";
f<<"nome: "<<nome<<"\n";
f<<"voto: "<<voto_diploma<<"\n";
};
};
list<primo_anno> l1;//DECLARE MY STL LIST
{//WHEN I CLICK ON MY REGISTER BUTTON THE PROGRAM RUN THIS
int mark;
primo_anno *s;
s=new primo_anno;
char* str1=(char*)(Marshal::StringToHGlobalAnsi(textBox1->Text)).ToPointer();
char* str2=(char*)(Marshal::StringToHGlobalAnsi(textBox2->Text)).ToPointer();
mark = Convert::ToInt16(textBox35->Text);
s->set(str1,str2,mark);
l1.push_back(*s);
list<primo_anno>::iterator it;
//I HAVE FOUND THIS METHOD BUT ITS NOT LINKED TO MY STL LIST.
//BY THE WAY I AM ABLE TO WRITE ON FILE WITH THIS.BUT LATER I DONT KNOW HOW TO MODIFY
//FOR EXAMPLE "DELETE THE LINE WHERE THERE IS Rossi SURNAME".HOW!!!
TextWriter ^tw = gcnew StreamWriter("primoAnno.txt", true);//true append
tw->WriteLine(textBox1->Text + "\t\t" + textBox2->Text + "\t\t" + textBox35->Text);
tw->Close();
Thank you in advance! And sorry again for my English... I'm just a student:)

Normally, you can convert a std::string into a System::String^ quite easily (it's even possible that simply using gcnew String(myPrimoAnnoObj.cognome) will give you a string with the right contents, easily written into the managed stream.
However you appear to have failed to grasp how new works for unmanaged objects: Your code allocates a primo_anno structure dynamically for no reason, before copying its value into the list and leaking the pointer. You also leak the pointers to the unmanaged strings you obtained from the Marshal class.
Are you sure you should be using unmanaged objects? It would be much easier to have everything in a managed System::Collections::Generic::List<> of managed objects...
Added: For writing everything in a file, you can try something like this:
ref class MyClass
{
public:
String^ cognome;
String^ nome;
int voto_diploma;
};
//...
List<MyClass^>^ primo = gcnew List<MyClass^>();
//...
MyClass^ myObj = gcnew MyClass();
myObj->cognome = textBox1->Text;
myObj->nome = textBox2->Text;
myObj->voto_diploma = Convert::ToInt32(textBox35->Text);
primo->Add(myObj);
//...
TextWriter ^tw = gcnew StreamWriter(L"primoAnno.txt", true);
for each(MyClass^ obj in primo)
{
//You can use any character or string as separator,
//as long as it's not supposed to appear in the strings.
//Here, I used pipes.
tw->Write(obj->cognome);
tw->Write(L"|");
tw->Write(obj->nome);
tw->Write(L"|");
tw->WriteLine(obj->voto_diploma);
}
tw->Close();
For reading, you can use a function like this:
MyClass^ ParseMyClass(String^ line)
{
array<String^>^ splitString = line->Split(L'|');
MyClass^ myObj = gcnew MyClass();
myObj->cognome = splitString[0];
myObj->nome = splitString[1];
myObj->voto_diploma = Convert::ToInt32(splitString[2]);
return myObj;
}
And for deleting:
TextWriter^ tw = gcnew StreamWriter(L"primoAnno2.txt", true);
TextReader^ tr = gcnew StreamReader(L"primoAnno.txt");
String^ line;
while((line=tr->ReadLine()) != nullptr)
{
MyClass^ obj = ParseMyClass(line);
if(obj->cognome != L"cat")
tw->WriteLine(line);
}
tr->Close();
tw->Close();
File::Delete(L"primoAnno.txt");
File::Move(L"primoAnno2.txt", L"primoAnno.txt");
It may not be the exact code, but it's overall what should work.
Note: If you want your separator to be spaces, and there can be spaces in the strings, things will get a lot more complicated.

I have tried to use a generic list..(thanks MSDN).in the comments below there are my dubts..
List<String^>^ primo=gcnew List<String^>();
int mark;
char* str1=(char*)(Marshal::StringToHGlobalAnsi(textBox1->Text)).ToPointer();
char* str2=(char*)(Marshal::StringToHGlobalAnsi(textBox2->Text)).ToPointer();
mark = Convert::ToInt16(textBox35->Text);
//here i add TEXTBOXES to my generic list...not objects of my stl list
primo->Add(textBox1->Text);
primo->Add(textBox2->Text);
primo->Add(textBox35->Text);
TextWriter ^tw = gcnew StreamWriter("primoAnno.txt", true);
for each(String^ prim in primo){
//here i write my string one by one in column..i want them all in a line!how?
tw->WriteLine(prim);
}
//i also have tried to delete an object..but i dont like the remove..i mean i want all the strings in a line, if i find "cat" for example i want to delete the ENTIRE line..not just "cat"
if(primo->Contains("cat"))tw->WriteLine("ok");primo->Remove("cat");
for each(String^ prim in primo){
tw->WriteLine(prim);
}
tw->Close();
i make an example of my primoAnno.txt file
first time i write(and push the register button) i want this:
cat gae 5
second time i write(and push the register button again) i want this:
cat gae 5
bla bla 1
then, when i remove(if there is "cat" in a line delete that line) i want this:
bla bla 1
hope it s useful. thanks to ones who will reply

Related

Use writer/reader for multiple fields

The following is my constructor for a Student object. I will be using a list of student. I need to store the list so even if the program is turned off, I can still access all the contents. The only way I could think of was to use reader/writer and a text file.
1) Is there a more efficient way to store this information?
2) If not, how can I use reader/writer to store each field?
public Student(String firstName, String lastName, String gender, String
state, String school, String lit, String wakeUp, String sleep, String
social,String contactInfo, String country, String major) {
this.firstName = firstName;
this.lastName = lastName;
this.gender = gender;
this.state = state;
this.school = school;
this.lit = lit;
this.wakeUp = wakeUp;
this.sleep = sleep;
this.social = social;
this.contactInfo = contactInfo;
this.country = country;
this.major = major;
}
The possibilities are really project specific and subjective.
Some possibilities include:
CSV file which makes it easy for exporting to other programs and parsing data
Online server which allows access from any computer that has the
program and an internet connection
Text file which works for local devices that won't require many
additions
It really just depends on how you want to implement it and what method suits your needs best.
To use reader/writer to store your fields, you could use the accessor methods of each variable to store them line by line in your text file. Below is some sample code to get you started on writing to the file:
PrintWriter outputStream = null;
try {
outputStream = new PrintWriter(new FileOutputStream(FILE_LOCATION));
}
catch (FileNotFoundException ex) {
JOptionPane optionPane = new JOptionPane("Unable to write to file\n " + FILE_LOCATION, JOptionPane.ERROR_MESSAGE);
JDialog dialog = optionPane.createDialog("Error!");
dialog.setAlwaysOnTop(true);
dialog.setVisible(true);
System.exit(0);
}
Iterator<YOUR_OBJECT> i = this.List.iterator();
YOUR_OBJECT temp = null;
while (i.hasNext()) {
temp = i.next();
if (temp instanceof YOUR_OBJECT) {
outputStream.println(temp.getAttribute());
}
}
outputStream.close();

Java: return a LinkedHashSet

Basically, I'm trying to return a collection of strings in Java.
But...
each string must be unique because they're all the names of ".db" files in current folder, so I thought this collection should be LinkedHashSet.
The elements (filenames) must maintain the exact same order, so I can choose one of them by it's order number in the collection.
My main routine will show this collection in a GUI component (maybe a JList) for the user to choose one of them (without the .db extension).
I'm totally newbie (as you can see), so if you think there are better options than LinkedHashSet please tell me.
Also, how can I grab this collection in the main class?
What I've got so far:
public Set GetDBFilesList() {
//returns ORDERED collection of UNIQUE strings with db filenames
LinkedHashSet a = new LinkedHashSet();
FilenameFilter dbFilter = (File file, String name) -> {
return name.toLowerCase().endsWith(".db");
};
String dirPath = "";
File dir = new File(dirPath);
File[] files = dir.listFiles(dbFilter);
if (files.length > 0) {
for (File aFile : files) {
a.add(aFile.getName());
}
}
return a;
}
You want an ordered and unique collection - LinkedHashSet is a good choice.
Some comments on your methode:
Your should use Generics f.e.: LinkedHashSet<File> or LinkedHashSet<String>
The check for files.length is unnecessary, but you could check for null if the path is not a directory or an I/O error occured
You should name your variables properly: a is not a good name
Your methode can be static - maybe in a static helper class?
The Set.add methode returns true or false if the item was added or not, you should check that just in case
Putting all together:
//Your Main class
public class Main
{
public static void main(String[] args)
{
File dir = new File("");
Collection<File> dbFiles = DbFileManager.getDatabaseFiles(dir);
}
}
//Your DB File Reader Logic
public class DbFileManager
{
public static Collection<File> getDatabaseFiles(File directory)
{
Collection<File> dbFiles = new LinkedHashSet<>();
//filter code etc.
boolean success = dbFiles.addAll(directory.listFiles(filter));
//Check if everthing was added
return dbFiles;
}
}

c++ How to serialize class to json and parse the json file?

I have an xObject Class which basically is a trivial "Person" Class and I want to be able to serialize the whole class to a .json file, and then read that file in order to be able to extract the variables from the file and link those variables to the name of the class.
So for example:
xObject Class Code:
class xObject{
string name;
string lastname;
int age;
public:
string getName(){
return name;
}
string getLastname(){
return lastname;
}
int getAge(){
return age;
}
}
And then I create an object with some attributes on it.
int main(){
xObject homer;
homer.name = "Homer";
homer.lastname = "Simpson";
homer.age = 30;
//SERIALIZATION OF HOMER.
homer.serialExport("File.json")
return 0;
}
So now, my File.json should look like this:
{"homer" :
{"name" : "Homer"
"lastname" : "Simpson"
"age" : 30
}
}
and then, I want to be able to read from the file to extract data from it with something like this:
int main(){
xObject bart;
bart.name = "Bart";
//ACTUAL USE OF THE .JSON FILE HERE
myFile = ("File.json");
bart.lastname = Deserializer(myFile).getLastname(); //It is supossed to assign "Simpson"
//to the lastname reading from the serialized
//homer class file described above.
bart.age = Deserializer(myFile).getAge() - 20; //Sets homer's age minus 20 years.
return 0;
}
So, how can I do that on c++? (Libraries implementation accepted)
And how could I retrieve the class name that has been serialized?
For example Deserialize(myFile).getClassName() should return "homer"
I've done something similar in java with XML serialization, and it was pretty straight forward, but it seems that in C++ this is not very easy to do, and I'm relatively new to C++.
In c++ there is not introspection/reflection, so you can't automatically serialize a class without explicitly write your member variables in your stream. For the same reason, you can't retrieved the class name that have been serialized.
So the solution is to write a function in your class that serializes the member variables you want.
Of course you will not reinvent the wheel to format your file in json. You can use: https://github.com/open-source-parsers/jsoncpp.
For instance you can write:
Json::Value root;
root["homer"]["name"]="Homer";
root["homer"]["lastname"]="Simpson";
//etc
ofstream file;
file.open("File.json");
file << root;
file.close();
However, for the read, you can do as you wanted:
Json::Value root2;
ifstream file2;
file2.open("File.json");
file2 >> root2;
file2.close();
xObject homer;
homer.lastname = root2["homer"]["lastname"].toStyledString();
//etc
Of course your attribute has to be public. Otherwise you need to add a setter function.

Drag & Drop Filename Visual (Managed) C++

I have a RichTextBox that I would like to allow a user to drag and drop a file from disk into. All that should appear in the textbox is the filename(s). This code currently adds "System.String[]" to the textbox instead of the filename. When I change the DataFormats::FileDrop to DataFormats::Text as this MSDN would seem to suggest, I get a NULL dereference error.
The RichTextBox name is rtbFile. In my constructor, I have:
this->rtbFile->AllowDrop = true;
I set up the events like this (within InitializeComponents):
this->rtbFile->DragEnter += gcnew System::Windows::Forms::DragEventHandler(this, &VanicheMain::rtbFile_DragEnter);
this->rtbFile->DragDrop += gcnew System::Windows::Forms::DragEventHandler(this, &VanicheMain::rtbFile_DragDrop);
The functions are defined as follows:
void rtbFile_DragEnter(System::Object ^sender, System::Windows::Forms::DragEventArgs ^ e) {
if (e->Data->GetDataPresent(DataFormats::FileDrop))
e->Effect = DragDropEffects::Copy;
else
e->Effect = DragDropEffects::None;
}
System::Void rtbFile_DragDrop(System::Object ^sender, System::Windows::Forms::DragEventArgs ^e){
int i = rtbFile->SelectionStart;;
String ^s = rtbFile->Text->Substring(i);
rtbFile->Text = rtbFile->Text->Substring(0, i);
String ^str = String::Concat(rtbFile->Text, e->Data->GetData(DataFormats::FileDrop)->ToString());
rtbFile->Text = String::Concat(str, s);
}
Dragging files always produces an array of strings. Each array element is the path to one of the files that are dragged. You'll need to write the extra code to cast the return value of GetData() to an array and iterate it, reading the content of each file. Similar to this:
array<String^>^ paths = safe_cast<array<String^>^>(e->Data->GetData(DataFormats::FileDrop));
for each (String^ path in paths) {
String^ ext = System::IO::Path::GetExtension(path)->ToLower();
if (ext == ".txt") rtbFile->AppendText(System::IO::File::ReadAllText(path));
}

Implementing bulk record fetching

At the start of my program, I need to read data from a MS Access database (.mdb) into a drop down control. This is done so that whenever the user types in that control, the application can auto-complete.
Anyway, the reading from database took forever so I thought I'd implement bulk row fetching.
This is the code I have:
CString sDsn;
CString sField;
sDsn.Format("ODBC;DRIVER={%s};DSN='';DBQ=%s",sDriver,sFile);
TRY
{
// Open the database
database.Open(NULL,false,false,sDsn);
// Allocate the rowset
CMultiRowset recset( &database );
// Build the SQL statement
SqlString = "SELECT NAME "
"FROM INFOTABLE";
// Set the rowset size. These many rows will be fetched in one bulk operation
recset.SetRowsetSize(25);
// Open the rowset
recset.Open(CRecordset::forwardOnly, SqlString, CRecordset::readOnly | CRecordset::useMultiRowFetch);
// Loop through each rowset
while( !recset.IsEOF() )
{
int rowsFetched = (int)recset.GetRowsFetched(); // This value is always 1 somehow
for( int rowCount = 1; rowCount <= rowsFetched; rowCount++ )
{
recset.SetRowsetCursorPosition(rowCount);
recset.GetFieldValue("NAME",sField);
m_nameDropDown.AddString(sField);
}
// Go to next rowset
recset.MoveNext();
}
// Close the database
database.Close();
}
CATCH(CDBException, e)
{
// If a database exception occured, show error msg
AfxMessageBox("Database error: "+e->m_strError);
}
END_CATCH;
MultiRowset.cpp looks like:
#include "stdafx.h"
#include "afxdb.h"
#include "MultiRowset.h"
// Constructor
CMultiRowset::CMultiRowset(CDatabase *pDB)
: CRecordset(pDB)
{
m_NameData = NULL;
m_NameDataLengths = NULL;
m_nFields = 1;
CRecordset::CRecordset(pDB);
}
void CMultiRowset::DoBulkFieldExchange(CFieldExchange *pFX)
{
pFX->SetFieldType(CFieldExchange::outputColumn);
RFX_Text_Bulk(pFX, _T("[NAME]"), &m_NameData, &m_NameDataLengths, 30);
}
MultiRowset.h looks like:
#if !defined(__MULTIROWSET_H_AD12FD1F_0566_4cb2_AE11_057227A594B8__)
#define __MULTIROWSET_H_AD12FD1F_0566_4cb2_AE11_057227A594B8__
class CMultiRowset : public CRecordset
{
public:
// Field data members
LPSTR m_NameData;
// Pointers for the lengths of the field data
long* m_NameDataLengths;
// Constructor
CMultiRowset(CDatabase *);
// Methods
void DoBulkFieldExchange(CFieldExchange *);
};
#endif
And in my database, the INFOTABLE looks like:
NAME AGE
---- ---
Name1 Age1
Name2 Age2
.
.
.
.
All I need to do is only read the data from the database. Can someone please tell me what I'm doing wrong? My code right now behaves exactly like a normal fetch. There's no bulk fetching happening.
EDIT:
I just poked around in DBRFX.cpp and found out that RFX_Text_Bulk() initializes my passed m_NameData as new char[nRowsetSize * nMaxLength]!
This means m_NameData is only a character array! I need to fetch multiple names, so wouldn't I need a 2D character array? The strangest thing is, the same RFX_Text_Bulk() initializes my passed m_NDCDataLengths as new long[nRowsetSize]. Why in the world would a character array need an array of lengths?!
According to http://msdn.microsoft.com/en-us/library/77dcbckz.aspx#_core_how_crecordset_supports_bulk_row_fetching you have to open CRecordset with CRecordset::useMultiRowFetch flag before call SetRowsetSize:
To implement bulk row fetching, you must specify the
CRecordset::useMultiRowFetch option in the dwOptions parameter of the
Open member function. To change the setting for the rowset size, call
SetRowsetSize.
You almost got it right. To fetch the values,
I would change your
for( int rowCount = 1; rowCount <= rowsFetched; rowCount++ )
{
recset.SetRowsetCursorPosition(rowCount);
recset.GetFieldValue("NAME",sField);
m_nameDropDown.AddString(sField);
}
by something like this
for( int nPosInRowset = 0; nPosInRowset < rowsFetched; nPosInRowset++ )
{
//Check if value is null
if (*(recset.m_NameDataLengths + nPosInRowset) == SQL_NULL_DATA)
continue;
CString csComboString;
csComboString = (recset.m_NameData + (nPosInRowset * 30)); //Where 30 is the size specified in RFX_Text_Bulk
m_nameDropDown.AddString(csComboString);
}
EDIT: To fetch more than one row, remove the CRecordset::forwardOnly option
EDIT 2 : You can also keep CRecordset::forwardonly, but add the CRecordset::useExtendedFetch option
Just faced the same problem.
You should use in recset.Open() call for dwOptions parameter only CRecordset::useMultiRowFetch, and not CRecordset::readOnly | CRecordset::useMultiRowFetch.
Hope this helps someone...
EDIT:- After re-check here is the situation - when using bulk recordset and opening with CRecordset::forwardOnly and CRecordset::readOnly, you must also specify CRecordset::useExtendedFetch in dwOptions. For other types of scrolling, using CRecordset::readOnly | CRecordset::useMultiRowFetch is just fine.