What is the right way to setup a global hot key in MFC app? - visual-c++

I know how to use the accelerator table to setup hot keys within my app. But how do I get a hot key at global level?
I.e. My app is running, but minimised. Yet I still want it to detect and process a hot key.

If you need a system-wide hotkey then you should use RegisterHotKey, passing in the window handle whose window procedure is responsible for handling the event. At the application level this is commonly the CFrameWnd/CFrameWndEx-derived window implementation.
Once the hotkey is registered, the receiving window can observe the event in its custom CWnd::OnHotKey override. Make sure to add the ON_WM_HOTKEY() message handler to the message map of the receiving window implementation.
For a standard, default-generated SDI/MDI application you'd need to apply the following changes.
MainFrm.h:
class CMainFrame : public CFrameWnd
{
// ...
private:
// Hot key handler routine
afx_msg void OnHotKey(UINT nHotKeyId, UINT nKey1, UINT nKey2);
};
MainFrm.cpp:
BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd)
// ...
// Make sure that WM_HOTKEY messages are routed to this window
ON_WM_HOTKEY()
END_MESSAGE_MAP()
// Hot key ID; can be any value in the range 0x0000 through 0xBFFF
// Allows the application to identify this hot key in case there is more than one
constexpr int MyHotKeyId = 42;
int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct) {
// ...
// Register a global system-wide hot key (Ctrl+Shift+Alt+I)
if (!::RegisterHotKey(this->m_hWnd, MyHotKeyId,
MOD_ALT | MOD_SHIFT | MOD_CONTROL | MOD_NOREPEAT,
'I')) {
auto const ec = ::GetLastError();
auto const err_msg = std::format(L"RegisterHotKey failed (error: {})\n",
ec);
::OutputDebugStringW(err_msg.c_str());
// Probably a good idea to handle failure more gracefully than this:
return -1;
}
return 0;
}
void CMainFrame::OnHotKey(UINT nHotKeyId, UINT nKey1, UINT nKey2) {
// Handle hot key; nHotKeyId is MyHotKeyId in this case
}
Note that receiving a WM_HOTKEY message grants the receiving thread foreground activation permission. If you want your application to come to the foreground upon receiving a registered hot key, you can use the following OnHotKey implementation:
void CMainFrame::OnHotKey(UINT nHotKeyId, UINT nKey1, UINT nKey2) {
if (this->IsIconic()) {
this->ShowWindow(SW_RESTORE);
}
this->SetForegroundWindow();
}
No flashing the taskbar button, everything just works as intended.

Related

Make Autohotkey ignore any special characters in string

We're using a parser program (which I have no access to) that parses a bunch of computer generated mails but needs some help to decide on what it has to do in particular. Because of that, an employee kann use the subject line for additional commands. Since there are more than 500 mails per day that we feed to the program and the commands do all look similar to that: Ba,Vi;#TD*; x0003, it's impossible to write them manually. So I wrote a small C# script that creates an Autohotkey script which does 90% of the work. In theory. It works but only as long as I don't use any special characters, like , : & % etc.
I tried:
clipboard := Ba{,}Vi{;}{#}TD{*}{;} x0003
clipboard := "Ba,Vi;#TD*; x0003"
clipboard := Ba',Vi';'#TD'*'; x0003
clipboard := {raw} Ba,Vi;#TD*; x0003
(plus some others that I probably forgot here)
Here's the entire AHK script with annotations. You start it while having an email selected in Outlook:
;Win+z -> start script
#z::
;Currently only one iteration
loop,1
{
;CTRL+F to forward selected mail,
;which then automatically selects the "To:" line
Send, {CTRLDOWN}f{CTRUP}
Sleep, 500
Send, someemail#adress
Sleep, 500
;Initialize GUI
Gui, +AlwaysOnTop
Gui, Font, S5, Verdana, bold
Gui, Add, Text,, SCANNING-BOOSTER:
Gui, Color, F4A460
;Example for the C# generated buttons below (they all do the same thing):
;Clicking the Button "Google" will run the following script
;Start:
;clipboard := www.Google.com
;return
;This is the part where AHK fails because instead
;of www.Google.com I have codes like "Ba,Vi;#TD*; x0003" which crash AHK
Gui,add,Button,gLabel,Google
Gui,add,Button, ......
Gui,add,Button, ......
Gui,add,Button, ......
Gui,add,Button, ......
Gui,add,Button, ......
..... (around 60 more auto-generated buttons)
Gui,show
Return
Label:
;run the script that has the same name as the Button
;in this case it would be Google.ahk
Run, % A_GuiControl ".ahk"
GuiClose:
Gui, Hide
Sleep, 1000
;after the user has pressed a button and the according code
;has been copied to the clipboard, the GUI closes, the
;mail window becomes active again and we can continue to paste
;the code into the subject line
;Go to subject line
Send, {ALTDOWN}r{ALTUP}
Sleep, 500
;CTRL+a
Send, {CTRLDOWN}a{CTRUP}
Sleep, 500
;Write text from your clipboard to the subject line
Send, %clipboard%
Sleep, 500
return
}
Apparently it's currently not possible to copy a (more or less) random string to your clipboard in Autohotkey and then paste it somewhere else without receiving so many errors that it's worth pursuing it. I was left with no choice other than programming everything myself in C#. Here's how I did it:
First, start a new Console Application and then change it to a Windows Application How to change a console application to a windows form application?
This is the easiest way to make a program completely invisible to the user.
Next, we need a key listener. Here's the one I used: https://blogs.msdn.microsoft.com/toub/2006/05/03/low-level-keyboard-hook-in-c/
I would advise you to put it in a new class. This code needs to be slightly altered anyway because it prints every key press to the console. We don't want that, we want to call a method from it. Change HookCallback to:
private static IntPtr HookCallback(int nCode, IntPtr wParam, IntPtr lParam)
{
if (nCode >= 0 && wParam == (IntPtr)WM_KEYDOWN)
{
int vkCode = Marshal.ReadInt32(lParam);
if (KeyPressed == vkCode)
DoWork();
}
return CallNextHookEx(_HookID, nCode, wParam, lParam);
}
For the code above to work, we need to add a new delegate and a new int variable to our class:
private static int KeyPressed;
public delegate void SomethingToDo();
private static SomethingToDo DoWork;
SetHook also needs some minor alterations:
private static IntPtr SetHook(KeyboardProc _proc, int KeyCode, SomethingToDo GiveMeWork)
{
DoWork = GiveMeWork;
KeyPressed = KeyCode;
...(leave the rest as it is)...
}
The program is now completely invisible and can react to a key press. Let's do the opposite, simulating keys!
Ctrl key kept down after simulating a ctrl key down event and ctrl key up event
This is a lot simpler. I made three methods, PressKey, ReleaseKey and TapKey. Keep in mind that ALT and F10 are special system keys which might not work.
[DllImport("user32.dll", SetLastError = true)]
static extern void keybd_event(byte bVk, byte bScan, int dwFlags, int dwExtraInfo);
private const int KeyPressCode = 0x0001;
private const int KeyReleaseCode = 0x0002;
public static void PressKey(System.Windows.Forms.Keys Key)
{
keybd_event((byte)Key, 0, KeyPressCode | 0, 0);
Thread.Sleep(10);
}
public static void ReleaseKey(System.Windows.Forms.Keys Key)
{
keybd_event((byte)Key, 0, KeyPressCode | KeyReleaseCode, 0);
Thread.Sleep(10);
}
public static void TapKey(System.Windows.Forms.Keys Key)
{
PressKey(Key);
ReleaseKey(Key);
}
That's it, a basic Autohokey-Clone that can cope with strings. If you want to go even further and make a try icon for it:
https://translate.google.de/translate?sl=de&tl=en&js=y&prev=_t&hl=de&ie=UTF-8&u=https%3A%2F%2Fdotnet-snippets.de%2Fsnippet%2Fvorlage-fuer-tray-notifyicon-anwendung%2F541&edit-text=&act=url
Showing a GUI with buttons is also surprisingly simple:
using (Form _form = new Form())
{
_form.size = ...
//....
Button MyButton = new Button();
//....
//closing is also pretty simple, just like your everyday Windows Forms Application:
MyButton.Click += new EventHandler((sender, e) => { Application.Exit(); });
_form.ShowDialog();
}
Putting everything together in the main method:
private static NotifyIcon TrayIcon = new NotifyIcon();
[STAThread]
static void Main(string[] args)
{
bool WindowOpen = true;
try
{
//Make a new method above or below Main() and replace DoSomething with it.
//It will be executed everytime the F2 key is pressed.
KeyListener._HookID = KeyListener.SetHook(proc, System.Windows.Forms.Keys.F2, DoSomething);
System.Windows.Forms.ContextMenu SmallMenu = new System.Windows.Forms.ContextMenu();
System.Windows.Forms.MenuItem MenuElement;
int MenuIndex = 0;
MenuElement = new System.Windows.Forms.MenuItem();
MenuElement.Index = ++MenuIndex;
MenuElement.Text = "Close";
MenuElement.Click += new EventHandler((sender, e) => { WindowOpen = false; System.Windows.Forms.Application.Exit(); });
SmallMenu.MenuItems.Add(MenuElement);
TrayIcon.Icon = new System.Drawing.Icon("Ghost.ico");
TrayIcon.Text = "String Compatible AHK";
TrayIcon.Visible = true;
TrayIcon.ContextMenu = SmallMenu;
while (WindowOpen)
System.Windows.Forms.Application.Run();
}
finally
{
TrayIcon.Dispose();
KeyListener.UnhookWindowsHookEx(KeyListener._HookID);
}
}

Is there a way to override the handler called when a user clicks a checkbox in a CListCtrl? (MFC)

I am trying to disable the user's ability to alter the state of a checkbox in a List Control. I am currently changing the state pragmatically. I already handle the LVN_ITEMCHANGED message, and trying to alter the state there isn't an option due to the layout of the rest of the program. I have also tried doing a HitTest when the user clicks in the List Control and simply resetting the checkbox there but that isn't giving me the exact results I am looking for.
Is there a specific message sent or a function I can override when the user clicks the checkbox itself? I would just like to override the handler or catch the message so that it doesn't go anywhere.
Solution:
I ended up removing the LVS_EX_CHECKBOXES flag and created my own implementation. That way the there is only one way to change the icons. Reading the link from the previous question gave me an idea to set a "busy" flag, otherwise I would get stack overflow errors.
// In my dialog class
m_CListCtrl.SetImageList(&m_ImgList, LVSIL_SMALL); // Custom checkboxes (only two images)
// ...
void CMyDialog::OnLvnItemchangedList(NMHDR *pNMHDR, LRESULT *pResult)
{
if(busy) { return; }
// ....
}
// When calling the SetCheck function:
busy = TRUE; // Avoid stack overflow errors
m_CListCtrl.SetCheck(index, fCheck);
busy = FALSE;
// I derived a class from CListCtrl and did an override on the get/set check:
class CCustomListCtrl : public CListCtrl
{
BOOL CCustomListCtrl::SetCheck(int nItem, BOOL fCheck)
{
TCHAR szBuf[1024];
DWORD ccBuf(1024);
LVITEM lvi;
lvi.iItem = nItem;
lvi.iSubItem = 0;
lvi.mask = LVIF_TEXT | LVIF_IMAGE;
lvi.pszText = szBuf;
lvi.cchTextMax = ccBuf;
GetItem(&lvi);
lvi.iImage = (int)fCheck;
SetItem(&lvi);
return TRUE;
}
// Just need to determine which image is set in the list control for the item
BOOL CCustomListCtrl::GetCheck(int nItem)
{
LVITEM lvi;
lvi.iItem = nItem;
lvi.iSubItem = 0;
lvi.mask = LVIF_IMAGE;
GetItem(&lvi);
return (BOOL)(lvi.iImage);
}
}
This is not as elegant as I had hoped, but it works flawlessly.

Starting Doc/View application hidden

Using Visual studio 2010 and MFC Doc/View Applications I want my SDI application to start up completely hidden, and after sometime or with receiving some message from tray icon it shows the mainframe, view and so on. I change the line m_pMainWnd->ShowWindow(SW_NORMAL); to m_pMainWnd->ShowWindow(SW_HIDE); in BOOL CMyApp::InitInstance() but the main frame just flickers after executing the application and then goes hiiden what should I do inorder to avoid this problem and keep the showing capability of main frame when ever I want.
Here is the solution for SDI/MDI app: The new MFC (with VC2010) overrides the m_nCmdShow value with a setting stored in the system registry. To change this behaviour, simply override the LoadWindowPlacement virtual function in the application class.
BOOL CAdVisuoApp::LoadWindowPlacement(CRect& rectNormalPosition, int& nFflags, int& nShowCmd)
{
BOOL b = CWinAppEx::LoadWindowPlacement(rectNormalPosition, nFflags, nShowCmd);
nShowCmd = SW_HIDE;
return b;
}
Normally if you have VC2005 or earlier the following will do:
// Parse command line for standard shell commands, DDE, file open
CCommandLineInfo cmdInfo;
ParseCommandLine(cmdInfo);
m_nCmdShow = SW_HIDE;
// Dispatch commands specified on the command line. Will return FALSE if
// app was launched with /RegServer, /Register, /Unregserver or /Unregister.
if (!ProcessShellCommand(cmdInfo))
return FALSE;
// The one and only window has been initialized, so show and update it
m_pMainWnd->ShowWindow( m_nCmdShow);
m_pMainWnd->UpdateWindow();
Note that m_nCmdShow should be set to SW_HIDE before ProcessShallCommand for the flicker not to occur.
It looks like there might be a bug in VC2010 though. Since I have done this before it intrigued me and tried a fresh VC2010 project but it was not working. I noticed the problem was deep in the following MFC function.
BOOL CFrameWnd::LoadFrame(UINT nIDResource, DWORD dwDefaultStyle,
CWnd* pParentWnd, CCreateContext* pContext)
{
// only do this once
ASSERT_VALID_IDR(nIDResource);
ASSERT(m_nIDHelp == 0 || m_nIDHelp == nIDResource);
m_nIDHelp = nIDResource; // ID for help context (+HID_BASE_RESOURCE)
CString strFullString;
if (strFullString.LoadString(nIDResource))
AfxExtractSubString(m_strTitle, strFullString, 0); // first sub-string
VERIFY(AfxDeferRegisterClass(AFX_WNDFRAMEORVIEW_REG));
// attempt to create the window
LPCTSTR lpszClass = GetIconWndClass(dwDefaultStyle, nIDResource);
CString strTitle = m_strTitle;
if (!Create(lpszClass, strTitle, dwDefaultStyle, rectDefault,
pParentWnd, ATL_MAKEINTRESOURCE(nIDResource), 0L, pContext))
{
return FALSE; // will self destruct on failure normally
}
// save the default menu handle
ASSERT(m_hWnd != NULL);
m_hMenuDefault = m_dwMenuBarState == AFX_MBS_VISIBLE ? ::GetMenu(m_hWnd) : m_hMenu;
// load accelerator resource
LoadAccelTable(ATL_MAKEINTRESOURCE(nIDResource));
if (pContext == NULL) // send initial update
SendMessageToDescendants(WM_INITIALUPDATE, 0, 0, TRUE, TRUE);
return TRUE;
}
m_nCmdShow is still SW_HIDE when this function executes but it changes to SW_SHOWNORMAL when if (!Create(lpszClass... line executes. I don't know why this happens in VC2010 project only, sounds like a bug to me.
My sample project was SDI.
This comes from a dialog based application but you should be able to convert it to a Doc/View app as well. You need to handle the OnWindowPosChanging event. The key line is the the one inside the if statement. This allows my application to start completely hidden from view.
void CIPViewerDlg::OnWindowPosChanging( WINDOWPOS FAR* lpWindowPosition )
{
if( !m_bVisible )
{
lpWindowPosition->flags &= ~SWP_SHOWWINDOW;
}
CDialog::OnWindowPosChanging( lpWindowPosition );
}
Make sure that you are correctly turning off the WS_VISIBLE bit in CMainFrame::PreCreateWindow(CREATESTRUCT& cs). Something like this should worK:
cs.style &= ~WS_VISIBLE;
We had simply been negating the bit instead of turning it off, and we got away with it in VS 6.0 because this function was called only once. It is called twice in newer versions of Visual Studio, so in the second call we were flipping it right back on again. :-O
I tried all for Visual Studio 2010 and finished up with:
class CMainFrame : public CFrameWndEx
{
// ...
// Attributes
public:
BOOL m_bForceHidden;
// ...
// Overrides
public:
virtual void ActivateFrame(int nCmdShow = -1);
//...
};
CMainFrame::CMainFrame() : m_bForceHidden(TRUE)
{
// ...
}
void CMainFrame::ActivateFrame(int nCmdShow)
{
if(m_bForceHidden)
{
nCmdShow = SW_HIDE;
m_bForceHidden = FALSE;
}
CFrameWndEx::ActivateFrame(nCmdShow);
}
Other tricks did not work for me.
Found solution at:
http://forums.codeguru.com/showthread.php?478882-RESOLVED-Can-a-Doc-view-be-hidden-at-startup
I found in VS2017 (using BCGControlBar Pro which is what MFC Feature Pack was based on) that you have to handle things in two places:
BOOL CMainFrame::LoadFrame(UINT nIDResource, DWORD dwDefaultStyle, CWnd* pParentWnd, CCreateContext* pContext)
{
if (!__super::LoadFrame(nIDResource, dwDefaultStyle, pParentWnd, pContext))
{
return FALSE;
}
// undo what __super::LoadFrame() does where it will set it to SW_NORMAL if not SW_MAXIMIZED
AfxGetApp()->m_nCmdShow = SW_HIDE;
}
BOOL CTheApp::LoadWindowPlacement(CRect& rectNormalPosition, int& nFflags, int& nShowCmd)
{
BOOL b = __super::LoadWindowPlacement(rectNormalPosition, nFflags, nShowCmd);
nShowCmd = SW_HIDE;
return b;
}

Best way to display progress of background thread in ATL?

When using ATL, what is the best way to display the progress of a background thread (e.g. when it's searching for a file) without blocking the UI?
I still want to be able to process messages, to allow for a Cancel button and to possibly allow the user to continue working with the program while the search happens.
There is no ATL specific here. One of the ways to do is to update progress details into member variable and post a message to GUI window, then handle the message by pulling the data from member variable and updating GUI, such as updating static and/or progress bar.
Worker thread pseudo-code:
m_DataCriticalSection.Lock();
m_nProgress = (INT) (nCurrent * 100 / nTotal);
m_DataCriticalSection.Unlock();
PostMessage(WM_MYUPDATEPROGRESS);
Window:
OnMyUpdateProgress()
{
m_DataCriticalSection.Lock();
INT nProgress = m_nProgress;
m_DataCriticalSection.Unlock();
m_ProgressBar.SetPos(nProgress);
}
UPD. A real code snippet, AddText is called on background thread, :
VOID AddText(const CString& sText)
{
_A(sText.Find(_T('\n')) < 0);
BOOL bIsTextEmpty;
{
CRoCriticalSectionLock TextLock(m_TextCriticalSection);
bIsTextEmpty = m_sText.IsEmpty();
m_sText.Append(sText);
m_sText.Append(_T("\r\n"));
}
if(bIsTextEmpty)
PostPrivateMessage(WM_UPDATETEXT);
}
And the code handler:
BEGIN_MSG_MAP_EX(CMainDialog)
// ...
MESSAGE_HANDLER_EX(WM_UPDATETEXT, OnUpdateText)
LRESULT OnUpdateText(UINT, WPARAM, LPARAM)
{
CString sText;
{
CRoCriticalSectionLock TextLock(m_TextCriticalSection);
sText = m_sText;
m_sText.Empty();
}
if(!sText.IsEmpty())
{
m_TextEdit.SetValue(m_TextEdit.GetValue() + sText);
const INT nTextLength = m_TextEdit.GetWindowTextLength();
m_TextEdit.SetSel(nTextLength, nTextLength);
}
return 0;
}
This uses custom classes (not 'pure' ATL), but I hope you get the idea.

Event when a particular Slider is changed

I am writing a C++ MFC Dialog based Application and my program has lots of sliders. I want the program to call a function depending on which Slider is being changed by the user. I tried using GetPos() but not much success so far. Any easier way of doing this?
Message Map:
BEGIN_MESSAGE_MAP(CSerialPortDlg, CDialog)
ON_WM_SYSCOMMAND()
ON_WM_PAINT()
ON_WM_QUERYDRAGICON()
//}}AFX_MSG_MAP
//ON_BN_CLICKED(IDC_BUTTON1, OnBnClickedButton1)
ON_BN_CLICKED(IDC_READ_COMM, OnBnClickedReadComm)
ON_WM_CLOSE()
ON_BN_CLICKED(IDC_WRITE, OnBnClickedWrite)
//ON_CBN_SELCHANGE(IDC_SENSORS, OnCbnSelchangeSensors)
//ON_CBN_SELCHANGE(IDC_SENSOR_LIST, OnCbnSelchangeSensorList)
ON_BN_CLICKED(IDC_GO, OnGo)
ON_WM_TIMER()
ON_BN_CLICKED(IDC_KILL_TIMER, OnBnClickedKillTimer)
ON_BN_CLICKED(IDC_READ_TIMER, OnBnClickedReadTimer)
ON_BN_CLICKED(IDC_WRITE_COMM, OnBnClickedWriteComm)
ON_BN_CLICKED(IDC_TERMINATE, OnBnClickedTerminate)
ON_BN_CLICKED(IDC_RUN, OnBnClickedRun)
ON_CONTROL(NM_CLICK,IDC_BOOM_SLIDER, Write_Boom)
ON_CONTROL(NM_CLICK,IDC_PITCH_SLIDER, Write_Pitch)
END_MESSAGE_MAP()
...
Slider controls send WM_HSCROLL or WM_VSCROLL notifications when they are scrolled, horizontally or vertically. Catch them in your dialog and there you can call your desired function, depending on who sent the notification.
BEGIN_MESSAGE_MAP(CMyDlg, CDialog)
//...
ON_WM_HSCROLL()
//...
END_MESSAGE_MAP()
//////////////////////////
// nSBCode: The operation performed on the slider
// nPos: New position of the slider
// pScrollBar: The scrollbar (slider ctrl in this case) that sent the notification
void CMyDlg::OnHScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)
{
CSliderCtrl* pSlider = reinterpret_cast<CSliderCtrl*>(pScrollBar);
// Check which slider sent the notification
if (pSlider == &c_Slider1)
{
}
else if (pSlider == &c_Slider2)
{
}
// Check what happened
switch(nSBCode)
{
case TB_LINEUP:
case TB_LINEDOWN:
case TB_PAGEUP:
case TB_PAGEDOWN:
case TB_THUMBPOSITION:
case TB_TOP:
case TB_BOTTOM:
case TB_THUMBTRACK:
case TB_ENDTRACK:
default:
break;
}
//...
}
`
BEGIN_MESSAGE_MAP(CMyDlg, CDialog)
//...
ON_WM_HSCROLL()
//...
END_MESSAGE_MAP()
void CMyDlg::OnHScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)
{
CSliderCtrl *ACSliderCtrl = (CSliderCtrl *)pScrollBar;
int nID = ACSliderCtrl->GetDlgCtrlID();
int NewPos = ((CSliderCtrl *)pScrollBar)->GetPos();
CWnd *ACWnd = GetDlgItem(nID);
switch (nID)
{
default:
break;
case IDC_SLIDER1:
m_edit1.Format( "%d", NewPos );
UpdateData(FALSE);
break;
}
CDialog::OnHScroll(nSBCode, nPos, pScrollBar);
}
I figured it out, I think. What you call a slider is commonly called a "Scrollbar". You're probably looking for the WM_VSCROLL message. As noted there, "lParam: If the message is sent by a scroll bar, this parameter is the handle to the scroll bar control."
See also CWnd::OnVScroll
You do have different ON_CONTROL macro's for the different controls? Because it's then just a matter of specifying different methods as the third argument to ON_CONTROL

Resources