Highlighting TGrid row in code - c++

I have a custom TGrid control which I am trying to make it so when the mouse hovers over a row, that row is highlighted. Rows are automatically highlighted if I used the arrow keys to navigate the grid. However, I am not sure how to replicate this effect for navigation with the mouse.
Currently, I have a MouseMove function which can detect which row of the grid the mouse is hovering over.
void __fastcall TFmSearchBar::GridMouseMove(TObject *Sender, TShiftState Shift, float X, float Y)
{
int rowSelected = FGrid->RowByPoint(X, Y);
if(rowSelected >= FGrid->RowCount)
rowSelected = FGrid->RowCount - 1;
if(rowSelected != -1)
{
FGrid->SelectRow(rowSelected);
}
}
I originally thought using the SelectRow function would achieve the desired effect, however nothing happens when that method is used. Additionally I have tried using
FGrid->SelectCell(0, rowSelected);
which did not work either.
I have verified that I am getting the right row from the function by setting a row's text to bold when the mouse hovers over it using
FGrid->RowObjects[rowSelected]->SetBold();

you must enable following options for TGrid component:
1) RowSelect = True
2) AlwaysShowSelection = True
Tested with Delphi XE8 -- works fine. My code:
procedure TForm1.Grid1MouseMove(Sender: TObject; Shift: TShiftState; X, Y: Single);
begin
Grid1.SelectRow(Grid1.RowByPoint(X, Y));
end;
if you want, i could provide you DFM file either.

Related

How to draw buttons in TStringGrid cells

I am trying to customize a TStringGrid by adding visual objects to cells within the grid. One column needs to contain standard windows push buttons in each row and another column needs to contain a drop down with pre-defined options.
From what I have read the best way to achieve this is to draw the buttons manually in the OnDrawCell event handler. All of the examples I have found use DrawFrameControl() which does not draw a themed button like you would expect in Windows 7 or later.
Is there an equivalent function to DrawFrameControl() that will allow me to draw a themed button and if so can someone please give an example of how I might use it?
I also tried creating a vector of TButtons and setting the parent of each button to be the StringGrid and placing each button within the relevant cell. This also works but does now allow for scrolling of the grid when there are more cells that can be displayed visible area.
I am using RAD Studio 10.2 C++ builder and using the BCC32C compiler (clang-enhanced). It is a VCL WIN32 application.
Is there an equivalent function to DrawFrameControl() that will allow me to draw a themed button
The Win32 DrawFrameControl() function is for drawing non-themed UI controls. To draw themed UI controls, you need to use the Win32 Theming functions instead - DrawThemeBackground(), DrawThemeEdge(), DrawThemeText(), etc. These functions are wrapped for you by the VCL's Vcl.Themes unit. In particular, use the TThemeServices class, which has various Draw...() methods that you can use when TThemeServices.Available and TThemeServices.Enabled are both true.
I also tried creating a vector of TButtons and setting the parent of each button to be the StringGrid and placing each button within the relevant cell. This also works but does now allow for scrolling of the grid when there are more cells that can be displayed visible area.
Correct. You would have to subclass the StringGrid to intercept the scrolling so you can reposition the buttons manually.
Just for completeness here is the code I got working in order to draw a Windows Themed button in a TStringGrid cell:
void __fastcall TForm_Controller::StringGrid1DrawCell(TObject *Sender, int ACol,
int ARow, TRect &Rect, TGridDrawState State)
{
TStringGrid *grid;
bool ButtonDown = false, ButtonHot = false, ButtonInFocus = false;
TThemedElementDetails LDetails;
TTextFormatFlags LTextFormat;
TColor LColor, TempColor;
TCustomStyleServices *LStyle;
int XPos, YPos;
TPoint points[3];
grid = (TStringGrid *)Sender;
//a cell with a button ('+' or '-')
if((ACol == 0) && (ARow > 0) && grid->Cells[ACol][ARow].Length())
{
Rect.Left -= 4; //override padding so button fills entire cell
if ((FocusCell.X == ACol) && (FocusCell.Y == ARow))
ButtonHot = true;
if ((CellDown.X == ACol) && (CellDown.Y == ARow))
ButtonDown = true;
LStyle = StyleServices();
if (LStyle->Enabled && LStyle->Available)
{
if (ButtonDown)
LDetails = LStyle->GetElementDetails(tbPushButtonPressed);
else if (ButtonHot)
LDetails = LStyle->GetElementDetails(tbPushButtonHot);
else if (ButtonInFocus)
LDetails = LStyle->GetElementDetails(tbPushButtonDefaulted);
else
LDetails = LStyle->GetElementDetails(tbPushButtonNormal);
LStyle->DrawElement(grid->Canvas->Handle, LDetails, Rect);
if (LStyle->GetElementColor(LDetails, ecTextColor, LColor))
grid->Canvas->Font->Color = LColor;
grid->Canvas->Font->Size = 13;
LTextFormat = (tfCenter);
LStyle->DrawTextA(grid->Canvas->Handle, LDetails, grid->Cells[ACol][ARow], Rect, TTextFormat() << tfCenter << tfVerticalCenter);
}
else //themed drawing not available so revert to old style
DrawButtonFace(grid->Canvas, Rect, 1, bsAutoDetect, true, ButtonDown, ButtonInFocus);
}
}
I then use the OnMouseDown, OnMouseMove, OnMouseLeave and OnMouseUp events to determine what state the buttons need to be in using 3 TPoint objects FocusCell, PrevCell, CellDown.

How to stop the bottom scrollbar from a CListCtrl from displaying?

I have a CListCtrl which is dynamically resized with the dialogue. I used a WM_SIZE message handler in the derived CListCtrl to resize the columns such that the total is the width of the control - 4, where the - 4 is to indicate the width of the border.
When I make the dialogue bigger, the control resizes correctly and I don't get the bottom scrollbar. However when I shrink the control, I sometimes get the horizontal scrollbar showing up.
void CMyListCtrl::OnSize(UINT nType, int cx, int cy)
{
CListCtrl::OnSize(nType, cx, cy);
ResizeLastColumn();
}
void CMyListCtrl::ResizeLastColumn()
{
LVCOLUMN column;
column.mask = LVCF_WIDTH;
LONG maxWidth = 0;
for (int i = 0; i < lastColumnIndex; ++i)
{
GetColumn(i, &column);
maxWidth += column.cx;
}
CRect wndRect;
GetWindowRect(&wndRect);
SetColumnWidth(lastColumnIndex, wndRect.Width() - maxWidth - 4);
}
It is like the WM_SIZE message is getting to the control before the control is finally resized.
This is related to How to determine if a scrollbar for a CListCtrl is displaying?. However, this question is not dealing with the right scrollbar, and is assuming that it is not being displayed.
Resizing the window generates a message to test for horizontal scroll. SetColumnWidth will also generate the same message. It depends how ListView handles this internally, but a vertical scroll could also come in and go, this will change the client area, so the code may have to make recursive calls to figure out if the scroll should be visible or not. You can see this can easily run in to problems.
Try resizing the columns in WM_WINDOWPOSCHANGED, before calling the default procedure. Use SetRedraw to stop redundant paint messages.
ON_WM_WINDOWPOSCHANGED()
...
void CMyListCtrl::OnWindowPosChanged(WINDOWPOS *wpos)
{
SetRedraw(FALSE);
ResizeLastColumn();
SetRedraw(TRUE);
CListCtrl::OnWindowPosChanged(wpos);
}
You can use GetClientRect for the client area, then you don't need to subtract the border thickness (which is not always 4).
void ResizeLastColumn()
{
int maxwidth = 0;
int index = GetHeaderCtrl()->GetItemCount() - 1;
for(int i = 0; i < index; ++i)
maxwidth += GetColumnWidth(i);
CRect rc;
GetClientRect(&rc);
SetColumnWidth(index, rc.Width() - maxwidth);
}
Also GetHeaderCtrl()->GetItemCount() returns the number of columns.
I'm facing the exact same issue, with the exact same Use Case.
To disable the horizontal scroll bar altogether, you add a message handler for WM_NCCALCSIZE
Using the Class Wizard this adds the following to your message map:
ON_WM_NCCALCSIZE()
In the implementation of this handler, you modify the style to disable the horizontal scroll bar.
void CMyListCtrl::OnNcCalcSize(BOOL bCalcValidRects, NCCALCSIZE_PARAMS* lpncsp)
{
ModifyStyle(WS_HSCROLL, 0); // disable the horizontal scroll bar
__super::OnNcCalcSize(bCalcValidRects, lpncsp);
}
In my own implementation of ResizeLastColumn(), which is similar to yours, I subtract ::GetSystemMetrics(SM_CXVSCROLL) from the width to account for the vertical scroll bar.
I realise this reply is rather late, but hopefully it will help someone.
(Edited to remove mentions of some problems I am having because a) they are off-topic and b) because I think they stem from me not using the Doc-View architecture and things not being wired up correctly as a result. I'm going to re-think my approach.)

TListView MouseUp event fires too early and MouseMove Shift doesn't work as expected either

Using C++ Builder 2009 (but I tagged Delphi too since this is VCL)
I created a class that inherits from TListView and when there are no objects to list, I 'DrawText' some text in the middle of the ListView Canvas, but with a hotspot in the text that can be clicked.
Basically I draw the text using two colors and the hotspot area is in blue.
I also keep track of the coordinates of that hotspot in a custom HotSpot class.
PS. I use MultiSelect = true
When I hover with the mouse over the hotspot I want to underline the text (which works using below code)
When I release a mouse click on the hotspot I want it to call an event (which works in below code, if MultiSelect = true)
But I also wanted to display the text in bold when the hotspot is clicked (MouseDown) before released again (MouseUp).
This doesn't work because the MouseDown code is not executed when the mouse is clicked, it only executes when the mouse click gets released and only just before the MouseUp code. Is this a bug ?
However the logic changes when MultiSelect is off (= false), the MouseUp event then triggers straight after MouseDown, but before the mouse button is released
So I figured I ignore MouseDown and put the logic in MouseMove and check Shift for the left mouse click. Yet that never works when MultiSelect = true, only when MultiSelect = false, which doesn't work for my needs because MouseUp triggers too fast.
Is there something obvious I'm missing ?
//---------------------------------------------------------------------------
void __fastcall ObjectListView::MouseMove(Classes::TShiftState Shift, int X, int Y)
{
TListView::MouseMove(Shift, X, Y);
if (Items->Count == 0 && HotSpot.Rect.Top > 0)
{
if (InRange(X, HotSpot.Rect.Left, HotSpot.Rect.Right) && InRange(Y, HotSpot.Rect.Top, HotSpot.Rect.Bottom))
{
if (!HotSpot.Hover)
{
HotSpot.Hover = true ;
Invalidate() ; // Paint the text again, but this time Hot
}
if (Shift.Contains(ssLeft)) // Left Mouse button is being held down
{
if (!HotSpot.Select)
{
HotSpot.Select = true ;
Invalidate() ; // Paint the text again, but this time Selected
}
}
else if (HotSpot.Select)
{
HotSpot.Select = false ;
Invalidate() ;
}
}
else if (HotSpot.Hover)
{
HotSpot.Hover = false ;
HotSpot.Select = false;
Invalidate() ;
}
}
}
//---------------------------------------------------------------------------
void __fastcall ObjectListView::MouseUp(TMouseButton Button, Classes::TShiftState Shift, int X, int Y)
{
TListView::MouseUp(Button, Shift, X, Y);
if (OnHotSpotClick && Items->Count == 0 && HotSpot.Rect.Top > 0)
{
if (Button == mbLeft)
{
if (InRange(X, HotSpot.Rect.Left, HotSpot.Rect.Right) && InRange(Y, HotSpot.Rect.Top, HotSpot.Rect.Bottom))
{
HotSpot.Select = false ;
Invalidate() ;
OnHotSpotClick(this, ParentObject, (TLVStatus)HotSpot.Data) ;
}
}
}
}
//---------------------------------------------------------------------------
void __fastcall ObjectListView::MouseDown(TMouseButton Button, Classes::TShiftState Shift, int X, int Y)
{
TListView::MouseDown(Button, Shift, X, Y);
// This event triggers when the mouse button is released (Bug ?) and if (MultiSelect == true)
// So there is no point trying to do something with the HotSpot state
// Instead, use the MouseMove event and check the Shift state
}
//---------------------------------------------------------------------------

Using Tchart in c++ to draw a vertical line

I got some values that i need to draw as a vertical line. The line should be from begin till the end of diagramm field.
I use VCLTee.Chart.hpp in Embarcadero. As I know it is the Tchart, that is actually used more for Delphi.
However:
I use this function:
DlgMainWindow->ChartTemperatureCurve->Canvas->DoVertLine(XValue,YValue,ZValue);
i canĀ“t find the description. As I see DoVertLine works with Pixel of the diagramm. But if my YValue = 10 , and should be always parallel to x for whole distance.
You should convert your YValue from axis values to pixels with the axis CalcPosValue function.
If you want to draw a line at a constant YValue, it would be an horizontal line, not a vertical line.
In the following example I'm drawing an horizontal line at YValue=10.
Note the drawing functions should be called at OnAfterDraw event or similar to make sure your custom drawings are done after every repaint.
To use OnAfterDraw event on RAD Studio, select the chart at design-time, navigate to the Events tab at the Object Inspector and double-click on the white cell next to OnAfterDraw.
This action should open the code view with the cursor inside a new and empty OnAfterDraw function.
Then you can add what you want to do there. Ie, drawing an horizontal line within the ChartRect, at YValue=10:
void __fastcall TForm1::Chart1AfterDraw(TObject *Sender)
{
Chart1->Canvas->Pen->Color = clRed;
int X0Pos = Chart1->ChartRect.Left;
int X1Pos = X0Pos + Chart1->ChartRect.Width();
double YVal = 10;
int YPos = Chart1->Axes->Left->CalcPosValue(YVal);
Chart1->Canvas->DoHorizLine(X0Pos, X1Pos, YPos);
}

c++ How to wait for multiple mouse clicks

I want to draw a Triangle in CImg library. However, I do not know how to write the code that will allow me to draw the triangle using three mouse clicks.
The code in the documentation is this:
while (!main_disp.is_closed() && !draw_disp.is_closed())
{
main_disp.wait();
if (main_disp.button() && main_disp.mouse_y()>=0)
http://cimg.sourceforge.net/reference/group__cimg__tutorial.html
But it is for one mouse click, which I implemented successfully to draw a circle on the mouse click. But to do for three mouse clicks or two has proven to be difficult for me.
I also have the problem of inputting the color I want. I wrote red for example as:
const unsigned char red[] = {250, 0, 0};
Then I want the user to choose which color, enter his choice(assume it's red) and then pass this definition of red into the image.
Anyone can help with this ?!
If you know how to detect when the mouse button is clicked, you can store information about that click for later. For example, you can store previous mouse clicks in a deque.
struct point
{
int x,y;
};
...
std::deque<point> clicks;
while (!main_disp.is_closed() && !draw_disp.is_closed())
{
main_disp.wait();
if (main_disp.button())
{
clicks.push_front({mouse_disp.mouse_x(), mouse_disp.mouse_y()});
if (clicks.size() >= 3)
{
// draw a triangle using clicks[0], clicks[1] and clicks[2]
}
}
}