Programmatically drawing the lines in a delphi drawgrid and merge cells - delphi

I want to disable the gridlines in a drawgrid and draw the grid lines myself for every other columns. Row lines are not needed.
I want to merge two cells in the fixed area so that it looks like as it is one column, like in this picture:
I have added this code to the ondrawcell event of the drawgrid to achieve this:
procedure Tbookings3_Frm.bgridDrawCell(Sender: TObject; ACol, ARow: Integer;
Rect: TRect; State: TGridDrawState);
var
CellIndex: Integer;
s:string;
x:integer;
begin
CellIndex := (ARow * bgrid.ColCount) + ACol;
if gdFixed in State then
begin
bgrid.Canvas.Brush.Color := clskyblue;
end
else if (State * [gdSelected, gdHotTrack]) <> [] then
begin
bgrid.Canvas.Brush.Color := clHighlight;
end
else
begin
bgrid.Canvas.Brush.Color := Cells[CellIndex].BkColor;
end;
bgrid.Canvas.FillRect(Rect);
if gdFixed in State then
Frame3D(bgrid.Canvas, Rect, clHighlight, clBtnShadow, 1);
if gdFocused in State then
bgrid.Canvas.DrawFocusRect(Rect);
//---------------
with (Sender as TDrawGrid).Canvas do
begin
// set font
Font.Color := CLblack;
FillRect(Rect);
if ARow = 2 then
begin
x := (Rect.Right - Rect.Left - TextWidth(days_h[ACol])) div 2;
TextOut(Rect.Left + x, Rect.Top + 2, days_h[ACol]);
end;
if ARow = 1 then
begin
x := (Rect.Right - Rect.Left - TextWidth(sun_mon[ACol])) div 2;
TextOut(Rect.Left + x, Rect.Top + 2, sun_mon[ACol]);
end;
if ARow = 0 then
begin
x := (Rect.Right - Rect.Left - TextWidth(mon[ACol])) div 2;
TextOut(Rect.Left + x, Rect.Top + 2, mon[ACol]);
end;
if (Acol = 0) and (ARow > 2) then
begin
s:=rooms[Arow];
x := (Rect.Right - Rect.Left - TextWidth(s)) div 2;
TextOut(Rect.Left + x, Rect.Top + 2, s);
end;
//-------------------------------------------------
end; //end canvas
//----------------
if gdFocused in State then
bgrid.Canvas.DrawFocusRect(Rect);
end;

You need to disable the grid's native gridlines, and then you can draw your own gridlines surrounding each cell in the OnDrawCell event as needed. The TRect represents the inside area of the cell being drawn, but you can draw outside of that Rect as well. To make two cells appear merged, you would simply not draw a gridline between them.

Related

Delphi multi color value in TStringGrid

I want the currency values ​​in the TStringGrid table to have different color decimals. How can do that?
You need to draw the cells yourself by implementing an OnDrawCell handler.
Something like this:
procedure TForm1.StringGrid1DrawCell(Sender: TObject; ACol, ARow: Integer;
Rect: TRect; State: TGridDrawState);
var
Grid: TStringGrid;
S: string;
Val: Double;
FracVal, IntVal: Integer;
FracStr, IntStr: string;
IntW, FracW, W, H: Integer;
Padding: Integer;
const
PowersOfTen: array[0..8] of Integer =
(
1,
10,
100,
1000,
10000,
100000,
1000000,
10000000,
100000000
);
Decimals = 2;
BgColor = clWhite;
IntColor = clBlack;
FracColor = clRed;
begin
Grid := Sender as TStringGrid;
if (ACol < Grid.FixedCols) or (ARow < Grid.FixedRows) then
Exit;
Grid.Canvas.Brush.Color := BgColor;
Grid.Canvas.FillRect(Rect);
S := Grid.Cells[ACol, ARow];
Padding := Grid.Canvas.TextWidth('0') div 2;
if not TryStrToFloat(S, Val) or not InRange(Val, Integer.MinValue, Integer.MaxValue) then
begin
Grid.Canvas.TextRect(Rect, S, [tfSingleLine, tfVerticalCenter, tfLeft]);
Exit;
end;
IntVal := Trunc(Val);
IntStr := IntVal.ToString;
if Decimals > 0 then
IntStr := IntStr + FormatSettings.DecimalSeparator;
IntW := Grid.Canvas.TextWidth(IntStr);
FracVal := Round(Frac(Abs(Val)) * PowersOfTen[Decimals]);
FracStr := FracVal.ToString.PadRight(Decimals, '0');
if Decimals = 0 then
FracStr := '';
FracW := Grid.Canvas.TextWidth(FracStr);
W := IntW + FracW;
H := Grid.Canvas.TextHeight(IntStr);
if W >= Grid.ColWidths[ACol] - 2*Padding then
begin
S := '###';
Grid.Canvas.TextRect(Rect, S, [tfSingleLine, tfVerticalCenter, tfRight]);
end
else
begin
Grid.Canvas.Font.Color := IntColor;
Grid.Canvas.TextOut(Rect.Right - Padding - W,
Rect.Top + Rect.Height div 2 - H div 2, IntStr);
Grid.Canvas.Font.Color := FracColor;
Grid.Canvas.TextOut(Rect.Right - Padding - FracW,
Rect.Top + Rect.Height div 2 - H div 2, FracStr);
end;
end;
This code will write non-numeric data left-aligned as is. For numeric data, it will draw the values with a fixed number of decimals. You can choose the decimals (0..8), as well as the colours of the integral and fractional parts. If the number doesn't fit in its cell, ### will be displayed instead.
I haven't fully tested the code. I'll leave that to you as an exercise.
Update: Sorry, I forgot you are using Delphi 7. This means that you need to replace IntVal.ToString with IntToStr(IntVal) and so on.

Open form at cursor position, Delphi

I am trying to figure out how to position a Form to open at a given mouse location, despite my monitor settings.
In the Form's OnCreate event, I have this:
procedure TSplashScreen.FormCreate(Sender: TObject);
Var
oMousePos: TPoint;
nLeft, nTop: Integer;
begin
Scaled := false;
PixelsPerInch := Screen.PixelsPerInch;
Scaled := true;
//Position:=poScreenCenter;
//center form for 2nd monitor //zzz
if (Screen.MonitorCount > 1) then //zzz
begin
GetCursorPos(oMousePos);
if (oMousePos.X > Screen.Width) or (oMousePos.X < 0) then
begin
Self.Position := poDesigned;
nLeft := Screen.Monitors[1].Left + Round(Screen.Monitors[1].Width / 2) - Round(Self.Width / 2);
nTop := Screen.Monitors[1].Top + Round(Screen.Monitors[1].Height / 2) - Round(Self.Height / 2);
Self.Left := nLeft;
Self.Top := nTop;
end;
end;
end;
When I have 2 monitors, and monitor 1 is set as primary monitor, the Form will open at the mouse cursor.
However, if I set monitor 2 to primary, the Form will always open on monitor 2.
If you just want to position the Form on the same monitor that the mouse cursor is currently in, use the Win32 API MonitorFromPoint() function (which is wrapped by the VCL's TScreen.MonitorFromPoint() method), eg:
procedure TSplashScreen.FormCreate(Sender: TObject);
var
r: TRect;
begin
if (Screen.MonitorCount > 1) then
begin
r := Screen.MonitorFromPoint(Mouse.CursorPos).WorkareaRect;
Self.Position := poDesigned;
Self.Left := r.Left + ((r.Width - Width) div 2);
Self.Top := r.Top + ((r.Height - Height) div 2);
{ alternatively:
Self.SetBounds(
r.Left + ((r.Width - Width) div 2),
r.Top + ((r.Height - Height) div 2),
Width, Height);
}
end else begin
Self.Position := poScreenCenter;
end;
end;

The arrow pointer in a TStringGrid

Is it possible to add that arrow pointer thing to a String Grind in Delphi 7? You know what I mean, that arrow pointer that you can see at the left in a DBGrid.
Yes, but not automatically. You would need to display a triangle manually. You can override OnDrawCell for your grid. It seems you need to set the FixedCols to 0 since it doesn't appear to redraw the fixed cells again when the row selection changes.
procedure TForm1.StringGrid1DrawCell(Sender: TObject; ACol, ARow: Integer;
Rect: TRect; State: TGridDrawState);
var
aCanvas: TCanvas;
oldColor: TColor;
triangle: array [0..2] of TPoint;
const
spacing = 4;
begin
if (ACol = 0) and (aRow = StringGrid1.Row) then
begin
aCanvas := (Sender as TStringGrid).Canvas; // To avoid with statement
oldColor := aCanvas.Brush.Color;
// Shape the triangle
triangle[0] := TPoint.Create(Rect.Left + spacing, Rect.Top + spacing);
triangle[1] := TPoint.Create(Rect.Left + spacing, Rect.Top + Rect.Height - spacing);
triangle[2] := TPoint.Create(Rect.Left + Rect.Width - spacing, Rect.Top + Rect.Height div 2);
// Draw the triangle
aCanvas.Pen.Color := clBlack;
aCanvas.Brush.Color := clBlack;
aCanvas.Polygon(triangle);
aCanvas.FloodFill(Rect.Left + Rect.Width div 2, Rect.Top + Rect.Height div 2, clBlack, fsSurface);
aCanvas.Brush.Color := oldColor;
end;
end;
This draws a triangle in the box. You should get the general idea.
Not automatically; it's not part of the standard TStringGrid. The "arrow pointer thing" is called the row indicator, and it's a feature added in TDBGrid. It's declared in TDBGridOptions, specifically dgIndicator, as seen below:
TDBGridOption = (dgEditing, dgAlwaysShowEditor, dgTitles, dgIndicator,
dgColumnResize, dgColLines, dgRowLines, dgTabs, dgRowSelect,
dgAlwaysShowSelection, dgConfirmDelete, dgCancelOnExit, dgMultiSelect);
Note that this is different from TGridOption declared in the Grids unit, which does not contain anything similar. (There is no goIndicator or equivalent.)
In order to get the indicator, you'd have to draw it yourself in the OnDrawCell event when you receive a ACol value of 0 with ARow equivalent to the Grid.Row value. There's an example of TStringGrid.OnDrawCell in this answer, although it's demonstrating setting a custom row height and not drawing the row indicator.

How to make a list box with Office XP theme like selection rectangle?

What would be the most simple and clean way to show a focused/selected listbox item with a Office XP style?
See this sample image to show the idea more clearer:
I think I need to set the Listbox Style to either lbOwnerDrawFixed or lbOwnerDrawVariable and then modify the OnDrawItem event?
This is where I am stuck, I am not really sure what code to write in there, so far I tried:
procedure TForm1.ListBox1DrawItem(Control: TWinControl; Index: Integer;
Rect: TRect; State: TOwnerDrawState);
begin
with (Control as TListBox).Canvas do
begin
if odSelected in State then
begin
Brush.Color := $00FCDDC0;
Pen.Color := $00FF9933;
FillRect(Rect);
end;
TextOut(Rect.Left, Rect.Top, TListBox(Control).Items[Index]);
end;
end;
I should of known that would not work, I get all kind of funky things going on:
What am I doing wrong, more importantly what do I need to change to make it work?
Thanks.
You forgot to paint the items for different states. You need to determine in what state the item currently is and according on that draw it.
What you have on your picture you can get this way. However this doesn't looks well if you have enabled multiselect and select more than one item:
procedure TForm1.ListBox1DrawItem(Control: TWinControl; Index: Integer;
Rect: TRect; State: TOwnerDrawState);
var
Offset: Integer;
begin
with (Control as TListBox) do
begin
Canvas.Font.Color := Font.Color;
if (odSelected in State) then
begin
Canvas.Pen.Color := $00FF9932;
Canvas.Brush.Color := $00FDDDC0;
end
else
begin
Canvas.Pen.Color := Color;
Canvas.Brush.Color := Color;
end;
Canvas.Rectangle(Rect);
Canvas.Brush.Style := bsClear;
Offset := (Rect.Bottom - Rect.Top - Canvas.TextHeight(Items[Index])) div 2;
Canvas.TextOut(Rect.Left + Offset + 2, Rect.Top + Offset, Items[Index]);
end;
end;
And the result with ItemHeight set to 16:
Bonus - continuous selection:
Here is a tricky solution implementing a continuous selection. The principle is to draw the item like before but then overdraw the item's border top and bottom lines with the lines of a color depending on selection state of the previous and next item. Except that, must be rendered also outside of the current item, since the item selection doesn't naturally invoke neighbour items to be repainted. Thus the horizontal lines are painted one pixel above and one pixel below the current item bounds (colors of these lines depends also on the relative selection states).
Quite strange here is the use of item objects to store the selected state of each item. I did that, because when using a drag & drop item selection, the Selected property doesn't return the real state until you release the mouse button. Fortunately, the OnDrawItem event of course fires with the real state, so as a workaround I've used storing of these states from the OnDrawItem event.
Important:
Notice, that I'm using the item objects to store the actual selection state, so be careful, and when you're using item objects for something else, store this actual states e.g. into an array of Boolean.
procedure TForm1.ListBox1DrawItem(Control: TWinControl; Index: Integer;
Rect: TRect; State: TOwnerDrawState);
const
SelBackColor = $00FDDDC0;
SelBorderColor = $00FF9932;
var
Offset: Integer;
ItemSelected: Boolean;
begin
with (Control as TListBox) do
begin
Items.Objects[Index] := TObject((odSelected in State));
if (odSelected in State) then
begin
Canvas.Pen.Color := SelBorderColor;
Canvas.Brush.Color := SelBackColor;
Canvas.Rectangle(Rect);
end
else
begin
Canvas.Pen.Color := Color;
Canvas.Brush.Color := Color;
Canvas.Rectangle(Rect);
end;
if MultiSelect then
begin
if (Index > 0) then
begin
ItemSelected := Boolean(ListBox1.Items.Objects[Index - 1]);
if ItemSelected then
begin
if (odSelected in State) then
begin
Canvas.Pen.Color := SelBackColor;
Canvas.MoveTo(Rect.Left + 1, Rect.Top);
Canvas.LineTo(Rect.Right - 1, Rect.Top);
end
else
Canvas.Pen.Color := SelBorderColor;
end
else
Canvas.Pen.Color := Color;
Canvas.MoveTo(Rect.Left + 1, Rect.Top - 1);
Canvas.LineTo(Rect.Right - 1, Rect.Top - 1);
end;
if (Index < Items.Count - 1) then
begin
ItemSelected := Boolean(ListBox1.Items.Objects[Index + 1]);
if ItemSelected then
begin
if (odSelected in State) then
begin
Canvas.Pen.Color := SelBackColor;
Canvas.MoveTo(Rect.Left + 1, Rect.Bottom - 1);
Canvas.LineTo(Rect.Right - 1, Rect.Bottom - 1);
end
else
Canvas.Pen.Color := SelBorderColor;
end
else
Canvas.Pen.Color := Color;
Canvas.MoveTo(Rect.Left + 1, Rect.Bottom);
Canvas.LineTo(Rect.Right - 1, Rect.Bottom);
end;
end;
Offset := (Rect.Bottom - Rect.Top - Canvas.TextHeight(Items[Index])) div 2;
Canvas.Brush.Style := bsClear;
Canvas.Font.Color := Font.Color;
Canvas.TextOut(Rect.Left + Offset + 2, Rect.Top + Offset, Items[Index]);
end;
end;
And the result:
You need to look at the value of the State variable that is passed into the function. This tells you if the item is selected or not and you can then set the brush and pen appropriately.

String Grid and Graphic in cells

I have put icons in the string grid but I ran into a problem with not all the graphics are aligned. I have tried to rework the centering the text to make the icons align but no luck. I have tried to research the bitmap and its functionality but I havent (so I think) found anything that will help me. Can anyone help me please?
EDIT (from code added in answer to question by mistake):
bitmap := Tbitmap.Create;
bitmap.LoadFromFile('equal.bmp');
bitmap.SetSize(150,60);
stringgrid1.Canvas.StretchDraw(stringgrid1.CellRect(3,J), bitmap);
SetTextAlign(StringGrid1.Canvas.Handle, TA_CENTER);
StringGrid1.Canvas.TextRect(stringgrid1.CellRect(3,J),
(stringgrid1.CellRect(3,J).Left+stringgrid1.CellRect(3,J).Right) div 2,
stringgrid1.CellRect(3,J).Top + 5,StringGrid1.Cells[3,J]);
SetTextAlign(StringGrid1.Canvas.Handle, TA_LEFT);
Here's an example (Delphi 7, since it's what I had handy, but the code should work in D2010):
procedure TForm1.StringGrid1DrawCell(Sender: TObject; ACol, ARow: Integer;
Rect: TRect; State: TGridDrawState);
var
Bmp: TBitmap;
CellText: string;
R: TRect;
const
L_PAD = 5; // Amount between right side of image and start of text
T_PAD = 5; // Amount between top of cell and top of text
begin
// Some text to display in cells.
CellText := Format('Row: %d Col: %d', [ARow, ACol]);
// Draw an image along the left side of each cell in the first
// col (not the fixed ones, which we'll leave alone)
if ((ACol = 1) or (ACol = 3)) and (ARow > 0) then
begin
Bmp := TBitmap.Create;
try
Bmp.LoadFromFile('C:\glyfx\common\bmp\24x24\favorites24.bmp');
if ACol = 1 then // left align image
begin
R.Top := Rect.Top + 1;
R.Left := Rect.Left + 1;
R.Right := R.Left + Bmp.Width;
R.Bottom := R.Top + Bmp.Height;
StringGrid1.Canvas.StretchDraw(R, Bmp);
StringGrid1.Canvas.TextOut(R.Right + L_PAD, R.Top + T_PAD, CellText);
end
else
begin // right align image
StringGrid1.Canvas.TextOut(Rect.Left + L_PAD,
Rect.Top + L_PAD,
CellText);
R.Top := Rect.Top + 1;
R.Left := Rect.Right - Bmp.Width - 1;
R.Right := Rect.Right - 1;
R.Bottom := R.Top + L_PAD + Bmp.Height;
StringGrid1.Canvas.StretchDraw(R, Bmp);
end;
finally
Bmp.Free;
end;
end
else
StringGrid1.Canvas.TextOut(Rect.Left + L_PAD, Rect.Top + T_PAD, CellText);
end;
Here's what it looks like:

Resources