A MDI example?

Hello, everyone!

I’m absolutely new here. I like JUCE at first sight. It’s not very difficult to start with. However, I still got troubles with some components. Like MultiDocumentPanel, I couldn’t use it well when I enabled it in “floating window” mode. (I added a DocumentWindow as input for addDocument function, and there had two title bars for each mdi child window. ) I couldn’t figure it out by myself, partially because I didn’t find any example on this component. Could anybody here show me an example on how I should use this component and add floating child windows correctly? Of course I need each child window has only one title bar and normal window control buttons. (The button I created also had problem. When I closed the minimize button on one of the child windows, my whole app minimized too. Strange!) I’m using MinGW on winxp now.

Again, it will be highly appreciated if someone could provide an MDI example (not tabbed). I guess this will not cost more than 10 mins if you know how to do it. That’s the advantage of JUCE:)

BTW, I’d like to tell some of my feeling in choosing a GUI toolkit as a beginner. I like the beautiful interface of JUCE and also its capabilities. (Covers almost all the necessary features a toolkit should have.) However, without handyxn’s brief tutorial, I think I would have spent much more time to get to know JUCE. In fact I almost turned to wxWidgets, for which you can find lots of good examples and tutorials. Nevertheless, I still think JUCE is a good choice. I just wish it could be better. Forget these nonsense, but don’t forget to give me a MDI example;D

your problem is a simple one - DONT use a DocumentWindow for the components you add; they’re wrapped in the floating window (with title bar) automatically. As a DocumentWindow has its own titlebar, you’ll obviously get two, and they won’t behave properly [plus, DocumentWindow is designed to be added to the desktop, which is why you get the odd minimise behaviour].

Just add a normal component as a document instead :slight_smile:

One day… one day i’ll get my tutorial expanded as it should be…

Thank you for the reply! Waiting for your new tutorials!

I’ll try what you suggested.

I think I really need some examples. For example, how should I use createNewDocumentWindow in the MDI app, I have no idea. (I created one and pass the pointer to addDocument, again double title bar.) Now I know I must do something wrong there. I think a empty app framework should solve my problem. Other things like how to arrange the child windows, tile, cascade, split…, are also standard operations for a mdi app. Again it seems not straightforward to me to do these with JUCE. Any suggestion on how I should start instead of trail and error? Thanks in advance!

well the MDI component in Juce doesn’t have all those arrangement features - it just lets you have them as floating windows or tabs, and that’s your onion. If you wanted something more advanced, you’d have to do it yourself!

But really it is a simple case of using normal Components. Design a component to be as you like, and add that to the MultiDocumentPanel as a document. DO NOT USE DOCUMENTWINDOW!!! :slight_smile:

I’m working on an app framework to extend juce as it goes, but it’s still got a lot of things to be done to it before it can really be released. I’m too busy at work to spend much time on it lately :’(

but one day…

one day!

You shouldn’t use createNewDocumentWindow at all. You’re trying too hard here. Just create a basic component, and call addDocument() with your component.

…are they? Maybe they used to be typical for a certain kind of Windows app, but are people even writing MDI apps any more? Has anyone ever clicked the “tile” button or “cascade” button and not been disappointed by the result?

I’ve always hated the whole MDI design anyway - it’s always felt to me like the thing that programmers do when they can’t be bothered writing a proper UI. And it’s a waste of screen space. Microsoft invented it, and like a lot of their bad designs, even they seem to be avoiding it these days.

second that :slight_smile:

[quote=“jules”]You shouldn’t use createNewDocumentWindow at all. You’re trying too hard here. Just create a basic component, and call addDocument() with your component.
[/quote]

Yeah I thought DocumentWindow and at least the MultiDocumentPanelWindow are also components. So I tried to add them by addDocument(). But there were some unexpected behaviors. I think this could be prevented by restricting people from using these ‘dangerous’ components in a MultiDocumentPanel. They are not clear to a user. I just take the description of MultiDocumentPanelWindow from the API doc:

[quote]Detailed Description
This is a derivative of DocumentWindow that is used inside a MultiDocumentPanel component.

It’s like a normal DocumentWindow but has some extra functionality to make sure everything works nicely inside a MultiDocumentPanel.[/quote]

How could I understand this properly?

As to the mdi layout, I do often use it in some softwares. Like in some scientific plotting software, I often have tens of windows opened and switch among them by ctrl+tab. More usefully I can tile some of windows and compare the data or plots. Splitting a window can be very useful for editing of a long document. I don’t really want to argue if the mdi is good or not. I’m sure all those functions can be mimicked by using JUCE’s components. However, a user has to deal with the window arrangement himself. Go back to the topic, I simply just want to make it clear how I should use the available functionalities of JUCE, nothing more.

Thank you for the hints you gave! If understand correctly, I should not use ‘window’ components but the basic components or derived ones in the MultiDocumentPanel, right? Keep on learning JUCE:)

Thanks - I guess I could add an assertion if you try to pass a documentwindow to addDocument, that’d have caught it for you. And I should add a bit more info to the help.

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!

it’s probably a good idea to let you know at this point (even though you’re not there yet) that you will have issues using an OpenGL context in an MDI with floating windows. An OpenGL context is created as a heavyweight window, and is just positioned and controlled by the juce Component wrapping it. It will overlap any other Juce components you have within the same top-level window - which means it will do very odd things when you have floating subwindows (which are all lightweight).

There’s not really any way around this, save for maybe dividing the OpenGL context up into smaller chunks (and i don’t know if that is even possible, let alone practical).

Just a warning!

Thank you, Handyxn! I’m curious about how the windows would behave if I have several opengl child windows, which can be overlapped. This could be a big issue for my application, since the app will have many windows to display 3D models and user should be able to create more. I don’t think I should tile them all on screen. Maybe tab is a choice. But as mentioned before, I cannot compare two randomly chosen windows easily. Is there a better solution?

Use normal components as panels, and allow the user to select from one of a handful of preset layouts. The choice is the theirs if they want one-up, two-up, three-up displays, and achieving that is only a single menu selection, rather than a few minutes of shuffling windows around.

Additionally, adding support for heavy weight windows for dual-head set-ups is simple too.

Thank you,valley! And thank you all for the discussion here. I fully agree with the solution valley gave. It’s true it has advantage for dual-head display.

However, I have tried to build an opengl child window. As warned by haydxn (I hope this time your name is correct;) ), the opengl window is really ‘heavy’. As you can see in the following picture, the opengl context is always top-most in the floating window mode. However, it’s not the case in the tabbed mode. I don’t really know the reason why JUCE cannot handle this normally. I think it must also be possible to override the opengl render window in the floating layout if we can do it in the tabbed mode. I agree with JUCE there is always a workaround without building a real mdi app. But I think it would be perfect if JUCE can do that. (On another aspect I think it should. Or why it provides such ability?) Will you have a look on this if it doesn’t cost you too much, Jules? Though it seems I’m the only guy who likes to stick to the mdi layout. Am I too stubborn?

A mess:

Normal behaved:

:slight_smile: I finally solved window’s coloring problem! When I create a background color for a newly created child window in float layout using addDocument(), I got all child windows having the same color as their parent - the MultiDocumentPanel. But no problem in the tabbed layout. (See above two pictures) After a little digging into the source codes, I found it’s just a bug. In MultiDocumentPanel::AddWindow(), the call of createNewDocumentWindow() uses the panel background color to create a new MultiDocumentPanelWindow. But the property “mdiDocumentBkg_” stored by addDocument() is not used. So I added a line in addWindow() after createNewDocumentWindow:

    dw->setResizable (true, false);
    dw->setContentComponent (component, false, true);
    dw->setName (component->getName());
 [color=red]dw->setBackgroundColour (component->getComponentPropertyColour (T("mdiDocumentBkg_"), false, Colours::white));//by little[/color]

This solved the problem.

Ah - nice work spotting that one! Thanks, I’ll get it fixed right away.

I think you might not be understanding the overlapping opengl problem - there’s no way that a heavyweight window can overlap a lightweight one because it’d somehow have to draw the lightweight one partly over the top of the heavy one, which isn’t possible. In the tabbed view it’s fine because it just makes the heavyweight window invisible. You 'll get the same problem in all other toolkits that work this way - java, qt, etc., it’s just one of those things. The only workaround would be to render the opengl off-screen and draw it as a normal component would, but that’d lose all the hardware acceleration.

Thank you, Jules! Indeed I have no much experience in doing graphic stuffs. Can you explain me why I have no such problem with MFC? I have a mdi program written in MFC. The children windows behave as I expected. No overlapping problem. I don’t really know the difference.

Well that’s because MFC uses heavyweight child windows.

How can I make a subwindow heavyweight then? Is that possible conceptually?

I found menu panel can override the opengl window all the time:

Is a menu panel a window too? Can I borrow some idea from it?
Can you tell me which components are heavyweight in JUCE? (Opengl component, …?) This brings another question: Can I add a scrollbar component for an component warpping an opengl window? This will have problem if the scrollbar is also lightweight I suppose.

Thanks!

The menu’s a completely separate window.

Only quicktime and opengl comps use a heavyweight window to do their stuff - there’s basically a normal juce component in there, and a heavyweight window that follows it around the screen, staying in the same position. Really, there’s nothing you can do to work around this, so either stick your opengl component in its own desktop window or just stop it overlapping anything.

Everything is clear to me now. Thank you all very much!