Перейти к содержанию

Custom Keyboard Accelerators


Antonshka

Рекомендуемые сообщения

Привет, с наступившим Новый Годом🌲! Хорошо его провести.

Кто-нибудь игрался с самодельной таблицей акселераторов?

Как известно, стандартная WinAPI таблица акселераторов поддерживает только одну нажатую клавишу, совместно с SHIFT, CONTROL, или ALT. Например, позволено делать только так, - "SHIFT + ALT + M", или "ALT + G".

Но что если нужно так, "SHIFT + ALT + M + A + T"?

 

У меня дело дошло до горячих клавиш для пунктов меню. Текст то выводится, а про саму реализацию клавиш я и забыл. Сейчас думаю как реализовать самодельную таблицу.

В основном цикле сообщений будет своя функция translateAccelerator

Спойлер
while ((resultOfGetMessage = GetMessageW(&msg, NULL, 0, 0)) != 0)	{
  if (resultOfGetMessage == -1) {
    break;
  }
  else {
    translateAccelerator(&msg);
    TranslateMessage(&msg);
    DispatchMessageW(&msg);
  }
}

 

Далее, в этой функции (translateAccelerator), нужно сделать обход по всем виджетам. Для тех виджетов, чей список горячих клавиш непустой, сделать проверку, на совпадение.

Список горячих клавиш думаю будет таким -

Спойлер
struct Keystroke {
  std::array<BYTE> hotkeys;
  CustomAcceleratorsIDs id{ 0 }
};
    
std::vector<Keystroke*> Keystrokes; //Это будет полем класса, у каждого виждета свое.

 

 

Если проверка на совпадение покажет, что такие клавиши для этого виджета установленны, то будет послано сообщение, этому виджету -

Спойлер
constexpr DWORD WM_USERAcceleratorMessage = WM_APP + 1; //Допустим это будет тип сообщения
//a это, существующие различные ID
enum class CustomAcceleratorsIDs : INT { CreateFile, OpenFile };


BOOL isSequenceFound{ FALSE };

//Допустим проверка на совпадение показала TRUE
isSequenceFound = TRUE;
Keystroke* foundedKeytroke = Keystrokes.at(индекс, при котором найдено совпадение клавиш)
  
  
if (isSequenceFound){
  PostMessageW(hWnd, WM_USERAcceleratorMessage, (WPARAM)foundedKeytroke, 0); //hWnd - это хендл окна у которого нашлось совпадение клавиш
}

//Далее, в своей оконой процедуре, это окно проверит сообщение
switch (msg){
  case WM_USERAcceleratorMessage: return wmAcceleratorMessage(wParam);
}

LRESULT wmAcceleratorMessage(WPARAM wParam){
  Keystroke* foundedKeytroke = reinterpret_cast<Keystroke*>(wParam)
  switch (foundedKeytroke.id){
    case CustomAcceleratorsIDs::CreateFile: return createFile();
    case CustomAcceleratorsIDs::OpenFile: return openFile(); 
  }
}

 

 

Осталось придумать как эффективнее проверять клавиши. Как вообще отслеживать последовательность нажатых клавиш.

Изменено пользователем Antonshka
Ссылка на комментарий
Поделиться на другие сайты

Не знаю, хорошо ли это, писать сейчас на форуме. Ведь форум сейчас на компьютере у @Xipho, плюс никто не пишет ничего чего-то, плюс форум как я понял будет скоро закрыт.

Однако, напишу все же, раз тему завел. Да простят меня все.

 

Я лишь хотел сказать, что я закончил с этой таблицей акселератора. И для тех кому интересно, реализовал я ее так.

Это пример того как создается главное окно, создается меню, назначаются горячие клавиши.

Спойлер
constexpr UINT ACCELERATOR_OpenProject = 0;
constexpr UINT ACCELERATOR_OpenFolder = 1;
constexpr UINT ACCELERATOR_Delete = 2;

int APIENTRY wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int nCmdShow) {
	WGW::GUI::initializeWGWLibrary(hInstance);
  
	WGW::Window* mainWindow = new WGW::Window(TRUE, 0, 0, L"MainWindowName", 0, 0, 900, 900);
	mainWindow->menu.enabled = TRUE;
	WGW::PopupMenu* popupMenuFile = new WGW::PopupMenu();
	WGW::ItemOfMainMenu* fileItem = mainWindow->menu.addItem(WGW::EnabledState::Enabled, L"File", nullptr, popupMenuFile);

	WGW::ItemOfMainMenu* editItem = mainWindow->menu.addItem(WGW::EnabledState::Enabled, L"Edit", nullptr, nullptr);
	WGW::PopupMenu* popupMenuEdit = new WGW::PopupMenu();
	editItem->setPopupMenu(popupMenuEdit);

	WGW::ItemOfPopupMenu* openProjectItem = popupMenuFile->addItem(WGW::EnabledState::Enabled,
		WGW::TypeOfItemOfMenu::Default, WGW::CheckableState::Uncheckable, WGW::CheckedState::Unchecked,
		WGW::CheckMarkType::Flag, L"OpenProject", L"Ctrl + G", nullptr, nullptr, WGW::SettingsButtonState::Disabled, nullptr);
	
	WGW::ItemOfPopupMenu* openFolderItem = popupMenuFile->addItem(WGW::EnabledState::Enabled,
		WGW::TypeOfItemOfMenu::Default, WGW::CheckableState::Uncheckable, WGW::CheckedState::Unchecked,
		WGW::CheckMarkType::Flag, L"OpenFolder", L"Ctrl + Shift + K", nullptr, nullptr, WGW::SettingsButtonState::Disabled, nullptr);

	WGW::ItemOfPopupMenu* deleteItem = popupMenuEdit->addItem(WGW::EnabledState::Enabled,
		WGW::TypeOfItemOfMenu::Default, WGW::CheckableState::Uncheckable, WGW::CheckedState::Unchecked,
		WGW::CheckMarkType::Flag, L"Delete", L"Ctrl + D", nullptr, nullptr, WGW::SettingsButtonState::Disabled, nullptr);


	mainWindow->keystroke->addKeystrokeEntry(ACCELERATOR_OpenProject, FALSE, VK_CONTROL, 0x47);
	mainWindow->keystroke->addKeystrokeEntry(ACCELERATOR_OpenFolder, FALSE, VK_CONTROL, VK_SHIFT, 0x4B);
	mainWindow->keystroke->addKeystrokeEntry(ACCELERATOR_Delete, FALSE, VK_CONTROL, 0x44); //FALSE значит NoRepeat
  
  	mainWindow->runMessageCycle();
 	return 0;
}

 

 

Это главный messageLoop

Спойлер
while ((resultOfGetMessage = GetMessageW(&msg, NULL, 0, 0)) != 0)	{
  if (resultOfGetMessage == -1) {
    break;
  }
  else {
    if (!translateAccelerator(&msg)) {
      TranslateMessage(&msg);
      DispatchMessageW(&msg);
    }
  }
}

 

 

Это translateAccelerator

Спойлер
BOOL Window::translateAccelerator(MSG* msg) {
  BOOL isProcessed{ FALSE };
  if (msg->message == WM_KEYDOWN) {
    Keystroke::determinePressedKeys();
    for (auto& window : allCreatedLibWindowObjects) { //allCreatedLibWindowObjects = inline static std::unordered_map<size_t, Window*> allCreatedLibWindowObjects;
      for (auto& entry : window.second->keystroke->entries) {
        if (entry->isRepeated || !entry->isNeedReset) {
          if (entry->isKeysMatches(Keystroke::pressedKeys)) {
            entry->isNeedReset = TRUE;
            PostMessageW(window.second->hWnd, WM_USERAcceleratorEvent, entry->commandID, 0);
            isProcessed = TRUE;
          }
        }
      }
    }
  }
  else if (msg->message == WM_KEYUP) {
    Keystroke::determinePressedKeys();
    if (!Keystroke::pressedKeys.size()) {
      for (auto& window : allCreatedLibWindowObjects) {
        for (auto& entry : window.second->keystroke->entries) {
          entry->isNeedReset = FALSE;
        }
      }
    }
    else {
      for (auto& window : allCreatedLibWindowObjects) {
        for (auto& entry : window.second->keystroke->entries) {
          if (entry->isNeedReset) {
            if (!entry->isKeysMatches(Keystroke::pressedKeys)) {
              entry->isNeedReset = FALSE;
            }
          }
        }
      }
    }
  }
  return isProcessed;
}

 

 

А это классы для работы самодельного аккселератора. Кое-какой функционал будет еще добавлен для них. Keystroke.h

Спойлер
namespace WGW
{
	class Keystroke;

	class KeystrokeEntry {
		friend class Keystroke;
		friend class Window;
	protected:
		KeystrokeEntry() = default;
		KeystrokeEntry(CONST KeystrokeEntry&) = delete;
		KeystrokeEntry& operator=(CONST KeystrokeEntry&) = delete;
		~KeystrokeEntry() = default;
	protected:
		BOOL isKeysMatches(std::vector<UINT>& pressedKeys);
	protected:
		UINT commandID{ 0 };
		BOOL isNeedReset{ FALSE };
		BOOL isRepeated{ FALSE };
		std::vector<UINT> keys;
	};

	//-------------------------------------------------------------------------------------------

	class Keystroke {
		friend class Window;
	protected:
		Keystroke(Window* windowOwner);
		Keystroke(CONST Keystroke&) = delete;
		Keystroke& operator=(CONST Keystroke&) = delete;
		~Keystroke();
	public:
		BOOL addKeystrokeEntry(UINT commandID, BOOL isRepeated, UINT key1, UINT key2 = 0, UINT key3 = 0, UINT key4 = 0, UINT key5 = 0);
		BOOL deleteKeystrokeEntry(UINT commandID);
	protected:
		static VOID determinePressedKeys();
	protected:
		inline static std::vector<Keystroke*> allCreatedLibKeystrokeObjects;
	protected:
		std::vector<KeystrokeEntry*> entries;
		inline static BYTE keyboardState[256];
		inline static std::vector<UINT> pressedKeys;
		Window* windowOwner{ nullptr };
	};
}

 

 

И Keystroke.cpp

Спойлер
BOOL KeystrokeEntry::isKeysMatches(std::vector<UINT>& pressedKeys) {
		BOOL isKeysMathes{ FALSE };
		for (auto& key : keys) {
			isKeysMathes = FALSE;
			for (auto& pressedKey : pressedKeys) {
				if (key == pressedKey) {
					isKeysMathes = TRUE;
					break;
				}
			}
			if (!isKeysMathes) {
				break;
			}
		}
		return isKeysMathes;
	} 
  
  Keystroke::Keystroke(Window* windowOwner) : windowOwner(windowOwner) {
		allCreatedLibKeystrokeObjects.push_back(this);
	}

	Keystroke::~Keystroke() {
		if (!GUI::isMessageCycleTerminated) {
			std::erase(allCreatedLibKeystrokeObjects, this);
		}
		for (auto& entry : entries) {
			delete entry;
		}
	}

	BOOL Keystroke::addKeystrokeEntry(UINT commandID, BOOL isRepeated, UINT key1, UINT key2, UINT key3, UINT key4, UINT key5) {
		BOOL isCommandIDExist{ FALSE };
		BOOL isKeystrokeEntryAdded{ FALSE };
		for (auto& entry : entries) {
			if (entry->commandID == commandID) {
				isCommandIDExist = TRUE;
				break;
			}
		}
		if (!isCommandIDExist) {
			KeystrokeEntry* entry = new KeystrokeEntry();
			entry->commandID = commandID; 
			entry->isRepeated = isRepeated;
			entry->keys.push_back(key1);
			if (key2) { entry->keys.push_back(key2); }
			if (key3) { entry->keys.push_back(key3); }
			if (key4) { entry->keys.push_back(key4); }
			if (key5) { entry->keys.push_back(key5); }
			entries.push_back(entry);
			isKeystrokeEntryAdded = TRUE;
		}
		return isKeystrokeEntryAdded;
	}
  
  BOOL Keystroke::deleteKeystrokeEntry(UINT commandID) {
		BOOL isKeystrokeEntryDeleted{ FALSE };
		KeystrokeEntry* entryToDelete{ nullptr };
		for (auto& entry : entries) {
			if (entry->commandID == commandID) {
				entryToDelete = entry;
				break;
			}
		}
		if (entryToDelete) {
			std::erase(entries, entryToDelete);
			delete entryToDelete;
			isKeystrokeEntryDeleted = TRUE;
		}
		return isKeystrokeEntryDeleted;
	}

	VOID Keystroke::determinePressedKeys() {
		pressedKeys.clear();
		BOOL result = GetKeyboardState(keyboardState);
		if (result) {
			for (UINT i = 0; i < 256; i++) {
				if (i != VK_LSHIFT && i != VK_RSHIFT &&
					i != VK_LCONTROL && i != VK_RCONTROL &&
					i != VK_LMENU && i != VK_RMENU)
				{
					if (keyboardState[i] & 0x80) {
						pressedKeys.push_back(i);
					}
				}
			}
		}
	}
}

 

 

  • Плюс 2
Ссылка на комментарий
Поделиться на другие сайты

×
×
  • Создать...

Важная информация

Находясь на нашем сайте, Вы автоматически соглашаетесь соблюдать наши Условия использования.