It has methods like CRichEditCtrl::Copy(), CRichEditCtrl::Paste() which you can call, but I can't spot any messages the control is sent by Windows telling it to perform a paste operation. Does anyone know if such a thing exists? Or does CRichEditCtrl do something lower-level like monitoring WM_CHAR events? If so can I reuse any internal methods or would I just have to roll my own in order to override the standard paste functionality?
What I actually want is for my custom subclass (CMyRichEditCtrl : CRichEditCtrl) to ignore any formatting on text pasted in to the control. Either by getting the clipboard data in a different clipboard format, or by pasting it in as normal and immediately removing formatting on inserted text.
What I tried so far:
Checking the message for WM_PASTE in CMyRichEditCtrl::PreTranslateMessage()
Creating a method virtual void CMyRichEditCtrl::Paste()
Putting a breakpoint on CRichEditCtrl::Paste() in afxcmn.inl
Dumping every message passing through CMyRichEditCtrl::PreTranslateMessage()
Results:
1: No WM_PASTE message seen
2: It's never called
3: It's never hit... how?
4: The control never receives any WM_COMMAND, WM_PASTE or focus-related messages. Basically only mouse-move and key-press messages.
It seems other people have actually done this successfully. I'm wondering if my MFC version or something could be screwing it up, at this point.
Handle EN_PROTECTED message.
ON_NOTIFY_REFLECT(EN_PROTECTED, &YourClass::OnProtected)
// call this from the parent class
void YourClass::Initialize()
{
CHARFORMAT format = { sizeof(CHARFORMAT) };
format.dwEffects = CFE_PROTECTED;
format.dwMask = CFM_PROTECTED;
SetDefaultCharFormat(format);
SetEventMask(ENM_PROTECTED);
}
void YourClass::OnProtected(NMHDR* pNMHDR, LRESULT* pResult)
{
*pResult = 0;
ENPROTECTED* pEP = (ENPROTECTED*)pNMHDR;
if (pEP->msg == WM_PASTE)
pResult = 1; // prevent paste
}
What happens when the user requests a paste action is usually that a WM_COMMAND message with the identifier ID_EDIT_PASTE is sent to the rich edit control. By default in MFC this is handled by CRichEditCtrl::OnEditPaste(), which calls Paste() on the edit control itself.
The way I'd go about this is to derive a class from CRichEditCtrl, add an OnEditPaste method and route the message to it with a
ON_COMMAND(ID_EDIT_PASTE, OnEditPaste)
declaration, which should work. Alternatively, in your PreTranslateMessage you could look for WM_COMMAND with a wParam of ID_EDIT_PASTE.
By the way, I've solved a very similar problem to yours (paste without formatting) by just having an implementation of OnEditPaste with
void MyRichEdit::OnEditPaste()
{
SendMessage(EM_PASTESPECIAL,CF_UNICODETEXT);
}
This responds to the paste request by sending a paste message to the control that insists that the data format is plain text.
Finally, I should point out that the above technique is sufficient to catch all pastes triggered from the user interface. However, it won't catch programmatically triggered pastes, when your code sends WM_PASTE to the edit control. In those cases it's easiest to just change your code. However, if you really want to intercept such cases, you have to get your hands dirty with COM and IRichEditOleCallback::QueryAcceptData. But you almost certainly don't want to go there :-)
Windows defines messages for cut/copy/and paste. see WM_CUT.
It's probably responding to those messages rather than to WM_CHAR messages to know when to do clipboard operations.
Use the ON_MESSAGE Macro on your derived class.
ON_MESSAGE(WM_PASTE, OnPaste)
LRESULT CMyRichEditCtrl::OnPaste(WPARAM, LPARAM)
If you open the RichEdit.h file, you will notice that some of the messages are on the range of WM_USER. Maybe this is how MFC handles the events for the Rich Edit Control.
i have to perform like below
void MyRichEcit::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags)
{
if( ( GetKeyState(VK_CONTROL)<0 && nChar==88 ) || (nChar==VK_DELETE && GetKeyState(VK_SHIFT) < 0) ) //cut
{
}
if( ( GetKeyState(VK_CONTROL)<0 && nChar==86 ) || (nChar==VK_INSERT && GetKeyState(VK_SHIFT) < 0) ) //paste
{
}
CWnd::OnKeyDown(nChar, nRepCnt, nFlags);
}
Did you try CRichEditCtrl::PasteSpecial(CF_TEXT)? I believe it should do what you are wanting to do.
Related
I am currently writing a wrapper for an existing application that has its own GUI. I don't have access to original application's source code (unfortunately). The program that I am writing is in C++ and I am making use of WinAPI. I am manipulating target application by simulating button-clocks, ticking checkboxes etc.
The problem I am facing at the moment is following:
I need to make a selection in droplist implemented as WinAPI ComboBox. I am doing it by using macro ComboBox_SetCurSel. The selection in the droplist changes correctly. However in the original application there is a read-only textbox that changes the value depending on the selection in combobox. And this one does not change when I execute ComboBox_SetCurSel.
The assumption I made is that CBN_SELENDOK and/or CBN_SELCHANGE are sent when selecting an entry in ComboBox manually and this is the bit I am not doing when setting the selection with ComboBox_SetCurSel macro.
However due to lack of experience I cannot figure out how to resolve the problem. Who is normally listening for CBN_SELENDOK and CBN_SELCHANGE. Is it main application window, parent element of the combobox or main application thread? How do I find out.
Is there a macro that would do the whole thing? Like changing the selected item in ComboBox and sending all necessary notifications? Is there some smart workaround?
Any help on the subject, or any additional questions that would help to make situation more clear are welcome.
UPDATE: thanks for comment by Jonathan Potter. I am now attempting to send messages explicitly. Here is the part of the code where I am doing it:
int res = ComboBox_SetCurSel(this->handle, index);
if (res == CB_ERR)
{
return false;
}
PostMessage(GetParent(this->handle),WM_COMMAND, MAKEWPARAM(0,CBN_SELENDOK),0);
PostMessage(GetParent(this->handle),WM_COMMAND, MAKEWPARAM(0,CBN_SELCHANGE),0);
Note this->handle is just a handle to ComboBox itself as I have packed it into the structure for convenience. GetParent(this->handle) Should get immediate parent of ComboBox
Still no result. Does the order of messages matter? Also how do I obtain the identifier that needs to go into LOWORD of WPARAM sent along with WM_COMMAND?
ANSWER:
Thanks to AlwaysLearningNewStuff I have found and an answer. I have been sending messages with 0 as LPARAM. Apparently a handle to ComboBox itself neets to be sent as LPARAM in order for solution to work. This would take me ages to figure it out.
#AlwaysLearningNewStuff, you should have posted this as an answer, not a comment.
Also the bit about using GetDlgCtrlID() to get ControlID of the ComboBox is very useful. This makes code more reliable.
Thank you, everyone who participated.
Here is my final code:
if (this->handle == NULL)
{
return false;
}
int res = ComboBox_SetCurSel(this->handle, index);
if (res == CB_ERR)
{
return false;
}
PostMessage(GetParent(this->handle), WM_COMMAND, MAKEWPARAM(GetDlgCtrlID( this->handle ),CBN_SELENDOK),
(LPARAM)(this->handle));
PostMessage(GetParent(this->handle), WM_COMMAND, MAKEWPARAM(GetDlgCtrlID( this->handle ),CBN_SELCHANGE),
(LPARAM)(this->handle));
return true;
You are correct that CBN_SELCHANGE is not sent when using ComboBox_SetCurSel(), and the documentation says as much:
The CBN_SELCHANGE notification code is not sent when the current selection is set using the CB_SETCURSEL message.
So you have to send the notifications manually. However, you are missing key elements in your messages - the ComboBox's Control ID and HWND. The parent window uses those to identify which child control is sending messages to it so it can then act accordingly.
Try this instead:
int res = ComboBox_SetCurSel(this->handle, index);
if (res == CB_ERR)
{
return false;
}
HWND hParent = GetParent(this->handle);
int iCtrlId = GetDlgCtrlID(this->handle);
if (GetWindowLong(this->handle, GWL_STYLE) & CBS_SIMPLE)
PostMessage(hParent, WM_COMMAND, MAKEWPARAM(iCtrlId,CBN_SELENDOK), LPARAM(this->handle));
PostMessage(hParent, WM_COMMAND, MAKEWPARAM(iCtrlId,CBN_SELCHANGE), LPARAM(this->handle));
In a certain dialog I would like when the user presses the enter key for it to act as an "apply" button. So far I have at least been able to make the dialog not close upon pressing enter by overriding CWnd::PreTranslateMessage, so currently it just does nothing and I'm not sure how to send apply command from there.
Every dialog should have one and only one button with the BS_DEFPUSHBUTTON style, which indicates to the dialog that this is the button to activate with the Enter key. Usually this is the OK button, but you can make it the Apply button if you want to.
As Mark pointed out above the dialog manager already has all the logic built in to handle the Enter key by invoking the command associated with the default button. You can statically assign the BS_DEFPUSHBUTTON style or handle the DM_GETDEFID message.
The former is trivially easy and the latter is fairly simple to implement. Make sure you set the Default Button property to False for all buttons on your dialog. Now add a message handler for the DM_GETDEFID message. There is no dedicated macro for this message so you have to use the generic handler:
BEGIN_MESSAGE_MAP(CMyDialog, CDialogEx)
...
ON_MESSAGE(DM_GETDEFID, OnGetDefId)
END_MESSAGE_MAP()
The message handler is equally simple and uses the default message handler signature:
LRESULT CMyDialog::OnGetDefId(WPARAM wParam, LPARAM lParam)
{
return MAKELRESULT(ID_APPLY, DC_HASDEFID);
}
The message handler must return a value whose high-order word contains DC_HASDEFID and the low-order word contains the control ID.
If you navigate over the controls of the dialog you will see that the Apply button has the typical default button visual cue while focus is not on another command button. Pressing Enter while a non-button control has the input focus invokes the default button's command handler. No additional code required.
If your intent is to handle the Enter key without dismissing the dialog, you may be going about it incorrectly. Please take a look at this MSDN article. While using PreTranslateMessage should work, it is not the best way to handle these types of events.
You'll need to handle the OnKeyDown message, and handle the VK_RETURN character inside that function.
void MyCtrl::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags)
{
if(nChar == VK_RETURN)
{
// Do Stuff
return;
}
CWnd::OnKeyDown(nChar, nRepCnt, nFlags);
}
Another way to overwrite the message.
BOOL CMyDialog::PreTranslateMessage(MSG* pMsg)
{
if (pMsg->message == WM_KEYDOWN)
{
switch (pMsg->wParam)
{
case VK_RETURN:
{
UINT nID = ::GetDlgCtrlID(pMsg->hwnd);
if (nID == ID_APPLY)
{
//DO YOUR STUFF HERE
}
}
break;
default:
break;
}
}
return CDialog::PreTranslateMessage(pMsg);
}
Also, you don't need to use PreTranslateMessage if you are using ::OnKeyDown
Is there any way to prevent a window from being deactivated? The window is in a different process then mine.
This is for Windows.
Doing this can be dangerous, but the solution is to handle the WM_ACTIVATE message and check if the wParam is WA_INACTIVE. This means the window has been deactivated. When this happens, you can just reactivate it.
In order to do this for another process's window, you will need to install a message hook with SetWindowsHookEx.
However, it is possible that another application could do the same thing, putting each other in an endless loop of activation/deactivation.
This is also something that should never be done by software that is meant to run on a personal computer.
You can trap the WM_ACTIVATEAPP like this:
protected override void WndProc(ref Message m) {
// Trap WM_ACTIVATEAPP
if ((m.Msg == 0x1c) && (m.WParam == IntPtr.Zero))
{
// If WM_ACTIVATEAPP and WParam is deactivated, return
return;
}
base.WndProc(ref m);
}
So I thought this would be pretty simple, but I forgot it's MFC. Instead of registering a notification listener for data model changes that would possibly require a GUI update on each individual control I figure why not register it once and then send a message to all the open dock panes and allow them to update their controls as needed on their own terms for efficiency.
My callback function for handling the notification from the server looks something like this:
void CMainFrame::ChangeCallback(uint32_t nNewVersion, const std::vector<uint32_t>& anChangedObjectTypes)
{
CObList panes;
GetDockingManager()->GetPaneList(panes); // assert failure
if (!panes.IsEmpty())
{
POSITION pos = panes.GetHeadPosition();
while (pos)
{
CDockablePane* pPane = dynamic_cast<CDockablePane*>(panes.GetNext(pos));
if (pPane)
pPane->PostMessage(DM_REFRESH, nNewVersion);
}
}
}
The error I am getting is an assertion failure on line 926 of wincore.cpp
CHandleMap* pMap = afxMapHWND();
ASSERT(pMap != NULL); // right here
There is a comment below this saying this can happen if you pass controls across threads however this is a single threaded MFC application and this is all being done from the main frame.
Does anyone know what else can cause this?
If there is another way to go about sending a message to all the open CDockablePane derived windows in MFC that works as well ...
Here's the obvious workaround that I didn't want to have to do but after hours of debugging and no response here I guess this is a viable answer:
I added std::vector<CDockPane*> m_dockList; to the members of CMainFrame
Now after each call to AddPane in various places that can create and open new dock panes I make a subsequent call to push_back and then I override CDockablePane::OnClose like so:
CMainFrame* pMainFrame = reinterpret_cast<CMainFrame*>(AfxGetMainWnd());
if (pMainFrame)
{
std::vector<CDockPane*>::const_iterator found(
std::find(pMainFrame->DockList()->begin(), pMainFrame->DockList()->end(), this));
if (found != pMainFrame->DockList()->end())
pMainFrame->DockList()->erase(found);
}
CDockablePane::OnClose();
Now this list will only contain pointers to open dock panes which allows me to handle the event notification in my callback and simply do a for loop and PostMessage to each.
When we save a level in our editor, we create a log file of any errors it contains. These consist basically of an error message and a path that allows the user to find the erronous item in a tree view.
What I want is to make that path a link, something like
< a href="editor://path/to/gameobject" > Click to see object in editor< /a >
The SO questions I've seen regarding this seems to point to this msdn page:
http://msdn.microsoft.com/en-us/library/aa767914.aspx
But from what I can tell, it will spawn a new instance of the application. What I want to do is to simply "call" our editor somehow. One way to do it, I guess, is to spawn it, and in the beginning check if there's already an instance running, and if so, send the commandline to it.
Is that the best way to do it? If so, any ideas on how to do it best? What are otherwise some ways that this could be done?
Also: does the msdn solution work across browsers? Our editor runs in Windows only, but people use IE, Fx, GC and Opera.
If you need the link to work in any viewer, yes, registering a protocol handler is the best way.
As for launching the editor, you could implement it as an out-of-process COM server, but if you've already got command line parsing sorted, you might as well use a window message or named pipe to pass that to the editor. If you're sending a window message, you could use FindWindow (with a unique class name) to check for a running instance.
Sounds like you solved it already, by checking for a previous instance.
I would be surprised if the OS takes upon it to somehow "stamp" the association with data telling it to separate programs that should run multiple times from programs that should not.
Here's how I solved it. Basically, there are two parts. Or three.
First, the app needs to register itself in the registry, like this. It took some googling to find out how to use the windows register functions, but they were pretty straightforward. By adding this to the registry, your application will launch when a link with your custom url protocol is clicked.
Secondly, the app needs to detect that it's been started from a browser. Obviously quite trivial, just check the command line for "/uri" or however you chose to customize it.
Third, you don't actually want to start your application - it should already be running! Instead, when you've detected that you got started from a hyperlink, you need to detect if another instance of the application is already running. After that, you need to pass the command line to it. Here's how I did it:
bool ShouldContinueStartEditor( const std::string& command_line )
{
// Check if this instance was spawned from a web browser
if ( command_line.find( "/uri" ) != std::string::npos )
{
// Try to find other instance of JustEdit
HWND wnd = FindWindow( "AV_MainFrame", NULL );
if ( wnd )
{
COPYDATASTRUCT cds;
NEditorCopyData::SCommandLine data_to_copy;
strncpy( data_to_copy.m_CommandLine, command_line.c_str(), sizeof(data_to_copy.m_CommandLine) - 2 );
cds.dwData = NEditorCopyData::ECommandLine; // function identifier
cds.cbData = sizeof( data_to_copy ); // size of data
cds.lpData = &data_to_copy; // data structure
SendMessage( wnd, WM_COPYDATA, NULL, (LPARAM) (LPVOID) &cds );
}
return false;
}
return true;
}
"AV_Mainframe" is the name of the hwnd. If you happen to be using WTL, you can declare it like this.
DECLARE_FRAME_WND_CLASS("AV_MainFrame", IDR_MAINFRAME)
Now, in your window class, you need to handle the WM_COPYDATA message like this:
MESSAGE_HANDLER(WM_COPYDATA, OnCopyData);
LRESULT OnCopyData(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/);
LRESULT CMainFrame::OnCopyData(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM lParam, BOOL& /*bHandled*/)
{
PCOPYDATASTRUCT cds = (PCOPYDATASTRUCT) lParam;
if ( cds->dwData == NEditorCopyData::ECommandLine )
{
NEditorCopyData::SCommandLine* command_line = static_cast( cds->lpData );
const char* internal_path = strstr( command_line->m_CommandLine, "/uri" );
if ( internal_path != NULL )
{
// Do your thang
}
}
return 0;
}
And that's pretty much all there's to it. Oh, this is what the copy data namespace looks like:
namespace NEditorCopyData
{
enum ECopyDataMessages
{
ECommandLine = 0
};
struct SCommandLine
{
char m_CommandLine[512];
};
}