How to query an original line from Windows' multiline edit control?

287 Views Asked by At

I found some weirdness in how the standard Windows edit control works.

When word wrapping is on, it accepts whole logical lines, but returns screen lines on EM_GETLINE request. However, it behaves correctly on window resizing and re-splits the text regarding the original CR-LFs.

So, my idea to find out the original logical lines was to query the screen lines with EM_GETLINE, one after other, and detect CR-LF at the end of the last screen line in a block.

Unfortunately, lines requested by EM_GETLINE do not contain CR-LFs at all.

It seems that the control is storing the CR-LFs internally, but doesn't return them on EM_GETLINE. They could be got only when requesting the whole control text with WM_GETTEXT.

Is there probably some other way to request text pieces between adjacent CR-LFs except getting the whole text and splitting it?

program WindowsEditControl;

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils;

const
  IDM_EXIT = 100;
  GAP = 10;

var
  hEdit: HWND;
  hFnt: HFONT;

function GetLine(hEdit: HWND; Index: Integer): string;
var
  Text: array[0..4095] of Char;
begin
  Word((@Text)^) := Length(Text);
  SetString(Result, Text, SendMessage(hEdit, EM_GETLINE, Index, LPARAM(@Text)));
end;

function GetTxt(hEdit: HWND): string;
var
  Len: Integer;
begin
  Len := SendMessage(hEdit, WM_GETTEXTLENGTH, 0, 0);
  SetString(Result, PChar(nil), Len);
  if Len <> 0 then
  begin
    Len := Len - SendMessage(hEdit, WM_GETTEXT, Len + 1, LongWord(PChar(Result)));
    if Len > 0 then
      SetLength(Result, Length(Result) - Len);
  end;
end;

function WndFunc(h: HWND; iMessage: UINT; w: WPARAM; l: LPARAM): LRESULT; stdcall;
var
  nWidth, nHeight: NativeUInt;
  i, j, lineLength: Integer;
  s: string;
begin
  case iMessage of
    WM_CREATE: begin
      hEdit := CreateWindowEx(WS_EX_NOPARENTNOTIFY, 'edit', 'Control #1',
        WS_BORDER or WS_VISIBLE or WS_CHILD or ES_LEFT or ES_MULTILINE or ES_READONLY or WS_VSCROLL, GAP, GAP,
        810 - GAP*2 - 15, 260, h, 0, hInstance, nil);
      hFnt := CreateFont(20, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 'Calibri');
      SendMessage(h, WM_SETFONT, hFnt, MakeLong(1, 0));
      SendMessage(hEdit, WM_SETFONT, hFnt, MakeLong(1, 0));
      for i := 0 to 10 do begin
        for j := 0 to 20 do s := s + 'Item' + IntToStr(i) + '.' + IntToStr(j) + ' ';
        s := s + #13#10;
      end;
      SendMessage(hEdit, WM_SETTEXT, 0, LongWord(PWideChar(s)));

      //Attempts to query the edit control:
      //This gets the first SCREEN line of the edit control. CR-LFs aren't there.
      MessageBox(h, PWideChar('|'+GetLine(hEdit, 0)+'|'), 'Screen line 1', 0);
      //This gets the second SCREEN line of the edit control. CR-LFs aren't there.
      MessageBox(h, PWideChar('|'+GetLine(hEdit, 1)+'|'), 'Screen line 2', 0);
      //In a whole text are all the CR-LFs there
      MessageBox(h, PWideChar(GetTxt(hEdit)), 'Whole text', 0);
    end;
    WM_DESTROY: begin
      DeleteObject(hFnt);
      PostQuitMessage(0);
      Result := 0;
    end;
    WM_SIZE: begin
      nWidth := LOWORD(l);
      nHeight := HIWORD(l);
      SetWindowPos(hEdit, 0, GAP, GAP, nWidth - GAP*2, nHeight - GAP*2, 0);
      Result := 0;
    end;
    WM_COMMAND: if w = IDM_EXIT then PostMessage(h, WM_CLOSE, 0, 0);
    else
      Result := DefWindowProc(h, iMessage, w, l);
  end;
end;

var
  wndClass: TWndClass;
  h: HWND;
  msg: TMsg;

begin
  wndClass.style          := CS_HREDRAW or CS_VREDRAW;
  wndClass.lpfnWndProc    := @WndFunc;
  wndClass.cbClsExtra     := 0;
  wndClass.cbWndExtra     := 0;
  wndClass.hInstance      := hInstance;
  wndClass.hIcon          := 0;
  wndClass.hCursor        := LoadCursor(0, IDC_ARROW);
  wndClass.hbrBackground  := GetStockObject(WHITE_BRUSH);
  wndClass.lpszMenuName   := nil;
  wndClass.lpszClassName  := 'EditTest';

  if RegisterClass(wndClass) = 0 then Halt(0);

  h := CreateWindow(wndClass.lpszClassName, 'Edit Test', WS_OVERLAPPEDWINDOW, 35, 35, 810, 320, 0, 0, hInstance, nil);
  ShowWindow(h, SW_SHOW);

  while GetMessage(msg, 0, 0, 0) do begin
    TranslateMessage(msg);
    DispatchMessage(msg);
  end;
  Halt(msg.wParam);
end.
0

There are 0 best solutions below