Некоторые элементы управления не рисуются, казалось бы, наугад - PullRequest
1 голос
/ 07 мая 2019

Я пытаюсь написать небольшое приложение MFC только для себя, чтобы проверить некоторых ИИ, которых я тренирую.

Поэтому я добавил графический элемент и статический элемент управления, где я могу свободно рисовать вещи в методе OnPaint () моего главного окна.

Кажется, это работает, когда просто рисую мое приложение один раз, но теперь я добавил цикл, который выполняет OnPaint () несколько раз перед остановкой.

Когда в этом цикле некоторые другие элементы управления не отображаются, например, все мои кнопки пропали, а некоторые ползунки иногда отсутствуют, но в других случаях они есть.

Мой код выглядит так:

void CKiUebung1Dlg::OnBnClickedButtongo()
{
    m_bisGoing = true;
    OnPaint();
    if(m_fDiagramData.size() <= 0)
    {
        m_fDiagramData.push_back((float)rand() / RAND_MAX);
        InvalidateRect(NULL, TRUE);
    }
    OnPaint();
    for(int i(9); i >= 0; --i)
    {
        std::this_thread::sleep_for(std::chrono::milliseconds(1000));
        m_fDiagramData.push_back((float)rand() / RAND_MAX);
        InvalidateRect(NULL, TRUE);
        OnPaint();
    }
    m_bisGoing = false;
    OnPaint();
}
void CKiUebung1Dlg::OnPaint()
{
    if(IsIconic())
    {
        CPaintDC dc(this); // Gerätekontext zum Zeichnen

        SendMessage(WM_ICONERASEBKGND, reinterpret_cast<WPARAM>(dc.GetSafeHdc()), 0);

        // Symbol in Clientrechteck zentrieren
        int cxIcon = GetSystemMetrics(SM_CXICON);
        int cyIcon = GetSystemMetrics(SM_CYICON);
        CRect rect;
        GetClientRect(&rect);
        int x = (rect.Width() - cxIcon + 1) / 2;
        int y = (rect.Height() - cyIcon + 1) / 2;

        // Symbol zeichnen
        dc.DrawIcon(x, y, m_hIcon);
    }
    else
    {
        CDialogEx::OnPaint();
    }
    {
        constexpr const int border = 5;
        CPaintDC dc(&m_cDiagram);
        CRect l_cPos;
        m_cDiagram.GetClientRect(&l_cPos);
        const int width(l_cPos.Width() - border * 2 - 2), height(l_cPos.Height() - border * 2 - 12);
        const int numPoints(m_fDiagramData.size());
        POINT* points(new POINT[numPoints]);
        for(int i(numPoints - 1); i >= 0; --i)
        {
            const int
                x((float)i / (numPoints - 1) * width + border + 1),
                y(height - m_fDiagramData[i] * height + border + 9);
            points[i] = { x,y };
        }
        dc.Polyline(points, numPoints);

        static CString going(_T(" "));
        if(m_bisGoing) { going += _T("."); if(going.GetLength() > 300) going = _T(" ."); }
        else going = _T(" ");
        float fprog(0); if(m_fDiagramData.size() > 0) fprog = m_fDiagramData.back();
        CString prog; prog.Format(_T("Progress %03.2f%%"), fprog * 100); if(m_bisGoing) prog += going;
        m_cDiagram.SetWindowTextW(prog);

        m_cDiagram.RedrawWindow();

        delete[] points;
    }
}

Вот как это выглядит, когда цикл не работает:

This is how it looks when the loop isn't running

Вот как это выглядит, когда цикл работает:

This is how it looks when the loop is running

Ответы [ 2 ]

3 голосов
/ 08 мая 2019

CWnd::OnPaint является ответом на сообщение WM_PAINT и не должен вызываться напрямую.

WM_PAINT вызывает CWnd::OnPaint, что вызывает CPaintDC dc(this), что в свою очередь вызывает BeginPaint / EndPaint API. Эта последовательность сообщения + ответа должна быть оставлена ​​как есть.

Поэтому CPaintDC dc(this) должен появиться один раз - и только один раз - внутри OnPaint, а не где-либо еще. Переопределите OnPaint следующим образом:

void CMyDialog::OnPaint()
{
    CDialogEx::OnPaint(); //this will call CPaintDC dc(this);

    //optional: 
    CClientDC dc(this); //CClientDC can be used anywhere in a valid window
    //use dc for drawing
}

//or
void CMyDialog::OnPaint()
{
    CPaintDC dc(this); 
    //use dc for drawing
}

Вам также не нужно устаревшее if (IsIconic()) {...} условие.

Чтобы заставить окно перерисоваться, вызовите Invalidate() (тоже самое, что и InvalidateRect(NULL, TRUE))

InvalidateRect(NULL, TRUE) - запрос перекрасить окно. Система посмотрит на этот запрос и отправит сообщение WM_PAINT в это окно, когда будет такая возможность. Поэтому вызов InvalidateRect может не обработать то, как вы ожидаете, что он будет работать в последовательной программе. Например, второй последовательный вызов InvalidateRect не будет иметь никакого эффекта. Окно уже помечено для обновления.

 for(int i(9); i >= 0; --i)
 {
    std::this_thread::sleep_for(std::chrono::milliseconds(1000));
    m_fDiagramData.push_back((float)rand() / RAND_MAX);
    InvalidateRect(NULL, TRUE);
    OnPaint();
 }

OnPaint() следует удалить из приведенного выше кода. Тем не менее, анимация невозможна в одном потоке (по крайней мере, не таким образом). Программа занята прохождением цикла, она не может обрабатывать WM_PAINT и другие сообщения.

Итак, вам нужен дополнительный поток или просто используйте SetTimer, и ответьте на ON_WM_TIMER() / OnTimer для анимации. Пример:

int counter = 0;

BEGIN_MESSAGE_MAP(CMyDialog, CDialogEx)
    ON_WM_PAINT()
    ON_WM_TIMER()
    ...
END_MESSAGE_MAP()

void CMyDialog::OnPaint()
{
    CPaintDC dc(this);
    CString s;
    s.Format(L"%02d", counter);
    dc.TextOut(0, 0, s);
}

void CMyDialog::animate()
{
    counter = 0;
    SetTimer(1, 1000, NULL);
}

void CMyDialog::OnTimer(UINT_PTR n)
{
    if(n == 1)
    {
        Invalidate(); //force repaint
        counter++;
        if(counter == 10)
            KillTimer(1);
    }
}
2 голосов
/ 09 мая 2019

У вас, похоже, проблемы с пониманием того, как работает аннулирование / рисование.Сначала необходимо прочитать документацию: Живопись и рисунок

Хотя многие разработчики рекомендуют рисовать только при обработке WM_PAINT (OnPaint() в MFC), этоне всегда лучшее решение, потому что это сообщение с низким приоритетом, рисование может быть не немедленным (иметь «прерывистое» ощущение), и вы можете получить «мерцающий» эффект.

Вместо этого я иногда рекомендую сочетание рисунка и рисования:

  • Использование рисования в обработке WM_PAINT.Это должно нарисовать всю клиентскую область (или только недействительную ее часть, если вы хотите более «оптимизированную» реализацию).Обратите внимание, что сообщение WM_PAINT может быть получено в результате аннулирования части или всей клиентской области, из-за перемещения, изменения размера, отображения и т. Д. Окна, в дополнение к программной аннулированию его.Поэтому в ответ на сообщение WM_PAINT вы должны выполнить полную перерисовку, то есть все элементы, которые вы хотите отобразить.
  • Использовать чертеж для изменений, которые вы хотите, чтобы они были показаны немедленно, пока приложение занято (не ожидает получения «асинхронного» сообщения WM_PAINT).Обратите внимание, что они также должны обрабатываться WM_PAINT, поэтому вам скорее нужно написать некоторые процедуры рисования / рисования, взяв HDC (или CDC*) в качестве параметра (наряду с любым другим необходимым параметром), и вызватьих как из функции OnPaint() (передача туда ClientDC), так и из ваших дополнительных необходимых действий рисования (передача CDC*, полученное путем вызова GetDC()).

Итак, позвольте мнеподелитесь своим опытом с приложением, которое я написал (давно) назад.Это приложение для отображения / манипулирования изображениями (среди прочего), обрабатывающее изображения в произвольном формате и использующее специальную библиотеку, которая была довольно «медленной», так как обеспечивала только функцию отображения изображения в контексте устройства (это включает в себявозможное обрезание, корректировка, изменение размера и т. д., которые являются дорогостоящими операциями процессора).Вот изображение:

enter image description here

Вы можете видеть, как пользователь выполняет выбор.Приложение должно отобразить изображение и, возможно, прямоугольник выделения поверх него, и, конечно, это то, что делает OnPaint().«Легкая» (хотя и технически «правильная») реализация будет вызывать Invalidate() или InvalidateRect() в ответ на каждое сообщение о перемещении мыши (при выборе).Это может привести к полной перерисовке (что «ОК»), но также будет страдать от проблем с производительностью из-за медленной библиотеки изображений: если вы также вызовете UpdateWindow() после аннулирования (запроса немедленного обновления), производительность будет вялой (имеядля повторной обработки / повторного отображения изображения), если нет, обновление будет происходить через некоторое (заметное) время спустя.Это было решено путем использования drawign (не рисования) в ответ на сообщение WM_MOUSEMOVE: там не делается недействительных, вместо этого рисуется только прямоугольник выбора (после восстановления части, измененной в предыдущем сообщении выбора - я только резервное копирование / восстановление четырех сторонрамка, а не весь прямоугольник).В результате приложение работает быстро и плавно, несмотря на медленную библиотеку, и правильно отображает изображение и выделение, даже если вы переключаетесь на другое приложение и затем возвращаетесь к нему, пока отслеживается выделение (пунктирная линия).

Некоторые замечания и предложения по поводу вашей реализации (у нее довольно много проблем):

  • Как отметили другие участники, вы не можете звонить OnPaint() самостоятельно.Особенно те звонки после Invalidate() не имеют абсолютно никакого смысла.Вместо этого, позвоните по номеру UpdateWindow(), если вы хотите немедленное обновление.
  • Мне кажется, что выполнять вычисления в пределах OnPaint() НЕТ, и я имею в виду вычисления этих точек (хотя в вашем случае вычисление довольно тривиально),OnPaint() должен просто отображать данные, рассчитанные в другой части вашего кода.
  • Кроме того, установка текста m_cDiagram и перерисовка из OnPaint() тоже не подходят (могут вызвать дополнительные запросы рисования).Лучше переместить их в OnBnClickedButtongo().
  • Вам не нужно лишать законной силы (и особенно стирать) всю клиентскую область, чтобы заставить некоторые элементы управления перекрашиваться, вместо этого нужно сделать недействительными только эти элементы управления. Помните, что функция sleep_for() блокируется, и сообщение WM_PAINT не будет отправлено и обработано во время работы вашего цикла.
  • Кстати, рассмотрим неблокирующий подход, например, использование таймера, как предложил @Barmak Shemirani. В качестве альтернативы можно написать «неблокирующий sleep()», запустив цикл сообщений самостоятельно (возьмите части кода в CWinApp::Run() и измените его).
  • Поскольку у вас есть диалоговое окно и созданы отдельные элементы управления для отображения ваших данных, использование OnPaint() не является хорошей реализацией, поскольку оно затрагивает (рисует) всю клиентскую область. Это в основном полезно для таких классов, как CView или CScrollView (или обычная живопись CWnd s в целом). Вы рисуете график на поверхности диалогового окна и должны выполнить вычисления, чтобы получить координаты в m_cDiagram (кстати, вместо этого вы можете использовать GetWindowRect(), а затем ScreenToClient()), но было бы лучше использовать элемент управления, нарисованный владельцем ( рисовать / рисовать график), и это на самом деле не сложно, вы просто должны отвечать на запросы рисования (как в OnPaint()), и контекст устройства, который вы получаете, может рисовать только на элементе управления, а не в диалоге; координаты относятся к клиентской области элемента управления, начиная с (0,0).

Надеюсь, это поможет

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...