PDA

View Full Version : How to remove background



kawazoe
19th May 2007, 18:26
Hi, I'm trying to use the dwm api on a Qt application but I don't know how to remove the widget background. There's a way by using setPalette(0, 0, 0, 0); but i't will affect child widget.

I's there an other way?

marcel
19th May 2007, 19:02
Do you want a child widget to be transparent, or what?
Or do you want the main window to be transparent?

What exactly are you trying to achieve?
If you are using DWM composition, then you don't need to do any hacks to make the window transparent. The window manager does it for you.
Also Qt 4.3 transparently supports some DWM features.

kawazoe
19th May 2007, 19:12
I't ok, I found a way to do it.

But now I got two big problem. When I use QPainter to paint a semi-transparent rectangle on the glass. The widget will repaint over the last rectangle normaly (it will erase the widget and then paint the rectangle) but if I click on an other window (like the taskbar) it will repaint over the last rectangle without erase it.

And the text won't render corectly on glass.

PS : sorry for posting in the installation forum, if some one can move it to the programming forum it will be nice, thanks.

marcel
19th May 2007, 19:17
Still not clear :)
Try explaining what is it you are trying to achieve.
Anyway, there is QWidget::setWindowOpacity. But note this also affects the widget's children.

Regards

kawazoe
19th May 2007, 20:13
I'm trying to make the left side of a widget in glass. And for this, I use the DWM api of Windows Vista. But when I use the api, I still have the background color that cover the glass. To remove it, I used setAttribute(Qt::WA_NoSystemBackground);.

All worked fine until I draw a semi-transparent rectangle with a QPainter over the glass. It will render fine but if the widget get or lose the focus, the widget won't repaint itself corectly. Normaly when the widget repaint itselft, it will clear every object already drawn and then will paint the new one. But when the widget get or lose focus, it won't erase the previous draw. This will cause semi-transparent draw to append them self to the previous draw and they will be less and less transparent.

Here is the code to reproduce the bug


#include<QtGui/QApplication>
#include<QtGUI>

class test :public QMainWindow
{
Q_OBJECT

public:
test();

protected:
void paintEvent(QPaintEvent *event);
};

test::test()
{
setAttribute(Qt::WA_NoSystemBackground);
}

void test::paintEvent(QPaintEvent *)
{
QPainter painter(this);
//Here we set the brush to white with alpha
painter.setBrush(QColor(255, 255, 255, 64));
painter.drawRect(0, 0, 200, 200);
}


int main(int argc, char*argv[])
{
QApplication app(argc, argv);
test win1;
win1.show();
return app.exec();
}


I don't test it but it should show you what I mean. You just have to compile the code, start the application and then click on the taskbar or any other window and then click on the application. You will notice that the color should be more opaque than before and if you resize the widget, the color should return to the good one.

And I'm using Qt 4.2.2

marcel
19th May 2007, 20:17
WA_NoSystemBackground:


Indicates that the widget has no background, i.e. when the widget receives paint events, the background is not automatically repainted. Note: Unlike WA_OpaquePaintEvent, newly exposed areas are never filled with the background (e.g after showing a window for the first time the user can see "through" it until the application processes the paint events). Setting this flag implicitly disables double buffering for the widget. This is set/cleared by the widget's author.
This is your explanation.
I believe you have to update the background manually. In every paint event clear the background and draw it again.

Regards.

kawazoe
19th May 2007, 20:54
I can't use painter.eraseRect(0, 0, width(), height()); because it will erase using the current palette so it will paint the the zone with the color that I remove using setAttribute(Qt::WA_NoSystemBackground);.

Is there an onther way to clear it?

marcel
19th May 2007, 20:57
what about:


QPixmap pix( size() );
pix.fill( Qt::transparent );
painter.drawPixmap( 0, 0, pix );


that, or use QPixmap grabWidget after you first update the widget. Then you can paint this pixmap. But this is useful only if you don't want to paint other things too.

Regards

durbrak
19th May 2007, 21:59
Hey,

I got the same problem (top level window with WA_NoSystemBackground).

I finally managed to get my window painted but there an slight issue.
Everytime the window receives a new paint event, it just paints the new window content on top of the previously painted content without deleting it first.

The eraseRect() is the same as fill(rect(), background()) so there's no use to call it as it will just paint everything black (filling it with a transparent pixmap or a transparent brush also).

Is there any way to delete the previously drawn stuff? The only workaround I can think of right now is to delete and recreate the window everytime a new paint event comes up, but this is sorta stupid :)

marcel
19th May 2007, 22:08
OK.
Then if you want to paint with a certain transparency, why not use setWindowOpacity?

durbrak
19th May 2007, 22:46
Holy mother of god... I'm sorry to have to say this, but do you actually only post just to enlarge your posts count?

We just spoke 2 days ago and I told you a couple of times that this is not what I and the thread start want. Also I used the search a few times and every time someone asks about this kind of question, it always seems that your answer is "why not use setWindowOpacity()"...

Man, come on... I think you already know that we want to achieve semi transparent painting on a pixel basis and not stupidly for the whole window but still it seems like you're giving your standard-setWindowOpacity-reply :(

wysota
19th May 2007, 23:12
Behave, please.

Doesn't your platform API (you're using it to have the client rect changed in the first place, right?) allow you to somehow protect or retrieve the "glassy" area? I'm just guessing here, but it'd seem reasonable if such a call existed. You're trying to use a feature which is relatively new, so I suspect Qt doesn't have any support for this yet and you have to dig into WinAPI. I hope I'm just correctly guessing what you're trying to do :) If not then sorry for bothering you with my awful postcount-increasing-message (after all, we're all here just for the post count, right?).

kawazoe
19th May 2007, 23:22
Hi durbrak,
This bug it a Qt bug. I'm trying to make a workaround for this but it still not working fine yet. I will pm you when I will manage to make it work.

Sorry marcel but the pix trick don't work.

wysota, the problem is not the api, it's that Qt do not paint directly on the window but on one layer and won't leave us paint on the base window. If you extend the frame trought the client area, you won't see the glass. But if you look at the small line all around the client area, you will see that the glass is extend but cover by the background of the widget.

In fact, for the glass to extend, the dwm need to paint on a black background. So by using this api and then using setPalette(0, 0, 0, 0);, you will se than the widget background is just a layer because the real background is show as black.

If I'm right, the best version of Qt to work with this api is Qt 4.1 because of it's way to manage transparency and layers.

As writing this, I got an idea to finish my workaround, leave me 2 hours and I reply with the solution.

marcel
19th May 2007, 23:23
You're not respecting my "authorithy" here...


Holy mother of god... I'm sorry to have to say this, but do you actually only post just to enlarge your posts count?
Yes, I'm getting close to that magic 700 aren't I?
No, it's not for the posts, and the answer wasn't for you. I was trying to find a solution for the thread starter. It seemed that he was trying to achieve global widget transparency.

If you don't like a solution, then don't use it.



We just spoke 2 days ago and I told you a couple of times that this is not what I and the thread start want. Also I used the search a few times and every time someone asks about this kind of question, it always seems that your answer is "why not use setWindowOpacity()"...
I'm sure I answered about two similar posts about this.
By the way? Do I know you? This seems kind of personal.



Man, come on... I think you already know that we want to achieve semi transparent painting on a pixel basis and not stupidly for the whole window but still it seems like you're giving your standard-setWindowOpacity-reply
Maybe you, but not him, from what he explained.

Bottom line, it's not anybody's fault that you can't find solutions for your problems.
If a solution seems "stupid" to you, then don't use it.

Oh, and what do you know? Look at that! It's 660!

Regards

durbrak
19th May 2007, 23:26
Okay, sorry wysota :)

Well, actually I stopped using winapi calls for the time being out of curiosity (meaning that I just paint on the background of a top level window with WA_NoSystemBackground with nothing more) just to exclude the possibility that it's not Qt's "fault".


Window::Window() : QWidget(0)
{
this->setWindowFlags(Qt::FramelessWindowHint);
this->setAttribute(Qt::WA_NoSystemBackground);
}
Window::paintEvent(QPaintEvent *)
{
QPainter painter(this);
painter.setOpacity(.2);
painter.setPen(Qt::red);
painter.drawText(50, 50, "Please stop overpainting yourself.");
}

Now with this code, it just keeps painting the text over the previously painted text and I can see how the red text gets brighter every time (notice how I used painter.setOpacity() to see the overlapping easier).

I already tried filling it with transparent colors, transparent pixmaps etc. Nothing works :(

durbrak
19th May 2007, 23:28
Woh, relax :D
I wasn't angry or something, I was just pointing out that you frequently make use of the everything-will-work-with-setWindowOpacity solution.

Cool new signature by the way :P

Edit: kawazoe, thank you. I already know that this is lack of feature in Qt :) I tried to send you a PM but I couldn't because you disabled that option :cool:

marcel
19th May 2007, 23:30
This is why the background doesn't get updated!


WA_NoSystemBackground:
Indicates that the widget has no background, i.e. when the widget receives paint events, the background is not automatically repainted. Note: Unlike WA_OpaquePaintEvent, newly exposed areas are never filled with the background (e.g after showing a window for the first time the user can see "through" it until the application processes the paint events). Setting this flag implicitly disables double buffering for the widget. This is set/cleared by the widget's author.

This is your explanation.
I believe you have to update the background manually. In every paint event clear the background and draw it again.

Regards.

So try finding a way to update the background while using that flag. I that doesn't work, you can always use setWindowOpacity, as it proved to be a global solution.

661

durbrak
19th May 2007, 23:34
I know that it is because of WA_NoSystemBackground and the background kinda does get updated but Qt won't clear the widget's content before painting on it again.

Furthermore WA_NoSystemBackground proved to be the only solution (combined with a few dwmapi.dll calls) for now to achieve semi transparent painting on a pixel basis.

kawazoe
19th May 2007, 23:36
I updated my last post, go get a look and you will see the good news.

PS : You should be able to pm me now.

And by the way, yes, durbark have the same problem as me.

durbrak
19th May 2007, 23:44
Oh, I already can paint onto the glass frame (but this is a separate matter because I want to paint on the desktop (well, not really on the desktop, but you know what I mean)).

So kawazoe, I know a solution how you can paint on the glass :)


typedef struct
{
int cxLeftWidth;
int cxRightWidth;
int cyTopHeight;
int cyBottomHeight;
} MARGINS;
typedef HRESULT (WINAPI *PtrDwmExtendFrameIntoClientArea)(HWND hWnd, const MARGINS *margins);
static PtrDwmExtendFrameIntoClientArea pDwmExtendFrameIntoClientArea = 0;

Window::Window() : QWidget(0)
{
pDwmExtendFrameIntoClientArea = (PtrDwmExtendFrameIntoClientArea)QLibrary("dwmapi").resolve("DwmExtendFrameIntoClientArea");
MARGINS margins = {-1}; //setting it to -1 makes the glass available throughout the entire window,
// like you have in the Windows Sidebar Add Gadgets dialog
pDwmExtendFrameIntoClientArea(this->winId(), &margins);

this->setAttribute(Qt::WA_NoSystemBackground);
}

Now you're good to go and you can put your widgets and everything as usual in the window. It works for me on Windows Vista with Qt4.3RC1 :)

durbrak
19th May 2007, 23:54
Okay, I found the solution. It's weird though, I though I already tried that...
Anyway, resizing the window erases the content:


void Window::paintEvent(QPaintEvent *paintEvent)
{
static int isResizing = 0;

if(isResizing == 0)
{
QSize size = this->size();

size.setHeight(size.height()+1);
isResizing = 1;
this->resize(size);

QApplication::processEvents();

size.setHeight(size.height()-1);
isResizing = 2;
this->resize(size);

QApplication::processEvents();

isResizing = 3;
return;
}
else if(isResizing < 3)
{
QWidget::paintEvent(paintEvent);
return;
}
else
isResizing = 0;

//Now you can start painting
}
Somehow I'm thinking that I overkilled it a little bit with isResizing... am I so tired right now and just can't see a simpler solution to do that resizing thingy?

kawazoe
20th May 2007, 00:32
I don't have the problem when resizing but when changing the focus. Even resizing seem to restore the corect color.

marcel
20th May 2007, 00:34
to durbrak:


CTestWidget::CTestWidget(QWidget* parent )
:QWidget( parent )
{
setWindowFlags( Qt::FramelessWindowHint );
setFixedSize( 400, 400 );
mPixmap = QPixmap( "C:\\ellipse.png" );
QBitmap mask = mPixmap.createHeuristicMask(true);
setMask( mask );
}

CTestWidget::~CTestWidget()
{
}

void CTestWidget::paintEvent(QPaintEvent* e)
{
QPainter p( this );
p.drawPixmap( 0, 0, mPixmap );
}


I knew I did it some time ago.
I attached a screen of how it looks.
The png is just a black ellipse on a white background.
The method is limited because it does not work when the image you want to display contains transparent shadings ( such as shadows ).
However, you can give the whole masked widget a shadow if you set the flags Qt::FramessWindowHint | Qt::Popup.

Although, never tested it on Mac.

regards

durbrak
20th May 2007, 01:01
kawazoe: No, you missed the point. My solution fixes the problem where the background wasn't erased by Qt (it was just painted over). Now with this solution, everytime the window wants to repaint itself, it will resize itself first (thus erasing the garbage painted before), and then paint the stuff.

marcel: Stuff like shadows is exactly what I'm trying to do the whole time :) You scaled your screenshot a little bit too much and I can't see if your circle is anti aliased or not. But I guess not, since this would be equal to shadows :)

Anyway, I can finally do that now. But here comes the kinda weird part:
I paint text and circles (anti aliased) in my widget and everything looks smooth. But if I paint a pixmap first and then the text and the circles, the circles are still smooth but now the text is crispy as hell :(
Weird, I can paint every type of form/shape smooth on my png pixmap window, but not text...

marcel
20th May 2007, 01:06
No, it is not antialised.
For text: have you used QPainter::TextAntialiasing?

kawazoe
20th May 2007, 01:25
Ha ok!

I see your point now. I will try this.

durbrak
20th May 2007, 01:37
Yeah sure, like I said.

Well, I think I figured it out how I can also paint text... but this is sooo weird (and I'm convinced that this can be fixed easily by TT!)

Okay, now let's assume I begin painting my background in the Pos(0, 0) across the window and then I paint my text (a very long string) on the x-coord by 50px:

http://img527.imageshack.us/img527/3993/screwedce7.gif
As you can see the text is totally crispy, but the circle is smooth...

Now I do the same again, except that the background image is now positioned on the x-axis on 2px and the text on 1px on the x-axis (i.e. text.x() < background.x()):

http://img62.imageshack.us/img62/8598/almostjj4.gif
Now as you can see the text is perfectly smooth, and all I did is to set the x-coord of the text to be -1 of that of the background and it still paints perfectly onto the background.

But do you see the last three dots at the end? No, they weirdly enough aren't smooth but still crispy... So I thought to myself "wtf!?" and inserted a dot in my QString somewhere at the beginning:

http://img300.imageshack.us/img300/7094/lolie2.gif
Can you believe it? It screwed up everything behind the dot...

Anyway, I thought of a workaround (not for the dot issue) - if you need to paint the text inside the background, you could just prepend alot of whitespaces:

" Something is here"But this won't work. Though the text is inside the background, it is still crispy.
This works:

"g Something is here" :D

marcel
20th May 2007, 01:45
Well, I don't know about that.
It could be a problem related to text rendering ( small glyphs actually, like the dot ).

So, there is another possibility to draw text, without actually drawing text :), but paths.
You will have to use a QPainterPath and QPainterPath::addText. This way the glyphs will be converted to paths ( actually only contours ) and filled with whatever brush you have currently set.

Maybe the problem will go away.

durbrak
20th May 2007, 01:48
Yupp, I already thought of that but I wanted to give it a shot tomorrow... it's too late now :D