How to get notification for keydown for CMFCRibbonComboBox? - mfc

I have CMFCRibbonComboBox on ribonbar and I want when that user press on a key open droplist and select Item acurding to chars that press by user.
For this purpose I want to get notification for keydown.
How can I to do it?
Thanks

I asked a very similar question on MSDN here and eventually solved it myself with the following hack;
Save a local copy of C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\atlmfc\src\mfc\afxribbonedit.cpp to your project
In BOOL CMFCRibbonRichEditCtrl::PreTranslateMessage(MSG* pMsg) replace this
case VK_DOWN:
if (m_edit.m_bHasDropDownList && !m_edit.IsDroppedDown())
{
m_edit.DropDownList();
return TRUE;
}
with this
case VK_DOWN:
if (m_edit.m_bHasDropDownList && !m_edit.IsDroppedDown())
{
m_edit.DropDownList();
CMFCRibbonBaseElement* pRibbonBaseElement = m_edit.GetDroppedDown();
if (pRibbonBaseElement && (pRibbonBaseElement->IsKindOf(RUNTIME_CLASS(CMFCRibbonComboBox))))
{
CString str;
GetWindowText(str);
CMFCRibbonComboBox *pCombo = (CMFCRibbonComboBox*)pRibbonBaseElement;
int ItemNo = -1;
for (int i = 0; i < pCombo->GetCount(); i++)
{
CString ItemText = pCombo->GetItem(i);
if (ItemText.Left(str.GetLength()).CompareNoCase(str) == 0)
{
ItemNo = i;
break;
}
}
if (ItemNo != -1)
{
pCombo->OnSelectItem(ItemNo);
// Draw and redraw dropdown for selection to show
m_edit.DropDownList();
m_edit.DropDownList();
}
}
return TRUE;
}
For drop lists (as opposed to drop downs) you can similarly hand WM_CHAR to do a first letter search based on the next item after the current position. Note that the above hack would need to be checked against any future updates to the ribbon library and should be dumped once it has been properly implemented in the library.

Related

Different behavior of events in different projects

In the last question, I had a problem with placing the cursor in the right place when clicking on the LineEdit with the mouse.
The problem was solved, I, with God's help, wrote an algorithm that solves my problem. I wrote it in an EMPTY project, where there is nothing but LineEdit. Everything worked great there. Here is the code:
if(object == ui->lineEdit_newClientPhone && event->type() == QEvent::MouseButtonRelease)
{
QString line = ui->lineEdit_newClientPhone->displayText();
qDebug() << line;
ui->lineEdit_newClientPhone->setFocus();
bool isValid = true;
for(int i = 0; i<=15; i++){
if(line[i] == '_'){
ui->lineEdit_newClientPhone->setCursorPosition(i);
isValid = false;
break;
}
}
if(isValid){
ui->lineEdit_newClientPhone->setCursorPosition(16);
}
}
It worked great. You click on any place in lineEdit, the mouse cursor is placed on the first empty "_" character. I joyfully ran to transfer it to the main project.
But then I encountered a strange behavior - in the main project, exactly copied code does not give such behavior. When you click on lineEdit with the mouse, the cursor is placed in the place where you clicked. I debugged all the events associated with this LineEdit and found strange behavior - the order of the events is different. Excluding unnecessary events, which were the MOST:
if(object == ui->lineEdit_newClientPhone){
if(event->type() != QEvent::Paint && event->type() != QEvent::MouseMove && event->type() != QEvent::HoverMove){
qDebug() << event->type();
}
}
Got this result in "EMPTY" project:
QEvent::MouseButtonPress
QEvent::MouseButtonRelease
QEvent::InputMethodQuery
But in the main project, the sequence turned out to be different:
QEvent::MouseButtonPress
QEvent::InputMethodQuery
QEvent::MouseButtonRelease
I don't know if this is the case, but in this way, my logic does not work and the cursor remains in the place where you clicked. I was able to fix the situation by changing the event to QEvent::MouseButtonRelease, but this way I get mocking cursor travels first to the place where you clicked, then to the place I need. What could be the problem and how can it be fixed? Thanks to all!
I don't think this is the right decision, so I would like to hear more solutions, but QTimer::singleShot helped me
f(object == ui->lineEdit_newClientPhone && event->type() == QEvent::MouseButtonPress){
QTimer::singleShot(0,ui->lineEdit_newClientPhone,[this]
{
QString line = ui->lineEdit_newClientPhone->displayText();
qDebug() << line;
ui->lineEdit_newClientPhone->setFocus();
bool isValid = true;
for(int i = 0; i<=15; i++){
if(line[i] == '_'){
ui->lineEdit_newClientPhone->setCursorPosition(i);
isValid = false;
break;
}
}
if(isValid){
ui->lineEdit_newClientPhone->setCursorPosition(16);
}
});
}

C++ How do I Handle all possible Player Movement inputs?

I am trying to clean up movement code I followed from a video tutorial series that was never finished. My intent is for the character to only ever be able to move on X or Y at any given time (so no diagonal). The character has direction facing to keep in mind.
My issue is the player can still press any key they want, or accidently press two keys at the same time.
Ex. if you move Up and make a right turn, accidentally press Right before letting go of Up.
Or if you press Up, press and let go Right to make a slight movement right while continuing to press Up, the player should continue to move up after letting go of Right without having to re-press Up. etc.
Just to make sure all possible input cases are handled intuitively.
EDIT: This is the code so far and I'm getting weird errors I don't know what's wrong
#pragma once
#include "../game.h"
#include "ECS.h"
#include "Components.h"
#include <list>
using namespace std;
class KeyboardController : public Component
{
public:
TransformComponent *transform;
SpriteComponent *sprite;
std::list<SDL_Event> keyDownList;
SDL_Event lastDirection;
void updateKeyState()
{
if (Game::event.type == SDLK_ESCAPE) {
Game::isRunning = false;
}
else if (Game::event.type == SDL_KEYDOWN) {
keyDownList.push_back(Game::event.key.keysym.sym);
}
else if (Game::event.type == SDL_KEYUP) {
keyDownList.remove(Game::event.key.keysym.sym);
}
}
void init() override
{
transform = &entity->getComponent<TransformComponent>();
sprite = &entity->getComponent<SpriteComponent>();
}
void update() override
{
void updateKeyState();
void updateMovement();
}
};
Severity Code Description Project File Line Suppression State
Error (active) E0304 no instance of overloaded function "std::list<_Ty, _Alloc>::push_back [with _Ty=SDL_Event, _Alloc=std::allocator]" matches the argument list Sandbox C:\file_path\KeyboardController.h 31
Severity Code Description Project File Line Suppression State
Error (active) E0415 no suitable constructor exists to convert from "SDL_Keycode" to "SDL_Event" Sandbox C:\file_path\KeyboardController.h 34
You should basically clean up your code by separating the logic between key events and player movement. So your update() method could look like this:
void update() override
{
updateKeyState();
updateMovement();
}
Since you want the player to move only vertically or horizontally (never diagonally), you have to store the key press order in a data structure that can be easily accessed. I think you could use a doubly-linked list:
std::list<SDL_Event> keyDownList;
and we should also store the last key pressed in order to restore the idle animation of the player:
SDL_Event lastDirection;
The updateKeyState() method should add or remove the key to/from the linked list. We should also check if the player wants to leave the game by pressing ESC:
void updateKeyState() {
if (Game::event.type == SDLK_ESCAPE) {
Game::isRunning = false;
} else if (Game::event.type == SDL_KEYDOWN) {
keyDownList.push_back(Game::event.key.keysym.sym);
} else if (Game::event.type == SDL_KEYUP) {
keyDownList.remove(Game::event.key.keysym.sym);
}
}
The updatePlayerMovement() method is where the magic happens. We should basically check which key was pressed first and update the player position accordingly. We also save the down key in the lastDirection field in order to use it when no key is pressed.
void updateMovement() {
// if any key is down
if (keyDownList.size() > 0) {
const SDL_Event downKey = keyDownList.front();
switch (downKey) {
case SDLK_w:
transform->velocity.y = -1;
transform->velocity.x = 0;
sprite->Play("BackWalk");
lastDirection = downKey;
break;
case SDLK_a:
transform->velocity.x = -1;
transform->velocity.y = 0;
sprite->Play("SideWalk");
sprite->spriteFlip = SDL_FLIP_HORIZONTAL;
lastDirection = downKey;
break;
case SDLK_s:
transform->velocity.y = 1;
transform->velocity.x = 0;
sprite->Play("FrontWalk");
lastDirection = downKey;
break;
case SDLK_d:
transform->velocity.x = 1;
transform->velocity.y = 0;
sprite->Play("SideWalk");
sprite->spriteFlip = SDL_FLIP_NONE;
lastDirection = downKey;
break;
}
} else {
// no key is down, handle idle situations
transform->velocity.x= 0;
transform->velocity.y = 0;
switch (lastDirection) {
case SDLK_w:
sprite->Play("BackIdle");
break;
case SDLK_a:
sprite->Play("SideIdle");
break;
case SDLK_s:
sprite->Play("FrontIdle");
break;
case SDLK_d:
sprite->Play("SideIdle");
break;
}
}
}
Note: I haven't tested this code because I don't have the code and structures from your game. So you may have to edit a piece here and there to make it work for you.

RTS style of drag and select system

I'm trying to make a click and drag selection system in c++ and SDL2 like the kind used in real time strategy games. You click with the mouse and drag it over what you want to select. How can I go about making it?
Edit: I know how to handle the mouse inputs. I have used a Rect structure to track the size of the selection zone. However while the zone draws correctly, the objects inside the zone don't react at all. However individually clicking on them works fine every time.
I guess my question is what is the best way to check for a group selection vs individual object selection?
You have to check what's inside the rect you are gonna drag (the test depends on the shapes that are included) and the drag those shapes aswell
I can describe how i have implemented this.
In the event handling routine, do something like the code below. I think the method names explain quite well what's happening and how i'm thinking (this is copied from my hobby-hack-RTS-engine which is based on SDL2):
case SDL_MOUSEBUTTONDOWN:
{
// Calculate index, x and y for the tile that was clicked in the map.
int iClick = m_Map.getTileIndex(event.button.x, event.button.y);
if(iClick >= 0)
{
int xClick = m_Map.getTileX(iClick);
int yClick = m_Map.getTileY(iClick);
if((int)event.button.button == 1)
{
// Unmark all MO..
for(std::list<MGMovingObject>::iterator it = m_MO.begin(); it != m_MO.end(); it++)
{
it->unMark();
}
activateFraming(event.button.x, event.button.y);
}
else
{
...
}
}
break;
}
case SDL_MOUSEBUTTONUP:
{
if((int)event.button.button == 1)
{
int endClickX = m_Map.getTileX(m_Map.getTileIndex(getFrameEndX(), getFrameEndY()));
int endClickY = m_Map.getTileY(m_Map.getTileIndex(getFrameEndX(), getFrameEndY()));
int startClickX = m_Map.getTileX(m_Map.getTileIndex(getFrameStartX(), getFrameStartY()));
int startClickY = m_Map.getTileY(m_Map.getTileIndex(getFrameStartX(), getFrameStartY()));
if(endClickX > 0 && endClickY > 0 && startClickX > 0 && startClickY > 0)
{
for(int x = std::min(startClickX, endClickX); x <= std::max(startClickX, endClickX); x++)
{
for(int y = std::min(startClickY, endClickY); y <= std::max(startClickY, endClickY); y++)
{
for(std::list<MGMovingObject>::iterator it = m_MO.begin(); it != m_MO.end(); it++)
{
if(it->getTileX() == x && it->getTileY() == y)
{
it->mark();
}
}
}
}
}
deactivateFraming();
}
else
{
...
}
break;
}
My selectable objects are stored as std::list<MGMovingObject> in m_MO.
The idea is that i save tile coordinates of the start of the selection frame and the end of the selection frame. I then iterate over the selectable objects and detect the ones inside the selection frame. I select these (mark()) and when i iterate over the objsects at a later stage, say during rendering, i can read out if they are selected (isMarked()).
If you want to steal code or ideas, here is the actual source file i copied it from: https://github.com/qmargyl/mgframework/blob/master/src/mgframework/mgframework.cpp

When deleting item in Combobox the secong one pop-up MFC

I have combo box and delete button. I want to make next combo box item pop-up when delete button pressed and when last item deleted clean combo box selected item.
I tried several methods with indexes but even one wont help me.
there is my code:
if(IDYES == MessageBox(L"Delete save?",L"Delete", MB_YESNO|MB_ICONQUESTION)){
CString pFileName = L"Save\\"+str+".dat";
CFile::Remove(pFileName);
CComboBox* pComboBox = (CComboBox*)GetDlgItem(IDC_COMBO_SAVE);
pComboBox->ResetContent();
}
How I can to make next combo box item pop-up when delete button pressed and when last item deleted clean combo box selected item?
I found a solution:
void CL2HamsterDlg::OnBnClickedButtonDelete(){
if(Validate()){
if(IDYES == MessageBox(L"Delete save?",L"Delete", MB_YESNO|MB_ICONQUESTION)){
CString pFileName = L"Save\\"+str+".dat";
CFile::Remove(pFileName);
CComboBox* pComboBox = (CComboBox*)GetDlgItem(IDC_COMBO_SAVE);
lookforfile();
int nIndex = pComboBox->GetCurSel();
if (nIndex == CB_ERR)
pComboBox->SetCurSel(0);
else{
pComboBox->SetEditSel(0, -1);
pComboBox->Clear();
}
}
LoadSave(false);
}else
AfxMessageBox(L"Please select or write correct name!");
}
the function look for file refreshes index
void CL2HamsterDlg::lookforfile()
{
Value.GetWindowText(str);
CComboBox* pComboBox = (CComboBox*)GetDlgItem(IDC_COMBO_SAVE);
pComboBox->ResetContent();
GetCurrentDirectory(MAX_PATH,curWorkingDir);
_tcscat_s(curWorkingDir, MAX_PATH, _T("\\Save\\*.dat"));
BOOL bWorking = finder.FindFile(curWorkingDir);
while (bWorking){
bWorking = finder.FindNextFile();
if (!finder.IsDots())
pComboBox->AddString(finder.GetFileTitle());
}
GetDlgItem(IDC_COMBO_SAVE)->SetWindowText(str);
}
so, in this case you do not need to use ResetContent(). Provided you already know the currently selected Item in the combobox (I think somewhere along the track you would have used the line int iSel = pComboBox->GetCurSel();) you could use this code IN PLACE OF YOUR pComboBox->ResetContent();:
pComboBox->DeleteString(iSel);
if(iSel < pComboBox->GetCount())
pComboBox->SetCurSel(iSel);
else if(iSel > 0)
pComboBox->SetCurSel(iSel-1);
However, I think this will not be necessary. I think the item will move by itself. So, forget about the code above, just use this:
pComboBox->DeleteString(pComboBox->GetCurSel())

MFC limit selected item in ClistCtrl

Hi
I use ClistCtrl that have 20 items and I want to limit selected item number.
for example only 10 item can be selected.
how i can do it?
thanks for your help herzl.
You would have to handle the LVN_ODSTATECHANGED notification message and count the number of selected item each time the LVIS_SELECTED state changes
Thanks
So I wrote this code. It should work. Just create an event handler for the list
void CDatenbankView::OnLvnItemchangedList1(NMHDR *pNMHDR, LRESULT *pResult)
{
int SelctedItems;
SelctedItems = 0;
int Index;
LPNMLISTVIEW pNMLV = reinterpret_cast<LPNMLISTVIEW>(pNMHDR);
for (Index = 0; Index < m_List.GetItemCount(); ) //Check every Item
{
if (m_List.GetItemState (Index, LVIS_SELECTED) == LVIS_SELECTED) //Checks if it is selected
{
if (SelctedItems > 10)
{
MessageBox (_T("Cant select more than 10 Items"));
for (Index = 0; Index < m_List.GetItemCount(); )
{
m_List.SetItemState (Index, ~LVIS_SELECTED, LVIS_SELECTED);
Index++;
}
break;
}
else
{
SelctedItems++;
}
}
Index++;
}
*pResult = 0;
}
m_List is my control variable for the CListCtrl
There is no built-in functionality for such a feature. You'd have to write your our code for that. Maybe you can find another way to do it, like having a source list and a "selection list". You copy/move items from the first to the second, but you do not allow the users to put more than 10 items into the destination list.