I have a text box in my app. In this app, I am creating my own Undo/Redo functionality because the text in my textbox represents something much larger in memory, and I need to track these textbox changes to be able to undo/redo the whole project properly.
As part of that, I need to capture -- and handle -- many common keyboard scenarios on my own. Yes, I know this is going to be painful.
My major hurdle at the moment is the fact that the textbox intercepts (and even hides) the "usual" keys like Ctrl+X, Ctrl+V, Ctrl+Z and Ctrl+Y. It does this so that it can implement its own undo-redo, but I don't need the built-in undo/redo stuff. I would like to completely disable these keys in my UWP app.
I know that you can do this in winforms, but UWP appears to be a different beast.
private async void MyTextBox_KeyDown(object sender, KeyRoutedEventArgs e)
{
var ctrl = Window.Current.CoreWindow.GetKeyState(VirtualKey.Control).HasFlag(CoreVirtualKeyStates.Down);
// Handle special keys!!
if (ctrl && e.Key == Windows.System.VirtualKey.F)
{
Find_Click(null, null);
e.Handled = true;
}
else if (ctrl && e.Key == Windows.System.VirtualKey.R)
{
Replace_Click(null, null);
e.Handled = true;
}
...
else if (ctrl && e.Key == Windows.System.VirtualKey.Z)
{
// THIS DOES NOT FIRE
Undo();
e.Handled = true;
}
else if (ctrl && e.Key == Windows.System.VirtualKey.Y)
{
// THIS DOES NOT FIRE
Redo();
e.Handled = true;
}
else if (ctrl && e.Key == Windows.System.VirtualKey.X)
{
// THIS DOES NOT FIRE
CaptureUndo("");
e.Handled = true;
}
else if (ctrl && e.Key == Windows.System.VirtualKey.V)
{
// THIS FIRES, BUT THE PASTE HAS ALREADY HAPPENED
// SO e.Handled IS USELESS
string s=GetClipboardText();
CaptureUndo(s);
e.Handled = true;
}
}
I would really appreciate a nudge in the right direction.
Related
My main CDialog sometimes displays a child modeless dialog like this:
It is only displayed if the user has configured it to automatically display.
It gets displayed via the main dialogs OnInitDialog where this function is called:
void CChristianLifeMinistryEditorDlg::DisplayAssignHistoryDialog()
{
BOOL bShowAssignHistoryDialog;
bShowAssignHistoryDialog = theApp.GetNumberSetting(_T("Options"), _T("SM_ShowAssignHist"), TRUE);
if (bShowAssignHistoryDialog)
{
m_pAssignHistoryDlg = std::make_unique<CAssignHistoryDlg>(); // .release();
if (m_pAssignHistoryDlg != nullptr)
{
m_pAssignHistoryDlg->SetAssignHistMap(&m_mapSPtrHist, &m_HistoryOriginal);
m_pAssignHistoryDlg->Create(IDD_DIALOG_ASSIGN_HISTORY, this);
m_pAssignHistoryDlg->ShowWindow(SW_SHOWNORMAL);
m_pAssignHistoryDlg->UpdateWindow();
m_pAssignHistoryDlg->EnableTree(false);
}
}
}
WhatI have noticed is that some of my main windows ACCELERATOR hotkey keys don't always work. I then realised that this is because the popup window has the focus. If i single click anywhere on the main dialog to give it focus, then my accelerator hotkeys function.
Is there any way that we can easily still allow the main editor to process it's hotkeys even though the modeless window might have focus? A standard way to cater for this?
The main window handles the accelerators like this:
BOOL CChristianLifeMinistryEditorDlg::PreTranslateMessage(MSG * pMsg)
{
if (m_hAccelTable)
{
if (::TranslateAccelerator(GetSafeHwnd(), m_hAccelTable, pMsg))
return TRUE;
}
}
And the popup modeless window also makes use of PreTranslateMessage (incase it is relevant):
BOOL CAssignHistoryDlg::PreTranslateMessage(MSG* pMsg)
{
BOOL bNoDispatch{}, bDealtWith = bDealtWith = FALSE ;
if ( (pMsg->message == WM_KEYDOWN || pMsg->message == WM_KEYUP ||
pMsg->message == WM_CHAR)
&& pMsg->wParam == VK_RETURN)
{
// Eat it.
bNoDispatch = TRUE ;
bDealtWith = TRUE ;
}
if (!bDealtWith)
bNoDispatch = CResizingDialog::PreTranslateMessage(pMsg);
return bNoDispatch ;
}
I would pass your m_hAccelTable from CChristianLifeMinistryEditorDlg to CAssignHistoryDlg and add this to the beginning of CAssignHistoryDlg::PreTranslateMessage:
if (m_hAccelTable)
{
if (::TranslateAccelerator(GetParent()->GetSafeHwnd(), m_hAccelTable, pMsg))
return TRUE;
}
Am new to MFC, I want to replicate the exact Ctrl+Page Down and Ctrl+Page Up behavior to regular Page Down/Page Up keys without any supporting keys (Ctrl/Shift). I have been trying to clear the focus of item which is getting selected automatically on striking the keys Page Up and Page Down.
I've tried with this code but its not working:
case VK_NEXT: // pagedown
case VK_PRIOR: // pageup
lhItem = GetFocusedItem();
if (IsSelected(lhItem))
{
CTreeCtrl::SetItemState(lhItem, 0, TVIS_SELECTED);
}
break;
Can anyone please help me in solving it
The Code need to written in OnSelChanging & OnSelChanged Event Handler functions
void CTreeCtrl::OnSelchanging(NMHDR *pNMHDR, LRESULT *pResult)
{
HTREEITEM hNew = pNMTreeView->itemNew.hItem;
HTREEITEM hOld = pNMTreeView->itemOld.hItem;
m_bOldItemSelected = hOld && (CTreeCtrl::GetItemState(hOld, UINT(TVIS_SELECTED)) & TVIS_SELECTED);
if (GetSelectedCount() > 1)
{
if (m_bPgUpState || m_bPgDownState)
{
//Check the state of New Item
if ((pNMTreeView->itemNew.state & TVIS_SELECTED))
{
// If the item is selected, so make sure OnSelchanged()
// will "select" it !
m_bNewItemSelected = TRUE;
}
else if (!(pNMTreeView->itemNew.state & TVIS_SELECTED))
{
// The New item is not selected, so make sure OnSelchanged()
// will not "re-select" it !
m_bNewItemSelected = FALSE;
CTreeCtrl::SetItemState(hNew, UINT(~TVIS_SELECTED), UINT(TVIS_SELECTED));
}
}
}
void TreeCtrl::OnSelchanged(NMHDR* pNMHDR, LRESULT* pResult)
{
NM_TREEVIEW* pNMTreeView = (NM_TREEVIEW*)pNMHDR;
HTREEITEM itemNew = pNMTreeView->itemNew.hItem;
HTREEITEM itemOld = pNMTreeView->itemOld.hItem;
if ((m_bPgUpState || m_bPgDownState) && (GetSelectedCount() > 1)
&& (pNMTreeView->itemOld.hItem != NULL || pNMTreeView->itemNew.hItem != NULL))
{
// It had the focus so Keep selection at old item
if (itemOld && m_bOldItemSelected)
{
CTreeCtrl::SetItemState(itemOld, UINT(TVIS_SELECTED), UINT(TVIS_SELECTED));
m_bOldItemSelected = FALSE;
}
else
{
// Do-not select the item if it is not selected
CTreeCtrl::SetItemState(itemOld, UINT(~TVIS_SELECTED), UINT(TVIS_SELECTED));
}
}
In this article you'll find solution for every thing about CTreeCtrl
Full-Featured Tree Control
To be precise, I don't want to use standard rectangular buttons. Is there a way to create your own buttons and incorporate them into Visual C++ application? For example, a button in a shape of a fruit, animal, random object...? How can this be achieved?
There are two common approaches to custom buttons. The first is to use the owner-draw optional style on a standard button. With this style set you receive a message (WM_DRAWITEM) when the button needs to be painted and you paint it yourself any way you like. A second approach is to draw the image on an existing window and analyze mouse messages to determine when a click is on the image. The common toolbar uses this approach: It doesn't really have button controls on it, just pictures that look like buttons. With this second approach you are not limited to clicks on rectangular areas.
define your own callback function for button
SetWindowLong(hMyButtonWnd,GWL_WNDPROC,(LONG)&MyButtonProc);
and here example of button callback
LRESULT CALLBACK MyButtonProc(HWND hWnd,UINT message,WPARAM wParam,LPARAM lParam)
{
if (message == WM_LBUTTONDOWN)
{
bBtnClicked = true;
bBtnDown = true;
SetCapture(hWnd);
nMouseX = LOWORD(lParam);
nMouseY = HIWORD(lParam);
InvalidateRect(hWnd,NULL,false);
UpdateWindow(hWnd);
}
else if (message == WM_LBUTTONUP)
{
bBtnClicked = false;
bBtnDown = false;
InvalidateRect(hWnd,NULL,false);
UpdateWindow(hWnd);
RECT rec;
GetClientRect(hWnd,&rec);
if (nMouseX > rec.left && nMouseY > rec.top
&& nMouseX < rec.right && nMouseY < rec.bottom)
{
MessageBox(NULL,L"Button clicked",L"Test",MB_OK);
}
ReleaseCapture();
}
else if (message == WM_MOUSEMOVE)
{
nMouseX = LOWORD(lParam);
nMouseY = HIWORD(lParam);
RECT rec;
GetClientRect(hWnd,&rec);
if (nMouseX <= rec.left || nMouseY <= rec.top
|| nMouseX >= rec.right || nMouseY >= rec.bottom) bBtnDown = false;
InvalidateRect(hWnd,NULL,false);
UpdateWindow(hWnd);
}
if (message == WM_PAINT)
{
PAINTSTRUCT ps;
HDC dc = BeginPaint(hWnd,&ps);
HDC c_dc = CreateCompatibleDC(NULL);
if (bBtnDown) SelectObject(c_dc,hBtnDown);
else SelectObject(c_dc,hBtnUp);
RECT rec;
GetClientRect(hWnd,&rec);
BitBlt(dc,0,0,rec.right,rec.bottom,c_dc,0,0,SRCCOPY);
DeleteDC(c_dc);
EndPaint(hWnd,&ps);
return 0;
}
else return CallWindowProc(DefProc,hWnd,message,wParam,lParam);
return 1;
}
I catch the event down, but I need to save the clicked buttons.
for example a way to get modifiers + e.key
This is my code:
protected override void OnKeyDown(KeyEventArgs e)
{
//base.OnKeyDown(e);
if (Keyboard.Modifiers == ModifierKeys.Control && e.Key == Key.E)
{
MessageBox.Show(test.ToString());
}
}
I tried to combine two keys like
Key test = Key.Ctrl | e.Key ;
but it always give wrong results , for exampl Ctrl + E result in due to the above code to J
I want to save it, and I want to make those shortcuts general in all UserControls.
Try like this using KeyGesture:
private void textBox1_KeyDown(object sender, KeyEventArgs e)
{
if(e.Key ==Key.E && e.KeyboardDevice.Modifiers==ModifierKeys.Control)
{
KeyGesture keyGesture = new KeyGesture(e.Key, e.KeyboardDevice.Modifiers);
if (keyGesture.Matches(null, e))
{
//This should match!
}
}
}
ok as I use silverlight4 , I will use
Tuple<ModifierKeys, Key>
it work great and its performance is great
I have a CDialog and I am trying to override PreTranslateMessage like this:
BOOL CWeekendMeetingDlg::PreTranslateMessage(MSG* pMsg)
{
if (pMsg->message == WM_SETFOCUS)
{
if (::GetDlgCtrlID(pMsg->hwnd) != IDC_COMBO_PT_CHAIRMAN &&
::GetDlgCtrlID(pMsg->hwnd) != IDC_COMBO_PT_OPEN_PRAYER &&
::GetDlgCtrlID(pMsg->hwnd) != IDC_COMBO_WT_CLOSE_PRAYER &&
::GetDlgCtrlID(pMsg->hwnd) != IDC_COMBO_WEEKEND_HOST &&
::GetDlgCtrlID(pMsg->hwnd) != IDC_COMBO_WEEKEND_COHOST &&
::GetDlgCtrlID(pMsg->hwnd) != IDC_COMBO_PT_READER)
{
if (m_gridAssignHist.GetRowCount() != 1)
{
m_gridAssignHist.SetRowCount(1);
}
}
}
return CDialog::PreTranslateMessage(pMsg);
}
It is not working. What I want to do is reset my CGridCtrl row count to 1 when other controls gain focus (eg: edit combo). But if I put a breakpoint inside the first if it is never intercepted.
At the moment the only thing I can think of is to renumber all of the combo IDs on the dialog so they are sequential and then use a command range for OnSetFocus and detect in that handler. But there are also some CEdit controls.
Can't I use PTM though and avoid that? For me it seems it would be the easiest.
Here is the dialog:
At the moment I have 7 combo OnSetFocus handlers. When they fire the display specific assignment history in the grid on the right.
So if the user moves to any other control on the dialog, the assignment history control will not apply. And that was why i wanted to reset the history to just the header row. And I was hoping to do this with PTM.
Child control notifications are received in the parent dialog via WM_COMMAND messages. Overriding MFC dialog's OnCommand catches those notifications (CBN_SETFOCUS, EN_SETFOCUS etc).
void DoSomething()
{ /* ... */ }
BOOL CWeekendMeetingDlg::OnCommand(WPARAM wParam, LPARAM /*unused*/)
{
switch (HIWORD(wParam))
{
case CBN_SETFOCUS:
switch (LOWORD(wParam))
{
case IDC_COMBO_PT_CHAIRMAN:
// ...more combobox IDCs
DoSomething();
break; // or return TRUE to bypass any message map handlers
}
break;
case EN_SETFOCUS:
switch (LOWORD(wParam))
{
case IDC_EDIT_WHATEVER:
// ...more editbox IDCs
DoSomething();
break;
}
break;
// ...more notification codes
}
return CDialog::OnCommand(wParam, lParam);
}
I have added a 2 second timer to the dialog:
void CWeekendMeetingDlg::OnTimer(UINT_PTR nIDEvent)
{
if (nIDEvent == 1)
{
CWnd* pControl = GetFocus();
if (pControl != nullptr)
{
CWnd* pParent = pControl->GetParent();
if (pParent != nullptr)
{
int iCtrlID = pParent->GetDlgCtrlID();
if (iCtrlID != IDC_COMBO_PT_CHAIRMAN &&
iCtrlID != IDC_COMBO_PT_OPEN_PRAYER &&
iCtrlID != IDC_COMBO_WT_CLOSE_PRAYER &&
iCtrlID != IDC_COMBO_WEEKEND_HOST &&
iCtrlID != IDC_COMBO_WEEKEND_COHOST &&
iCtrlID != IDC_COMBO_PT_READER)
{
if (m_gridAssignHist.GetRowCount() != 1)
{
m_gridAssignHist.SetRowCount(1);
UpdateData(TRUE);
m_strAssignHistLabel = _T("Assignment:");
UpdateData(FALSE);
}
}
}
}
}
CDialog::OnTimer(nIDEvent);
}
This seems to work fine.