Visual Component Library

In Delphi 2 nicht gekapselte TListView-Styles

Kann mir jemand sagen, wie ich in einem ListView die ganze Zeile selektiert angezeigt werden kann? (nicht nur den Eintrag in der 1. Kolonne)? Es handelt sich vermutlich wieder um einen versteckten API-Befehl.

In Delphi haben die Borländer solche Sachen einfach vergessen. :-) Gib in deiner FormCreate-Routine folgendes ein:

SendMessage(ListView.Handle,$1036,0,LVS_EX_FULLROWSELECT);

wobei du LVS_EX_FULLROWSELECT als Konstante mit einem Wert von $20 deklarieren mußt.

Es gibt auch noch andere schöne, nicht von Delphi unterstützte Styles:

LVS_EX_GRIDLINES = $1;
LVS_EX_SUBITEMIMAGES = $2;
LVS_EX_CHECKBOXES = $4;
LVS_EX_TRACKSELECT = $8;
LVS_EX_HEADERDRAGDROP = $10;
LVS_EX_FULLROWSELECT = $20;
LVS_EX_ONECLICKACTIVATE = $40;
LVS_EX_TWOCLICKACTIVATE = $80;

{ folgende erst ab Internet Explorer 4.0 }
LVS_EX_FLATSB = $100;
LVS_EX_REGIONAL = $200;
LVS_EX_INFOTIP = $400;
LVS_EX_UNDERLINEHOT = $800;
LVS_EX_UNDERLINECOLD = $1000;
LVS_EX_MULTIWORKAREAS = $2000;

Du mußt dabei nur beachten, daß du die Styles mit OR verknüpfst z.B:

SendMessage(ListView.Handle,$1036,0,LVS_EX_FULLROWSELECT or
            LVS_EX_HEADERDRAGDROP);

Den Signalton bei Eingabe von [Enter] in einem Edit-Feld uterdrücken

Eine Eingabe soll in einem Edit-Feld durch [Enter] bestätigt werden. Dabei ertönt jedesmal, das unter "Akustische Signale" (Systemsteuerung) eingestellte, sogenannte Standardsignal oder aber ein "Plopp" aus dem PC-Speaker. Wie kann man das abstellen?

Zeilenumbrüche in einzeiligen Eingabefeldern sind logischerweise nicht erlaubt und deshalb ertönt bei Eingabe von [Enter] ein Fehlerton. Fang den Tastendruck im "OnKeyPress"-Handler des Edit-Controls ab und lösche ihn bei Bedarf (nämlich wenn es die [Enter]-Taste ist):

if Key=#13 then  // #13 = [Enter]
  Key:=#0;

Wie ermittle ich die Cursorposition (Zeile und Spalte) in einem TRichEdit?

Hier eine Prozedur, die die Zeile und Spalte des Textcursors ermittelt:

procedure TMDIChild.PositionAnzeigen;
var CurLine, CurCol: Integer;
    x, y: string;
begin
  { Memo ist entw. TMemo oder TRichEdit }
  CurLine := SendMessage (Memo.Handle, EM_LINEFROMCHAR,
                          Memo.SelStart, 0);
  CurCol := SendMessage (Memo.Handle, EM_LINEINDEX, CurLine, 0);
  CurCol := Memo.SelStart - CurCol;
  Inc (CurLine);
  Inc (CurCol);
  str(CurLine, x);
  str(CurCol, y);
  StatusBar1.Panels[0].Text := x + ' : ' + y;
  {Ausgabe in Statuszeile}
end;

Wie kann ich zur ersten oder letzten Zeile im Memo scrollen?

Dafür gibt es die EM_LineScroll-Nachricht:

{zur ersten Zeile:}
  Memo1.Perform(EM_LineScroll, 0 , -Memo1.Lines.Count-1);
{zur letzen Zeile:}
  Memo1.Perform(EM_LineScroll, 0 , Memo1.Lines.Count-1);
Mit der EM_ScrollCaret-Nachricht scrollt man die aktuelle Cursorposition im Memo in die Anzeige:

  Memo1.Perform(EM_ScrollCaret, 0, 0);

Wie fügt man an einer bestimmten Position Text in ein TMemo oder TRichEdit ein?

Die Einfügeposition wird mit der Eigenschaft SelStart festgelegt. Der einzufügende Text wird der Eigenschaft SelText übergeben.

Mit der Eigenschaft SelLength kann man die Länge des markierten Textes im Memo festlegen. SelLength muß auf Null gesetzt werden, um keinen Text im Memo zu überschreiben:

Memo.SelStart:=Einfuegeposition;
Memo.SelLength:=0;
Memo.SelText:='Einzufügender Text';

Wie wechselt man in einem TMemo zwischen Einfüge- und Überschreibmodus?

TEdits, TMemos oder TRichEdits bieten von Hause aus keinen Überschreibmodus, d.h. wenn man mit der Tastatur Buchstaben eintippt, werden diese immer in den Text an der aktuellen Einügeposition (Memo.SelStart) eingefügt. Der folgende Text wird dabei um eine Position nach hinten gerückt.

Um wahlweise einen Überschreibmodus zu bieten, bei dem das erste Zeichen an der aktuellen Einfügeposition durch den neu eingetippten Buchstaben ersetzt wird, kann man so vorgehen:

Man erstellt eine TForm (Form1) und darauf z.B. ein TMemo (Memo1) und eine TCheckBox (CheckBox1), mit der man zwischen zwischen Einfüge- und Überschreibmodus wechseln kann. Dann weist man dem OnKeyPress-Ereignis des Memos folgende Methode zu:

procedure TForm1.Memo1KeyPress(Sender: TObject; var Key: Char);
var Zeile     : string;
    CursorPos : integer;
begin
  if CBUeberschreiben.Checked then begin
    if Key<#32 then
      Key:=#0
    else begin
      {Aktuelle Einfügeposition merken}
      CursorPos:=Memo1.SelStart;
      {Memotext in einer string-Variablen zwischenspeichern}
      Zeile:=Memo1.Text;
      {Falls sich an der aktuellen Cursorposition ein 
       Windows-Zeilenumbruch befindet, müssen zwei 
       Zeichen (#13=CR und #10=LF) gelöscht werden:}
      if (Zeile[CursorPos+1]=#13) and (Zeile[CursorPos+2]=#10) then
        delete(Zeile,CursorPos+1,1);
      {Das Zeichen an der Einfügeposition löschen}
      delete(Zeile,CursorPos+1,1);
      {Memotext aus der string-Variablen zurückladen}
      Memo1.Text:=Zeile;
      Memo1.SelStart:=CursorPos;
    end;
  end;
end;
Um mit der [Einfg]-Taste zwischen Einfüge- und Überschreibmodus zu wechseln, weist man dem OnKeyUp-Ereignis des Memos noch folgende Methode zu:

procedure TForm1.Memo1KeyUp(Sender: TObject; var Key: Word;
  Shift: TShiftState);
begin
  {mit der [Einfg]-Taste zwischen Einfüge- und Überschreibmodus wechseln}
  if (Key=VK_Insert) and (Shift=[]) then
    CBUeberschreiben.Checked:=not CBUeberschreiben.Checked;
end;

Den blinkenden Cursor in einem deaktivierten TEdit oder TMemo verstecken

Wenn ich ein TEdit, TMemo oder TRichEdit deaktiviere, bleibt trotzdem ein blinkender Cursor im jeweiligen Control, der den Eindruck vermittelt, man könne noch Eingaben machen. Da das aber nicht möglich ist, möchte ich auch keinen Cursor haben. Wie kann man den abstellen?

Probier folgendes:

{Cursor verstecken}
HideCaret(Memo1.Handle);

{Cursor verstecken}
ShowCaret(Memo1.Handle).
Beachte aber, daß diese Funktion kumulativ ist (sooft du den Cursor ausblendest, mußt Du ihn auch wieder einblenden), und daß es bei jedem OnEnter auf den Standardcursor reinitialisert wird.

Falls es generell ohne Cursor sein sollte, kannst Du ja eine Ableitung von TMemo bilden, die den Cursor nach dem OnEnter rauswirft. [Volker Heinrich]

Die maximale Textlänge eines TRichEdit erhöhen

Wenn ich zu einem RichEdit mittels Lines.Add Zeilen hinzufüge, ist irgendwann Schluß. Hat auch die TRichEdit-Komponente (wie TMemo) eine Maximallänge?

Im Prinzip nicht. Die Längenbeschränkung bei TRichEdit ist jedenfalls jenseits normalerweise nutzbarer Grenzen. Die RichEdit-Komponente besitzt aber wie auch TMemo und TEdit die Eigenschaft MaxLength, die die maximale Textlänge begrenzt. Per Voreinstellung hat diese immer den Wert "0". Das wird sowohl für TEdit und TMemo, als auch für TRichEdit als Maximalgröße von 32 Kilobyte interpretiert.

Nach der Erstellung kann ein RichEdit also erstmal nicht größer werden, als ein TMemo. Während bei diesem die 32kB aber bereits den höchtmöglichen Wert darstellen (zumindest bis Delphi 3), kann man der MaxLength-Eigenschaft der RichEdit-Komponente einfach einen höheren Wert zuweisen:

RichEdit1.MaxLength:=2147483647; //damit kann das Teil 2^31 Byte groß werden.
MaxLength ist als "integer" definiert und kann somit als höchsten Wert 2^31 annehmen.

Vorsicht, Richedits mit sehr großen Texten neigen zu schneckenhaftem Verhalten!

Wie kann ich mit der HTML-Komponente in Delphi 4 ein lokales Dokument öffnen?

Ich habe es folgendermaßen versucht: "HTML1.RequestDoc('file://F:\index.htm');", das funktioniert aber leider nicht.

Probier es mal so:

HTML1.RequestDoc('file:///F:\index.htm');

//oder:

HTML1.RequestDoc('file://localhost/F:\index.htm');

Der dritte Schrägstrich steht einfach für LocalHost

Wie kann man bei einer Grid-Komponente für jede Zelle einen anderen Hint anzeigen?

Ich möchte für jede Zelle eines StringGrids einen eigenen Hint anzeigen. Der Hinweistext wird aber erst aktualisiert, wenn der Mauszeiger das Grid verlässt. Wie kann ich einen neuen Hinweis anzeigen, wenn der Mauszeiger über eine neue Zelle bewegt wird?

Damit der Hint wieder auftaucht, muß man nur Application.CancelHint aufrufen - die MouseMove-Methode sieht dann wie folgt aus:

var
  LastCol, LastRow : longint;

procedure TForm1.StringGridMouseMove(Sender: TObject; Shift: TShiftState;
  X, Y: Integer);
var ACol, ARow: longint;
begin
  StringGrid.MouseToCell(X, Y, ACol, ARow);
  StringGrid.Hint:='Dieser Hinweis gilt nur für die Zelle '
                            +IntToStr(ACol)+':'+IntToStr(ARow);
  if (ACol<>LastCol) or (ARow<>LastRow) then begin
    Application.CancelHint;
    LastCol:=ACol;
    LastRow:=ARow;
  end;
end;

Wie kann man in einer StringGrid-Komponente mehrzeiligen Text ausgeben?

Dazu muß man das Zeichnen der Zelleninhalte selber übernehmen. Zu diesem Zweck schreibt man eine Methode für das OnDrawCell-Ereignis des StringGrids. In dieser Methode kann man direkt auf das Ausgaberechteck der jeweiligen Zelle zugreifen, es wird in der Variablen "Rect" übergeben.

Für die Ausgabe von mehrzeiligem Text ist "DrawText" ist die Funktion der Wahl. Mit dem Parameter DT_CalcRect kann man die Größe des Ausgaberechtecks ermitteln, der Parameter DT_WordBreak sorgt für den Zeilenumbruch.

Hier mal ein paar Beispielzeilen aus einer Druckroutine eines meiner Programme:

outRect:=Rect;

{Dimension des Ausgaberechtecks ermitteln (wird in outRect
zurückgegeben):}
DrawText(StringGrid.Canvas.Handle,
         PCHar(SText),
         length(SText),
         outRect,
         DT_CalcRect or DT_WordBreak or DT_NoClip);

{Text mehrzeilig und horizontal zentriert im Recteck outRect ausgeben:}
DrawText(StringGrid.Canvas.Handle,
         PChar(SText),
         length(SText),
         outRect,
         DT_Center or DT_WordBreak);

{Text einzeilig und zentriert im Recteck outRect ausgeben:}
DrawText(StringGrid.Canvas.Handle,
         PChar(SText),
         length(SText),
         outRect,
         DT_VCenter or DT_Center or DT_SingleLine);

Wie kann man in einer ListBox eine horizontale Scrollbar anzeigen?

Man sendet eine "LB_SetHorizontalExtent"-Nachricht an die ListBox. Das kann man z.B. in der OnCreate-Methode des übergeordneten Formulars machen::

procedure TForm1.FormCreate(Sender: TObject);
begin
  SendMessage(Listbox1.Handle, LB_SetHorizontalExtent, 1000, Longint(0));
end;

Wie kann man in einem TreeView einen bestimmten Knoten suchen?

Aufgrund der Datenstruktur eines TreeViews bieten sich bei der Arbeit mit TreeNodes grundsätzlich rekursive Routinen an. Diese Routine durchsucht alle Kinder eines vorgegebenen Knotens "Root" rekursiv nach einem Knoten mit dem gesuchten Text "Name":

function FindNode(Root: TTreeNode; Name: string): TTreeNode;
var
  Temp: TTreeNode;
begin
  Result := Root.GetFirstChild;
  while Result <> nil do
  begin
    if Result.Text = Name then Exit;
    Temp := FindNode(Result, Name);
    if Temp <> nil then
    begin
      Result := Temp;
      Exit;
    end;
    Result := Root.GetNextChild(Result);
  end;
end; {Robert Roßmair}

Die Hints in einem TreeView abschalten

Ich möchte gerne die automatische Hint-Anzeige eines TreeViews abschalten, also die "Tooltips", die erscheinen, wenn ein Node nicht vollständig im Fenster angezeigt wird. Wer weiß welche Möglichkeiten ich habe?

Ab Delphi4 hat TTreeView dafür die Eigenschaft "Tooltips". In Delphi3 wurde diese Eigenschaft nicht gekapselt, man muß sich deshalb mit der API-Funktion "SetWindowLong" behelfen:

const
  TVS_NoTooltips = $80;

begin
  with TreeView1 do
    SetWindowLong(Handle,
                  GWL_Style,
                  GetWindowLong(Handle, GWL_Style) or TVS_NoTooltips);
end; {Michael Hanel}

Wie kann man andere Komponenten auf einer TStatusBar plazieren?

TStatusBar bietet von sich aus keine Möglichkeit, andere Komponenten auf ihr zu plazieren. In vielen Programmen ist das aber eine gern genutzte Möglichkeit, um Zusatzinformationen wie z.B. eine Fortschrittanzeige anzuzeigen.

Es gibt zwei Möglichkeiten, trotzdem Komponenten auf einer StatusBar zu plazieren. Die erste Möglichkeit ist, zur Laufzeit die StatusBar als Parent einer Komponente festzulegen. Folgendes Beispiel positioniert eine TProgressBar exakt in eines der angegebenen Panels einer Statusbar:

procedure TForm1.FormShow(Sender: TObject);
var ARect : TRect;
begin
  Statusbar1.Perform(SB_GetRect, 0, integer(@ARect));
  {SB_GetRect benötigt die Unit CommCtrl
   0 = das erste Panel der Statusbar; 1 = das Zweite usw.
   in ARect wird das Anzeigerechteck zurückgegeben}

  with ProgressBar1 do begin
    Top := ARect.Top;
    Left := ARect.Left;
    Width := ARect.Right-ARect.Left;
    Height := ARect.Bottom-ARect.Top;
    Parent := Statusbar1;
  end;
end; {Alex Schlecht}
Die zweite Möglichkeit ist eine eigene Ableitung von TStatusBar, die andere Komponenten als Kindchen akzeptiert. Das hat den Vorteil, daß bereits zur Design-Zeit Komponenten auf dieser plaziert werden können. Zudem ist das mit einer einzigen Zeile erledigt. Marian Aldenhövels Beispielkomponente TFriendlyStatusBar zeigt, wie's geht:

unit FreundlicheStatusBar;

interface
 
uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  ComCtrls, DsgnIntf;

type
  TFriendlyStatusBar = class(TStatusBar)
  public
    constructor Create(AOwner: TComponent); override;
  end;

procedure Register;

implementation

constructor TFriendlyStatusBar.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  ControlStyle:= ControlStyle + [csAcceptsControls];
end;

procedure Register;
begin
  RegisterComponents('FreeWare', [TFriendlyStatusBar]);
end;

end.

Wie man die Anzeige des Fokus-Rechtecks bei OwnerDraw-Listboxen verhindert

Ich benutze eine TListBox im Ownerdraw-Modus. Da habe ich mir nun viel Mühe gegeben, um auch ja hübsche Programmoberfläche zu haben und dann kommt Windows und knallt mir einfach sein Focus-Rechteck auf den aktiven Eintrag meiner TListBox. Wie kann ich das verhindern?

Das Problem liegt in TCustomListBox.CNDrawItem. Diese Methode fängt die CN_DRAWITEM messages ab und zeichnet einen Eintrag, wenn einer der OwnerDraw-Styles ausgewählt ist. Du mußt eine eigene Komponente von TListbox ableiten und diese Methode überschreiben. Da sie in TCustomListBox private ist, bleibt dir nichts anderes übrig, als den Quelltext der Methode zu kopieren und komplett in deine neue Komponente zu übernehmen. Du mußt in dem kopierten Quelltext alle Verweise auf private Felder in Verweise auf Eigenschaften ändern und die Zeile "if odFocused in State then DrawFocusRect(hDC, rcItem);" auskommentieren.

Die Komponente könnte also etwa so aussehen, wie in diesem Beispiel von Oliver Stör:

interface

  TMyListbox = class(TListbox)
  private
    procedure CNDrawItem(var Message: TWMDrawItem); message  
      CN_DRAWITEM;
  end;

implementation

procedure TMyListBox.CNDrawItem(var Message: TWMDrawItem);
var
  State: TOwnerDrawState;
begin
  with Message.DrawItemStruct^ do
  begin
    State := TOwnerDrawState(LongRec(itemState).Lo);
    Canvas.Handle := hDC;
    Canvas.Font := Font;
    Canvas.Brush := Brush;
    if (Integer(itemID) >= 0) and (odSelected in State) then
    begin
      Canvas.Brush.Color := clHighlight;
      Canvas.Font.Color := clHighlightText
    end;
    if Integer(itemID) >= 0 then
      DrawItem(itemID, rcItem, State) else
      Canvas.FillRect(rcItem);
//    if odFocused in State then DrawFocusRect(hDC, rcItem);
    Canvas.Handle := 0;
  end;
end;

Eine Liste aller VCL Component Messages und Component Notifications

Component Messages (CM_) sind Nachrichten, die ausschließlich von der VCL generiert werden, Component Notifications (CN_) sind dagegen reflektierte Windows Messages.

Der Sinn ist, dass Windows oft Nachrichten an das Elternfenster eines Controls anstelle des Controls selbst versendet. Die VCL konvertiert diese einfach zu Component Notifications und sendet sie dann wieder an das Control für das die Message eigentlich bestimmt war.

Mike Lischke hat eine Liste aller Component Messages und Component Notifications der Delphi-VCL zusammengestellt. Ich habe diese Liste zur besseren Übersicht formatiert und man kann sie als Dokument im Rich Text Format (25 kB) laden.