[C++/Qt/SFML] SFML in ein QFrame sperren

cwriter

Erfahrenes Mitglied
Hallo Welt

Beim Versuch, SFML in ein QFrame einzubetten (Weil Qt sich standhaft weigert, teils als Thread, teils integriert zu laufen), bin ich zu einem Punkt gekommen, wo ich schlicht nicht mehr weiter weiss.
Gemäss der offiziellen Dokumentation (die allerdings uralt ist und daher einige Anpassungen benötigte), habe ich die Klassen gekapselt und mit Vererbung das SFML-Fenster hin- und hergeschoben.
Soweit, so gut. Doch nun: Sämtliche events (trotz StrongFocus) werden im SFML-Fenster einfach ignoriert. Zudem führt ein resizen des Fensters zu einem netten kleinen Crash, der insofern ärgerlich ist, da der Fehler in der Qt-DLL auftritt, ich also weder Funktionsname noch sonstwas erkennen kann.
Ein bisschen Code (auf die meiner Meinung nach wichtigen Bereiche gekürzt):
C++:
class QSFMLCanvas : public QWidget, public sf::RenderWindow
{
public :

    explicit QSFMLCanvas(QWidget* Parent, const QPoint& Position, const QSize& Size, unsigned int FrameTime = 0);

	virtual ~QSFMLCanvas(){};

private :

	virtual void OnInit(){};

	virtual void OnUpdate(){};

    virtual QPaintEngine* paintEngine() const;

    virtual void showEvent(QShowEvent*);

	virtual void paintEvent(QPaintEvent*);

    QTimer myTimer;
    bool   myInitialized;
};

class MyCanvas : public QSFMLCanvas
{
public :

    MyCanvas(QWidget* Parent, const QPoint& Position, const QSize& Size) :
    QSFMLCanvas(Parent, Position, Size)
    {
		this->window = NULL;
    }
	   void OnInit();

    void OnUpdate();

	void resizeEvent(QResizeEvent* event);
	//void paintEvent(QPaintEvent*);

public slots:
	void resized(sf::Vector2i r);

private :
	sf::RectangleShape mark;
	DLLLoader loader;
	sf::Clock cl;
	page pagesprite;
	double zoom;
	sf::View view;
	int Progress;
	bool mousePressed;
	sf::Vector2i oldpos;
	QWidget* mainw;
	sf::Vector2f oldMousePos;
	Textures textures;
	QMenu* menu;
	sf::RenderWindow* window;


};
C++:
QSFMLCanvas::QSFMLCanvas(QWidget* Parent, const QPoint& Position, const QSize& Size, unsigned int FrameTime) :
QWidget       (Parent),
myInitialized (false)
{
    // Setup some states to allow direct rendering into the widget
    setAttribute(Qt::WA_PaintOnScreen);
    setAttribute(Qt::WA_OpaquePaintEvent);
    setAttribute(Qt::WA_NoSystemBackground);

    // Set strong focus to enable keyboard events to be received
    setFocusPolicy(Qt::StrongFocus);

    // Setup the widget geometry
    move(Position);
    resize(Size);

    // Setup the timer
    myTimer.setInterval(FrameTime+1);
	myTimer.setSingleShot(false);
}


void QSFMLCanvas::showEvent(QShowEvent*)
{
    if (!myInitialized)
    {
        // Under X11, we need to flush the commands sent to the server to ensure that
        // SFML will get an updated view of the windows
        #ifdef Q_WS_X11
            XFlush(QX11Info::display());
        #endif

        // Create the SFML window with the widget handle
		this->sf::RenderWindow::create((HWND)winId());

        // Let the derived class do its specific stuff
        OnInit();

        // Setup the timer to trigger a refresh at specified framerate
        connect(&myTimer, SIGNAL(timeout()), this, SLOT(repaint()));
		myTimer.setSingleShot(false);
        myTimer.start();

        myInitialized = true;
    }
}
QPaintEngine* QSFMLCanvas::paintEngine() const
{
    return 0;
}
void QSFMLCanvas::paintEvent(QPaintEvent*)
{
    // Let the derived class do its specific stuff
    //HIER IST DER CRASH (OnUpdate())<---------------------------------------------------------
    OnUpdate();

    // Display on screen
	this->sf::RenderWindow::display();

	//draw
}

void MyCanvas::OnInit()
    {
		window = static_cast<sf::RenderWindow*>(this);
		printf("Initializing...\n");
		textures.load("images.list");
		zoom = 1.0;
		
		view.setViewport(sf::FloatRect(0,0,640,480));
	
		view.setCenter(1050,0);
		sf::RenderWindow::setView(view);
		sf::RenderWindow::setActive();
		bool running = true;
		
		oldpos.x = 0;
		oldpos.y = 0;

		pagesprite.attachTextures(&textures);
		pagesprite.addLine(200,200,1900,2);
		for(size_t i = 0; i < ns->size(); i++)
		{
			pagesprite.add((*ns)[i]);
		
		}
		pagesprite.update();
                //Dieser Bereich funktioniert. (Funktionierte schon immer)
    }

void MyCanvas::OnUpdate()
    {
		if(window->isOpen())
		{
			cl.restart();
			sf::Event event;
			while (window->pollEvent(event))
			{
                                //In diese Schlaufe wird gar nie gesprungen
				// Close window: exit
				if (event.type == sf::Event::Closed)
					window->close();
			
				// Resize event: adjust the viewport
				else if (event.type == sf::Event::Resized)
				{
					view.setSize(event.size.width,event.size.height);
					window->setView(view);
				}
				else if(sf::Keyboard::isKeyPressed(sf::Keyboard::Return))
				{
				
				}
				else if(event.type == sf::Event::MouseWheelMoved)
				{
					zoom += double(event.mouseWheel.delta/10.0f);
					printf("%f\n",zoom);
				}
				
			}

		
			window->clear(sf::Color(50,50,50));
		
			pagesprite.draw(window);
			window->draw(mark);

			window->setView(view);
			view.zoom(zoom);

			zoom = 1.0;
		}
	

    }

void MyCanvas::resizeEvent(QResizeEvent* event) //Versuch einer Reimplementierung: Crash. (derselbe)
{
	QWidget::resizeEvent(event);
	if(window != NULL)
	{
		window->setSize(sf::Vector2u(width(), height()));
		window->setView(view);
	}
}

Und dann noch die main:
C++:
int main(int argc, char* argv[])
{
	QApplication app(argc,argv);

	QFrame* MainFrame = new QFrame;
    MainFrame->setWindowTitle("Qt SFML");
    MainFrame->resize(400, 400);
    

    // Create a SFML view inside the main frame
    MyCanvas* SFMLView = new MyCanvas(MainFrame, QPoint(0, 0), QSize(1024, 1024));
	QVBoxLayout* lay = new QVBoxLayout();
	lay->addWidget(SFMLView);
	MainFrame->setLayout(lay);
	MainFrame->show();
	SFMLView->show();

	app.exec();
        //Aufräumen usw.
        return 0;
}

Ich sitze nun schon einige Tage daran und bin kurz davor, Qt von meiner Platte zu fegen...
Hätte vielleicht einer die Güte, mich zu erleuchten?

Gruss
cwriter

/EDIT:
Den Crash habe ich beheben können (undefined pointer usw.), die Eventweiterleitung jedoch nicht. Irgendeine Idee dazu?
 
Zuletzt bearbeitet:
Hallo cwriter,

welche Version von SFML und Qt verwendest du denn? Das verlinkte Tutorial bezieht sich auf SFML 1.6, ist das auch die Version die du einsetzt?

Grüße
Matthias
 
Achso, ich als Trendsetter verwende jeweils die neuste Version, d.h. SFML 2 und Qt 5. Die Änderungen bei SFML (die sichtbaren, d.h. create() statt Create() etc.) habe ich im Code entsprechend angepasst.

Interessanterweise wird der SFML-Resize-Event nun getriggert, doch alle anderen Inputs (Mausrad/Klick) werden ignoriert.

Edit: Wird immer besser...
Die Inputs werden gelesen, wenn window.pollEvent() ausgeführt wird, wenn gerade eine Taste gedrückt wird. Da das aber nur 30x pro Sekunde ist, ist das viel zu unzuverlässig.

Gruss
cwriter
 
Zuletzt bearbeitet:
Folgendes funktioniert bei mir (SFML 2.1, Qt 4.8.5, Visual Studio 2012, Windows x64) recht zuverlässig. D.h. es gehen keine Maus-, Tastatur- oder Resize-Events verloren.

SFMLCanvas.h
C++:
#pragma once

#include "QtGui/QWidget"

#include "SFML/Graphics/RenderWindow.hpp"

#include <memory>

class SFMLCanvas : public QWidget {
 public:
  explicit SFMLCanvas(int frameTime=0, QWidget* parent=nullptr);

 protected:
  virtual void onUpdate() {}
  virtual void onDraw(sf::RenderTarget& target) {}

 private:
  QPaintEngine* paintEngine() const override;
  void showEvent(QShowEvent*) override;
  void paintEvent(QPaintEvent*) override;
  void timerEvent(QTimerEvent*) override;

  const int frameTime_;
  std::unique_ptr<sf::RenderWindow> renderWindow_;
};

SFMLCanvas.cpp
C++:
#include "SFMLCanvas.h"

#include "QtCore/QDebug"

#include "SFML/Window/Event.hpp"

SFMLCanvas::SFMLCanvas(int frameTime, QWidget* parent)
    : QWidget(parent)
    , frameTime_(frameTime) {
  setAttribute(Qt::WA_PaintOnScreen);
}

QPaintEngine* SFMLCanvas::paintEngine() const {
  return nullptr;
}

void SFMLCanvas::showEvent(QShowEvent*) {
  if (!renderWindow_) {
    renderWindow_.reset(new sf::RenderWindow(winId()));
    startTimer(frameTime_);
  }
}

void SFMLCanvas::paintEvent(QPaintEvent*) {
  if (!renderWindow_) return;

  onDraw(*renderWindow_);

  renderWindow_->display();
}

void SFMLCanvas::timerEvent(QTimerEvent* e) {
  if (!renderWindow_) return;

  sf::Event event;
  while (renderWindow_->pollEvent(event)) {
    switch (event.type) {
    case sf::Event::Resized:
      renderWindow_->setView(
        sf::View(sf::FloatRect(0, 0, event.size.width, event.size.height)));
      break;
    case sf::Event::KeyPressed:
      qDebug() << "KeyPressed" << event.key.code;
      break;
    case sf::Event::KeyReleased:
      qDebug() << "KeyReleased" << event.key.code;
      break;
    case sf::Event::MouseMoved:
      qDebug() << "MouseMoved" << event.mouseMove.x << event.mouseMove.y;
      break;
    case sf::Event::MouseWheelMoved:
      qDebug() << "MouseWheelMoved" << event.mouseWheel.delta;
      break;
    }
  }

  onUpdate();
  repaint();
}

MyCanvas.h
C++:
#pragma once

#include "SFMLCanvas.h"

class MyCanvas : public SFMLCanvas {
 public:
  MyCanvas(QWidget* parent=nullptr);

 protected:
  void onUpdate() override;
  void onDraw(sf::RenderTarget& target) override;
};

MyCanvas.cpp
C++:
#include "MyCanvas.h"

#include "SFML/Graphics/CircleShape.hpp"
#include "SFML/Window/Keyboard.hpp"

MyCanvas::MyCanvas(QWidget* parent) : SFMLCanvas(0, parent) {
}

void MyCanvas::onUpdate() {
}

void MyCanvas::onDraw(sf::RenderTarget& target) {
  target.clear(sf::Color::Red);

  sf::CircleShape circle(10);
  circle.setFillColor(sf::Color::Green);
  if (sf::Keyboard::isKeyPressed(sf::Keyboard::Space)) {
    circle.setFillColor(sf::Color::Blue);
  }
  target.draw(circle);
}

main.cpp
C++:
#include "MyCanvas.h"

#include "QtGui/QApplication"

int main(int argc, char* argv[]) {
  QApplication app(argc, argv);

  MyCanvas canvas;
  canvas.show();

  return app.exec();
}
 
Ich bin gerade recht müde, daher werde ich mich erst morgen wieder damit befassen.
Auf jeden Fall schon jetzt vielen Dank für deine Hilfe und Arbeit!

Gruss
cwriter
 
Man möge mir den Doppelpost verzeihen.

@Matthias: Ich würde dir tausend "Danke"s geben, wenn es denn möglich wäre. Dein Code funktioniert (bis auf das Gemecker bei
C++:
renderWindow_.reset(new sf::RenderWindow(winId())); //winId() muss auf HWND gecastet werden (warum eigentlich?)
einwandfrei.

Wenn ich herausgefunden habe, wo ich den Fehler gemacht habe, werde ich das hier auch posten.

Gruss und nochmals ein dickes Danke!
cwriter
 
@Matthias: Ich würde dir tausend "Danke"s geben, wenn es denn möglich wäre. Dein Code funktioniert (bis auf das Gemecker bei
C++:
renderWindow_.reset(new sf::RenderWindow(winId())); //winId() muss auf HWND gecastet werden (warum eigentlich?)
einwandfrei.
Freut mich, dass ich dir helfen konnte :)

Wieso du hier casten musst, kann ich dir auch nicht sagen. Bei mir hat es jedenfalls auch ohne kompiliert, da der Rückgabewert von winId() und der Parameter für sf::RenderWindow beide vom Typ HWND sind. Vielleicht hat sich das ja mit Qt 5 geändert?

Was mir gerade noch aufgefallen ist: Korrekterweise sollte man auf QEvent::WinIdChange reagieren und ggf. das sf::RenderWindow neu anlegen mit der geänderten Id. Allerdings ist mir dieses Event unter Windows noch nie untergekommen. Ich schätze das ist auch nur relevant, wenn man z.B. den Parent des Widgets ändert.

Grüße
Matthias
 
Hallöchen, ich bin's mal wieder.

Den Fehler konnte ich immer noch nicht finden, dafür habe ich einige neue erstellt (juhee!).
Prämisse: Ich habe Matthias Code nur geringfügig angepasst, also keine weiteren events überschrieben o.ä..
1. Wenn MyCanvas an sich ausgeführt wird, funktioniert alles bestens. Sobald aber etwas darüber/darunter liegt, funktioniert z.B. das Mausrad nicht mehr. (resize geht aber, dafür die Focus-Erkennung wieder nicht)
C++:
	mainFrame frame;                          //class mainFrame : public QFrame (dasselbe aber auch direkt mit QFrame)
	frame.setMinimumSize(640,480);
	QVBoxLayout* lay = new QVBoxLayout();
	
	MyCanvas canvas;
	
	lay->addWidget(&canvas);
	frame.setLayout(lay);
        frame.show();
Ganz nackig geht's (geht alles) aber wieder:
C++:
MyCanvas canvas;
canvas.show();

Irgendwie könnte ich damit noch leben (auch wenn ich's nicht gut finde(TM)), doch dann das:
2. Das SFML-Fenster saugt mir einfach und ungefragt den Focus weg.
Konkreter Fall: Ein QToolBar wird verschoben. Andocken/Bewegen ist kein Problem, doch wenn ich die Toolbar in das SFML Fenster schiebe, den Mauszeiger dann in den SFML-Bereich fahre (also runter von der QToolBar, und dann wieder versuche, die Toolbar zu greifen, greife ich ins Leere bzw. führe die für den SFML-Bereich geplante Aktion aus.
Ergänzung (Edit): Wenn ich dann aber das SFML-Fenster so verschiebe, dass die Toolbar nicht mehr im Fenster ist, kann ich diese wieder ganz normal verschieben/verwenden.

Ich komme mir mittlerweile recht dumm vor. Ich habe diverse "setEnabled()" und Qt::StrongFocus-Dinger ausprobiert, jedoch ohne Erfolg.
Qt Version ist 5.1.0.

Liegt das an der winId(), die sich ändert oder bin ich da völlig auf dem Holzweg?

Einen schönen Sonntag Abend wünscht
cwriter

/EDIT 10.2.14:
Ich konnte das Problem mit einem Override der event()-Funktion genauer lokalisieren: Wenn ich alle events abblocke, geht's gut. Wenn ich nur den QEvent::Show abblocke, ebenfalls.
Dummerweise ist das ziemlich nötig, um etwas darzustellen...
Das Mausrad-Problem wird dadurch allerdings nicht behoben. Tastatur weigert sich übrigens auch...
Hat jemand eine Idee?

/EDIT 10.2.14 die Zweite:
Also: Sämliche Fenster, die vom Programm selbst erstellt werden, werden geschluckt (zwar dargestellt, aber wie gesagt nicht mehr greifbar). Ein Fenster eines anderen Programms (z.B. explorer.exe) ist davon nicht betroffen.
So langsam glaube ich, dass ich einen Exorzisten rufen muss...
 
Zuletzt bearbeitet:
In Anbetracht der inzwischen verstrichenen Zeit mache ich mal wieder einen Doppelpost.

Es wird immer kurioser: Wenn ich einen QInputDialog aus der MyCanvas-Klasse heraus starte und diesen dann ausfülle/beende, dann kriegt SFML den Fokus und Mausrad und Tastatur funktionieren wieder. Mit activateWindow() und raise() geht das aber nicht. Weiss jemand, wieso dem so ist?

Gruss
cwriter

/EDIT: So, für das eine Problem habe ich nun eine Lösung gefunden: enterEvent(QEvent*) override und daran dann QWidget::setFocus() aufrufen übergibt Mausrad & Tastatur an SFML.
Die anderen Fenster werden noch immer gefressen, es sei denn, man macht sie ApplicationModal oder WindowModal. Gibt es dazu noch einen anderen Weg?
Ansonsten muss man die Fenster erst anklicken, damit sie überhaupt einen hover-Event ausführen...
 
Zuletzt bearbeitet:
Hallo Welt

Die Lösung des vorherigen Posts geht nur in Qt 4.*. Mir persönlich wurde es mit Qt 5.1.0 jetzt zu bunt und ich bin wieder auf Qt 4.8 zurück.
Hach, wie das flüssig und toll und ohne diese dummen Platform plugins und ohne rumzuzicken läuft... Einfach ein Genuss.
Aber diese Warnung "Qt: Untested Windows version 6.2 detected!". Windows 8 ist ja auch erst ein knappes Jahr alt... Mal abgesehen davon nennt mir die cmd.exe eine Version von 6.3. Qt fand ich mal soo gut. Schade drum.
Naja, mit Qt 4.8 funktioniert's ja soweit. Ich werde mich dennoch in zukünftigen Projekten Richtung wxWidgets wenden. Schlimmer als dieser Quark hier kann's ja wohl nicht werden, oder?

Ich möchte allen danken, die hierfür - und sei es nur zum Durchlesen - ihre Zeit geopfert haben!

Gruss
cwriter
 
Zurück