0

In the main window of Delphi VCL application I'd like to have the caption this way:

text1 text2 text3

occupying all space available for the caption. Text1 is aligned to left of caption, text2 in the middle and text3 to the right. If I knew the current caption max length, could calculate spaces between text1 and text2 and spaces between text2 and text3 to get them aligned as wanted. The problem is that with different form sizes, the caption changes size. How can I know current max length of (TForm).caption ? Thanks

Edit
I tried to set caption to a way long 300 characters string and wait for the show event to present the caption with '...' at the end (meaning overflow). But when tried to search '...' in the caption, didn´t find it. If it had found '...' then could know caption's length. How can I find '...' ?

Josef Švejk
  • 1,047
  • 2
  • 12
  • 23
  • Yeah my bad that was shortstring – Ilyes Sep 23 '18 at 21:30
  • 4
    @Hector: This sound like a very bad idea. Window title bars aren't supposed to be used like this in Windows. Fighting against the system is prone to fail. – Andreas Rejbrand Sep 23 '18 at 21:30
  • 2
    I think there is a character limit that kicks in before you even reach the space limit. What you are attempting is probably a bad idea – David Heffernan Sep 23 '18 at 22:09
  • 1
    You will likely have to handle the `WM_NCPAINT` message to draw the titlebar yourself. Then you can just draw the three strings separately with calculated coordinates. But then you have to draw the ENTIRE titlebar, and that gets very tricky with each new Windows version. Probably easiest to just leave the `Caption` blank, then handle `WM_NCPAINT` by first calling the default handler, and then draw your text on top of whatever the OS draws – Remy Lebeau Sep 23 '18 at 22:58
  • 1
    @Andreas: Windows title bars were not made to contain main menus either, and yet the Firefox I am typing this in has that. But as Remy says, it would have to be a non-client paint. I agree that it may not be a good idea, but that is his decision. – Rudy Velthuis Sep 23 '18 at 23:32
  • @DavidHeffernan, I test it and it seems there is a limit for 255 symbols. – Josef Švejk Sep 24 '18 at 14:06

1 Answers1

1

You can calculate the current maximum length of the caption.

The current ClientWidth of the Form is available at runtime, and using the Form Designer gives an estimate of the space occupied by the icons. The pixel width of an AnsiString is returned by the Canvas->TextWidth function.

AnsiString Words = First + Middle + Last;

// store width of text in pixels
WordsWidthInPixels  = Canvas->TextWidth(Words);

The number of spaces can be found with the help of the TextWidth of a space or two.

Update:

Here is some code using system metrics instead of estimating from the designer. I've put almost all the code in a function called GetNumSpacesMetric.

The function header is added to the Form class in the header file :-

class TForm1 : public TForm
{
__published:    // IDE-managed Components
        void __fastcall FormResize(TObject *Sender);
private:    // User declarations
public:     // User declarations
        __fastcall TForm1(TComponent* Owner);

        int  __fastcall  GetNumSpacesMetric(TObject *Sender, TComponent* AForm);
};

On the Form create the Event OnResize, and add the code which updates the Caption when the Form is resized. If the name of the form is not Form1 then it will need to be changed in the code here :-

void __fastcall TForm1::FormResize(TObject *Sender)
{
  // strings
  const AnsiString    First  = AnsiString("First");
  const AnsiString    Middle = AnsiString("Middle");
  const AnsiString    Last   = AnsiString("Last");

  // get number of spaces
  int NumSpacesMetric = GetNumSpacesMetric(Sender, Form1);

  // print the caption
  if( NumSpacesMetric > 0 )      {
      AnsiString Spaces = AnsiString::StringOfChar(' ', NumSpacesMetric);
      AnsiString caption = First + Spaces + Middle + Spaces + Last;
      Form1->Caption = caption;
  }
}

Next add the GetNumSpacesMetric function definition. at the function head and where the Image is created.

// calculate the number of spaces needed between three words in Form Caption
int  __fastcall  TForm1::GetNumSpacesMetric(TObject *Sender, TComponent* AForm)
{
  const int          NumberOfMenuIcons = 3;
  const AnsiString   Words = "FirstMiddleLast";
  const AnsiString   TwinSpace = AnsiString::StringOfChar(' ', 2);
  const int          Squeeze   = 7 * 8;   //  tweak 1 - squeeze string length
  //const int          FineTune  = 840;     //  tweak 2 - lengthen string when width smaller
  //const int          LimitLength = 980;   //  tweak 3

  static int         WordsPixelWidth;
  static int         TwinSpacePixelWidth = 1;

  // get metric data
  static NONCLIENTMETRICS   ncm;
  static bool done = false;

  // do once
  if(!done)
  {
    ncm.cbSize =  sizeof(NONCLIENTMETRICS);
    SystemParametersInfo( SPI_GETNONCLIENTMETRICS,
                          sizeof(NONCLIENTMETRICS), &ncm, NULL);

    TImage *tmpImage = new TImage(AForm);

    // Font data
    tmpImage->Canvas->Font->Handle = CreateFontIndirect(&ncm.lfCaptionFont);

    // get pixel widths of Words and double space
    WordsPixelWidth     = tmpImage->Canvas->TextWidth(Words);
    TwinSpacePixelWidth = tmpImage->Canvas->TextWidth(TwinSpace);

    DeleteObject(tmpImage->Canvas->Font->Handle);

    done = true;
  }

  int clientwidth = ClientWidth;
  /*
  // limit length of text if required
  if( clientwidth > LimitLength)
    clientwidth = LimitLength;
  */

  // client width minus icon widths and words width
  int NumOfPixelsLeft  =  clientwidth
                       -  ncm.iCaptionWidth
                       - (ncm.iMenuWidth * NumberOfMenuIcons)
                       -  WordsPixelWidth
                       -  Squeeze
                     //  + ((8 * (FineTune - clientwidth))/100)
                       ;

  // return number of pixels available divided by size of two spaces
  return NumOfPixelsLeft / TwinSpacePixelWidth;
}
//---------------------------------------------------------------------------

There are tweaks which can be used to change the program and are given a short description in the code.

Update 2: Added a parameter to GetNumSpacesMetric, to pass the Form object.

There is a newer set of instructions which can get some of the metrics:-

The TITLEBARINFO structure, TITLEBARINFOEX structure, GetTitleBarInfo function and GetTitleBarInfoEx function.

Baxter
  • 126
  • 4
  • 1
    If you referred to `Form.Canvas` then it is a bad advice because font of `Form` is not equal to font that is used by OS for drawing caption text. To obtain dimensions of any element appeared on title bar it is best to use [GetSystemMetrics function](https://learn.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-getsystemmetrics), so there is no need to look at Designer and calculate *free* space on title bar by your eyes. But even if you get rid off *all* icons and button from title bar, there is still a limit for text: 255 chars. That's your answer is totally wrong. – Josef Švejk Sep 25 '18 at 07:21
  • Ok, thanks. Hector is looking for the _current_ maximum length of Form Caption. – Baxter Sep 26 '18 at 01:46
  • 1
    @DBB - To rephrase part of what Dima said: A canvas cannot help with calculating caption text width unless you retrieve and assign system caption font to it. To elaborate a little: the pixel width of a string depends on the font being used. And the caption does not use your form's font. That's, your answer is completely wrong. – Sertac Akyuz Sep 26 '18 at 01:58
  • @Sertac - You maybe completely correct, but if you personalize the Desktop and change the Active Window Text the Window Text changes as well. Of course the program can change the Font but it will be known that it is changed. – Baxter Sep 26 '18 at 02:40
  • @Sertac - I was just commenting that I thought that Hector wanted the biggest possible length of the visible Caption at any particular Form Size, are you saying that is completely incorrect? – Baxter Sep 26 '18 at 02:44
  • I think you're right about what Hector wants to know. Incorrect is the answer, canvas.textwidth is utterly irrelevant. As irrelevant as window text and title bar text. – Sertac Akyuz Sep 26 '18 at 02:53
  • Edit: I tried to set caption to a way long 300 characters string and wait for the show event to present the caption with '...' at the end (meaning overflow). But when tried to search '...' in the caption, didn´t find it. If it had found '...' then could know caption's length. How can I find '...' ? – Hector Rios Sep 26 '18 at 20:12
  • @Dima Thanks for your comments, I didn't know if it was appropriate to expand on the idea I had presented. I will look at producing some code and modifying the answer. – Baxter Sep 26 '18 at 23:09
  • @HectorRios, you cannot set caption length more than 255 characters. Three dots that you can see at the end of the string are *ellipsis* and output by OS while caption stays not changed itself. Your caption *will not contain* ellipsis unless you placed it into string. That's why you *will never* find ellipsis in the caption. – Josef Švejk Sep 27 '18 at 10:54
  • @DBBaxter the main principle of SO is simple: if you know correct answer - just publish it. With all information you have. Read this: [Answer]. If there are two answers with different nuances then asker is obligated to choose answer that fits his needs more than another. In a case you will decide to expand your answer it will perhaps solve Hector's problem and you will get postive feedback from him and another users with the same problem (reputation). – Josef Švejk Sep 27 '18 at 11:03
  • 1
    I removed my downvote but you'd better use GetTitleBarInfo instead of ClientWidth to find out maximum space. – Sertac Akyuz Sep 27 '18 at 15:16
  • I voted up for your answer. Seems it can help to OP with his puzzle. But (just an opinion) I would do `GetNumSpacesMetric` function independent from any `TForm` instance by declaring it out of `TForm` declarations. Adding a new parameter `AForm` in function could help you to get metrics of specified form by passing wished `TForm` instance to the function. I deleted my previous comments as they are obsolete. P.S. Is it C Builder? – Josef Švejk Sep 27 '18 at 15:55
  • @SertacAkyuz Thanks, I will add a note to the answer, but my present compiler is older than _GetTitleBarInfo_, I will look at it when I get a chance to update. – Baxter Sep 28 '18 at 01:00
  • @Dima Thanks, adding a parameter is a great idea. C++Builder 6, I've got 2006 on disk somewhere - they're getting a bit old I guess. My old computer gave up and I'm trying to sort out the new without much time or will. – Baxter Sep 28 '18 at 01:11
  • @DBBaxter, Yeah, I see. Good job! [GetTitleBarInfo](https://learn.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-gettitlebarinfo) is a pretty old function (exists since Win 2000 according to MSDN) so it should be presented in C++ Builder (although I never worked with it). – Josef Švejk Sep 28 '18 at 09:08
  • @DBB - You're welcome. [Here](https://stackoverflow.com/questions/8775428/what-uxtheme-function-i-must-use-to-get-the-default-size-of-the-minimize-maximi) is how I used the message on D2007. – Sertac Akyuz Sep 28 '18 at 11:30
  • Hi, sorry. Until now was able to try suggestions but they didn't work. GetThemePartSize gave all fields 0's. SendMessage(Handle, WM_GETTITLEBARINFOEX, 0, NativeInt(@TitleInfo)); sizeCloseButton:= TitleInfo.rgrect[5].Width; gave me inaccurate results. I have abandoned the issue for lack of time. Regards – Hector Rios Oct 07 '18 at 07:37