VCL Styles - уменьшен размер клиента - PullRequest
11 голосов
/ 25 февраля 2012

Я не знаю, если это ошибка ... Но когда я устанавливаю любой другой стиль VCL, кроме "Windows", ширина окна уменьшается.

Windows style Any other style looks like this... -

Есть ли какое-то решение для этого?

UPDATE Я отправил это в КК: http://qc.embarcadero.com/wc/qcmain.aspx?d=103697 Надеюсь, они это исправят ...

Ответы [ 6 ]

5 голосов
/ 26 февраля 2012

Это не ошибка стилей vcl, Так работают стили vcl, каждый стиль (обложка) имеет собственную ширину и высоту границы, которая иногда не соответствует размеру границы собственных окон,

проверьте следующие изображения

enter image description here

Карбоновый стиль имеет ширину границы и высоту 5 пикселей

enter image description here

стиль Amakrits имеет ширину и высоту границ 6 пикселей

enter image description here

Размер стиля границ каждого стиля можно проверить с помощью VCL Styles Designer

  • Объекты -> Форма-> Изображение -> Левая граница -> Ширина
  • Объекты -> Форма-> Изображение -> RigthBorder -> Ширина
  • Объекты -> Форма-> Изображение ->BottomBorder -> Высота

Таким образом, в зависимости от вышеупомянутых свойств, хук стиля формы пересчитывает границы клиентской области.

4 голосов
/ 28 января 2016

ОК - я провел еще несколько расследований и обнаружил основную проблему этой ошибки (пропустите до конца для обходного пути). Большинство / все другие обходные пути, рассредоточенные в Интернете и обсуждавшиеся до этого сообщения, по-видимому, просто маскируют симптомы ошибки, в действительности не найдя основную причину - и эти другие обходные пути могут иметь другие нежелательные побочные эффекты или ограничения ( как отметили некоторые их авторы).

Основная проблема заключается в том, что сообщение TFormStyleHook.WMNCCalcSize не обеспечивает ЛЮБОЙ обработки сообщений WM_NCCALCSIZE, когда параметр wParam равен FALSE. Функция в основном неполный. И поэтому обработчик окна по умолчанию называется - обработчик по умолчанию, предоставляемый Windows - который, конечно, возвращает клиентский прямоугольник для стиля Windows по умолчанию, а не пользовательский стиль VCL . Чтобы исправить эту ошибку, Embarcadero должен добавить обработку WM_NCCALCSIZE, когда wParam равен FALSE, чтобы информация о стиле VCL все еще возвращалась. Это было бы очень легко исправить для них, и теперь, когда я исследовал и нашел для них проблему, я надеюсь, что исправление может быть применено к следующему выпуску VCL.

Чтобы доказать, что это было причиной проблемы, я регистрировал все сообщения, отправленные в форму (путем переопределения WndProc), и для каждого сообщения отмечал, является ли клиент прямоугольным, как предусмотрено Win32 GetClientRect было правильно для стиля VCL. Я также отметил тип вызова функции WM_NCCALCSIZE (значение wParam). Наконец, я заметил новый клиентский прямоугольник, возвращаемый обработчиком WM_NCCALCSIZE.

Я обнаружил, что во время работы приложения почти каждое сообщение WM_NCCALCSIZE имело wParam, установленное на TRUE (что работает правильно), поэтому ошибка скрыта и не возникает. Вот почему Embarcadero до сих пор справился с этой ошибкой. Однако сообщение отправляется ОДИН РАЗ с wParam, установленным на FALSE, и это происходит в ключевой момент: непосредственно перед тем, как свойства ClientWidth / ClientHeight будут установлены на значения из файла DFM с помощью TCustomForm.ReadState , А функция TControl.SetClientSize работает, вычитая текущую ширину клиента (измеренную в Windows GetClientRect) из текущей общей ширины окна, а затем добавляет новую ширину клиента. Другими словами, TControl.SetClientSize требует, чтобы текущий прямоугольный клиент окна был точным, потому что он использует его для вычисления нового клиентского прямоугольника. А поскольку это не так, форма получает неправильный набор ширины, и отдых - это история.

О, вы удивляетесь, почему была затронута ширина, а не высота? Это было легко доказать - получается после установки ClientWidth, но до установки ClientHeight отправляется еще один WM_NCCALCSIZE - на этот раз с wParam из TRUE. VCL Styles правильно обрабатывает его и возвращает клиенту правильное значение - поэтому вычисления для ClientHeight, следовательно, оказываются правильными.

Обратите внимание, что будущие версии Windows могут сломаться хуже: если Microsoft решит более регулярно отправлять WM_NCCALCSIZE сообщения с wParam, установленным на FALSE, даже когда форма видна, для VCL все будет очень плохо.

Ошибка легко доказать, вручную отправив WM_NCCALCSIZE в форму. Шаги для воспроизведения:

  1. Создайте новое Приложение VCL Forms в C ++ Builder.
  2. Установите текущий / стандартный стиль VCL на Углеродный Стиль VCL из раздела Внешний вид в Параметры проекта .
  3. Добавить новый TButton элемент управления в форму.
  4. Добавьте следующий код к событию OnClick кнопки:

    void __fastcall TForm1::Button1Click(TObject *Sender)
    {
        // Compute the current cumulative width of the form borders:
        int CurrentNonClientWidth = Width - ClientWidth;
        // Get the current rectangle for the form:
        TRect rect;
        ::GetWindowRect(Handle, &rect);
        // Ask the window to calculate client area from the window rect:
        SendMessage(Handle, WM_NCCALCSIZE, FALSE, (LPARAM)&rect);
        // Calculate the new non-client area given by WM_NCCALCSIZE.  It *should*
        // match the value of CurrentNonClientWidth.
        int NewNonClientWidth = Width - rect.Width();
        if (CurrentNonClientWidth == NewNonClientWidth) {
            ShowMessage("Test pass: WM_NCCALCSIZE with wParam FALSE gave "
                "the right result.");
        } else {
            ShowMessage(UnicodeString::Format(L"Test fail: WM_NCCALCSIZE with "
                "wParam FALSE gave a different result.\r\n\r\nCurrent NC width: %d"
                "\r\n\r\nNew NC width: %d", ARRAYOFCONST((
                CurrentNonClientWidth, NewNonClientWidth))));
        }
    }
    
  5. Запустите проект и нажмите кнопку. Если вы проходите тестирование, это означает, что ширина ЧПУ в стиле VCL совпадает с шириной ЧПУ в Windows по умолчанию. Измените стиль границы формы или измените стиль VCL на другой и повторите попытку.

Обходной путь, конечно, заключается в том, чтобы найти способ перехватить WM_NCCALCSIZE сообщения, где wParam равен FALSE, а затем преобразовать его в сообщение, где wParam равно TRUE. На самом деле это можно сделать глобально: мы можем создать производный класс из TFormStyleHook, который решит проблему, а затем использовать хук глобально - это решит проблему во всех формах, включая формы, созданные в VCL (например, из Vcl .Диалог блок). В примере проекта, показанном выше, измените основной Project1.cpp следующим образом:

//---------------------------------------------------------------------------

#include <vcl.h>
#pragma hdrstop
#include <tchar.h>
#include <string.h>
#include <Vcl.Forms.hpp>
//---------------------------------------------------------------------------
#include <Vcl.Styles.hpp>
#include <Vcl.Themes.hpp>
USEFORM("Unit1.cpp", Form1);
//---------------------------------------------------------------------------
class TFixedFormStyleHook : public TFormStyleHook
{
public:
    __fastcall virtual TFixedFormStyleHook(TWinControl* AControl)
        : TFormStyleHook(AControl) {}
protected:
    virtual void __fastcall WndProc(TMessage &Message)
    {
        if (Message.Msg == WM_NCCALCSIZE && !Message.WParam) {
            // Convert message to format with WPARAM == TRUE due to VCL styles
            // failure to handle it when WPARAM == FALSE.  Note that currently,
            // TFormStyleHook only ever makes use of rgrc[0] and the rest of the
            // structure is ignored.  (Which is a good thing, because that's all
            // the information we have...)
            NCCALCSIZE_PARAMS ncParams;
            memset(&ncParams, 0, sizeof(ncParams));
            ncParams.rgrc[0] = *reinterpret_cast<RECT*>(Message.LParam);

            TMessage newMsg;
            newMsg.Msg = WM_NCCALCSIZE;
            newMsg.WParam = TRUE;
            newMsg.LParam = reinterpret_cast<LPARAM>(&ncParams);
            newMsg.Result = 0;
            this->TFormStyleHook::WndProc(newMsg);

            if (this->Handled) {
                *reinterpret_cast<RECT*>(Message.LParam) = ncParams.rgrc[0];
                Message.Result = 0;
            }
        } else {
            this->TFormStyleHook::WndProc(Message);
        }
    }
};
//---------------------------------------------------------------------------
int WINAPI _tWinMain(HINSTANCE, HINSTANCE, LPTSTR, int)
{
    // Register our style hook.  An audit of C++ Builder XE8 VCL source code
    // for registration of the existing TFormStyleHook shows that these are
    // the only two classes we need to register for.
    TCustomStyleEngine::RegisterStyleHook(__classid(TForm),
        __classid(TFixedFormStyleHook));
    TCustomStyleEngine::RegisterStyleHook(__classid(TCustomForm),
        __classid(TFixedFormStyleHook));

    Application->Initialize();
    Application->MainFormOnTaskBar = true;
    TStyleManager::TrySetStyle("Carbon");
    Application->CreateForm(__classid(TForm1), &Form1);
    Application->Run();
    return 0;
}
//---------------------------------------------------------------------------

Теперь запустите проект и нажмите кнопку; вы увидите, что WM_NCCALCSIZE теперь правильно обрабатывается. Также вы увидите, что если вы явно задали ClientWidth в файле DFM, теперь он будет использоваться правильно.

4 голосов
/ 25 февраля 2012

Это действительно похоже на ошибку VCL. * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Свойство ClientWidth неправильно выводится из файла .dfm, если в параметрах проекта установлен стиль, отличный от стиля системы.

Предлагаю вам отправить отчет в QualityCentral . Тем временем вы можете обойти эту проблему, установив стиль в файле .dpr после создания форм.

Application.Initialize;
Application.MainFormOnTaskbar := True;
Application.CreateForm(TMainForm, MainForm);
TStyleManager.SetStyle('Amakrits');//after CreateForm, rather than before
Application.Run;

Тем не менее, я не думаю, что это продвинет вас слишком далеко, потому что вы, вероятно, хотите иметь возможность создавать формы на лету и не должны создавать все при запуске.

3 голосов
/ 02 августа 2018

Для тех, кто ищет действительно умное решение для этого очень странного поведения, взгляните на ответ Джеймса Джонстона. Я применил его в своем проекте, и он работает безупречно. Ниже приведен перевод Delphi из ответа Джеймса. Спасибо Джеймс!

program Solve;

uses
  Vcl.Forms,
  Unit1 in 'Unit1.pas' {Form1},
  Windows,
  Messages,
  Vcl.Themes,
  Vcl.Styles;

type
  TFixedFormStyleHook = class(TFormStyleHook)
  protected
    procedure WndProc(var AMessage: TMessage); override;
  end;

{ TFixedFormStyleHook }

procedure TFixedFormStyleHook.WndProc(var AMessage: TMessage);
var
  NewMessage: TMessage;
  ncParams: NCCALCSIZE_PARAMS;
begin
  if (AMessage.Msg = WM_NCCALCSIZE) and (AMessage.WParam = 0) then
  begin
    // Convert message to format with WPARAM = TRUE due to VCL styles
    // failure to handle it when WPARAM = FALSE.  Note that currently,
    // TFormStyleHook only ever makes use of rgrc[0] and the rest of the
    // structure is ignored. (Which is a good thing, because that's all
    // the information we have...)
    ZeroMemory(@ncParams,SizeOf(NCCALCSIZE_PARAMS));
    ncParams.rgrc[0] := TRect(Pointer(AMessage.LParam)^);

    NewMessage.Msg := WM_NCCALCSIZE;
    NewMessage.WParam := 1;
    NewMessage.LParam := Integer(@ncParams);
    NewMessage.Result := 0;
    inherited WndProc(NewMessage);

    if Handled then
    begin
      TRect(Pointer(AMessage.LParam)^) := ncParams.rgrc[0];
      AMessage.Result := 0;
    end;
  end
  else
    inherited;
end;

{$R *.res}

begin
  // Register our style hook. An audit of Delphi XE8 VCL source code
  // for registration of the existing TFormStyleHook shows that these are
  // the only two classes we need to register for.
  TCustomStyleEngine.RegisterStyleHook(TForm,TFixedFormStyleHook);
  TCustomStyleEngine.RegisterStyleHook(TCustomForm,TFixedFormStyleHook);

  Application.Initialize;
  Application.MainFormOnTaskbar := True;
  TStyleManager.TrySetStyle('Glossy Light');
  Application.CreateForm(TForm1, Form1);
  Application.Run;
end.

При использовании этого кода размеры ClientWidth / ClientHeight соблюдаются, а внутреннее содержимое отображается правильно. Конечно, внешний размер Window будет больше, чтобы вместить размеры ClientWidth / ClientHeight , но это не так уж и плохо, потому что обычно содержимое окна более важно.

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

0 голосов
/ 24 июля 2015

Эта проблема все еще присутствует в Delphi XE8.Простой обходной путь - использовать следующий код, который просто восстанавливает время разработки ClientWidth / ClientHeight с несколькими оговорками (наиболее важно, чтобы AutoScroll было установлено на False):

type
  TFormHelper = class helper for Vcl.Forms.TCustomForm
  private
    procedure RestoreDesignClientSize;
  end;

procedure TfrmTestSize.FormCreate(Sender: TObject);
begin
  RestoreDesignClientSize;
end;

{ TFormHelper }

procedure TFormHelper.RestoreDesignClientSize;
begin
  if BorderStyle in [bsSingle, bsDialog] then
  begin
    if Self.FClientWidth > 0 then ClientWidth := Self.FClientWidth;
    if Self.FClientHeight > 0 then ClientHeight := Self.FClientHeight;
  end;
end;

Учитывая следующую форму времени проектирования:

design time form

Это исправляет время выполнения с:

enter image description here

Кому:

enter image description here

У меня есть больше деталей и фотографий на моем блоге: http://marc.durdin.net/2015/07/fixing-the-incorrect-client-size-for-delphi-vcl-forms-that-use-styles/

0 голосов
/ 25 февраля 2012

Здесь нет XE2, но это звучит очень знакомо.Попробуйте установить AutoScroll в True (как ни странно, противоположность этот ответ ) для хранения размера клиентской формы, а не размера границы.

...