I want my BHO to listen to onmousedown event of some element in a certain webpage.
I have all the code that find the specific element, and in msdn its says that I need to use the get_onmousedown event. I came up with this code.
CComQIPtr<IHTMLElement> someElement;
VARIANT mouse_eve;
someElement->get_onmousedown(&mouse_eve);
The question is, how do I tell it to run some function when this event occurs?
v - VARIANT of type VT_DISPATCH that specifies the IDispatch interface of an object with a default method that is invoked when the event occurs.
Event handlers in this context are COM instances that implement IDispatch - so you need to pass a pointer to an IDispatch that your event handler object implements:
CComQIPtr<IDispatch> spDisp(spMyHandler); // something like that
someElement->put_onmousedown(CComVariant(spDisp));
Note: put_ instead of get_ - you want to register a handler.
On this, IDispatch::Invoke() gets called with:
wFlags containing DISPATCH_METHOD ("a method is getting invoked")
dispIdMember being 0 / DISPID_VALUE ("the default method")
Put together this should become something like:
HRESULT MyHandler::Invoke(DISPID dispIdMember, REFIID, LCID, WORD wFlags,
DISPPARAMS*, VARIANT*, EXCEPINFO*, UINT*)
{
// ...
if((wFlags & DISPATCH_METHOD) && (dispIdMember == DISPID_VALUE))
{
// ...
}
}
Related
I need to get fullDescription property of a UI element using get_CurrentFullDescription method of UIAutomation library of c++ windows.
Issue is I have element as IUIAutomationElement instead of IUIAutomationElement6, get_CurrentFullDescription can only be invoked on element with IUIAutomationElement6 type.
How can I convert IUIAutomationElement to IUIAutomationElement6?
I am using HandlePropertyChangedEvent method to listen on changes in UI, which returns:
HRESULT HandlePropertyChangedEvent(
[in] IUIAutomationElement *sender,
[in] PROPERTYID propertyId,
[in] VARIANT newValue
);
https://learn.microsoft.com/en-us/windows/win32/api/uiautomationclient/nf-uiautomationclient-iuiautomationpropertychangedeventhandler-handlepropertychangedevent
Here, I need to access FullDescription property of sender element coming from HandlePropertyChangedEvent function.
#IInspectable mentioned QueryInterface hint in comments, based on that here is answer:
CComPtr<IUIAutomationElement> pElement;
// Code to initialize pElement goes here
CComPtr<IUIAutomationElement6> pElement6;
HRESULT hr = pElement->QueryInterface(&pElement6);
if (SUCCEEDED(hr))
{
// Use the pElement6 object here
}
ParentWnd contains mfc control called modeOfOperation (drop down list). When modeOfOperation is Normal everything is normal. We added new modeOfOperation=Extreme. When modeOfOperation is Extreme I want to disable 90% of existing ParentWnd controls because they don't work in Extreme mode. I have existing codebase with hundreds of UI controls. I want to find one place in code to disable 90% of them without hurting rest of functionality.
I know that 90% of UI controls that I need to disable are in several child windows. One of them is m_childWindow1. I need to tell if given message is is handled by m_childWindow1,...,m_childWindowN.
So ParentWnd routes messages to childWindow. I want to override childWindow handler in case when given message is handled by childWindow. So I need function like bool CWnd::isMessageIdInMessageMap(int id).
BOOL ParentWnd::OnCmdMsg( UINT nID, int nCode, void* pExtra, AFX_CMDHANDLERINFO* pHandlerInfo )
{
if ( nCode == CN_UPDATE_COMMAND_UI )
{
CWnd *contents = m_childWindow1->getContents();
if( contents )
{
if( contents->OnCmdMsg( nID, nCode, pExtra, pHandlerInfo ) )
{
//I want to enter additional code here
//But I don't want to call contents->OnCmdMsg
return true;
}
}
}
}
...
}
Simply use the existing functions (OnCmdMsg).
Create your own CCmdUI object (overwrite the Enable... functions if required) pass is as pExtra argument to OnCmdMsg and you know after the call if the was a handler or not.
There are no side effects...
We have an existing network messaging server that implements a custom communications protocol that we are migrating to an in-process COM object. The server is implemented as a free threaded out-of-process COM server. The clients can register with the server (think publish-subscribe) to receive messages.
After migration we noticed several dead locks when calling GUI related functions, e.g. SetWindowPos, RedrawWindow, EnumWindows, etc. After doing some research I found that this was due to callbacks happening on a thread other than the main GUI thread (message pump). The callbacks are custom COM callbacks that derive from IUnknown, so we are not using connection points.
What's interesting is if a create a simple MFC dialog project, everything works. But in our main application it fails. Our main application (20+ years old and doing things now it was never designed for) is an MFC dialog project. In response to a message, the MFC dialog project loads a DLL that, in turn, creates an MFC dialog and registers with the COM server for messages. When the DLL dialog receives a message it calls one of the three example GUI related functions above, and blocks.
This use to work for an out-of-process COM server, so I know there is a way to get the callback to be within the context of the clients main GUI thread but have been unable find the 'magic' code to make it work. I did stumble upon SendMessageCallback but can't get the asynchronous callback function to be called.
The COM callbacks into the client are handled via a thread inside the in-process COM server. The thread is initialized with CoInitialize, which from my research means STA. I've tried changing this to CoInitializeEx and tested with MTA. I've also tried changing the COM server threading model to STA, MTA, Both, and Free. As you can see, I'm really starting to throw darts. here.
Any help would be appreciated.
I did order the Don Box books, Essential COM and Effective COM, but they will not arrive until later this week.
EDIT:
Our main application:
Inside of CApp derived class' InitInstance
AfxOleInit()
Inside of CDialog derived class' OnInitDialog
CoCreateInstance (Messaging COM Object)
Allocate callback pointer. The client callback is an interface that derives from IUnknown. Then a callback class derives from the client callback and implements the AddRef/Release interfaces. When the client creates the callback the client passes a pointer to itself so the callback can call the client.
MessageComObject->Register(callback pointer)
Inside MessageCOMObject:
MessageComObject adds the callback to the GIT and saves the cookie.
MessageComObject starts a thread to 'send' callbacks to the client.
Some point in time later the main application receives a 'message' via the callback pointer. The callback is initiated from within the MessageComObject.
Inside the MessageCOMObject:
MessageComObject gets the callback from the GIT using the cookie
MessageComObject calls a function on the callback interface
Inside the callback interface's derived class:
Calls the client callback function
Inside the CDialog derived class' callback handler:
Calls LoadLibrary on a DLL (what gets loaded is data driven)
Calls exported function from DLL.
Inside DLL's exported function:
Create CWnd object
CoCreateInstance (Messaging COM Object)
Allocate callback pointer (same as above)
MessageCOMObject->Register
Inside MessageCOMObject:
MessageComObject adds the callback to the GIT and saves the cookie.
MessageComObject starts a thread to 'send' callbacks to the client.
Some point in time later the DLL receives a message:
MessageComObject gets the callback from the GIT using the cookie, GIT returns 'Catastrophic Failure'
Code
Declaring the callback in the IDL file:
[
object,
uuid(...),
pointer_default(unique)
]
interface IMessageRouterCallback : IUnknown
{
...
};
[
object,
uuid(...),
pointer_default(unique)
]
interface IMessageRouter : IUnknown
{
...
};
[
uuid(....),
version(1.0),
]
library MessageRouterCOMLib
{
importlib("stdole2.tlb");
[
uuid(....)
]
coclass MessageRouter
{
[default] interface IMessageRouter;
};
};
The in-process COM DLL
class ATL_NO_VTABLE CMessageRouter :
public CComObjectRootEx<CComMultiThreadModel>,
public CComCoClass<CMessageRouter, &CLSID_MessageRouter>,
public IMessageRouter
{
public:
DECLARE_GET_CONTROLLING_UNKNOWN()
BEGIN_COM_MAP(CMessageRouter)
COM_INTERFACE_ENTRY(IMessageRouter)
COM_INTERFACE_ENTRY_AGGREGATE(IID_IMarshal, m_pUnkMarshaler.p)
END_COM_MAP()
CComPtr<IUnknown> m_pUnkMarshaler;
DECLARE_PROTECT_FINAL_CONSTRUCT()
DWORD callbackRegistrationId;
HRESULT FinalConstruct()
{
//- TODO: Add error checking
IUnknown *unknown;
DllGetClassObject(IID_IMessageRouterCallback, IID_IUnknown, (void**)&unknown);
CoRegisterClassObject(IID_IMessageRouterCallback, unknown, CLSCTX_INPROC_SERVER, REGCLS_MULTIPLEUSE, &callbackRegistrationId);
CoRegisterPSClsid(IID_IMessageRouterCallback, IID_IMessageRouterCallback);
return CoCreateFreeThreadedMarshaler(GetControllingUnknown(),
}
void FinalRelease()
{
if (callbackRegistrationId)
CoRevokeClassObject(callbackRegistrationId);
callbackRegistrationId = 0;
if (m_pUnkMarshaler)
m_pUnkMarshaler.Release();
}
}
Where callback is registered:
boost::lock_guard<boost::mutex> guard(callbacksMutex);
//- callback is the raw interface pointer from the client
//- The class Callback contains a pointer to the raw client callback
//- and the global process ID. The raw pointer is AddRef/Release in
//- the Callback class' constructor and destructor.
ptr = Callback::Pointer(new Callback(callback));
DWORD callbackId = 0;
HRESULT result = globalInterfaceTable->RegisterInterfaceInGlobal(callback, IID_IMessageRouterCallback, &callbackId);
if (SUCCEEDED(result))
{
ptr->globalCallbackId = callbackId;
callbackMap[callback] = ptr;
//- NOTE: uses raw pointer as key into map. This key is only
//- ever used during un-register.
//- callbackMap is a std::map of Callback instances indexed by the raw pointer.
}
Callback thread:
CoInitialize(NULL);
while (processCallbackThreadRunning)
{
QueueMessage message = messageQueue.Pop();
if (!processCallbackThreadRunning)
break;
//- Make a copy because callbacks may be added/removed during
//- this call.
CallbackMap callbacks;
{
boost::lock_guard<boost::mutex> guard(callbacksMutex);
callbacks = callbackMap;
}
for (CallbackMap::iterator callback = callbacks.begin(); callback != callbacks.end(); ++callback)
{
try
{
IMessageRouterCallback *mrCallback = NULL;
HRESULT result = globalInterfaceTable->GetInterfaceFromGlobal(callback->second->globalCallbackId,IID_IMessageRouterCallback,(void **) &mrCallback);
if (SUCCEEDED(result))
{
result = mrCallback->MessageHandler((unsigned char*)message.messageBuffer->Data(), message.messageBuffer->Length(), message.metaData.id, message.metaData.fromId, CComBSTR(message.metaData.fromAddress.c_str()));
if (FAILED(result))
{
... log debug
}
}
else
{
... log debug
}
}
catch (...)
{
... log debug
}
}
MessagePool::Push(message.messageBuffer);
}
CoUninitialize();
Client's implementation of the callback:
template <class CALLBACKCLASS>
class CMessageRouterCallback :
public CComBase<IMessageRouterCallback>
{
CMessageRouterCallback( CALLBACKCLASS *pCallbackClass = NULL) :
m_pCallbackClass(pCallbackClass)
{
AddRef(); //- Require by CComBase. This makes it so that this
//- class does not get automatically deleted when
//- Message Router is done with the class.
}
STDMETHODIMP MessageHandler( UCHAR *szBuffer, int nSize, DWORD dwTransCode, DWORD dwSenderID, BSTR bstrFromIP )
{
if ( m_pCallbackClass != NULL )
{
m_pCallbackClass->MessageHandler( szBuffer, nSize, dwTransCode, dwSenderID, bstrFromIP );
}
return S_OK;
}
}
CComBase implements the IUnknown interface:
template < class BASE_INTERFACE, const IID* piid = &__uuidof(BASE_INTERFACE) >
class CComBase :
public BASE_INTERFACE
{
protected:
ULONG m_nRefCount;
public:
CComBase() : m_nRefCount(0) {}
STDMETHODIMP QueryInterface(REFIID riid, void** ppv)
{
if (riid == IID_IUnknown || riid == *piid )
*ppv=this;
else
return *ppv=0,E_NOINTERFACE;
AddRef();
return S_OK;
}
STDMETHODIMP_(ULONG) AddRef()
{
return ++m_nRefCount;
}
STDMETHODIMP_(ULONG) Release()
{
if (!--m_nRefCount)
{
delete this;
return 0;
}
return m_nRefCount;
}
virtual ~CComBase() {}
};
The client then uses it:
class Client
{
CMessageRouterCallback<Client> *callback;
Client(IMessageRouter *messageRouter)
{
callback = new CMessageRouterCallback<this>();
messageRouter->Register(..., callback);
}
void MessageHandler(...) { ... }
}
There's something wrong with how those callbacks are being registered. Possible causes may be:
A direct pointer to the callback manager's interface in the GUI thread, so you're providing direct pointers to STA objects to the callback manager too.
Your Callback instance in the code you added seems to be doing exactly this, it can't blindly call the raw pointer's Release when destroyed.
Your server objects are marshaled with CoCreateFreeThreadedMarshaler (not much different).
With the FTM, you must never use raw pointers, and you must always marshal interface pointers you intend to keep and unmarshal interface pointers you previously kept, preferrably using the GIT. And I mean always, if you intend to keep things safe.
I recommend you keep your server objects in MTA (ThreadingModel="Free") or NA (ThreadingModel="Neutral"), make sure you're accessing them somehow in the GUI thread through CoCreateInstance[Ex] or CoGetClassObject and IClassFactory::CreateInstance (or any other object activation API), and let the "magic" happen. This is as transparent as it can get without using the GIT or manually marshaling things between threads.
I hope this falls within the realm of this forum:
I want to use the windows shell(?) to allow users to select a number of files before allowing my programme to do a few things to them. For this I found the MSDN sample "CommonFileDialogModes" - http://msdn.microsoft.com/en-us/library/windows/desktop/dd940350%28v=vs.85%29.aspx
In the sample under this class:
class CFileOpenBasketPickerCallback : public IFileDialogEvents, public IFileDialogControlEvents
they have this function:
// IFileDialogEvents
IFACEMETHODIMP OnFileOk(IFileDialog *pfd)
{
// if this button is in the "Add" mode then do this, otherwise return S_OK
IFileOpenDialog *pfod;
HRESULT hr = pfd->QueryInterface(IID_PPV_ARGS(&pfod));
if (SUCCEEDED(hr))
{
IShellItemArray *psia;
hr = pfod->GetSelectedItems(&psia);
if (SUCCEEDED(hr))
{
ReportSelectedItems(pfd, psia);
psia->Release();
}
pfod->Release();
}
return S_FALSE; // S_FALSE keeps the dialog up; return S_OK to allow it to dismiss.
}
which calls:
void ReportSelectedItems(IUnknown *punkSite, IShellItemArray *psia)
{
DWORD cItems;
HRESULT hr = psia->GetCount(&cItems);
for (DWORD i = 0; SUCCEEDED(hr) && (i < cItems); i++)
{
IShellItem *psi;
hr = psia->GetItemAt(i, &psi);
if (SUCCEEDED(hr))
{
PWSTR pszName;
hr = GetIDListName(psi, &pszName);
// .. I've cut some of this out for the example
CoTaskMemFree(pszName);
}
psi->Release();
}
}
}
Now I know pszName contains the names of the files selected. So I can add some extra code in to write this to disk. That works fine. But I dont want to write it to disk. I want to pass it back to the original functions that called this. The arguments for ReportSelectedItems can be altered, but IFACEMETHODIMP OnFileOk(IFileDialog *pfd) cannot as it is inherited. Adding a vector& file_names to the argument will stop it compiling.
So how should I deal with this? I could use a global variable for file_names, but everything I am learning about programming is telling me not to. It would be a quick fix, but I worry that would encourage me to be lazy in the future. I find it difficult to read the windows code and I don't really want to delve too much into the details of it. I can't even find what is calling the OnFileOk function, even though I know it is from one of the two base classes.
Do I really need to work at understanding all the library code just to get this one function doing what I'd like? Is there an faster way of going about this?
So to summarize, how would I get information from this inherited function without using a global variable or writing to disk? As I mentined before, I don't have much of a grasp of the code I am working with. And for future reference, how should I deal with this type of situation? I use c++ and would like to avoid c# and c as much as possible.
Thanks as always.
It seems a fairly big omission for Microsoft to have left out any sort of user data associated with the IFileDialog callbacks, but that does seem to be the case.
I'm assuming that simply calling GetSelectedItems() once the dialog returns is something you don't want to do for some reason - because that would obviously be the simplest solution.
From a quick look at the docs one way you may be able to pass data back from the event callback is using the owner window that you pass to IFileDialog::Show() (which is actually IModalWindow::Show()).
In the event handler, you get given the IFileDialog* pointer. From this, you can QI the address of the IOleWindow interface which will give you the dialog's window:
IFACEMETHODIMP OnFileOk(IFileDialog *pfd)
{
CComPtr<IOleWindow> pWindow;
if (SUCCEEDED(pfd->QueryInterface(IID_IOleWindow, reinterpret_cast<void**>(&pWindow))))
{
HWND hwndDlg;
if (SUCCEEDED(pWindow->GetWindow(&hwndDlg)))
{
HWND hwndOwner;
if (hwndOwner = GetWindow(hwndDlg, GW_OWNER))
{
// hwndOwner is the owner window of the dialog
}
}
}
// more code
}
Now assuming that hwndOwner is your own window, you can associate any data you like with it using SetProp()/GetProp() - so you could use this as a mechanism to pass data back from within the callback.
A simple solution was to add member data inside the inherited class and link it from the constructor:
class CFileOpenBasketPickerCallback : public IFileDialogEvents, public IFileDialogControlEvents
{
public:
CFileOpenBasketPickerCallback(vector<wstring>& files) : files_(files)
{
}
// functions
private:
vector<wstring>& files_;
};
When constructing the object
vector<std::wstring> files
CFileOpenBasketPickerCallback foacb(files);
And in IFACEMETHODIMP OnFileOk(IFileDialog *pfd)
ReportSelectedItems(pfd, psia, files_);
ReportSelectedItems is not a member so you can alter the arguments.
I want to add visible window titles to a combobox. Here is my source:
BOOL CALLBACK EnumWindowsProc(HWND hWnd, long lParam)
{
TCHAR buff[255];
CComboBox* pComboBox = (CComboBox*)GetDlgItem(IDC_COMBO_PROCESS);
if (IsWindowVisible(hWnd))
{
GetWindowText(hWnd, buff, 254);
pComboBox->AddString(buff);
}
return TRUE;
}
void CFindProcess::OnDropdownComboProcess()
{
EnumWindows(EnumWindowsProc, 0);
}
but I get error:
error C2660: 'GetDlgItem' : function does not take 1 arguments 60
How I can correctly add titles to combo?
MFC objects are thread-sensitive, GetDlgItem works well in the thread that created the object, probably the main UI thread. Function EnumWindows probably creates a worker thread to access the callback function, and that is why GetDlgItem failed to get a valid handle of the combobox.
To access the combobox properly in another thread, you have to use the static function: CWnd::FromHandle with the raw handle of the combobox object as follows:
BOOL CALLBACK EnumWindowsProc(HWND hWnd, long lParam)
{
if (IsWindowVisible(hWnd))
{ TCHAR szBuffer[255];
INT nLength = GetWindowText(hWnd, szBuffer, 254);
if (nLength>0)
{ // only add windows that has a caption
CComboBox *pComboBox = (CComboBox*)CWnd::FromHandle((HWND)lParam);
pComboBox->AddString(szBuffer);
}
}
return TRUE;
}
// call EnumWindows --------------------
CComboBox *pComboBox = (CComboBox *)GetDlgItem(IDC_COMBO1);
// passing the raw handle of the combobox as parameter
EnumWindows(EnumWindowsProc, (LPARAM)pComboBox->m_hWnd);
Firstly, your GetDlgItem has two parameters, and the first is a handle to the dialog box that contains the control.
So it expects a HWND parameter of the dialog that contains this control, I would presume that will be the HWND you pass as a parameter to your function.
CComboBox* pComboBox = (CComboBox*)GetDlgItem(hWnd,IDC_COMBO_PROCESS);
^^^^ added parameter
If you look at EnumWindows in MSDN, you'll see you have to pass a callback and it has a HWND parameter, if you look at what this parameter is for it says:
A handle to a top-level window.
This is exactly what you have to pass to GetDlgItem.
Also, you should check the return value of GetWindowText as this returns the number of characters written to the buff you passed it.
int ret = GetWindowText(hWnd, buff, 254);
if (ret > 0) pComboBox->AddString(buff); // only add non-empty strings.
In addition to what user #mfc has provided, I would not do UI update from a different thread. I believe EnumWindows does not create thread for enumeration. It would call the callbacks within the call-stack of current thread.
This, in turn, means that UI may freeze for a while. Thus, it is recommended to create a thread for enumeration. More over, I would not directly update UI from different thread. May be a vector of string, or a PostMessage (on each iteration) I would have used.
It is true that EnumWindows may perform quite fast. But when you move to enumerate other (kernel) objects like file, printers, users etc - the UI is definitely going to freeze. So, better practice writing multithreaded code. Initially writing MT-code would be a pain, but later you'd love it, appreciate it, and cannot live without it.