Пользовательский оконный фрейм с DWM: как правильно обрабатывать WM_NCCALCSIZE

Я пытаюсь создать индивидуальную оконную раму для своей формы с помощью DWM. Платформа - C # WinForms, Pinvoking DWM.

Следуя статье MSDN о создании пользовательских оконной рамы с DWM, основные шаги следующие:

  1. Удалить стандартный фрейм (неклиентская область), возвращая 0 в ответ на сообщение WM_NCCALCSIZE
  2. Расширьте фрейм в клиентскую область с помощью функции DwmExtendFrameIntoClientArea

Я обрабатываю сообщение WM_NCCALCSIZE следующим образом:

protected override void WndProc(ref Message m)
{
   switch (m.Msg)
   {
       case WM_NCCALCSIZE:
            if (isDwmWindowFramePaintEnabled() && m.WParam != IntPtr.Zero)
            {
                m.Result = IntPtr.Zero;
            }
            else
            {
                base.WndProc(ref m);
            }
            return;
   }
}

Согласно документации MSDN по WM_NCCALCSIZE < / а>,

Когда wParam имеет значение TRUE, простой возврат 0 без обработки прямоугольников NCCALCSIZE_PARAMS приведет к изменению размера клиентской области до размера окна, включая рамку окна. Это удалит рамку окна и элементы заголовка из вашего окна, оставив отображаемую только клиентскую область.

У меня все нормально, работает, кроме одной проблемы. Когда я увеличиваю / восстанавливаю окно, оно всегда немного увеличивается при восстановлении. Думаю, проблема примерно такая:

  1. Когда окно восстанавливается, оно содержит только клиентскую область
  2. Windows пытается предоставить окну неклиентскую область
  3. В WM_NCCALCSIZE клиентская область увеличивается, чтобы содержать неклиентскую область

Итак, как будто это окно немного увеличивается каждый раз, когда я увеличиваю / восстанавливаю его. Мне нужно удалить неклиентскую область, чтобы нарисовать рамку нестандартной формы с помощью DWM. Я не могу просто установить для стиля границы окна значение «Нет», поскольку тогда DWM не будет рисовать заголовок и границы окна.

Пожалуйста, помогите решить проблему и с радостью сделайте нестандартную оконную раму.


person Artem Kachanovskyi    schedule 20.11.2013    source источник


Ответы (1)


На самом деле это ошибка Windows Forms, и есть обходной путь. В функции Form.SizeFromClientSize(int, int) функция AdjustWindowRectEx используется для преобразования размера, она всегда использует измерения по умолчанию и не может быть отменена. Эта функция вызывается из двух мест:

  1. RestoreWindowBoundsIfNecessary в обработчике сообщений окна WM_WINDOWPOSCHANGED
  2. SetClientSizeCore

Обходной путь следующий:

  • Переопределить CreateParams в форме:

    private bool createParamsHack;
    
    protected override CreateParams CreateParams
    {
        get
        {
            CreateParams cp = base.CreateParams;
            // Remove styles that affect the border size
            if (createParamsHack)
                cp.Style &= ~(int)(WS_BORDER | WS_CAPTION | WS_DLGFRAME | WS_THICKFRAME);
            return cp;
        }
    }
    
  • Переопределите WndProc и вставьте следующий код для обработки WM_WINDOWPOSCHANGED:

        if (m.Msg == WM_WINDOWPOSCHANGED)
        {
            createParamsHack = true;
            base.WndProc(ref m);
            createParamsHack = false;
        }
    
  • Переопределить SetClientSizeCore:

    protected override void SetClientSizeCore(int x, int y)
    {
        createParamsHack = true;
        base.SetClientSizeCore(x, y);
        createParamsHack = false;
    }
    

Также может быть хорошей идеей переопределить SizeFromClientSize(Size), чтобы получить правильные измерения, но это не является строго необходимым.

person Filip Navara    schedule 06.12.2013
comment
Спасибо! Я скоро попробую и скажу, поможет ли! - person Artem Kachanovskyi; 09.12.2013
comment
Прошу прощения за задержку с этим ответом. Я попробовал ваше предложение с переопределением CreateParams, и это помогло! Кроме того, я попытался переопределить метод Form.SizeFromClientSize (Size), чтобы просто вернуть размер клиента, который передается методу в качестве аргумента. Но это не помогло. - person Artem Kachanovskyi; 17.12.2013