Getting the cell clicked on in a TGridPanel - delphi

I have a TGridPanel on a form and wish to add a control to a specific "cell" that is clicked on.
I can get the point easily enough:
procedure TForm1.GridPanel1DblClick(Sender: TObject);
var
P : TPoint;
InsCol, InsRow : Integer;
begin
P := (Sender as TGridPanel).ScreenToClient(Mouse.CursorPos);
if (Sender as TGridPanel).ControlAtPos(P) = nil then
begin
InsCol := ???;
InsRow := ???;
(Sender as TGridPanel).ControlCollection.AddControl(MyControl, InsCol, InsRow)
end;
end;
I probably don't need the if ControlAtPos(P) = nil then line, but I want to make sure I'm not inserting a control in a cell that already has one in it.
So... what code do I use to get InsCol and InsRow? I've been up and down the TGridPanel and TControlCollection class code and can't find anything that will give me a column or row value from mouse coordinates. Nor does their seem to be a relevant event to use other than OnDblClick().
Any help would be greatly appreciated.
EDIT: Changed variable Result to MyControl to avoid confusion.

procedure TForm1.GridPanel1Click(Sender: TObject);
var
P: TPoint;
R: TRect;
InsCol, InsRow : Integer;
begin
P := (Sender as TGridPanel).ScreenToClient(Mouse.CursorPos);
for InsCol := 0 to GridPanel1.ColumnCollection.Count - 1 do
begin
for InsRow := 0 to GridPanel1.RowCollection.Count - 1 do
begin
R:= GridPanel1.CellRect[InsCol,InsRow];
if PointInRect(P,R) then
begin
ShowMessage (Format('InsCol = %s and InsRow = %s.',[IntToStr(InsCol), IntToStr(InsRow)]))
end;
end;
end;
end;
function TForm1.PointInRect(aPoint: TPoint; aRect: TRect): boolean;
begin
begin
Result:=(aPoint.X >= aRect.Left ) and
(aPoint.X < aRect.Right ) and
(aPoint.Y >= aRect.Top ) and
(aPoint.Y < aRect.Bottom);
end;
end;

Here is an optimization of Ravaut123's approach (should be MUCH faster for larger grids). This function will return the X/Y grid location in a TPoint. If the user clicked on a valid column but not a valid row, then the valid column information is still returned, and the same goes for rows. So it isn't "all or nothing" (valid cell or invalid cell). This function assumes the grid is "regular" (every column has the same row height as the first column, likewise every row has the same column width as the first row). If the grid is not regular then Ravaut123's solution is the better choice.
// APoint is a point in local coordinates for which you want to find the cell location.
function FindCellInGridPanel(AGridPanel: TGridPanel; const APoint: TPoint): TPoint;
var
ICol, IRow : Integer;
R : TRect;
begin
Result.X := -1;
Result.Y := -1;
for ICol := 0 to AGridPanel.ColumnCollection.Count - 1 do
begin
R := AGridPanel.CellRect[ICol, 0];
if (APoint.X >= R.Left) and (APoint.X <= R.Right) then
begin
Result.X := ICol;
Break;
end;
end;
for IRow := 0 to AGridPanel.RowCollection.Count - 1 do
begin
R := AGridPanel.CellRect[0, IRow];
if (APoint.Y >= R.Top) and (APoint.Y <= R.Bottom) then
begin
Result.Y := IRow;
Break;
end;
end;
end;

Related

How to get the number of displayed lines in TMemo?

I need to get the number of displayed lines in TMemo (this include the lines that was wrapped because WordWrap is set to true). I need this to auto adjust the height of the Tmemo to it's content.
lines.count of course don't care about wrapped lines so i can't use it. strangely TextPosToPos also don't care about wrapped lines so i can't use it too ...
I m under firemonkey and delphi Berlin
Edit after I learned about ContentsBounds. My original (and obsolete) answer is still visible in the revisions.
Why do you need the number of displayed lines to adjust the height of the TMemo? This resizes the TMemo to its content:
Memo1.Height := Memo1.ContentBounds.Height + 5; // Small const to allow for rendering margins
It also takes word-wrap into consideration.
I don't know why using ContentBounds is "not really ideal". Here's how I do it:
uses
FMX.TextLayout, FMX.Graphics;
function MeasureTextHeight(const AFont: TFont; const AText: string): Single;
var
LLayout: TTextLayout;
begin
LLayout := TTextLayoutManager.DefaultTextLayout.Create;
try
LLayout.BeginUpdate;
try
LLayout.WordWrap := False;
LLayout.Font.Assign(AFont);
LLayout.Text := AText;
finally
LLayout.EndUpdate;
end;
Result := LLayout.TextHeight;
finally
LLayout.Free;
end;
end;
procedure TForm1.Button1Click(Sender: TObject);
var
LTextHeight: Single;
LLines: Integer;
begin
LTextHeight := MeasureTextHeight(Memo1.TextSettings.Font, Memo1.Text);
LLines := Round(Memo1.ContentBounds.Height / LTextHeight);
end;
This is the rough way i use to calculate the number of lines an TMemo (styled) under firemonkey :
type
_TStyledMemoProtectedAccess = class(TStyledMemo);
procedure TALStyledMemo.OnApplyStyleLookupImpl(sender: Tobject);
Var I, j, k, l: integer;
begin
// TALStyledMemo
// TStyledMemo
// TLayout
// TActiveStyleObject
// TLayout
// TScrollBar
// TScrollBar
// TLayout
// TSmallScrollBar
// TSmallScrollBar
// TScrollContent
for I := 0 to controls.Count - 1 do begin
if (controls[i] is TStyledMemo) then begin // << TStyledMemo
fStyledMemo := TStyledMemo(controls[i]);
end;
end;
end;
function TALStyledMemo.getLineHeight: Single;
begin
if fStyledMemo <> nil then result := _TStyledMemoProtectedAccess(fStyledMemo).GetLineHeight
else result := 0;
end;
function TALStyledMemo.getLineCount: integer;
var aLineHeight: Single;
begin
aLineHeight := getLineHeight;
if compareValue(aLineHeight, 0, Tepsilon.Position) > 0 then result := round(ContentBounds.Height / aLineHeight)
else result := 0;
end;

Delphi: Adding Combobox Dropdown to TADVStringGrid

I have a form that has a TADVStringGrid. I am trying to add a combobox to some rows in a specific column (2); however, I can't get the data to show on the dropdown. I added the HasComboBox event, I set the DirectComboDrop, and it still did not show any data in the dropdown. They were just empty. I check the object that I am adding to the dropdown, and they had data. What am I missing here?
procedure UserForm.DisplayGrid(Sender: TObject);
var
J : Integer;
begin
... additional logic
...
if OutputList.Count > 2 then
begin
with UserGrid.Combobox do
begin
for J := 0 to OutputList.Count - 1 do
BEGIN
if not(OutputList[J] = '') then
begin
dValue := DropDownValue.Create;
dValue.ID := J + 1;
dvalue.Name := OutputList[J];
dvalue.TestValue := OutputList[J] + 'testvalue'; // where value will be a list to choose from
ListOfTest.Add(dValue); // this is a glabal variable where I for later reference
ItemIndex := dValue.ID;
end;
END;
end;
end;
//event
procedure UserForm.UserGridHasComboBox(Sender: TObject; ACol, ARow: Integer;
var HasComboBox: Boolean);
begin
HasComboBox := True;
end;
There is an event handle called EditorProp that needed to be added. Data that need to be added for a specific column have to be added when the EditorProp event is called. The piece of code below was moved into the editorprop event, and it's working fine since.
for J := 0 to OutputList.Count - 1 do
BEGIN
if not(OutputList[J] = '') then
begin
dValue := DropDownValue.Create;
dValue.ID := J + 1;
dvalue.Name := OutputList[J];
dvalue.TestValue := OutputList[J] + 'testvalue'; // where value will be a list to choose from
ListOfTest.Add(dValue); // this is a glabal variable where I for later reference
ItemIndex := dValue.ID;
end;

How to get the bottom coordinate of visible items inTRichView?

I am working on one project where I need the pixel of Last visible item in the TRichview.
By using 'LastVisibleItem' Property of TRichView I am able to find the Item Start Cordinate.
but the Problem is I need a pixel value of very last visible word.
Can any one tell me how I can get that?
Thanks in advance.
I'm a bit unsure how your LastVisibleItem property works. Below is a suggested solution to get the top-right coordinate of the last visible character. Hope it works for you.
//Function GetCharPos source: http://www.delphipages.com/forum/showthread.php?t=33707
function GetCharPos(AOwner : TControl; Index : LongInt) : TPoint;
var
p : TPoint;
begin
AOwner.Perform(EM_POSFROMCHAR,WPARAM(#p),Index);
Result := p;
end;
//Inspired by: http://www.swissdelphicenter.ch/en/showcode.php?id=1213
function GetLastVisibleCharIndex(AOwner : TControl):integer;
var
r: TRect;
begin
//The EM_GETRECT message retrieves the formatting rectangle of an edit control.
AOwner.Perform(EM_GETRECT, 0, Longint(#r));
r.Right := r.Right - 1;
r.Bottom := r.Bottom - 2;
//The EM_CHARFROMPOS message retrieves information about the character closest to a specified point in the client area of an edit control
result := AOwner.Perform(EM_CHARFROMPOS, 0, Integer(#r.BottomRight));
end;
//Get the Top-Right coordinate of the last visible character
function GetLastVisibleCharPos(AOwner : TControl):TPoint;
var Index : integer;
begin
index := GetLastVisibleCharIndex(AOwner);
result := GetCharPos(AOwner, index);
end;
Example usage:
procedure TForm2.Button3Click(Sender: TObject);
var
p : TPoint;
begin
p := GetLastVisibleCharPos(RichEdit1);
DrawCrossHair(p); //Help visualize the point
end;
//Helper proc to draw a cross-hair
procedure TForm2.DrawCrossHair(p : TPoint);
var
aCanvas: Tcanvas;
X, Y: Integer;
begin
aCanvas := TCanvas.Create;
Y := RichEdit1.Height;
X := RichEdit1.Width;
try
aCanvas.Handle := GetDC(RichEdit1.Handle);
aCanvas.Font := RichEdit1.Font;
aCanvas.Pen.color := clGreen; // Color of line
//Draw vertical line
aCanvas.MoveTo(p.x, 0);
aCanvas.LineTo(p.x, Y);
//Draw horizontal line
aCanvas.MoveTo(0, p.Y);
aCanvas.LineTo(x, p.y);
finally
ReleaseDC(RichEdit1.Handle, aCanvas.Handle);
aCanvas.Free;
end;
end;

Word blocks in TMemo

I am trying to make a basic Hex viewer out of a TMemo, I know this is probably not ideal but it will be only me personally using it so that does not really matter.
(1)
Firstly, suppose a Memo is filled with Hex information like so:
How could I get a count of all the text blocks shown, ignoring the white space? So using the image the result in this case would be 28.
This is what I tried and I know it is completely wrong as I am accessing the Memo lines but I don't know how to access each character.
I cant seem to solve this simple problem :(
function CountWordBlocks(Memo: TMemo): Integer;
var
i: Integer;
vCount: Integer;
begin
for i := 0 to Memo.Lines.Count - 1 do
begin
if Length(Memo.Lines.Strings[i]) = 2 then
begin
Inc(vCount);
end;
end;
Result := vCount;
end;
Here is the code I am using to display the Hex values in the Memo:
procedure ReadFileAsHex(const AFileName: string; ADestination: TStrings);
var
fs: TFileStream;
buff: Byte;
linecount: Byte;
line: string;
begin
linecount := 0;
line := '';
fs := TFileStream.Create(AFileName, fmOpenRead);
try
ADestination.BeginUpdate;
try
while fs.Position < fs.Size do
begin
fs.Read(buff, 1);
line := line + IntToHex(buff, 2) + ' ';
Inc(linecount);
if linecount = 16 then
begin
ADestination.Add(line);
line := '';
linecount := 0;
end;
end;
if Length(line) <> 0 then
ADestination.Add(line);
finally
ADestination.EndUpdate;
end;
finally
fs.Free;
end;
end;
(2)
If I click onto the Memo and a text block is under the cursor, how could I know which number the selected block is out of all the others?
So using the same first image, the caret is at the top line next to 68, so the result would be 3 as it is the third text block out of 28.
This should be so easy but I cannot think clearly, I don't have the right programming mind yet and so really struggle with basic logic and solving problems!
(3)
Finally I would like to select a block at runtime by passing a block number value. I tried this without much success:
procedure FindBlock(Memo: TMemo; BlockNumber: Integer);
var
i: Integer;
txt: string;
ThisWhite, PrevWhite: boolean;
vRead: Integer;
begin
txt := Memo.Text;
vRead:= 0;
PrevWhite := True;
for i := 1 to Length(txt) do
begin
ThisWhite := Character.IsWhiteSpace(txt[i]);
if PrevWhite and not ThisWhite then
begin
Inc(vRead);
PrevWhite := False;
end;
PrevWhite := ThisWhite;
if vRead = BlockNumber then
begin
Memo.SelStart := vRead;
Memo.SetFocus;
Exit;
end;
end;
end;
(1)
This works:
function TForm1.CountBlocks: integer;
var
i: Integer;
txt: string;
ThisWhite, PrevWhite: boolean;
begin
txt := Memo1.Text;
result:= 0;
PrevWhite := true;
for i := 1 to Length(txt) do
begin
ThisWhite := Character.IsWhiteSpace(txt[i]);
if PrevWhite and not ThisWhite then
begin
inc(result);
PrevWhite := false;
end;
PrevWhite := ThisWhite;
end;
end;
However, it can be optimized if more detailed information about the memo contents is available. For instance, if you know that each line consists of four blocks, then the number of blocks is simply 4*Memo1.Lines.Count. My code above will even accept blocks of different width.
(2)
Simply replace
for i := 1 to Length(txt) do
by
for i := 1 to Memo1.SelStart + 1 do
Since you are in control of the formatting of your lines, and the lines have a fixed format, it is very easy to calculate the number of bytes being displayed without resorting to looping through the individual lines one a time. Every line displays 3 characters per byte, and every line other than the last line displays 16 bytes, thus 48 characters per complete 16-byte line. Use those facts to your advantage to calculate the number of bytes based on the number of complete 16-byte lines present, and then you can add on the number of remaining bytes from just the last line:
function CountWordBlocks(Memo: TMemo): Integer;
var
Count: Integer;
begin
Count := Memo.Lines.Count;
if Count > 0 then
Result := (16 * (Count-1)) + (Length(Memo.Lines[Count-1]) div 3);
else
Result := 0;
end;
You can do something similar to translate a character offset within the Memo into a work block number:
function GetCurrentWordBlock(Memo: TMemo): Integer;
var
SelStart, LineStart, LineNum: Integer
begin
Result := 0;
SelStart := Memo.SelStart;
if SelStart < 0 then Exit;
LineStart := Memo.Perform(EM_LINEINDEX, SelStart, 0);
if LineStart < 0 then Exit;
LineNum := Memo.Perform(EM_LINEFROMCHAR, LineStart, 0);
Result := (16 * LineNum) + ((SelStart - LineStart) div 3) + 1;
end;
To select a given block number, you can do this:
procedure FindBlock(Memo: TMemo; BlockNumber: Integer);
var
LineNum, LineStart: Integer;
begin
if BlockNumber < 1 then Exit;
LineNum = (BlockNumber - 1) div 16;
LineStart = Memo.Perform(EM_LINEINDEX, LineNum, 0);
if LineStart < 0 then Exit;
Memo.SelStart = LineStart + (((BlockNumber - 1) - (16 * LineNum)) * 3);
Memo.SelLength := 2;
Memo.SetFocus;
end;

First chance exception at $7C81EB33

I have an application that when run at home works fine, however when ran on school computers(Windows XP) i get the following message. (This is recompiling it, not just running the .exe)- In Delphi 2005
First chance exception at $7C81EB33. Exception class EAccessViolation with message 'Access violation at address 0045E5E2 in module 'Project2.exe'. Read of address 00000198'. Process Project2.exe (440)
Code: Ignoring unneeded stuff.
Image1: TImage; // Image(all the way to 72)
Timer1: TTimer; Timer2: TTimer;
procedure Button1Click(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure SomeOtherProcedure(Sender: TImage);
procedure Timer1Timer(Sender: TObject);
procedure Timer2Timer(Sender: TObject);
private
{ private declarations }
public
{ public declarations }
end;
var
Form1: TForm1;
left : integer;
top : integer;
gap : integer;
type
coordinates = record
row : integer ;
col : integer;
end;
var
picarray : array[0..5,0..5] of timage;
thiscover, midcover, lastcover : timage;
imageindex : array[0..5,0..5] of integer;
picloc: array[0..3] of coordinates;
clickcount, pairsfound, attemptcount : integer;
implementation
{$R *.lfm}
procedure initialise();
var
i, j, whichcol, whichrow : integer;
begin
for i := 0 to 5 do
for j := 0 to 5 do
imageindex[i,j] := -1; // not used
randomize;
for i := 0 to 11 do
for j := 1 to 3 do
begin
repeat
begin
whichcol := random(6) ;
whichrow := random(6) ;
end;
until imageindex[whichcol, whichrow] = -1;
picarray[whichcol, whichrow].Picture.LoadFromFile('C:\Users\Hayden\Pictures\'+ inttostr(I+1) +'.jpg');
imageindex[whichcol, whichrow] := I ;
end;
clickcount := 0 ; //
pairsfound := 0 ;
attemptcount := 0 ;
end;
procedure TForm1.FormCreate(Sender: TObject);
var
cpic : tcomponent;
whichcol: integer;
whichrow : integer;
begin
gap := image2.left - image1.left;
top := image1.Top;
left := image1.left;
for cpic in form1 do
begin
if (cpic.ClassType = timage) and (cpic.Tag = 10) then
begin
whichcol := (timage(cpic).left - left) div gap;
whichrow := (timage(cpic).Top - top) div gap;
picarray[whichcol, whichrow] := timage(cpic) ;
end;
end;
initialise;
end;
Line >>> picarray[whichcol, whichrow].Picture.LoadFromFile('C:\Users\Hayden\Pictures\'+ inttostr(I+1) +'.jpg');
seems to cause the error. And if it is a coding error, what is the correct way to do this?
First, I'm going to clean up your code a little, because as it stands, it's very difficult to figure what's going on. I highly recommend you get into the habit of taking a few minutes to keep your code clearly formatted - it will save you hours of debugging.
I've applied only the following simple changes: Indentation, Blank lines, and liberal use of begin .. end;
var
picarray : array[0..5,0..5] of timage;
thiscover, midcover, lastcover : timage;
imageindex : array[0..5,0..5] of integer;
picloc: array[0..3] of coordinates;
clickcount, pairsfound, attemptcount : integer;
implementation
{$R *.lfm}
procedure initialise();
var
i, j, whichcol, whichrow : integer;
begin
for i := 0 to 5 do
begin
for j := 0 to 5 do
begin
//It's clear you're initialising the 36 entries of imageindex to -1
imageindex[i,j] := -1; // not used
end;
end;
randomize;
for i := 0 to 11 do
begin
for j := 1 to 3 do
begin
//This loop also runs 36 times, so it fills the whole of imageindex with new values
//It also loads all 36 entries of picarray with an image specfied by the current value of i
//The approach is dangerous because it depends on the 'loop sizes' matching,
//there are much safer ways of doing this, but it works
repeat
begin //This being one of the only 2 begin..end's you provided inside this is routine is pointless because repeat..until implies it.
whichcol := random(6) ;
whichrow := random(6) ;
end;
until imageindex[whichcol, whichrow] = -1;
//This line itself will throw an access violation if picarray[whichcol, whichrow] doesn't
//contain a valid TImage instance... we have to check other code to confirm that possibility
picarray[whichcol, whichrow].Picture.LoadFromFile('C:\Users\Hayden\Pictures\' + inttostr(I+1) + '.jpg');
imageindex[whichcol, whichrow] := I ;
end;
end;
clickcount := 0 ; //
pairsfound := 0 ;
attemptcount := 0 ;
end;
Moving on to the next piece of code:
procedure TForm1.FormCreate(Sender: TObject);
var
cpic : tcomponent;
whichcol: integer;
whichrow : integer;
begin
gap := image2.left - image1.left;
top := image1.Top;
left := image1.left;
for cpic in form1 do
begin
//This loop attempts to assign existing TImage instances to picarray
//However, the way you're going about it is extremely dangerous and unreliable.
//You're trying to use the position of a component on the form to determine its
//position in the array.
//There are many things that could go wrong here, but since this seems to be a
//homework excercise, I'll just point you in the right direction - you need
//to debug this code.
if (cpic.ClassType = timage) and (cpic.Tag = 10) then
begin
whichcol := (timage(cpic).left - left) div gap;
whichrow := (timage(cpic).Top - top) div gap;
picarray[whichcol, whichrow] := timage(cpic) ;
end;
end;
//Here you call initialise, which as I said before, will
//cause an Access Violation if picarray is not correctly 'set up'
//The previous code in this method certainly has a bug which is
//preventing one or more picarray entries from being assigned a
//valid TImage instance.
//You could write a simple for I := 0 to 5, for J := 0 to 5 loop
//here to check each of picarray entries and pinpoint which is
//incorrect to aid your debugging of the pevious loop.
initialise;
end;
The critical section is the initialization of picarray. You can't be sure that every array element is assigned with a TImage component. If at least one Image has a wrong left or top you have a double assignment to one element and another is left nil. This will result in an Access Violation when you use it for the first time e.g. in picarray[whichcol, whichrow].Picture.LoadFromFile.
I would recommend to redesign the picarray initalization with for loops for every dimension. To get the correct TImage I would name them like 'Image_2_3' and get the instances in the loop by name.
you can check if the file exists and try to catch the exception to display a meaningful message
try
if FileExists('C:\Users\Hayden\Pictures\'+ inttostr(I+1) +'.jpg') then
picarray[whichcol, whichrow].Picture.LoadFromFile('C:\Users\Hayden\Pictures\'+ inttostr(I+1) +'.jpg');
else
ShowMessage("File not found");
except
on E : Exception do
ShowMessage(E.ClassName+' error raised, with message : '+E.Message);
end;

Resources