switch über QObject.inherits

Alles rund um die Programmierung mit Qt
Antworten
mastershybby
Beiträge: 31
Registriert: 24. Dezember 2008 23:10

switch über QObject.inherits

Beitrag von mastershybby »

Hallo,

ich versuche nun schon seit einiger Zeit eine Sinnvolle struktur in mein Programm zu bringen. Dabei habe ich folgendes Problem:
Ich habe mehrere QWidgets (z.B. QPushButton, QSpinBox etc) welche auf einen slot aufrufen. In diesem Slot sollte dann Objektspezifisch gehandelt werden.
Nun schön wäre natürlich ein Switch case welches alle Objekte beinhaltet. Da dies aber nicht von QT/C++ unterstützt wird, wollte ich fragen ob jemand eine bessere Idee, als 100 if-else Abfragen, hat?

Gruss
mastershybby
franzf
Beiträge: 3114
Registriert: 31. Mai 2006 11:15

Beitrag von franzf »

100 Unterschiedliche Widgets auf einen einzigen SLOT connecten und dann den Sender (aus den 100) wieder zu ermitteln um was spezifisches zu machen ist irgendwie auch kein so günstiges Design. Kannst du das nicht irgendwie besser managen?
Ansonsten gehen für switch keine Klassentypen, switch braucht einen integralen Typ (char, int, ...). Wenn du also wirklich dein Konstrukt beibehalten willst, gehts mit if/else.
AuE
Beiträge: 918
Registriert: 5. August 2008 10:58

Beitrag von AuE »

Was soll denn Qt da nicht unterstützen????

Code: Alles auswählen

	if(qobject_cast<QPushbutton *>(QObject::sender()) || (qobject_cast<QAction *>(QObject::sender())  == ui->action_Beenden))
mastershybby
Beiträge: 31
Registriert: 24. Dezember 2008 23:10

Beitrag von mastershybby »

Zuerst vielen Dank für die Antworten.

@franzf:
das bessere managen sehe ich nicht, da ich eine Art Messageing-System aufbaue. Dabei könnten Messages von allen Objekten kommen und müssen dann verschieden verarbeitet werden. Ebenfalls ist nicht bekannt wieviele Objekte senden. Deshalb wird ein klareres Managen meiner Meinung nach nicht möglich sein.

@AuE:
Wie auch schon franzf schreibt, unterstützt QT bzw. C++ nur Switch mit integralen Typen (char, int, ...) aber eben nicht QObjects o.ä.
Das ich mit if und else operieren kann ist klar aber nicht schön (Code-Style usw.).
franzf
Beiträge: 3114
Registriert: 31. Mai 2006 11:15

Beitrag von franzf »

mastershybby hat geschrieben:das bessere managen sehe ich nicht, da ich eine Art Messageing-System aufbaue. Dabei könnten Messages von allen Objekten kommen und müssen dann verschieden verarbeitet werden. Ebenfalls ist nicht bekannt wieviele Objekte senden. Deshalb wird ein klareres Managen meiner Meinung nach nicht möglich sein.
Es geht immer besser. Und wenn du dir deine Beschreibung der Problematik nochmal ansiehst, kommst du vielleicht darauf, wie man das machen tut:

"Ich möchte je nach Klasse eine andere Aktion ausführen".
(inherits bezieht sich ja nur auf Klassen)

Na, klingelts? Genau, Polymorphie ;)
Definier dir ein Interface mit einer virtuellen Funktion "putMessage(Messager& m)", und alle deine Klassen, die sich am Messaging-System beteiligen wollen, implementieren diese Funktion dann. Da braucht es nichtmal QObject + SIGNAL/SLOT.
Im Übrigen wüsste ich jetzt nicht, wie du eine Message von einem QPushButton speziell behandeln willst. Da solltest du dir vllt. auch mal "QObject::objectName" anschauen.

Sollte das immer noch nicht das sein was du dir wünschst poste doch mal konkret was das für "Messages" werden sollen (SIGNAL/SLOT ist ja an sich schon ein Message-Sytem), wieso ein QPushButton/QLineEdit eine Message senden können soll, etc.

Dein Versuch über ihnerits() ist auch nicht mal ungefährlich, da du bei der Reihenfolge der if/else GENAU aufpassen musst, dass du auch die Vererbungshierarchie korrekt auffängst. Ein QWidget vor einem QPushButton abgefragt und du wirst dich wundern dass keine Messages eines QPushButtons auftauchen :P
mastershybby
Beiträge: 31
Registriert: 24. Dezember 2008 23:10

Beitrag von mastershybby »

@franzf:
ok ich sehe schon du drängst mich meinen Programmaufbau nochmals zu überarbeiten ;-).

Gut wenn ich schon dabei bin kann ich das ganze Problem auch ein Wenig herunterbrechen. Dazu ein vereinfachtes Beispiel welches auf das gleiche Problem führt:

Aufgabe soll es sein, alle Widgets(QPushButton QLineEdit usw.) welche sich auf dem MainWindow befinden bei deren Änderung bestimmt einzufärben.
Zumbeispiel: Alle QPushButtons werden beim Drücken Rot. Alle LineEdits beim bearbeiten Grün, alle SpinBoxen Blau usw.

Meine Lösung wäre ein gemeinsamer Slot:

Code: Alles auswählen

void MainWindow::colorSlot(){
  QObject *o = QObject::sender();
  if(o->inherits("QLineEdit")){
     ((QLineEdit*)o)->setPalette(green);
  }
  else{
    if(o->inherits("QSpinBox")){
       ((QSpinBox*)o)->setPalette(blue);
    }
    else{
      if(o->inherits("QPushButton")){
         ((QPushButton*)o)->setPalette(red);
      }
    }
  }
}
Der ganze connect ablauf lautet dann:

Code: Alles auswählen

widgets = this->findChildren<QWidget *>();
 for(int i = 0; i<widgets.size(); i++) {
 if(widget[i]->inherits("QLineEdit")){
 connect(((QLineEdit *)widgets[i]),SIGNAL(textEdited(QString)),this,SLOT(colorSlot()));
 ...(weiter mit den anderen Widgets)
  }
 }
Ist das nicht geschickt?? und wenn Nein Was könnte ich besser machen?
franzf
Beiträge: 3114
Registriert: 31. Mai 2006 11:15

Beitrag von franzf »

Code: Alles auswählen

class WidgetChanger {
public:
    void change(QLineEdit* w) { w->setPalette(lineEditPalette); }
    void change(QPushButton* w) { w->setPalette(pushButtonPalette); }
};
wäre eine Option. In Kombination mit QSignalMapper sollte das RuckZuck fertig sein.
Vorteil: Es ist schneller als mit Strings vergleichen und sicherer. Vor allem sparst du dir die casts (die BTW. C sind, und C-Casts sind in C++ böse -> dynamic_cast/static_cast resp. qobject_cast verwenden)

// edit:
Sry, ist natürlich Mist :( Du hast ja am Ende nur ein QObject*/QWidget*, und da funktiert die Auflösung nicht. Müsstest du wieder mit className+cast ran. Da du die Widgets (den Code) nicht selber verändern kannst um eine virtuelle Methode hinzuzufügen, musst du wohl mit dem className arbeiten...
Wie viele Widgetklassen sollen denn unterschieden werden?
mastershybby
Beiträge: 31
Registriert: 24. Dezember 2008 23:10

Beitrag von mastershybby »

Dann würde dein SignalMapper-part etwa so aussehen??

Code: Alles auswählen

QPushButton* buttons = this->findChildren<QPushButton *>();
 for(int i = 0; i<buttons.size(); i++) { 
  connect(buttons[i], SIGNAL(clicked()), signalMapper, SLOT(map()));
 
  signalMapper->setMapping(button[i], button[i]);
}
//(Wiederholung für alle anderen möglichen widgets)

connect(signalMapper, SIGNAL(mapped(QWidget *)), this, SLOT(change( QWidget *)));
mastershybby
Beiträge: 31
Registriert: 24. Dezember 2008 23:10

Beitrag von mastershybby »

// edit:
Sry, ist natürlich Mist Sad Du hast ja am Ende nur ein QObject*/QWidget*, und da funktiert die Auflösung nicht. Müsstest du wieder mit className+cast ran. Da du die Widgets (den Code) nicht selber verändern kannst um eine virtuelle Methode hinzuzufügen, musst du wohl mit dem className arbeiten...
Wie viele Widgetklassen sollen denn unterschieden werden?
Nun ja ich schätze so:
QLineEdit
QSpinBox
QCheckBox
QGroupBox
QTextEdit

Dies sind sicher mal die HauptWidgets die Unterstützt werden müssen.
solarix
Beiträge: 1133
Registriert: 7. Juni 2007 19:25

Beitrag von solarix »

Ich bin mir immer noch nicht sicher was du genau brauchst.. das Problem "ich-bin-ein-LineEdit-und-werde-nach-dem-Editieren-Rot" (das Objekt hat schon alle Infos) ist nicht dasselbe wie "ich-bin-ein-Button-und-auf-Klick-sollen-sich-alle-anderen-LineEdits-Rot-färben" (Message-System).

Wenn du an einer Basis-Software arbeitest, welche häufig wiederverwendet wird (weil das Verhalten mit der Einfärbung "Firmenstandard" ist), würde ich eigene Designer-Plugins machen. Dies hätte folgende Vorteile
- das MainView muss nichts davon wissen
- im konkreten Beispiel kann die Farbe von LineEdit zu LineEdit unterschiedlich sein und kann im Designer gewählt werden
- es können beliebige weitere "Firmenspezifische"-Properties eingebaut werden
- In einem gemeinsamen Interface lassen sich beliebige andere Funktionen unterbringen (auch ein Message-System)

Wenn du das nicht brauchst (weil es einmalig ist) und daher basteln möchtest: kein Problem.. aber um Casts wirst du nicht herumkommen. Du kannst das Gebastel nur noch etwas mehr oder weniger elegant hinkriegen:

Im konkreten Beispiel könntest du evt. ein "Typ-auf-Palette-Mapping" machen.. so ungefaehr:

Code: Alles auswählen

  // CTor
  lineedits = this->findChildren<QLineEdits*>();
  foreach..
      signalMapper->setMapping(edit, red); 
  
 // im slot:
 void ... ::colorSlot(const QPalette &p) {
    QWidget *w = dynamic_cast<QWidget*>(sender()); Q_ASSERT(w);
   w->setPalette(p);
}
Falls sowas möglich ist könnte das Aufsetzen des Mappings noch in eine Template-Methode verpackt werden, worauf sich der Ctor verkleinert:

Code: Alles auswählen

  setupMapping<QLineEdit*>(red);
  setupMapping<QCheckBox*>(green);
  ...
Falls die Palette nur ein Beispiel war und du mehrere Slots hast, könntest auch die Operation in typenspezifische Klassen auslagern und diese z.B. in einem QMap sammeln. Z.B.

Code: Alles auswählen

  // CTor:
  // mOperations ist ein QMap<QString,AbstractOperations*>
  mOperations.insert("QLineEdit",new LineEditOperations());

  // Slot 1:
  void ...:colorSlot() {
    Q_ASSERT(mOperations.contains(sender()->metaObject()->className()));
    mOperations[sender()->metaObject()->className()]->setColor(sender());
  }

  // in LineEditOperations:
  voi LineEditOperations::setColor(QObject *o) {
    QLineEdit *e = dynamic_cast<QLineEdit*>(o); Q_ASSERT(e);
    e->setPalette(red);
    e->nochAndereAufgaben...
  }
Aber eben... alles kein schönes OOP..

hth..
mastershybby
Beiträge: 31
Registriert: 24. Dezember 2008 23:10

Beitrag von mastershybby »

@solarix:
Also das color Beispiel führt eigentlich nur auf das selbe problem. Es ist einfach nur einfacher zu verstehen, aber ich versuchs nochmals über meinen code:
Mein Hauptproblem liegt im "Sortieren von QObjects.
Stell dir vor, du hast eine Funktion welche Messages empfängt. Diese Messages beinhalten das empfänger Widget. Zum Beispiel:
message1 msg"12" QWidget"spin_box_2"
message2 msg"Hallo" QWidget"line_edit_10"
usw.

Was ich nun machen muss ist ja offensichtlich falls es ein line_edit widget ist, soll die msg mit setText gesetzt werden und falls es ein spin_box widget ist, soll die msg mit setValue gesetzt werden.

Gleichzeitig sollen alle Widgets beim editieren rot werden und bei fertiger editierung wieder eine msg absetzten (was zum gleichen Problem wie beim empfangen führt).
franzf
Beiträge: 3114
Registriert: 31. Mai 2006 11:15

Beitrag von franzf »

Ich denke es geht ohne casts, usw...

Code: Alles auswählen

void sendMessage(int value, QWidget* widget) {
    widget->setProperty("value", value);
}

void sendMessage(QString text, QWidget* widget) {
    widget->setProperty("text", text);
}

void sendMessage(bool val, QWidget* widget) {
    widget->setProperty("checked", cal);
}
Alles über Properties ;)
Ein Widget hat eh ne Palette. Aber das würde ich nicht an dieses Message-System koppeln. Das solltest du intern im MainWindow regeln. Editieren ist normalerweise fertig, wenn ein "save"-Button geklickt wurde. Dann würde ich einen bool setzen "saved" auf true. Dann an das SIGNAL QApplication::focusChanged hängen, und da entsprechend Palette neu setzen, wenn gerade saved auf true ist.
mastershybby
Beiträge: 31
Registriert: 24. Dezember 2008 23:10

Beitrag von mastershybby »

ok das sieht nach einer sehr schönen Lösung aus, aber jetzt wird die unterscheidung anhand der zuübergebenden datentyps gemacht... das in meinem fall nicht brauchbar, da ich bei einer combobox z.b. ein int index übergebe und bei der spinbox ebenfalls ein int.

aber wie sieht das aus, wenn ich zwar in der in der message ein WidgetPointer ablege, beim aufruf zur bearbeitung jedoch eine QWidget spezifische routine schreibe?
Etwa so:

Code: Alles auswählen



msguser.MsgAnswer(msg, msg.widget);

msguser::MsgAnswer(Msg* msg, QLineEdit*){...};
msguser::MsgAnswer(Msg* msg, QSpinBox*){...};
msguser::MsgAnswer(Msg* msg, QCheckBox*){...};
msguser::MsgAnswer(Msg* msg, QComboBox*){...};
etc.
würde das gehen? oder verliert meine Msg die widget-typen-information?
oder ist das genau das was du vorhin gesagt hast würde nicht gehen wegen dem auflösen?
Antworten