CListCtrl selection - c++

I'm trying to do something that I think is simple but I can't seem to make it work!
I have a CListCtrl and I want to select the last element in the list if the user clicks in the view empty space. I can do that just by calling Select(lastElementInList), but the element that was previously selected and that is now unselected still has a "bounding rectangle" around it.
The code that Implements this is as follows:
int nSel = GetNextItem(-1, LVNI_SELECTED);
if (nSel != -1)
SetItemState(nSel, 0, LVIS_SELECTED);
Select(lastElementInList);
Any hints? What am I missing?

The "bounding rectangle" you see indicates that the element currently is "focused", ie. in a state where a user interaction, such as pressing the down and up arrows, would start off from this point.
Change focused element
To move focus to your newly selected element you'll have to use SetItemState together with LVIS_FOCUSED, as in the below example:
if (nSel != -1)
SetItemState (nSel, ~LVIS_FOCUSED, LVIS_FOCUSED); // (1)
SetItemState (lastElementInList, LVIS_FOCUSED, LVIS_FOCUSED); // (2)
// (1) -> Remove focus from `nSel`
// (2) -> Add focus to `lastElementInList`

Related

Make ShowHint work on custom control with several different Rects, each with their own Hint

Using C++ Builder 2009
I have a custom control that inherits from TCustomControl, on which I paint several squares (TRect) with content etc.
I now wanted to show Hint as I hover over every square, but I'm not sure how to implement this best.
I attempted to simply use TCustomControl's ShowHint, and change Hint as I hover over the squares, but the problem is that the control doesn't show hint anymore after it first disappears, unless I leave the control and come back to it.
I hoped I could simply 'reset' it's state while hovering from one square to another but it doesn't work.
Assuming my approach is wrong to start with, kindly let me know what I should do to get the desired effect ?
if (State == rsHover && Item->FState != rsHover) // Not in the rsHover state yet, but going to
{
if (Item->Hint.Length())
{
if (ShowHint)
{
// Attempt to reset Hint's internal working, to no avail
Hint = L"" ;
ShowHint = false ;
}
Hint = Item->Hint ;
ShowHint = true ;
}
else
{
ShowHint = false ;
}
}
else if (State != rsHover)
{
ShowHint = false ;
}
The correct way to implement this feature is to make your component handle the CM_HINTSHOW message. The message's LParam value will be a pointer to a THintInfo record, whose fields you can freely modify as needed (in particular, HintStr and CursorRect).
To access the record, you can either
type-cast the LParam directly to THintInfo*.
type-cast the entire TMessage to TCMHintShow, and then access its HintInfo field.
By defining your own CursorRect rectangles, you can "[divide your control] into several hint regions", each with a different HintStr value. The CursorPos field indicates the mouse's current position within the control. When the mouse moves outside of the current CursorRect, the control will receive a new CM_HINTSHOW message, and you can update the CursorRect and HintStr fields as needed.

Accessing a Combobox inside a dataGridView Column?

I'm working on a scheduling program, and inside the dataGridView, we have a few ComboBox Columns that are populated by 3 entries upon creation, but I wanted to be able to add more as the user creates them, but I have no idea how you would access the combobox data. Any help is appreciated!
// this is initialized in a separate part.
/* System::Windows::Forms::DataGridView^ dataGridView;*/
System::Windows::Forms::DataGridViewComboBoxColumn^ newCol =
(gcnew System::Windows::Forms::DataGridViewComboBoxColumn());
dataGridView->Columns->AddRange(gcnew cli::array< System::Windows::Forms::DataGridViewComboBoxColumn^ >(1) {newCol});
// add the choices to the boxes.
newCol->Items->AddRange("User inputted stuff", "More stuff", "Add New...");
Solution
If you have access to the data from the user entry and you know the column index for the DataGridViewComboBoxColumn, you should be able to just do the following wherever needed:
DataGridViewComboBoxColumn^ comboboxColumn = dataGridView->Columns[the_combobox_column_index];
if (comboboxColumn != nullptr)
{
comboboxColumn->Items->Add("the new user entry");
}
Comments Response
how could you change the selected index of that combobox (the one that
the edit was triggered on)? [...] we want it so that when the new item
is added the selected index is set to that new item).
Couple of ways come to mind.
Add a single line within the if-statement of the above code. This will set the default displayed value for each DataGridViewComboBoxCell in the DataGridViewComboBoxColumn.
if (comboboxColumn != nullptr)
{
comboboxColumn->Items->Add("the new user entry");
comboboxColumn->DefaultCellStyle->NullValue = "the new user entry";
}
Pros: Clean, efficient. Previous user-selected values are left intact. The cell's FormattedValue will display the new user value by default if no other selection has been made.
Cons: Doesn't actually set a cell's selected value, so Value will return null on cells not explicitly user-selected.
Actually set the value of certain cells (based on your criteria) to the user-added value.
if (comboboxColumn != nullptr)
{
comboboxColumn->Items->Add("the new user entry");
for (int i = 0; i < dataGridView->Rows->Count; i++)
{
DataGridViewComboBoxCell^ cell = dataGridView->Rows[i]->Cells[the_combobox_column_index];
if ( cell != nullptr /* and your conditions are met */ )
{
cell->Value = "the new user entry";
}
}
}
Pros: The Value of targeted cells is actually set to the new user value.
Cons: Logic deciding which cells should be affected is more complicated.

Qt / C++ - Traverse Menu with Keys (Not Mouse)

I'm trying to do what you'd see in a video game menu while playing with a controller, but with a keyboard and QT/C++.
I don't know where to begin and because I don't know what the term is I don't know what to search for. Can't find anything on Google.
I am making a menu where left/right/up/down select items. No mouse input.
Can anyone help with a starting point, please?
Here is a sample method for handling key presses.
The class Widget is assumed to subclass QWidget
_rows member variable is number of menu rows
_row member variable is the currently selected row index starting from 0
updateSelectedMenuItem() is a method for doing whatever you want to do to have certain menu item selected, such as moving some marker on screen or changing colors.
// overriding superclass method
void Widget::keyPressEvent(QKeyEvent *ev)
{
switch(ev->key()) {
case Qt::Key_Up:
if (--_row < 0) _row = _rows-1;
updateSelectedMenuItem();
break;
case Qt::Key_Down:
if (++_row >= _rows) _row = 0;
updateSelectedMenuItem();
break;
default:
// call superclass event handler method
QWidget::keyPressEvent(ev);
}
// Note: QKeyEvent starts with isAccepted() == true
}
Note that this will rely on normal keyboard repeating, so it will behave the same as if you pressed up/down in any normal application where cursor moves. If you want different behaviour (for example if you are controlling a game, instead of moving in a menu), you should also override keyReleaseEvent, so you know when key is actually pressed down and released again.

How to prevent user from entering invalid values in wxGrid cell?

I'd like to create wxGrid where user can edit some cells, but prohibit entering incorrect valued. For example, only strings of length 4 could be entered there. So, if user enter string of another length, I'd like to show a error message and return to cell editor. How to do it?
If I handle a cell change event EVT_GRID_CELL_CHANGE, for example
void Frame::OnGridCellChange(wxGridEvent& event)
{
int r = event.GetRow(); // get changed cell
int c = event.GetCol(); // get changed cell
if (Grid->GetCellValue(r, c).length() != 4)
{Error E (this);
/* Create the Error message */
E.ShowModal();
// The error message shown, uses clicks OK
// So, what to do here?
}
Grid->ShowCellEditControl(); is not a solution because cell change won't be generated if user edit nothing, but just click another cell - incorrect value would appear in Grid.
Handling EVT_GRID_EDITOR_HIDDEN seems not suitable since it appears before new value actually saved to cell.
You need to use your own specialization of the cell editor.
http://docs.wxwidgets.org/trunk/classwx_grid_cell_editor.html
Perhaps using an event of type wxEVT_GRID_CELL_CHANGING would work for you? If the string that is returned by calling GetString() on the event is not four characters long then you can veto the event, something like:
void Frame::OnGridCellChanging(wxGridEvent& event)
{
if (event.GetString().length() != 4)
{
//Veto the event so the change is not stored
event.Veto();
Error E (this);
E.ShowModal();
}
This does seem to require a wxWidgets 2.9.x release however.

How to move an item up and down in a wxListCtrl (wxwidgets)

This should be pretty easy but I'm having a heck of a time doing it. Basically I want to move a row in my wxListCtrl up or down. I posted this to wxwidgets forum and got the following code.
m_list->Freeze();
wxListItem item;
item.SetId(item_id); // the one which is selected
m_list->GetItem(item); // Retrieve the item
m_list->DeleteItem(item_id); // Remove it
item.SetId(item_id - 1); // Move it up
m_list->SetItem(item); // Apply it's new pos in the list
m_list->Thaw();
which doesn't work. The element is deleted but not moved up (I guess the setitem line is not working). Then I thought to just switch the text and the image but I can't even get the text from the row reliably. I have
int index = m_right->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
wxString label = m_right->GetItemText(index);
if(index == 0)
return;
wxListItem item;
item.SetId(index);
bool success = m_right->GetItem(item);
wxString text = item.GetText();
but text is blank even though there is text and the index is correct. So, I'm stuck not even being able to do the most basic task. Anybody know how to do this? The code runs in a button callback (the user presses a little up arrow and my code executes to try to move it). I'm using 2.9.1 on windows.
I made it work like this with wxWidgets 2.9.3 :
void FileSelectionPanel::OnMoveUp( wxCommandEvent& WXUNUSED(evt) )
{
int idx = _listCtrl->GetNextItem( -1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED );
if( idx == 0) idx = _listCtrl->GetNextItem( 0, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED );
_listCtrl->Freeze();
while( idx > -1 ) {
wxListItem item;
item.SetId(idx); _listCtrl->GetItem(item);
item.SetId(idx-1); _listCtrl->InsertItem(item);
_listCtrl->SetItemData( idx-1, _listCtrl->GetItemData( idx+1 ));
for( int i = 0; i < _listCtrl->GetColumnCount(); i++ ) {
_listCtrl->SetItem( idx-1, i, _listCtrl->GetItemText( idx+1, i ));
}
_listCtrl->DeleteItem( idx + 1 );
idx = _listCtrl->GetNextItem( idx-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED );
}
_listCtrl->Thaw();
}
The thing I noticed it that wxListItem is more of a convenience struct, for storing state of the view and help pass values into the wxListCtrl "nicely". It is in no way bound to what is actually inside of the wxListCtrl.
Hope this still helps anyone !
Even there is already an answer that is checked. I have the same problem here, but my list is unordered. By looking into wxWidgets' code I found out there is another important information inside the wxListItem object - the mask. I got my reordering to work correctly by setting the mask value to -1, which means that all data shall be copied. This includes the item text as well as other information, like the item data (which was important in my case).
wxListItem item;
item.SetId(item_id); // set needed id
item.SetMask(-1); // set needed data
m_list->GetItem(item); // actually retrieve the item
m_list->DeleteItem(item_id); // remove old copy
item.SetId(item_id - 1); // move item up
m_list->InsertItem(item); // insert copy of item
I also had to use "InsertItem" instead of "SetItem". Otherwise, there was no new item inserted, but an existing one overwritten (see also tomcat31's answer).
Is the list ordered? if it is auto ordering it may be ignoring the order you are trying to apply.
From recollection the internal order was not necessarily sequential, you might have to get the index of the previous item and go one before it.