Drag & Drop Filename Visual (Managed) C++ - 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));
}

Related

How to load from memory stream to firemonkey (FMX) memo component in C++ Builder Berlin?

I used to use below code to load plenty of text to VCL Memo.
LogMemo->Lines->BeginUpdate();
LogMemo->SelStart = LogMemo->GetTextLen();
LogMemo->SelLength = 0;
LogMemo->SelText = AnsiString((char *)LogMemoBufPtr->Memory, LogMemoBufPtr->Size);
LogMemo->SelStart = LogMemo->GetTextLen();
LogMemo->Perform(EM_SCROLLCARET, 0, 0);
LogMemo->Lines->EndUpdate();
But in FMX Memo component, I can't use "LogMemo->SelText = AnsiString(strFromMemStream)" anymore.
And I also can't use "GetTextLen" method to set selection start.
I tried to modify the code in below but it still didn't work. It always overwrite original content and can't append new text.
LogMemo->Lines->BeginUpdate();
LogMemo->GoToTextEnd();
LogMemo->SelStart = LogMemo->Text.Length();
LogMemo->SelLength = 0;
LogMemo->Text = AnsiString((char *)LogMemoBufPtr->Memory, LogMemoBufPtr->Size);
LogMemo->GoToTextEnd();
LogMemo->SelStart = LogMemo->Text.Length();
LogMemoBufPtr->Clear();
LogMemo->Lines->EndUpdate();
Is there any one know how to do it in FMX Memo component or just to display plenty of text smoothly?
Thanks!
Use the SetText function or Lines property.
In both cases I think you will have to convert the ASCII text to Unicode first.
Solution 1 - Based to the second source code provided, here is a solution to append a text in the TMemo object in FMX/FireMonkey Library.
Step 1 - Instead of trying to place the cursor and select the end of the text.
Before:
LogMemo->GoToTextEnd();
LogMemo->SelStart = LogMemo->Text.Length();
LogMemo->SelLength = 0;
Select all the text and store it in a temporary string.
After:
System::UnicodeString suTemp;
LogMemo->Lines->BeginUpdate();
LogMemo->GoToTextEnd();
LogMemo->SelStart = 0;
LogMemo->SelLength = LogMemo->Text.Length();
suTemp = LogMemo->SelText;
Step 2 - Then append the new text to the temporary string and update the Memo
suTemp += AnsiString((char *)LogMemoBufPtr->Memory, LogMemoBufPtr->Size);
LogMemo->Text = suTemp;
LogMemo->GoToTextEnd();
LogMemo->SelStart = LogMemo->Text.Length();
LogMemoBufPtr->Clear();
LogMemo->Lines->EndUpdate();
Solution 2 - Simple and faster solution when add the text at the end.
Store the current text into a temporary string and add the new text
then update the memo.
System::UnicodeString suTemp;
LogMemo->Lines->BeginUpdate();
suTemp = LogMemo->Text;
suTemp += AnsiString((char *)LogMemoBufPtr->Memory, LogMemoBufPtr->Size);
LogMemo->Text = suTemp;
LogMemo->GoToTextEnd();
LogMemoBufPtr->Clear();
LogMemo->Lines->EndUpdate();
TMemo in FireMonkey has a GoToTextEnd() method:
Moves the cursor to the end of the text in the memo control.
You can't use AnsiString in mobile platforms (without a compiler patch), nor should you anyway since TMemo holds Unicode text (same as VCL's TMemo in Delphi 2009 and later). If your TMemoryStream contains 8bit characters, you need to convert them to Unicode, such as with TEncoding, before appending them to the TMemo. The TEncoding::GetString() methods take a TBytes as input, so you could use TBytesStream instead of TMemoryStream. TBytesStream wraps a TBytes and has a public Bytes property.
Try something like this:
LogMemo->Lines->BeginUpdate();
try
{
LogMemo->GoToTextEnd();
LogMemo->SelLength = 0;
LogMemo->SelText = TEncoding::Default->GetString(LogMemoBufPtr->Bytes, 0, LogMemoBufPtr->Size);
/* or:
TEncoding *enc = TEncoding::GetEncoding(L"desired charset here");
try {
LogMemo->SelText = enc->GetString(LogMemoBufPtr->Bytes, 0, LogMemoBufPtr->Size);
}
__finally {
delete enc;
}
*/
LogMemo->GoToTextEnd();
LogMemo->SelLength = 0;
// not sure if this is the best replacement for EM_SCROLLCARET...
LogMemo->VScrollBar->Value = LogMemo->VScrollBar->Max;
}
__finally {
LogMemo->Lines->EndUpdate();
}
Update: I didn't realize that SelText is read-only in FireMonkey, unlike in VCL. In that case, you have no choice but to append to the Text property, which is not as efficient, especially for large text.
LogMemo->Lines->BeginUpdate();
try
{
LogMemo->Text = LogMemo->Text + TEncoding::Default->GetString(LogMemoBufPtr->Bytes, 0, LogMemoBufPtr->Size);
/* or:
TEncoding *enc = TEncoding::GetEncoding(L"desired charset here");
try {
LogMemo->Text = LogMemo->Text + enc->GetString(LogMemoBufPtr->Bytes, 0, LogMemoBufPtr->Size);
}
__finally {
delete enc;
}
*/
LogMemo->GoToTextEnd();
LogMemo->SelLength = 0;
// not sure if this is the best replacement for EM_SCROLLCARET...
LogMemo->VScrollBar->Value = LogMemo->VScrollBar->Max;
}
__finally {
LogMemo->Lines->EndUpdate();
}

Add unique suffix to file name

Sometimes I need to ensure I'm not overwriting an existing file when saving some data, and I'd like to use a function that appends a suffix similar to how a browser does it - if dir/file.txt exists, it becomes dir/file (1).txt.
This is an implementation I've made, that uses Qt functions:
// Adds a unique suffix to a file name so no existing file has the same file
// name. Can be used to avoid overwriting existing files. Works for both
// files/directories, and both relative/absolute paths. The suffix is in the
// form - "path/to/file.tar.gz", "path/to/file (1).tar.gz",
// "path/to/file (2).tar.gz", etc.
QString addUniqueSuffix(const QString &fileName)
{
// If the file doesn't exist return the same name.
if (!QFile::exists(fileName)) {
return fileName;
}
QFileInfo fileInfo(fileName);
QString ret;
// Split the file into 2 parts - dot+extension, and everything else. For
// example, "path/file.tar.gz" becomes "path/file"+".tar.gz", while
// "path/file" (note lack of extension) becomes "path/file"+"".
QString secondPart = fileInfo.completeSuffix();
QString firstPart;
if (!secondPart.isEmpty()) {
secondPart = "." + secondPart;
firstPart = fileName.left(fileName.size() - secondPart.size());
} else {
firstPart = fileName;
}
// Try with an ever-increasing number suffix, until we've reached a file
// that does not yet exist.
for (int ii = 1; ; ii++) {
// Construct the new file name by adding the unique number between the
// first and second part.
ret = QString("%1 (%2)%3").arg(firstPart).arg(ii).arg(secondPart);
// If no file exists with the new name, return it.
if (!QFile::exists(ret)) {
return ret;
}
}
}
QTemporaryFile can do it for non-temporary files, despite its name:
QTemporaryFile file("./foobarXXXXXX.txt");
file.open();
// now the file should have been renamed to something like ./foobarQSlkDJ.txt
file.setAutoRemove(false);
// now the file will not be removed when QTemporaryFile is deleted
A better solution is to use GUID
Or you can generate a hash based on bytes collected from within a file, either randomly or based on some data property that is fairly unique from file to file.

File I/O with Windows Forms

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

RichTextBox SaveFile()

How can I pass a string as a path into here
void SaveLogFile()
{
logTxt->SaveFile(String::Concat
(System::Environment::GetFolderPath
(System::Environment::SpecialFolder::Personal),
"\\Testdoc.rtf"), RichTextBoxStreamType::RichNoOleObjs);
}
I can't figure out how to set a non SpecialFolder
From MSDN:
void SaveMyFile()
{
// Create a SaveFileDialog to request a path and file name to save to.
SaveFileDialog^ saveFile1 = gcnew SaveFileDialog;
// Initialize the SaveFileDialog to specify the RTF extention for the file.
saveFile1->DefaultExt = "*.rtf";
saveFile1->Filter = "RTF Files|*.rtf";
// Determine whether the user selected a file name from the saveFileDialog.
if ( saveFile1->ShowDialog() == System::Windows::Forms::DialogResult::OK &&
saveFile1->FileName->Length > 0 )
{
// Save the contents of the RichTextBox into the file.
richTextBox1->SaveFile( saveFile1->FileName );
}
}
See how the System::String^ is created here. Do it this way, too...

Using Conversion Studio by To-Increase to import Notes into Microsoft Dynamics AX 2009

Currently, I'm using Conversion Studio to bring in a CSV file and store the contents in an AX table. This part is working. I have a block defined and the fields are correctly mapped.
The CSV file contains several comments columns, such as Comments-1, Comments-2, etc. There are a fixed number of these. The public comments are labeled as Comments-1...5, and the private comments are labeled as Private-Comment-1...5.
The desired result would be to bring the data into the AX table (as is currently working) and either concatenate the comment fields or store them as separate comments into the DocuRef table as internal or external notes.
Would it not require just setting up a new block in the Conversion Studio project that I already have setup? Can you point me to a resource that maybe shows a similar procedure or how to do this?
Thanks in advance!
After chasing the rabbit down the deepest of rabbit holes, I discovered that the easiest way to do this is like so:
Override the onEntityCommit method of your Document Handler (that extends AppDataDocumentHandler), like so:
AppEntityAction onEntityCommit(AppDocumentBlock documentBlock, AppBlock fromBlock, AppEntity toEntity)
{
AppEntityAction ret;
int64 recId; // Should point to the record currently being imported into CMCTRS
;
ret = super(documentBlock, fromBlock, toEntity);
recId = toEntity.getRecord().recId;
// Do whatever you need to do with the recId now
return ret;
}
Here is my method to insert the notes, in case you need that too:
private static boolean insertNote(RefTableId _tableId, int64 _docuRefId, str _note, str _name, boolean _isPublic)
{
DocuRef docuRef;
boolean insertResult = false;
;
if (_docuRefId)
{
try
{
docuRef.clear();
ttsbegin;
docuRef.RefCompanyId = curext();
docuRef.RefTableId = _tableId;
docuRef.RefRecId = _docuRefId;
docuRef.TypeId = 'Note';
docuRef.Name = _name;
docuRef.Notes = _note;
docuRef.Restriction = (_isPublic) ? DocuRestriction::External : DocuRestriction::Internal;
docuRef.insert();
ttscommit;
insertResult = true;
}
catch
{
ttsabort;
error("Could not insert " + ((_isPublic) ? "public" : "private") + " comment:\n\n\t\"" + _note + "\"");
}
}
return insertResult;
}