Trouble getting `QWidget* editor` for a `QModelIndex` - qt

I am having trouble augmenting the default edit behavior of QTableView. I want the following behavior when the Enter key is pressed:
Start editing the current cell if it is not already being edited.
If the cell is being edited,
2a. commit the data and close the editor. Then,
2b. make the cell below, if present, the current cell.
2a is the default behavior, and 2b can likely be achieved by using QAbstractItemView::setCurrentIndex() in a re-implementation of QItemDelegate::eventFilter() (as suggested here in a similar context).
The problem is in achieving 1. I list below the approaches I have tried till now.
Reconfigure the "platform edit key" By default, "Editing starts when the platform edit key has been pressed over an item." (QAbstractItemView::EditKeyPressed) This key is F2 on my platform (Ubuntu 12.04). I could reconfigure the platform edit key to Enter but
Altering platform defaults seems like a bad idea.
I could not find out how to do it.
Capture the Enter key press I use QShortCut to do this as follows:
class CourseTable : public QTableView {
/* ... */
};
/* ... */
CourseTable::CourseTable(/* ... */) {
/* ... */
QShortcut* shortcut = new QShortcut(QKeySequence(Qt::Key_Return), this);
connect(shortcut, SIGNAL(activated()), this, SLOT(handleEnter_()));
/* ... */
}
/* ... */
void CourseTable::handleEnter_() {
QModelIndex idx = this->currentIndex();
if (this->state() != QAbstractItemView::EditingState)
this->edit(idx);
/* else // see below */
}
This does capture the Enter key-press and accomplishes 1 (from above) but now 2 is broken. So, I need to look into the else clause in CourseTable::handleEnter_() above, possibly calling QAbstractItemView::commitData() and QAbstractItemView::closeEditor in it. The problem is that both these functions require a QWidget *editor argument which I just cannot figure out how to get. I could subclass QAbstractItemDelegate, add a getEditor() method to the derived class, and modify existing code to pass instances of the derived delegate class to CourseTable::setItemDelegate*() functions. But that sounds like too much work.
So, any ideas how I can cleanly accomplish both 1 and 2 without having to rewrite my code?

Why cant you just filter the event also for starting the edit?
Just handle the event if state is != QAbstractItemView::EditingState
Returning true in this function makes the event stop propagating to the filtered object.
If state is Editing you can just return falseand allow the table and editor continue processing the event.
Something like this:
bool FilterObject::eventFilter(QObject *object, QEvent *event)
{
if (object == tableView && event->type() == QEvent::KeyPress) {
QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
if (keyEvent->key() == Qt::Key_Return && tableView->state() != QAbstractItemView::EditingState) {
// set current cell to edit
return true;
} else
return false;
}
return false;
}

Related

Multiple OnSaving event in DevExpress XAF

Im working on a piece of code using DevExpress XAF, I noticed that if im using the event OnSaving that the code executes 2 times, how can i prevent that
protected override void OnSaving()
{
if (PrestamoP != null)
{
PrestamoP.Prestado -= Monto;
PrestamoP.Save();
}
else if (PrestamoG != null)
{
PrestamoG.Prestado -= Monto;
PrestamoG.Save();
}
base.OnSaving();
}
XPO does not guarantee that the OnSaving method is called once. See the corresponding note in the XPO Best Practices article.
I can see that you are changing the PrestamoP.Prestado property based on the value of the Monto property. This code is fine if you execute it only once and only when the Monto property is specified for the first time. This code is not fine if you:
Save this object without changing the Monto property;
Update the early specified Monto value.
So, it appears that a more complex logic is required for the PrestamoG.Prestado property. First, I would move it to the Monto property setter and take the previous value into account (do not forget to check the IsLoading property in this case). Second, I would consider calculating the Prestado value dynamically instead of storing its value. This will allow you to resolve issues with the duplicate business logic execution. See an example here: How to: Calculate a Property Value Based on Values from a Detail Collection.
I can offer different methods for CRUD functions on onSaving method.
IsNewObject, IsDeleted.
// insert
if (Session.IsNewObject(this))
{
a = new a(Session);
a.CreateReferencedProperties(this);
}
// delete
else if (IsDeleted)
{
a= Session.FindObject<A>(PersistentCriteriaEvaluationBehavior.InTransaction, CriteriaOperator.Parse("A=?", this));
if (a!= null)
a.Delete();
}
// update
else
{
a= Session.FindObject<A>(PersistentCriteriaEvaluationBehavior.InTransaction, CriteriaOperator.Parse("A=?", this));
if (a!= null)
a.CreateReferencedProperties(this);
}
You can use the code below to prevent xaf from entering on saving twice.
base.OnSaving();
SessionObjectLayer sessionObjectLayer = this.Session.ObjectLayer as SessionObjectLayer;
if (sessionObjectLayer == null || sessionObjectLayer.ParentSession == null)
{
//Enter only once
}

Update QMessageBox continuously within specific function in Qt

I have a program which when it runs, at first the user is asked to initialize the system. In that question form, there are 3 checkboxes that the user can check them for specific person or every persons and the system initializes the items related to that checkbox for the person(s).
When a checkbox is selected, a specific function and subsequently the specific class is called and initialization is done.
In the mainwindow.cpp I have:
InitializeDialog *id=new InitializeDialog;
connect(id,&InitializeDialog::initializeSignal,this,&MainWindow::initializeSlot);
id->exec();
id is the question form which has 3 checkboxes in it. And:
void MainWindow::initializeSlot(QStringList persons, bool interests, bool plots, bool graphs)
{
initializeMBox->setWindowTitle(tr("Initializing System")+"...");
initializeMBox->setText(tr("Please wait until initialization has been done") + ".<br>");
initializeMBox->show();
initializeMBox->setStandardButtons(0);
if (interests)//checkbox 1 is checked
initializeInterests(persons);
if (plots)//checkbox 2 is checked
initializePlots(persons);
if(graphs)//checkbox 3 is checked
initializeGraphs(persons);
initializeMBox->setStandardButtons(QMessageBox::Ok);
}
And again:
void MainWindow::initializeInterests(QStringList persons)
{
for(int p=0;p<persons_comboBox_->count();p++)
{
persons_comboBox_->setCurrentIndex(p);
if (persons.contains(persons_comboBox_->currentText()))
{
//..
//create a specific class object and some specific functions
//..
//*
initializeMBox->setText(initializeMBox->text() + "<div><img src=\":/tickIcon.png\" height=\"10\" width=\"10\">" + " " + tr("Interests analyzed for the persons") + ": " + persons_comboBox_->currentText() + ".</div>");
}
}
}
initializePlots and initializeGraphs are similiar to initializeInterests.
The problem starts from here:
I want to show a message after initialization for every person (as I mentioned by star in initializeInterests) but my initializeMBox (is a QMessageBox) does not show the message continuously and when all persons are initialized, all messages are shown suddenly. It should be noted that I see my initializeMBox is getting bigger but it seems that my QMessageBox is Freezed.
I can't use QtConcurrent::run because my QMessageBox is updated from mainwindow (and so from the base thread) by the line that I mentioned by star.
How can I have a QMessageBox which be updated continuously?
Don't reenter the event loop. Replace id->exec() with id->show(). Manage the dialog's lifetime - perhaps it shouldn't be dynamically created at all.
Don't block in initializeInterests. Instead of changing the combo box, get its data, send it out to an async job, set everything up there, then send the results back.
Pass containers by const reference, not value.
Don't create strings by concatenation.
If the input persons list is long, sort it to speed up look-ups.
For example:
class StringSignal : public QObject {
Q_OBJECT
public:
Q_SIGNAL void signal(const QString &);
};
void MainWindow::initializeInterests(const QStringList &personsIn) {
auto in = personsIn;
std::sort(in.begin(), in.end());
QStringList persons;
persons.reserve(in.size());
for (int i = 0; i < persons_comboBox_->count(); ++i) {
auto const combo = persons_comboBox->itemText(i);
if (std::binary_search(in.begin(), in.end(), combo))
persons << combo;
}
QtConcurrent::run([persons = std::move(persons), w = this](){
StringSignal source;
connect(&source, &StringSignal::signal, w, [w](const QString & person){
w->initalizeMBox->setText(
QStringLiteral("%1 <div><img src=...> %2: %3.</div>")
.arg(w->initalizeMBox->text())
.arg(tr("Interests analyzed for the person"))
.arg(person)
);
});
for (auto &person : persons) { // persons is const
// create a specific object etc.
QThread::sleep(1); // let's pretend we work hard here
source.signal(person);
}
});
}
The creation of the "specific objects" you allude to should not access anything in the gui thread. If it doesn't - pass a copy of the required data, or access it in a thread-safe manner. Sometimes it makes sense, instead of copying the data, move it into the worker, and then when the worker is done - move it back into the gui, by the way of a lambda.

How to refresh Factbox

I have a form, when i click on my button.It adds to my table A (what my factbox shows)is it possible to refresh the factbox with X++ code? I can't figure out how to refresh my infopart or query which factbox uses.
For an infopart you can call an update of the data source of the infopart's form run:
void clicked()
{
PartList partList;
int i;
FormRun infoPartFormRun;
FormDataSource infoPartDataSource;
super();
partList = new PartList(element);
for (i = 1; i <= partList.partCount(); i++)
{
infoPartFormRun = partList.getPartById(i);
if (infoPartFormRun.name() == identifierStr(MyInfoPart))
{
infoPartDataSource = infoPartFormRun.dataSource();
if (infoPartDataSource)
{
infoPartDataSource.research();
}
}
}
}
I added the check for the infoPartDataSource because I first tested this with a cue group fact box, which does not have a data source (or at least I could not figure out how to get the data source of one of the cues in the cue group and since you asked for an infopart fact box, I did not investigate further).
Update: The issue seems to be popular at the moment, Martin DrĂ¡b also wrote in his blog about it: Refreshing form parts

Disabling F5 key on ListPage AX 2012

I want to disable the F5 refresh on a specific listpage, I disabled the refresh action key but the F5 still works. I can't overide the Task method on the SysSetupFormRun because it's a listpage. Is any way to do this?
The reason I want to do this is because some refreshproblems when having multiple datasources and I want to disable it for the customer until microsoft comes with a solution (issue is already logged).
Edit:
As a fix I intercepted the task method on the SysSetupFormRun class, but this method only triggers when you have selected a record in your listpage. When you first open your listpage and immediately press F5 it does refresh and doesn't go in the task method.
I know you mentioned that you had no success with overwriting task of SysSetupFormRun, but the code below worked for me in our test system.
I inserted this code into method task of class SysSetupFormRun to disable F5 for SalesTableListPage:
public int task(int _p1)
{
#task
FormDataSource formDataSource;
int ret;
if (_p1 == #taskFilter)
{
formDataSource = this.objectSet();
if (formDataSource &&
formDataSource.queryRun() &&
formDataSource.queryRun().args() &&
!formDataSource.queryRun().args().caller())
{
formDataSource.queryRun().args().caller(this);
}
}
// -- Here is the interesting stuff
if ((_p1 == #taskF5 || _p1 == #taskRefresh) &&
(this.name() == formStr(SalesTableListPage)))
{
return ret;
}
return super(_p1);
}
If the approach is the same as the one you already tried, then you can ignore this answer - otherwise feel free to try it out ;)

Qt: two actions in menu (with the same text)

I create a menu dynamically. I add several checkable actions into one menu. Sometimes actions may have the same text that user sees. It's up to user (actually user adds commands into menu).
The problem is in this case clicking works wrong. If I click on the first action (from 2 with the same texts) everything is good but if I click on the second one, both actions are selected. I don't understand why. The code where actions have been created is here:
for (int i = 0; i< currentList.size(); i++)
{
QString lanKey = currentList.at(i)->Language->toString();
QAction* lanAction = new QAction(this);
QString name ="action_" + currentList.at(i)->Id->toString();
lanAction->setObjectName(name);
lanAction->setText(lanKey);
lanAction->setCheckable(true);
lanAction->setData(i);
connect(lanAction, SIGNAL(triggered(bool)), this, SLOT(ShowSomething(bool)));
ui->menuMy->addAction(lanAction);
}
Here, lanKey is language that may be the same for different actions. Anyway click on the specific action should lead only to checking of this action. What's wrong?
The slot is here:
void VMainWindow::ShowSomething(bool IsTriggered)
{
QAction* senderAction = (QAction*)sender();
int listIndex = senderAction->data().toInt();
if (IsTriggered)
{
CreateEditor(subtitles, listIndex);
}
else
{
//hide this editor
QString name = "editor" + editorsList->Id->toString();
QDockWidget* editorDock = this->findChild<QDockWidget*>(name);
if (editorDock != 0)
{
this->removeDockWidget(editorDock);
this->setLayout(layout());
}
}
}
Thanks
The source of problem is found: it turned out that the slot finds the checked action wrong - by text, not by id.
I can't find a logical issue in the code you posted so far. Here are a couple of options which I would try in order to resolve this problem:
Limit the users possibilities when adding items to a menu so that he can't add two items with the same name.
Add qDebug() output to ShowSomething to see if there is a problem with signals&slots. For example, if the slot gets called once for the first item but twice for the second item there is a problem there.
Debug into CreateEditor step-by-step.
As the problem seems to appear only for actions with a similar name, you should make sure that you never make a lookup of an action (or something related) by its text() but rather by its data() or objectName() (assuming that currentList.at(i)->Id will always be unique)

Resources