Firemonkey: How do I use a TComboBox in a TStringGrid to make it work from the keyboard? - c++builder

TComboBox in TStringGrid does not work when used from the keyboard. It does not update the Cells value.
I expected it to work from the keyboard when it works when I use the mouse. How should I change the code to make it work from the keyboard? Or is it a dead case?
Thanks Mika
void __fastcall TForm1::ChangeStringGridComboBox(TObject* Sender)
{
TComboBox* combobox = dynamic_cast<TComboBox*>(Sender);
if (combobox && combobox->ItemIndex > -1) {
StringGrid1->Cells[StringGrid1->Col][StringGrid1->Row] =
combobox->Items->Strings[combobox->ItemIndex];
}
}
//---------------------------------------------------------------------------
void __fastcall TForm1::StringGrid1CreateCustomEditor(
TObject* Sender, TColumn* const Column, TStyledControl*&Control)
{
TComboBox* combobox = new TComboBox(this);
if (Column == Column1) {
Control = combobox;
combobox->Items->Assign(Memo1->Lines);
combobox->ItemIndex = combobox->Items->IndexOf(
StringGrid1->Cells[StringGrid1->Col][StringGrid1->Row]);
if (combobox->ItemIndex > -1) {
StringGrid1->Cells[StringGrid1->Col][StringGrid1->Row] =
combobox->Items->Strings[combobox->ItemIndex];
}
combobox->OnChange = &ChangeStringGridComboBox;
}
}

From Firemonkey: How do I use a TComboBox in a TStringGrid to make it work from the keyboard?:
To use a TComboBox in a TStringGrid and make it work from the keyboard, you can follow these steps:
Place a TComboBox component on your form and set its Parent property to the TStringGrid.
Set the TComboBox's Visible property to False.
Handle the TStringGrid's OnSelectCell event. In the event handler, check if the current column of the selected cell is the column that the TComboBox is associated with. If it is, set the TComboBox's Left and Top properties to the coordinates of the selected cell, and set its Visible property to True.
Handle the TStringGrid's OnKeyPress event. In the event handler, check if the key pressed is the down arrow key. If it is, and the TComboBox is visible, set the focus to the TComboBox.
Handle the TComboBox's OnExit event. In the event handler, set the TComboBox's Visible property to False, and set the focus back to the TStringGrid.
In the TComboBox's OnSelect event, you can get the selected value and assign it to the selected cell of the TStringGrid.
You can also create a custom component that inherits from TStringGrid and add the TComboBox and the necessary events to it.
Since you are creating the TComboBox as a custom InplaceEditor, most of that should already be handled for you by the TStringGrid. All you are really missing is the Key Press handler, eg:
// Unlike VCL, FMX does not expose access to TStringGrid's
// active Editor, so keep track of it manually...
protected:
void __fastcall Notification(TComponent* AComponent, TOperation Operation) override;
private:
TComboBox *myComboBox;
...
void __fastcall TForm1::StringGrid1CreateCustomEditor(
TObject* Sender, TColumn* const Column, TStyledControl* &Control)
{
if (Column == Column1) {
myComboBox = new TComboBox(this);
myComboBox->Items->Assign(Memo1->Lines);
myComboBox->ItemIndex = myComboBox->Items->IndexOf(
StringGrid1->Cells[StringGrid1->Col][StringGrid1->Row]);
myComboBox->OnChange = &ChangeStringGridComboBox;
Control = myComboBox;
}
}
void __fastcall TForm1::ChangeStringGridComboBox(TObject* Sender)
{
if (myComboBox->ItemIndex > -1) {
StringGrid1->Cells[StringGrid1->Col][StringGrid1->Row] =
myComboBox->Items->Strings[myComboBox->ItemIndex];
}
}
void __fastcall TForm1::StringGrid1KeyPress(
TObject* Sender, Word &Key, WideChar &KeyChar, TShiftState Shift)
{
if ((Key == vkDown) && StringGrid1->EditorMode)
{
Key = 0;
myComboBox->SetFocus();
}
}
void __fastcall TForm1::Notification(TComponent* AComponent, TOperation Operation)
{
TForm::Notification(AComponent, Operation);
if ((Operation == TOperation::opRemove) && (AComponent == myComboBox)) {
myComboBox = nullptr;
}
}

With the following changes, TComboBox works fine:
ChangeStringGridComboBox
{
...
temp = myComboBox->Items->Strings[myComboBox->ItemIndex];
}
StringGridEditingDone
{
if (Column1 == StringGrid1->ColumnByIndex(ACol) && temp.Length()) {
StringGrid1->Cells[StringGrid1->Col][StringGrid1->Row] = temp;
temp = "";}
}

Related

Catching WM_POWERBROADCAST in a TWinControl child control

I have a TWinControl that needs to catch WM_POWERBROADCAST messages, but they never seem to reach it despite adding the message handler to the control's VCL_MESSAGE_MAP. I've also tried a custom WndProc() and that also never receives these messages. Other messages are working fine.
I can catch the message successfully in the main form, but it's never passed to my controls.
BEGIN_MESSAGE_MAP
VCL_MESSAGE_HANDLER(WM_PAINT, TMessage, WMPaint); // Works
VCL_MESSAGE_HANDLER(WM_ERASEBKGND, TMessage, WMEraseBackground); // Works
VCL_MESSAGE_HANDLER(WM_POWERBROADCAST, TMessage, WMPower); // Doesn't work!
END_MESSAGE_MAP(inherited);
WM_POWERBROADCAST is sent only to top-level windows, never to child windows. So, you have a few choices:
have your WinControl intercept the message that is sent to the hidden TApplication window by using the TApplication.HookMainWindow() method. Be sure to remove the hook when your WinControl is destroyed.
__fastcall TMyControl::TMyControl(TComponent *Owner)
: TWinControl(Owner)
{
Application->HookMainWindow(&AppHook);
}
__fastcall TMyControl::~TMyControl()
{
Application->UnhookMainWindow(&AppHook);
}
bool __fastcall TMyControl::AppHook(TMessage &Message)
{
if (Message.Msg == WM_POWERBROADCAST)
{
// ...
}
return false;
}
intercept the message that is sent to the TForm window, either by applying a MESSAGE_MAP to the Form class, or by overriding the Form's virtual WndProc() method, and then have the From forward the message to your WinControl.
BEGIN_MESSAGE_MAP
...
VCL_MESSAGE_HANDLER(WM_POWERBROADCAST, TMessage, WMPowerBroadcast);
END_MESSAGE_MAP(inherited);
...
void __fastcall TForm1::WMPowerBroadcast(TMessage &Message)
{
inherited::Dispatch(&Message);
MyControl->Perform(Message.Msg, Message.WParam, Message.LParam);
}
Or:
void __fastcall TForm1::WndProc(TMessage &Message)
{
inherited::WndProc(Message);
if (Message.Msg == WM_POWERBROADCAST)
MyControl->Perform(Message.Msg, Message.WParam, Message.LParam);
}
have your WinControl create its own hidden top-level window by using the RTL's AllocateHWnd() function.
private:
HWND FPowerWnd;
void __fastcall PowerWndProc(TMessage &Message);
...
__fastcall TMyControl::TMyControl(TComponent *Owner)
: TWinControl(Owner)
{
FPowerWnd = AllocateHWnd(&PowerWndProc);
}
__fastcall TMyControl::~TMyControl()
{
DeallocateHWnd(FPowerWnd);
}
void __fastcall TMyControl::PowerWndProc(TMessage &Message)
{
if (Message.Msg == WM_POWERBROADCAST)
{
// ...
}
else
{
Message.Result = ::DefWindowProc(FPowerWnd, Message.Msg, Message.WParam, Message.LParam);
}
}

How to manually get TMouseButton in C++Builder

I am using C++Builder from Embarcadero Technology. The built in OnClick event handler does not identify if the mouse click is the left or right button. Is there a function I can call to manually fill the values for TMouseButton. Below is the OnClick event handler?
void __fastcall TForm::ListBox1Click(TObject *Sender)
{
TMouseButton Button;
Button = ???
}
As others have mentioned, you can use the OnMouseDown event to remember the current mouse button state for use in OnClick, eg.
private:
bool LButtonDown;
bool RButtonDown;
...
void __fastcall TForm1::ListBox1MouseDown(TObject *Sender, TMouseButton Button, TShiftState Shift, int X, int Y)
{
switch (Button) {
case mbLeft:
LButtonDown = true;
break;
case mbRight:
RButtonDown = true;
break;
}
}
void __fastcall TForm1::ListBox1MouseUp(TObject *Sender, TMouseButton Button, TShiftState Shift, int X, int Y)
{
switch (Button) {
case mbLeft:
LButtonDown = false;
break;
case mbRight:
RButtonDown = false;
break;
}
}
void __fastcall TForm::ListBox1Click(TObject *Sender)
{
if (LButtonDown) ...
if (RButtonDown) ...
}
If you don't want to do that way, you can use the Win32 API GetKeyState() or GetAsyncKeyState() function to query the current state of the mouse's left and right buttons, using the VK_LBUTTON and VK_RBUTTON virtual key codes, eg:
void __fastcall TForm::ListBox1Click(TObject *Sender)
{
if (GetKeyState(VK_LBUTTON)) ...
if (GetKeyState(VK_RBUTTON)) ...
}
The correct event to use for details of mouse click events is OnMouseDown (also OnMouseUp and OnMouseMove).
Override the event and then implement MouseDown event like this
void __fastcall TMyListView::MouseDown(System::Uitypes::TMouseButton Button, System::Classes::TShiftState Shift, int X, int Y)
{
if (Button == mbLeft){
}
if (Button == mbRight){
}
}
See also Vcl.Controls.TControl.OnMouseDown in Embarcadero's documentation.

Using StyleHook in C++ Builder

I need to create a style hook for TEdit in C++ Builder XE7, in order to override the style color management, as the following Delphi example. Could somebody post a complete example in C++ Builder (hook unit, registration and example form)? Thanks!
A translation of the Delphi example would look something like this:
class TEditStyleHookColor : public TEditStyleHook
{
typedef TEditStyleHook inherited;
private:
void UpdateColors();
protected:
virtual void __fastcall WndProc(TMessage &Message);
public:
__fastcall TEditStyleHookColor(TWinControl *AControl);
};
#include <Vcl.Styles.hpp>
class TWinControlH : public TWinControl {};
__fastcall TEditStyleHookColor::TEditStyleHookColor(TWinControl *AControl)
: TEditStyleHook(AControl)
{
//call the UpdateColors method to use the custom colors
UpdateColors();
};
//Here you set the colors of the style hook
void TEditStyleHookColor::UpdateColors()
{
if (Control->Enabled)
{
Brush->Color = (static_cast<TWinControlH*>(Control)->Color; //use the Control color
FontColor = static_cast<TWinControlH*>(Control)->Font->Color;//use the Control font color
}
else
{
//if the control is disabled use the colors of the style
TCustomStyleServices *LStyle = StyleServices();
Brush->Color = LStyle->GetStyleColor(scEditDisabled);
FontColor = LStyle->GetStyleFontColor(sfEditBoxTextDisabled);
}
}
//Handle the messages of the control
void __fastcall TEditStyleHookColor::WndProc(TMessage &Message)
{
switch (Message.Msg)
{
case CN_CTLCOLORMSGBOX:
case CN_CTLCOLORSCROLLBAR:
case CN_CTLCOLORSTATIC:
{
//Get the colors
UpdateColors();
SetTextColor(reinterpret_cast<HDC>(Message.WParam), ColorToRGB(FontColor));
SetBkColor(reinterpret_cast<HDC>(Message.WParam), ColorToRGB(Brush->Color));
Message.Result = reinterpret_cast<LRESULT>(Brush->Handle);
Handled = true;
break;
}
case CM_ENABLEDCHANGED:
{
//Get the colors
UpdateColors();
Handled = false;
break;
}
default:
inherited::WndProc(Message);
break;
}
}
...
TStyleManager::Engine->RegisterStyleHook(__classid(TEdit), __classid(TEditStyleHookColor));
TStyleManager::Engine->RegisterStyleHook(__classid(TMaskEdit), __classid(TEditStyleHookColor));
TStyleManager::Engine->RegisterStyleHook(__classid(TLabeledEdit), __classid(TEditStyleHookColor));
TStyleManager::Engine->RegisterStyleHook(__classid(TButtonedEdit), __classid(TEditStyleHookColor));

How to check if a component is TEdit type in c++builder?

How do I check through all the components of a form and verify that components are of type TEdit?
You can use the dynamic_cast operator.
Excuse me if I'm wrong, but won't embarcadero automatically add all form-component object pointers to the class definition ( in the header file )..
Such as:
class TFormSomeForm : public TForm
{
__published:
TEdit *SomeEditBox;
TEdit *AnotherEditBox;
...
}
Meaning that you can tell from the header which components are of type TEdit.
Or you can click on the components in the Design View and the Object Inspector will show the type.
My function sets Text property of all edits in a TWinControl and its children.
void __fastcall SetEditsText(TWinControl* winControl, UnicodeString editsText)
{
for (int c = 0; c < winControl->ControlCount; c++)
{
TControl* ctrl = winControl->Controls[c];
TWinControl* wc = dynamic_cast<TWinControl*>(ctrl);
// Check if it's grouping component
if (wc != NULL)
{
// Set edits of children
SetEditsText(wc, editsText);
}
else
{
if (ctrl->ClassType() == __classid(TEdit))
{
TEdit* ecomp = (TEdit*) ctrl;
ecomp->Text = editsText;
}
}
}
}
Using:
void __fastcall TForm1::Button1Click(TObject *Sender)
{
SetEditsText(form1, ""); // Clear all edits
}

When to redraw VirtualTreeView after OnNewText event?

I use this code to fill VirtualStringTree and allow renaming items:
//---------------------------------------------------------------------------
// Structure for the tree
//---------------------------------------------------------------------------
struct TVSTdata
{
UnicodeString Name;
};
//---------------------------------------------------------------------------
// Initialization of the tree
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner)
{
VirtualStringTree1->NodeDataSize = sizeof(TVSTdata);
// Fill all nodes with initial data
InitializeTree();
}
//---------------------------------------------------------------------------
// Fill all nodes with data and assign FocusedNode
//---------------------------------------------------------------------------
void TForm1::InitializeTree()
{
TVirtualNode* pNode;
TVirtualNode* pActiveNode;
TVSTdata* pData;
VirtualStringTree1->BeginUpdate();
VirtualStringTree1->Clear();
pNode = VirtualStringTree1->AddChild(NULL); pData = static_cast<TVSTdata*>(VirtualStringTree1->GetNodeData(pNode)); pData->Name = "This is name 1";
pNode = VirtualStringTree1->AddChild(NULL); pData = static_cast<TVSTdata*>(VirtualStringTree1->GetNodeData(pNode)); pData->Name = "This is name 2";
pNode = VirtualStringTree1->AddChild(NULL); pData = static_cast<TVSTdata*>(VirtualStringTree1->GetNodeData(pNode)); pData->Name = "This is name 3"; pActiveNode = pNode;
pNode = VirtualStringTree1->AddChild(NULL); pData = static_cast<TVSTdata*>(VirtualStringTree1->GetNodeData(pNode)); pData->Name = "This is name 4";
pNode = VirtualStringTree1->AddChild(NULL); pData = static_cast<TVSTdata*>(VirtualStringTree1->GetNodeData(pNode)); pData->Name = "This is name 5";
VirtualStringTree1->Selected[pActiveNode] = true;
VirtualStringTree1->FocusedNode = pActiveNode; // PROBLEM -> if assigned from within OnNewText will still remain NULL and won't be set to pActiveNode!
VirtualStringTree1->EndUpdate();
}
//---------------------------------------------------------------------------
// Just display the text
//---------------------------------------------------------------------------
void __fastcall TForm1::VirtualStringTree1GetText(TBaseVirtualTree *Sender, PVirtualNode Node, TColumnIndex Column, TVSTTextType TextType, UnicodeString &CellText)
{
TVSTdata* pData = static_cast<TVSTdata*>(Sender->GetNodeData(Node));
CellText = pData->Name;
}
//---------------------------------------------------------------------------
// Allow editing
//---------------------------------------------------------------------------
void __fastcall TForm1::VirtualStringTree1Editing(TBaseVirtualTree *Sender, PVirtualNode Node, TColumnIndex Column, bool &Allowed)
{
Allowed = true;
}
//---------------------------------------------------------------------------
// Now this is where ideally I would reload the tree with new data - after rename
//---------------------------------------------------------------------------
void __fastcall TForm1::VirtualStringTree1NewText(TBaseVirtualTree *Sender, PVirtualNode Node, TColumnIndex Column, UnicodeString NewText)
{
NewText = "not important for this example as tree is reloaded anyway";
InitializeTree(); // ERROR is here - after assigning FocusedNode it is still NULL
//Timer1->Enabled = true; // If delayed call FocusedNode is correctly assigned and not NULL
}
//---------------------------------------------------------------------------
void __fastcall TForm1::Timer1Timer(TObject *Sender)
{
//InitializeTree();
//Timer1->Enabled = false;
}
//---------------------------------------------------------------------------
The problem - when InitializeTree() is called initially and VirtualStringTree1->FocusedNode is assigned it is correctly assigned (not NULL).
However, if this InitializeTree() function is called within OnNewText to actually reload the tree from database after rename event - after assigning FocusedNode it is remains NULL. So obviously tree cannot be reloaded and assigned FocusedNode from within the OnNewText event.
I implemented delayed call to reload new tree and reassign FocusedNode - by implementing a quick and dirty timer (could have used PostMessage for delayed function call but this is just a dumb example) - after assigning within a timer it no longer NULL and works as expected.
Can anyone point me what is the optimal way to implement reloading of the tree - like a particular event to use in which it is safe to set new FocusedNode and it won't be reassigned back to NULL? Is delayed function call the only way to achieve this or is there a better event to trap (for example if one occurs after OnNewText if this one doesn't allow setting focused node). Of course this works but I am interested if there is a better way to do this.
You can't change the FocusedNode when you're in the tsEditing tree state and until you leave the OnNewText event, you're in that state. The OnNewText itself is more for edit validation; it is the event, where you can modify the edited value. Instead you should use the OnEdited event which is fired after the edit is actually done. So move your database update and tree reloading stuff there like shown in the following C++ Builder pseudocode:
void __fastcall TForm1::VirtualStringTree1Edited(TBaseVirtualTree *Sender,
PVirtualNode Node, TColumnIndex Column)
{
// update your database here; with VirtualStringTree1.Text[Node, Column] you
// can access the current node text after edit; when you update your DB, call
InitializeTree();
}

Resources