lenmus / lomse

A C++ library for rendering, editing and playing back music scores.
MIT License
117 stars 28 forks source link

How to update view during runtime (Windows)? #358

Closed AndreasKoettl95 closed 2 years ago

AndreasKoettl95 commented 2 years ago

Hi,

I'm using this library on a windows machine, and I'm having trouble getting the view updated during runtime.

My starting point was the Tutorial 1 for Windows so I am using the same Bitmap class for example. On certain events, I want the view to be updated. By now, I simply want to hide/unhide certain notes during runtime. This does work, if I hide these notes directly after opening the MusicXML document, but not anymore once I've entered the applications message loop.

Maybe I missed something, but for me it looks like it should work. As soon as the WM_PAINT message is fired, the view should be updated:

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch (message)
    {
    case WM_PAINT:
    {
        if (app.get_view_needs_rerender())
        {
            app.update_rendering_buffer();
        }

        app.set_view_needs_rerender(false);

        PAINTSTRUCT ps;
        HDC paintDC = ::BeginPaint(m_hWnd, &ps);
        app.copy_buffer_to_dc(paintDC);
        ::EndPaint(m_hWnd, &ps);
        break;
    }

    case WM_SIZE:
        app.create_rendering_buffer_bitmap(LOWORD(lParam), HIWORD(lParam));
        break;
}
}
void Application::update_rendering_buffer()
{
    if (!m_pPresenter)
    {
        return;
    }

    if (m_view_needs_rerender)
    {
        if (lomse::SpInteractor spInteractor = m_pPresenter->get_interactor(0).lock())
        {
            spInteractor->set_rendering_buffer(m_bitmap.buf(), m_bitmap.width(), m_bitmap.height());
            spInteractor->force_redraw();
        }
        m_view_needs_rerender = false;
    }
}
void Application::copy_buffer_to_dc(HDC dc)
{
    m_bitmap.draw(dc);
}
void Application::create_rendering_buffer_bitmap(unsigned width, unsigned height)
{
    m_bitmap.create(width, height, m_bits_per_pixel);
    m_view_needs_rerender = true;
}

All these steps are executed as expected, spInteractor->force_redraw(); gets called, m_bitmap.draw(dc); gets called, and it has to work somehow, otherwise I wouldn't see anything. But if I make changes to the model e.g. hide a note, the view doesn't get updated.

Is there anything missing, to make this work? Maybe it's just a tiny thing, but if it's a bigger issue and needs lots of investigation, I might simply switch to wxWidgets, since I can also use it on Windows.

AndreasKoettl95 commented 2 years ago

Of course after making my changes I'm calling app.set_view_needs_rerender(true); and all these functions mentioned get called after that as expected, but no update for the view.

cecilios commented 2 years ago

All these steps are executed as expected, spInteractor->force_redraw(); gets called, m_bitmap.draw(dc); gets called, and it has to work somehow, otherwise I wouldn't see anything. But if I make changes to the model e.g. hide a note, the view doesn't get updated.

Lomse manages different internal structures. The main one is the internal model (IM). It is an internal agnostic representation of the document. You could imagine it as the DOM model in html. From the IM other representations are derived:

When you modify the document you are modifying the IM. But once it has been modified it is necessary to update the GM and invalidate the SM and this is not always an automatic operation: it depends on the methods used to modify the document.

When you invoke spInteractor->force_redraw(); what you are doing is just requesting to render the GM and to generate a Paint event. But not to update the GM before rendering it. This explains why you never see the changes you've done in the IM.

I don't know how you are modifying the IM, and so I'm assuming that you are directly modifying ImoObj objects. If this is the case, you are taking the route that requires most knowledge about Lomse internals. But it is not complex for simple IM modifications. A possible way:

  1. Once you have finished doing all necessary modifications in the IM, you must invoke either ImoScore::end_of_changes(); or Document::end_of_changes(). This will trigger the necessary methods for updating other internal structures associated to the IM, such as the StaffObjs collection and the SM.

  2. Then, you must inform Lomse that the document has been updated by invoking Interactor::on_document_updated(). This will trigger the update of the GM.

  3. And finally you must request to update the bitmap for the View by invoking Interactor::force_redraw();. This will generate a Paint event and your window will get updated.

So, in your app probably you are missing step 2 (to invoke Interactor::on_document_updated()) and perhaps also step 1 (ImoScore::end_of_changes();).

AndreasKoettl95 commented 2 years ago

I understand, and this works just fine, thank you very much. :)