I have generated a Visual MFC project with a CMultiDocTemplate.
New and File open is already implemented. But I want now to open a database and query the data.
Under CDocument::OnOpenDocument I could read that it would be possible to write an application for that. But I have no idea how to open a document with an attached view without open an file.
What I need is a function OnOpenDB(LPCTSTR tableName) which creates the document and the view but not trying to open a file and extent the table name.
Google did not help me. I could not find any useful documentation.
#Adrian,
is there maybe another solution?
Finally I want to use more than one template for different functions.
In your solution I am missing a possibility to send the table name to the doc for doing the database query and provide the data to the view.
Can I do it in override the template class?
In your code, is dynamic_cast<CMyDoc *> ... ceating the view?
So finally I would need a public function which allows me to send a request for a new document with the table name.
Is this possible?
Update:
I tried to override CMultiDocTemplate::OpenDocumentFile(...), but it does not work. It seem that this function is not virtual.
Therefore I created a OpenDocumentFile(...) inside the override of the CMultiDocTemplate
CDocument* CStreamAuswertungMDT::OpenDocumentFile(LPCTSTR lpszPathName, BOOL bAddToMRU, BOOL bMakeVisible)
{
CDocument* pDoc = CreateNewDocument();
if (pDoc == NULL)
{
TRACE(traceAppMsg, 0, "CDocTemplate::CreateNewDocument returned NULL.\n");
AfxMessageBox(AFX_IDP_FAILED_TO_CREATE_DOC);
return NULL;
}
ASSERT_VALID(pDoc);
BOOL bAutoDelete = pDoc->m_bAutoDelete;
pDoc->m_bAutoDelete = FALSE; // don't destroy if something goes wrong
CFrameWnd* pFrame = CreateNewFrame(pDoc, NULL);
pDoc->m_bAutoDelete = bAutoDelete;
if (pFrame == NULL)
{
AfxMessageBox(AFX_IDP_FAILED_TO_CREATE_DOC);
delete pDoc; // explicit delete on error
return NULL;
}
ASSERT_VALID(pFrame);
// avoid creating temporary compound file when starting up invisible
if (!bMakeVisible)
pDoc->m_bEmbedded = TRUE;
if (!pDoc->OnNewDocument())
{
// user has be alerted to what failed in OnNewDocument
TRACE(traceAppMsg, 0, "CDocument::OnNewDocument returned FALSE.\n");
pFrame->DestroyWindow();
return NULL;
}
// it worked, now bump untitled count
pDoc->SetTitle(lpszPathName);
pDoc->OnDocumentEvent(CDocument::onAfterOpenDocument);
InitialUpdateFrame(pFrame, pDoc);
return pDoc;
}
This is now working for me!
You have to be a bit careful but you can do this by creating a local (static) 'copy' of your CMultiDocTemplate object and then manually calling OpenDocumentFile() on that with a nullptr argument:
static CMultiDocTemplate MY_tmplate(IDR_MYDOC, RUNTIME_CLASS(CMyDoc), RUNTIME_CLASS(CMDIChild), RUNTIME_CLASS(CMyView));
void OnOpenDB(LPCTSTR tableName)
{
CMyDoc* pDoc = dynamic_cast<CMyDoc *>(MY_tmplate.OpenDocumentFile(nullptr));
//... do stuff with pDoc (using `tableName` to probe the database, maybe)
//
}
You will need to be careful when initializing any required data members and any limits, et cetera, in the pDoc object and its associated view(s).
You can set the document's filename/path at any stage, using the SetPathName() base class function; or you can just change the displayed title (that shown in the view's frame windows) with pDoc->SetTitle().
Alternatively, rather than using a local (copy) document template, you could add a member to your application class to run through its list of 'installed' templates, looking for a match to a given string identifier:
CMultiDocTemplate *MyApp::GetDocTemplate(CString name)
{
POSITION dtPos = GetFirstDocTemplatePosition(); CString dtName;
while (dtPos != nullptr) {
CMultiDocTemplate *mdTmp = dynamic_cast<CMultiDocTemplate *>(GetNextDocTemplate(dtPos));
if ((mdTmp == nullptr) || !mdTmp->GetDocString(dtName, CDocTemplate::docName)) break;
if (dtName == name) return mdTmp;
}
return nullptr;
}
Then, if that function returns a non-NULL pointer, use that to call OpenDocumentFile. For a list of the various string components you can use to match the relevant template, see the CDocTemplate documentation.
Related
From various questions, I know it is impossible to create DataTemplate from the code behind without using the XamlReader. So I want to ask if there is a way to programatically generates the UI for each Item in a ListView. I don't seem to find any relevant event handler or member of ListView for this purpose. Ideally, I want the ListView to invoke my handler code to generate UI for each data item it needs to display.
Imitating the official XamlTreeView sample, I have tried overriding some ListView method such as PrepareContainerForItemOverride but this won't work. The solution I found is as #JustinXL suggests: producing ListViewItem and insert them to the ListView->Items directly
//assume that items is a list of items we want to bind
myListView->Items->Clear();
for(auto i : items)
{
ListViewItem^ v = ref new ListViewItem();
v->Content = GenerateUIFor(i);
myListView->Items->Append(v); // NOTE: a wrapping ListViewItem is required!
}
To support usual data binding, it would be best to make the data structure to cache the generated UI. For example,
ref class MyDataStructure
{
public:
property ListViewItem^ Item
{
ListViewItem^ get()
{
if (_item == nullptr)
GenerateUI();
return _item;
}
}
void GenerateUI()
{
_item = ref new ListViewItem();
_text_block = ref new TextBlock(); // sample
_item->Content = _text_block;
UpdateUI();
}
// Invoke this when changing the state of this object
void UpdateUI()
{
if (_text_block != nullptr) // sample
{
_text_block->Text = this->ToString(); // sample
}
}
private:
ListViewItem^ _item;
TextBlock^ _text_block;
};
The downside of this is of course we can't make use of data virtualization. But it works properly for small set of data. For large set, one can use website's approach with Next and Prev button to load next page or go back to previous page.
ItemContainerGenerator should let you construct the entire UI for an item inside a list view. Unfortunately there doesn't appear to be much in the way of non MSDN documentation/samples for this.
Alternatively if you can maintain a list of all the DataTemplates you might need to show, you could use a DataTemplateSelector to choose which DataTemplate you want to show for each individual item.
I'm doing my controller. When I click on an item in my ListWidget, the following method is called :
void ContactBookController::setCurrentlySelectedItem(QListWidgetItem *item)
{
setCurrentItem(*item);
m_window.setEditButtonsState(true);
}
And the setCurrentItem() method is as follows : (m_current_item is a private variable):
void ContactBookController::setCurrentItem(const QListWidgetItem ¤t_item)
{
m_current_item = current_item;
}
Now, when I create a contact, I add an item to the QListWidget, but I also create a Contact object, but I also bind them together in a QHash so I know what QListWidgetItem corresponds to what Contact. In short, here is what I did :
void ContactBookController::createContact()
{
auto new_contact = m_contact_book.createContact();
if(new_contact != nullptr)
{
new_contact->setName(tr("New contact"));
}
auto list_item = m_window.createContact(new_contact->getName());
m_window.clearSelection();
m_contact_map.insert(list_item, new_contact);
}
Now, when clicking on a QListWidgetItem, I activate the edit button and I would like to retrieve the corresponding Contact object. However, when doing this, the Contact object doesn't seem to be correct. Indeed, if I use a method on him (like getName()), my application instantly crashes.
Here is what I did :
void ContactBookController::editContact()
{
auto list_item = m_current_item;
auto contact = m_contact_map.value(&list_item); /* doesn't work */
}
My hash table is declared as such :
QHash<QListWidgetItem*, Contact*> m_contact_map;
Any idea what I did wrong ?
Your hash has the type QHash<QListWidgetItem*, Contact>. So, mapping a item pointer to a contact value.
When you save the mapping for a specific item with m_contact_map.insert(list_item, new_contact), you add a mapping from the item pointer to the contact. But when you try to retrieve the contact with m_contact_map.value(&list_item), you look up the value for the pointer to the local list_item variable, which points to somewhere on the stack, and has nothing to do with the pointer to the item that is shown in the view.
Either you need to save to pointer to the selected value, i.e. make m_current_item a QListWidgetItem *m_current_item, or you simply use QListWidget::currentItem() to retrieve the current item without the need for an additional member variable.
(Side note: You should check for currentItem() != nullptr, since a list widget does not necessarily has an item selected.)
Assigning names to my objects (like the ones returned from the global Instantiate method) is breaking my tests. Anyone have any ideas for how to get around such things in test?
using System;
using NUnit.Framework;
using UnityEngine;
[TestFixture()]
public class BoardSpec{
[Test()]
public void NamePropertyWorks(){
var obj = new UnityEngine.Object();
obj.name = "object name";
}
}
The error I'm receiving: System.MissingMethodException : Cannot find the requested method.
The first line of the stack trace gives: at (wrapper managed-to-native) UnityEngine.Object:set_name (string)
Unity 5.2.0f3, running tests in MonoDevelop-Unity 4.0.1 on osx
_____update 9/20______
It sounds like creating new Objects is not recommended, but this code fails with the same error:
[Test()]
public void ScriptableObjectNamePropertyWorks(){
var obj = new ScriptableObject();
obj.name = "object name";
}
Short answer: Never create nor inherit directly form UnityEngine.Object, use ScriptableObject instead
Doing something like (new UnityEngine.Object()).name = "text" will always throw a null reference exception, as I'll explain below. From there to the System.MissingMethodException you are getting, it depends on the inner workings of NUnit.
Unity uses UnityEngine.Object in a very special way, keeping track of objects, and considering some "not alive" despite existing. The Equals comparison for UnityEngine.Object is overriden to reflect this, and an existing Object might equal null:
// Both Mesh and MyObject inherit directly from UnityEngine.Object
Object plainObject = new UnityEngine.Object();
Mesh meshObject = new UnityEngine.Mesh();
MyNumber myNumber = new MyNumber(123456f);
// using the overriden Unity.Object.Equals()
print(plainObject != null); // false
print(meshObject != null); // true
print(myNumber != null); // false
// With traditional null checking, everything exists of course, as we just created them
print(plainObject as System.Object != null); // true
print(meshObject as System.Object != null); // true
print(myNumber as System.Object != null); // true
print(myNumber.number); // prints 123456, the object is there and perfectly operational
print(myNumber.name); // the program ends with a null reference exception thrown by the name set accessor, because myNumber is not "alive"
Every native Unity class that inherits from UnityEngine.Object makes any needed internal call to register the object as "alive", but this is not done in the base class itself. Basically, any attempt to create a vanilla Object, or something directly inherited from it, will be considered stillborn by the engine, and you won't get it to work as usual. If you need to create or inherit from Objects, use ScriptableObject instead, that has an explicitly special behaviour in the inner workings.
I've created a abstract file parser cfc. This, as it sounds, abstracts some common tasks that are required when I read files from the file system.
One child of said component is for parsing XML Files and returns a coldfusion XML document. Now this all works perfectly, however, one thing I am unable to figure out is how to explicitly define the return type of the xml document, something I am keen to enforce.
The method responsible for returning the XML document is below:
public coldfusion.xml.XmlNodeList function parse(string filePath = "", boolean isCaseSensitive = false, string validator = "")
{
super.parse(arguments.filePath);
var data = getData();
if (len(arguments.validator)) {
setDocument(xmlParse(data, arguments.isCaseSensitive, arguments.validator));
} else {
setDocument(xmlParse(data, arguments.isCaseSensitive));
}
return getDocument();
}
You can see that the return type is coldfusion.xml.XmlNodeList. This I have managed to figure out by using the following client code.
<cfscript>
factory = new Library.parser.Factory();
parser = factory.getParser("XmlFileParser");
xmlDoc = parser.parse("/var/www/development/Framework/test/testfile.xml");
/** XMLDoc is now a coldfusion document object **/
writeDump(xmlDoc);
writeDump(getMetadata(xmlDoc).getName()); /** outputs coldfusion.xml.XmlNodeList **/
</cfscript>
This is incorrect, the error I get is:
The value returned from the parse function is not of type coldfusion.xml.XmlNodeList.
Am I missing something really simple? Should I care so much?
EDIT:
Added the getDocument method (which as you can see has a return type of "any")
public any function getDocument()
{
return variables.document;
}
Yep. As per the docs, the return type you want for XML is... err... "XML".
I'm trying to create a custom lookup filter in a dialog in AX.
I've followed the instructions in this post x++ filter lookup in dialog and am getting a Stack Trace error -- FormRun object not initialized -- when I'm run my code.
What I am trying to do is filter the lookup() for the ConfigId EDT based on the selection from the ItemId EDT. I have the custom lookup() ready to go and working properly, I just can't get it called from my dialog box.
public Object dialog(DialogRunbase _dialog, boolean _forceOnClient)
{
DialogRunBase dialog;
;
dialog = super(_dialog, true);
dialog.caption('#RID2885');
dfItem = dialog.addField(typeid(ItemId));
dfInventLoc = dialog.addField(typeid(InventLocationId));
dfReplaceCost = dialog.addField(typeid(PdsCost));
dfItemConfig = dialog.addField(typeid(ConfigId));
dfColorId = dialog.addField(typeid(InventColorId), '#RID101');
return dialog;
}
Here's the call to the lookup():
void Fld_7_lookup()
{
Formrun fr = this.dialogModify().parmDialog();
Object control = fr.controlCallingMethod();
;
ConfigTable::lookupConfigIdSimple(control, dfItem.value());
}
And this is where it keeps getting the Stack Trace error:
public void dialogPostRun(DialogRunbase _dialog)
{
;
super(_dialog);
**_dialog.formRun().controlMethodOverload(true);** // Causes Stack Trace error
_dialog.formRun().controlMethodOverloadObject(this);
}
I have tried multiple configurations with the dialog box. When the code reaches that point, it still has information passed in from the dialog() method, but when it goes to get the FormRun, that object is blank.
Could someone please help me understand why there is no FormRun object associated with the DiaglogRunBase that is passed-in?
Thanks.
Mayby you should call super(_dialog) last in the dialogPostRun method.
Have a look on a similar solution and one more.
Did you check to see if your class is set to run at "Called From"?
Here is an example code for overriding the modified method. Maybe lookup has the same requirements:
public void dialogPostRun(DialogRunbase _dialog)
{
// Must be overriden to enable overriding modified method
;
_dialog.dialogForm().formRun().controlMethodOverload(true);
_dialog.dialogForm().formRun().controlMethodOverloadObject(this);
_dialog.formRun().controlMethodOverload(true);
_dialog.formRun().controlMethodOverloadObject(this);
super(_dialog);
}
And for the custom method:
boolean Fld2_1_modified()
{
FormStringControl c = dialog.formrun().controlCallingMethod();
boolean ret;
;
ret = c.modified(); // Super() Call the FormControl ->modified
dlgCustomField.value(MyClass::someMethod(dlgCustomField.value())); // example
return ret;
}