I try to load to plugins. If they are loaded with success , then i must connect returned widgets.
With one plugin i create an action and add it to a menu , with another plugin i create a label and add it to window. Even if i get this error during runtime (when app loads plugins) , these two widgets are created and are visible. But there is no connection between them.
This is how i try to connect widgets
QObject *plugin = pluginLoader.instance();
if (plugin) {
myAction = qobject_cast<ActionInterface *>(plugin);
if (myAction) {
pluginMenu->addAction(myAction->newAction());
verify ++;
}
myLabel = qobject_cast<LabelInterface *>(plugin);
if (myLabel) {
layout->addWidget(myLabel->newLabel());
verify++;
}
if (verify == 2)
connect(myAction, SIGNAL(pushMyAction()),
myLabel, SLOT(setTextforLabel()));
}
...
}
Error message is :
QObject::connect: Cannot connect (null)::pushMyAction() to LabelPlugin::setTextforLabel()
You have two different plugins. Apparently one can be cast to an ActionInterface but not a LabelInterface, and the other can be cast to a LabelInterface but not an ActionInterface.
Your idea here seems to be that once you have both plugins loaded (and a verify count of 2) then it's safe to make a connect call between these plugins. However you appear to be trying to cast the second loaded plugin to serve as both the signal and the slot. This is because each time you run the code you overwrite both myAction and myLabel. So at minimum:
QObject* plugin = pluginLoader.instance();
if (plugin) {
ActionInterface* myActionTemp = qobject_cast<ActionInterface*>(plugin);
if (myActionTemp) {
myAction = myActionTemp;
pluginMenu->addAction(myAction->newAction());
verify++;
}
LabelInterface* myLabelTemp = qobject_cast<LabelInterface*>(plugin);
if (myLabelTemp) {
myLabel = myLabelTemp;
layout->addWidget(myLabel->newLabel());
verify++;
}
/* if (myAction and myLabel) would be less convoluted... */
if (verify == 2) {
connect(myAction, SIGNAL(pushMyAction()),
myLabel, SLOT(setTextforLabel()));
}
...
}
Still, this looks like a fairly brittle design that could use some rethinking...!
QObject *plugin = pluginLoader.instance();
if (plugin) {
if (plugin->inherits("ActionInterface")) {
myAction = qobject_cast<ActionInterface *>(plugin);
pluginMenu->addAction(myAction->newAction());
}
if (plugin->inherits("LabelInterface")) {
myLabel = qobject_cast<LabelInterface *>(plugin);
layout->addWidget(myLabel->newLabel());
}
if (myLabel && myAction)
connect(myAction, SIGNAL(pushMyAction()),
myLabel, SLOT(setTextforLabel()));
}
...
}
Remove this "ugly" verify counter. Remember to initialize myLabel and myAction with NULL
Related
I would like to get notification in main Qt app in case any control in QML (loaded via QQuickWidget) changes its value. There are CheckBox'es, ComboBox'es, SpinBox'es and TextEditor's.
My current approach is to declare a slot for every control (onCheckedChanged, onCurrentIndexChanged, onValueChanged and onTextChanged respectively) and call mainApp.notifyMe() from them, there mainApp is a link to parent Qt Widget propagated to QML with help of
QQmlEngine* engine = new QQmlEngine(this);
if (QQmlContext* cntx = engine->rootContext()) {
cntx->setContextProperty("mainApp", this);
}
and notifyMe() is a slot in it on C++ side.
But this requires dozens of functions with the same code bcs I have dozens of controls. It would be ideal to have one or 4 functions with notifyMe() in QML which could be connected to all controls value changes.
Is there a way in QML to create such slot and connect it to multiple objects property changes?
I've end up with following in my root item:
MySettingsForm {
signal notify()
onNotify: {
mainApp.notifyMe();
}
Component.onCompleted: {
var cbs = [Combobox1, Combobox2, Combobox3];
for (var i in cbs) {
cbs[i].checkedChanged.connect(notify);
}
var sbs = [SpinBox1, SpinBox2, SpinBox3];
for (i in sbs) {
sbs[i].valueChanged.connect(notify);
}
var tes = [TextField1, TextFiel2, TextField3];
for (i in tes) {
tes[i].textChanged.connect(notify);
}
var cxs = [ComboBox1, ComboBox2, ComboBox3];
for (i in cxs) {
cxs[i].currentIndexChanged.connect(notify);
}
}
...
}
The trick is in creation of custom signal notify and slot attached to it.
I am creating some automated GUI tests in my application using QTest.
I can access the widgets from my application using the command:
savePushButton = mainWindow->findChild<QPushButton *>("savePushButton");
It is working fine, but now I have to click on the OK button of a QMessageBox.
I created the QMessageBox in my application like this:
if( something_wrong )
{
QMessageBox::warning(new Widget(), "Title", "Something wrong!");
}
How can I have access to this QMessageBox, and its buttons?
I found a solution on the following link: http://www.qtcentre.org/threads/31239-Testing-modal-dialogs-with-QTestLib .
It uses the command QApplication::topLevelWidgets(); to get a widget list. Then it searches for the message box widget and simulates a key enter (QTest::keyClick(mb, Qt::Key_Enter);) which closes the message box.
Example:
void MyTest::testDialog()
{
QTimer::singleShot(500, this, SLOT(timeOut()));
QVERIFY(functionThatProducesMessageBox());
}
void MyTest::timeOut()
{
QWidgetList allToplevelWidgets = QApplication::topLevelWidgets();
foreach (QWidget *w, allToplevelWidgets) {
if (w->inherits("QMessageBox")) {
QMessageBox *mb = qobject_cast<QMessageBox *>(w);
QTest::keyClick(mb, Qt::Key_Enter);
}
}
}
The header file must contain the Q_OBJECT macro to use the signals and slots mechanism.
Example:
class MyClass: public QWidget
{
Q_OBJECT
public:
...
It worked well for me since the UI (thread) is blocked when the message box appears.
Note: remember to rebuild the project when you add the Q_OBJECT macro.
It often helps to look to Qt's auto tests:
void ExecCloseHelper::timerEvent(QTimerEvent *te)
{
if (te->timerId() != m_timerId)
return;
QWidget *modalWidget = QApplication::activeModalWidget();
if (!m_testCandidate && modalWidget)
m_testCandidate = modalWidget;
if (m_testCandidate && m_testCandidate == modalWidget) {
if (m_key == CloseWindow) {
m_testCandidate->close();
} else {
QKeyEvent *ke = new QKeyEvent(QEvent::KeyPress, m_key, Qt::NoModifier);
QCoreApplication::postEvent(m_testCandidate, ke);
}
m_testCandidate = Q_NULLPTR;
killTimer(m_timerId);
m_timerId = m_key = 0;
}
}
Judging from that code, you can get the message box via QApplication::activeModalWidget(). Testing native (I'm assuming they're native) widgets is difficult, which is likely why they chose to send key events, as you don't need to know e.g. the location of the buttons for those, as you would with a mouse click.
I've added a menu context item to the TreelistEx. This menu item sends a messages that I later catch in a HandleMessage method.
In this method i create a new item ( template type and parent item are given in the source of the treelist field ).
All i need now is a way to ask the user for a name. But i haven't been able to find a simple way to do this.
class MyTreeListEx : TreelistEx, IMessageHandler
{
void IMessageHandler.HandleMessage(Message message)
{
if (message == null)
{ return; }
if (message["id"] == null)
{ return; }
if (!message["id"].Equals(ID))
{ return; }
switch (message.Name)
{
case "treelist:edit":
// call default treelist code
case "mytreelistex:add":
// my own code to create a new item
}
}
}
Does anyone have any suggestions on how to achieve this ?
Edit: added image & code + i'm using Sitecore 8 Update 1
I don't know which version of Sitecore you use but what you can try is SheerResponse.Input method.
You can use it like this:
using Sitecore.Configuration;
using Sitecore.Globalization;
using Sitecore.Shell.Applications.ContentEditor.FieldTypes;
using Sitecore.Web.UI.Sheer;
void IMessageHandler.HandleMessage(Message message)
{
...
case "mytreelistex:add":
Sitecore.Context.ClientPage.Start(this, "AddItem");
break;
}
protected static void AddItem(ClientPipelineArgs args)
{
if (args.IsPostBack)
{
if (!args.HasResult)
return;
string newItemName = args.Result;
// create new item here
// if you need refresh the page:
//SheerResponse.Eval("scForm.browser.getParentWindow(scForm.browser.getFrameElement(window).ownerDocument).location.reload(true)");
}
else
{
SheerResponse.Input("Enter the name of the new item:", "New Item Default Name", Settings.ItemNameValidation,
Translate.Text("'$Input' is not a valid name."), Settings.MaxItemNameLength);
args.WaitForPostBack();
}
}
This code will even validate your new item name for incorrect characters and length.
I'm implementing a DynamicItemStart button inside a Menu Controller. I'm loading the dynamic items for this button when Visual Studio starts. Everything is loaded correctly so the initialize method is called an I see all the new items in this Dynamic button. After the package is completely loaded I want to add more items to this Dynamic button, but since the package is already loaded the initialize method is not called again and I cannot see the new items in this Dynamic button. I only see the ones that were loaded when VS started.
Is there any way that I can force the update of this Dynamic button so it shows the new items?. I want to be able to update the VS UI after I added more items but outside the Initialize method.
The implementation I did is very similar to the one showed on this msdn example:
http://msdn.microsoft.com/en-us/library/bb166492.aspx
Does anyone know if an Update of the UI can be done by demand?
Any hints are greatly appreciated.
I finally got this working. The main thing is the implementation of a derived class of OleMenuCommand that implements a new constructor with a Predicate. This predicate is used to check if a new command is a match within the DynamicItemStart button.
public class DynamicItemMenuCommand : OleMenuCommand
{
private Predicate<int> matches;
public DynamicItemMenuCommand(CommandID rootId, Predicate<int> matches, EventHandler invokeHandler, EventHandler beforeQueryStatusHandler)
: base(invokeHandler, null, beforeQueryStatusHandler, rootId)
{
if (matches == null)
{
throw new ArgumentNullException("Matches predicate cannot be null.");
}
this.matches = matches;
}
public override bool DynamicItemMatch(int cmdId)
{
if (this.matches(cmdId))
{
this.MatchedCommandId = cmdId;
return true;
}
this.MatchedCommandId = 0;
return false;
}
}
The above class should be used when adding the commands on execution time. Here's the code that creates the commands
public class ListMenu
{
private int _baselistID = (int)PkgCmdIDList.cmdidMRUList;
private List<IVsDataExplorerConnection> _connectionsList;
public ListMenu(ref OleMenuCommandService mcs)
{
InitMRUMenu(ref mcs);
}
internal void InitMRUMenu(ref OleMenuCommandService mcs)
{
if (mcs != null)
{
//_baselistID has the guid value of the DynamicStartItem
CommandID dynamicItemRootId = new CommandID(GuidList.guidIDEToolbarCmdSet, _baselistID);
DynamicItemMenuCommand dynamicMenuCommand = new DynamicItemMenuCommand(dynamicItemRootId, isValidDynamicItem, OnInvokedDynamicItem, OnBeforeQueryStatusDynamicItem);
mcs.AddCommand(dynamicMenuCommand);
}
}
private bool IsValidDynamicItem(int commandId)
{
return ((commandId - _baselistID) < connectionsCount); // here is the place to put the criteria to add a new command to the dynamic button
}
private void OnInvokedDynamicItem(object sender, EventArgs args)
{
DynamicItemMenuCommand invokedCommand = (DynamicItemMenuCommand)sender;
if (null != invokedCommand)
{
.....
}
}
private void OnBeforeQueryStatusDynamicItem(object sender, EventArgs args)
{
DynamicItemMenuCommand matchedCommand = (DynamicItemMenuCommand)sender;
bool isRootItem = (matchedCommand.MatchedCommandId == 0);
matchedCommand.Enabled = true;
matchedCommand.Visible = true;
int indexForDisplay = (isRootItem ? 0 : (matchedCommand.MatchedCommandId - _baselistID));
matchedCommand.Text = "Text for the command";
matchedCommand.MatchedCommandId = 0;
}
}
I had to review a lot of documentation since it was not very clear how the commands can be added on execution time. So I hope this save some time whoever has to implement anything similar.
The missing piece for me was figuring out how to control the addition of new items.
It took me some time to figure out that the matches predicate (the IsValidDynamicItem method in the sample) controls how many items get added - as long as it returns true, the OnBeforeQueryStatusDynamicItem gets invoked and can set the details (Enabled/Visible/Checked/Text etc.) of the match to be added to the menu.
Issue comes up when i try to unload plugin that is loaded and load a new one. So both plugins are loaded correctly, but when switching them (first is loaded, second is unloaded and viceversa) my app crashes. What can be the problem ?
First what i'm doing i try to unload a plugin stored into a QList of QPluginLoader, then i check (depdend on id(integer number) passed from a special menu for loading plugins ) what plugin to load. First load is well (first plugin is loaded, nothing at this point to unload) , second load (unload first plugin, second is loaded), at third load i get crash
void MainWindow::loadPluginUsingId (int plugin_id) {
foreach (QPluginLoader* pluginLoader, plugins) {
pluginLoader->unload();
delete pluginLoader;
}
switch (plugin_id) {
case 0 : {
foreach (QString fileName, pluginDir.entryList(QDir::Files)) {
if (fileName == fullNameOfPlugins.value(plugin_id)) {
QPluginLoader* pluginLoader = new QPluginLoader(pluginDir.absoluteFilePath(fileName));
QObject *plugin = pluginLoader->instance();
IndicatorInterface *indicator = qobject_cast<IndicatorInterface*>(plugin);
indicator->initIndicator();
plugins.append(pluginLoader);
}
}
}
break;
case 1 : {
foreach (QString fileName, pluginDir.entryList(QDir::Files)) {
if (fileName == fullNameOfPlugins.value(plugin_id)) {
QPluginLoader* pluginLoader = new QPluginLoader(pluginDir.absoluteFilePath(fileName));
QObject* plugin = pluginLoader->instance();
PlotterInterface *plotter = qobject_cast<PlotterInterface*>(plugin);
plotter->initPlotter();
plugins.append(pluginLoader);
}
}
}
break;
default :
break;
}
}
foreach (QPluginLoader* pluginLoader, plugins) {
pluginLoader->unload();
delete pluginLoader; // this could be your problem
}
You need to remove the dangling pointer from the plugins list. Failure to do that would result in what you're describing.
Try this:
while (!plugins.isEmpty()) {
QPluginLoader* pluginLoader = plugins.takeFirst();
pluginLoader->unload();
delete pluginLoader;
}