After few hours work, I finally made an mdi framework using MultiDocumentPanel. I hope the implementation complies with the intended usage of these components. Here is the code:
/*
==============================================================================
==============================================================================
*/
#include "juce.h"
class MyVMPanel : public MultiDocumentPanel
{
// MultiDocumentPanelWindow *win;
// MyContentComponent *client[3];
public:
Label *lb[3];
MyVMPanel()
{
setLayoutMode(MultiDocumentPanel::FloatingWindows);
// setLayoutMode(MultiDocumentPanel::MaximisedWindowsWithTabs);
setBackgroundColour(Colours::silver);
//do not add any window component in the panel
//normal components should be fine.
// win = createNewDocumentWindow();
String title;
for(int i=0;i<3;i++){
title=T("client ")+String(i);
lb[i] = new Label(title,T("some text"));
addAndMakeVisible(lb[i]);
lb[i]->setBounds(50,50,200,100);
addDocument(lb[i],Colours::lightgoldenrodyellow,true);
}
// for(int i=0;i<3;i++){
// client[i] = new MyContentComponent();
// addAndMakeVisible(client[i]);
// client[i]->setBounds(5,50,400,200);
// addDocument(client[i],Colours::bisque,true);
// }
}
~MyVMPanel()
{
// if(win!=0) delete win;
}
bool tryToCloseDocument(Component *component)
{
return true;
}
};
class MyContentComponent : public Component,
public ButtonListener
{
TextButton *bt1;
MyVMPanel * vmpanel;
public:
MyContentComponent()
{
//add more components here
bt1=new TextButton(T("Change layout"));
addAndMakeVisible(bt1);
bt1->addButtonListener(this);
vmpanel = new MyVMPanel();
addAndMakeVisible(vmpanel);
}
~MyContentComponent()
{
deleteAllChildren();
}
void paint (Graphics& g)
{
// clear the background with solid white
g.fillAll (Colours::antiquewhite);
// set our drawing colour to black..
g.setColour (Colours::darkseagreen);
// choose a suitably sized font
g.setFont (20.0f, Font::bold);
// and draw the text, centred in this component
g.drawText (T("Hello!"),0, 0, getWidth(), getHeight(),Justification::centred, false);
}
void resized()
{
bt1->setBounds( getWidth()/2-50,5,100,20);
vmpanel->setBounds(0,30,getWidth(),getHeight()-30);
}
void buttonClicked(Button *button)
{
if(button==bt1)
{
//toggle client window layout mode
if(vmpanel->getLayoutMode()!=MultiDocumentPanel::FloatingWindows)
{
vmpanel->setLayoutMode(MultiDocumentPanel::FloatingWindows);
vmpanel->lb[0]->setText(T("Floating layout"),false);
}else{
vmpanel->setLayoutMode(MultiDocumentPanel::MaximisedWindowsWithTabs);
vmpanel->lb[0]->setText(T("Tabbed layout"),false);
}
}
}
};
//==============================================================================
/** This is the top-level window that we'll pop up. Inside it, we'll create and
show a VMContentComponent component.
*/
class MyVMWindow : public DocumentWindow
{
public:
//==============================================================================
MyVMWindow()
: DocumentWindow (T("Welcome to VM World"),
Colours::lightgrey,
DocumentWindow::allButtons,
true)
{
// setContentComponent (new MyVMPanel(),true,true);
setContentComponent (new MyContentComponent(),true,true);
setVisible (true);
// centre the window on the desktop with this size
centreWithSize (800, 600);
}
~MyVMWindow()
{
// (the content component will be deleted automatically, so no need to do it here)
}
//==============================================================================
void closeButtonPressed()
{
// When the user presses the close button, we'll tell the app to quit. This
// window will be deleted by the app object as it closes down.
JUCEApplication::quit();
}
};
//==============================================================================
/** This is the application object that is started up when Juce starts. It handles
the initialisation and shutdown of the whole application.
*/
class JUCEVMApplication : public JUCEApplication
{
/* Important! NEVER embed objects directly inside your JUCEApplication class! Use
ONLY pointers to objects, which you should create during the initialise() method
(NOT in the constructor!) and delete in the shutdown() method (NOT in the
destructor!)
This is because the application object gets created before Juce has been properly
initialised, so any embedded objects would also get constructed too soon.
*/
MyVMWindow* vmwin;
public:
//==============================================================================
JUCEVMApplication()
: vmwin(0)
{
// NEVER do anything in here that could involve any Juce function being called
// - leave all your startup tasks until the initialise() method.
}
~JUCEVMApplication()
{
// Your shutdown() method should already have done all the things necessary to
// clean up this app object, so you should never need to put anything in
// the destructor.
// Making any Juce calls in here could be very dangerous...
}
//==============================================================================
void initialise (const String& commandLine)
{
// just create the main window...
vmwin = new MyVMWindow();
/* ..and now return, which will fall into to the main event
dispatch loop, and this will run until something calls
JUCEAppliction::quit().
In this case, JUCEAppliction::quit() will be called by the
hello world window being clicked.
*/
}
void shutdown()
{
// clear up..
if (vmwin != 0) delete vmwin;
}
//==============================================================================
const String getApplicationName()
{
return T("VM");
}
const String getApplicationVersion()
{
return T("1.0");
}
bool moreThanOneInstanceAllowed()
{
return true;
}
void anotherInstanceStarted (const String& commandLine)
{
}
};
//==============================================================================
// This macro creates the application's main() function..
START_JUCE_APPLICATION (JUCEVMApplication)
Even the default look-and-feel is pretty good! I like it very much. If you see the code, I really did nothing! Think of MFC, it will be ten times longer to get similar function and even less control. Thanks to JUCE!
While writing this code, the hints given by Jules and haydxn are very helpful. First, you’d better not pass a ‘window’ component directly to addDocument(), instead, a basic derived component is just fine. Then I wrapped the MultiDocumentPanel in the normal component. This works well. I tried not to do in this way, e.g., pass a DocumentWindow or createNewDocumentWindow to addDocument(), it makes a really ugly interface and strange behavior. Also if I directly pass a MultiDocumentPanel pointer to addDocument(), of course with some other components wrapped inside, I got segmentation fault when I tried to maximize the child window. I don’t know the reason.
Another uncontrolable thing is from the floatingwindows layout once you maximize a child window, the whole panel goes to tabbed layout and you cannot go back. This is really annoying and should be solved. So as a solution I added a button which allows me to switch between these two layouts. In fact it feels not bad.
There is still one bug I don’t get a solution immediately. As you can see, in the constructor I set the background color of the MultiDocumentPanel to silver, add the labels in each child window to yellow, but when the program starts, all child windows are also in silver. the color is overwritten?! However, if I maximize the child window, (go to tabbed mode), the tabs are in the right color – light yellow. Why? How could I set the bgcolor for each child? Any help will be appreciated.
Now if I’m in the correct way, here we have an example of MDI app, haha! I’m feeling great I could do this, because I think for single window app it will be easier. Next I will play with the child windows and make them multi-view and multi-document, e.g. one child presents an opengl context, others play music and provide controls. Please don’t forget to give me some advice on the coloring issue. Thanks!