I am currently working on an existing Linux application called Xpad, which is a sticky notes application written in C. I try to implement transparency but I have a hard time achieving what I want. A sticky note looks like this.
I have a (toplevel) gtk_window with a gtk_textview.
If the background color of the textview is set with the function gtk_widget_override_background_color() to a transparant color (GdkRGBA where alpha value is smaller than 1), the color of the gtk_window behind it, becomes more visible.
However, I don't want to see the gtk_window, but the applications behind the gtk_window, such as the browser, libreoffice, or the desktop.
If the gtk_window is set to a certain transparancy, either with gtk_widget_override_background_color() or with gtk_widget_set_opacity(), the whole widget, including the window decorations become (partially) transparent.
To make the relations between the visible objects more clear, I have created a diagram of the different parts, and marked the place where I believe the transparency issue is taking place.
Anybody have any ideas how to make the textview background transparent, without making the window decorations transparent, so I can see whatever is behind this application?
Set a proper RGBA visual for the widget
w = //some GtkWidget or GtkWidget derived klass (i.e. GtkWindow)
gtk_widget_set_app_paintable (w, TRUE); // important or you will get solid color
// the next 3 lines should be wrapped in a func which is also hooked to "screen-changed"
GdkScreen *screen = gtk_widget_get_screen (w);
GdkVisual *visual = gdk_screen_get_rgba_visual (screen);
gtk_widget_set_visual(w, visual);
gtk_widget_show_all(w);
g_signal_connect(G_OBJECT(w), "screen-changed", G_CALLBACK(screen_changed_contaniing_above_code), NULL);
Related
The Issue
This is how my gtk3 app currently look like on Ubuntu 20.04:
The tree view on the left has a slightly brighter background color than the others. I created the tree view by:
tree_view = gtk_tree_view_new();
gtk_widget_set_margin_bottom(tree_view, 12);
gtk_widget_set_margin_top(tree_view, 12);
gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(tree_view), FALSE);
All my other widgets are created similarly with modifying any styles.
Desired Behavior
I want all the bright backgrounds to match each other. I don't mind if they all become the brighter white or the darker white. An example is gtk3-demo, where all backgrounds become the brighter white, as in the tree view:
(Its stack header is still the darker white, but I don't mind that as long as the major regions match.)
Attempted Solutions
It's tempting to set it with CSS and make them match. But I realize those backgrouds can change with different themes. I still want my app to respond to theme changes. That is, if the user is using a dark theme, I still want my app to become dark (the same dark color).
Is there a way to do this? Thank you!
EDIT:
I noticed that gtk3-demo got its bright background color because it used GtkNotebook. I tried but GtkNotebook only gives a light color when it has its tabs enabled, which I don't want. Once it disable show-tabs, its background returns to the darker white.
I manually queried the background of the GtkTreeView and applied it to other widgets. This solution is much more ugly than I want it to be, but I guess it works.
context = gtk_widget_get_style_context(GTK_WIDGET(tree_view));
GdkRGBA *c;
gtk_style_context_get(context, GTK_STATE_FLAG_NORMAL, "background-color", &c,
NULL);
GdkDisplay *display;
GdkScreen *screen;
display = gdk_display_get_default();
screen = gdk_display_get_default_screen(display);
GtkCssProvider *provider;
provider = gtk_css_provider_new();
gtk_style_context_add_provider_for_screen(
screen, GTK_STYLE_PROVIDER(provider),
GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
char css[128];
snprintf(css, 128, ".custom_bg { background-color: rgba(%d, %d, %d, 1) }",
(int)(c->red * 255), (int)(c->green * 255), (int)(c->blue * 255));
gtk_css_provider_load_from_data(provider, css, -1, NULL);
g_object_unref(provider);
gdk_rgba_free(c);
I have a very simple code, wich create a GtkWindow and place in it a GtkButton.
window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
gtk_widget_set_size_request(_window, 800, 450);
gtk_window_set_decorated(GTK_WINDOW (_window), FALSE);
gtk_window_set_position(GTK_WINDOW (_window),GTK_WIN_POS_CENTER_ALWAYS);
gtk_window_set_resizable(GTK_WINDOW (_window), FALSE);
_startbutton = gtk_button_new_with_label("myLabel");
gtk_container_add(GTK_CONTAINER(_window), _startbutton);
gtk_widget_show_all(_window);
Yet, this doesn't work as expected because the button fills the whole window.
I tried to find a way to change the button size, but all the methods that i found use some methods that are deprecated...
Can someone explain to me the way to do this ?
Because the GtkButton is the only control in the GtkWindow, it will be given the entire area of the GtkWindow to fill. If you want to do anything more complicated, you will need to use layout containers like GtkBox and GtkGrid to explicitly lay out the button, usually in relation to other controls that you will also have in the window.
Once you do lay out your controls, you can use expansion and alignment to control how the button makes use of its allotted space.
I could begin by asking the question outright or by citing my sources (this, this, this, and this) descriptively, but I'll walk you ll through what I'm trying to do instead.
Let's start with a main window. It has its own window class whose hbrBackground is set to COLOR_BTNFACE + 1. Now let's do
EnableThemeDialogTexture(hwnd, ETDT_ENABLE | ETDT_USETABTEXTURE)
so the tab control we're about to add will be drawn with visual styles. (Try Windows XP with the standard Luna theme for best results.) Now let's add a tab control and two tabs.
On the first tab, we create an instance (let's call it container) of a new window class. This window class is going to hold various controls. I could set hbrBackground to COLOR_BTNFACE + 1, but then it will draw over the tab background. So I want this new child window to be transparent. So what I do is
set the class hbrBackground to GetStockObject(HOLLOW_BRUSH)
set container's extended style to WS_EX_TRANSPARENT
set the class WM_ERASEBKGND handler to do SetBkMode((HDC) wParam, TRANSPARENT); return 0; to set the device context and have Windows draw the transparent background.
So far so good, right? I'm not sure if I'm really doing all this correctly, and I'd like this to also be flicker-free, which doesn't seem to happen: when I resize the window (at least in wine) I get either flicker or garbage drawn (even in child controls, somehow!). Windows XP in a VM just shows flicker. I tried tweaking some settings but to no avail.
But wait, now I want to have another control, one that just draws some bitmap data. On the next tab, create another container, then have a third window class area as a child of that. area only draws in the upper-left 100x100 area and has scrollbars; the rest of the window area should be transparent.
Right now, what I have for area is:
the window class hbrBackground set to NULL and styles CS_HREDRAW and CS_VREDRAW set
the extended window style being 0
the WM_ERASEBKGND simply doing return 1;
the WM_PAINT filling the entire update rect with COLOR_BTNFACE + 1 before drawing, and rendering all of it
This is flicker-free, but obviously not transparent. NOW I'm really not sure what to do, because I want the area to be transparent in such a way that it shows the tab control background. Again, I tried tweaking settings to bring them closer to what I tried above with container, but I got either flicker or invalidation leftovers when I tried.
So how do I get both of these custom control types (the container and the drawing area) to be both flicker-free and transparent?
I presently must target Windows XP at a minimum, though if the solution would be easier with Vista+ only I'd be happy to keep that solution on the side in case I ever drop XP support (unfortunately Stack Overflow doesn't let me hand out silver medals...).
Thanks!
To paint your window in a manner that is "flicker free", you will need to paint your window to a bitmap, then copy the bitmap to the destination device context. On Windows XP, you will need to create a bitmap, adjust the origin of the drawing DC and then paint your window. On Vista and later you can use BeginBufferedPaint and its associated routines to do the buffering for you.
Once you have buffered painting working, you can then use WM_PRINTCLIENT to paint your window's parent window into the your drawing DC before you do any actual drawing. Unfortunately, not all windows will support WM_PRINTCLIENT.
You could consider using DrawThemeParentBackground, rather than WM_PRINTCLIENT directly.
Combining these two methods together will leave you with transparent flicker-free drawing.
I think I must be missing something obvious, but I'm unable to find this after several hours of searching. Is there no way to use a PictureBox or other control to contain an image with partial transparent/alpha-blended pixels, and place that over another image and have the blending be based on the image under it?
For example, this produces the results I want:
Place a panel on a form.
Add an OnPaint handler.
In the OnPaint handler draw 1 PNG, then draw another PNG over it, using Graphics.DrawImage for both.
This does not:
Place a PictureBox on a form and set it to a PNG.
Place another PictureBox on the form and set it to a PNG.
Place the 2nd picture box over the first.
...even if the 2nd picture box is just empty and has a background color of Transparent, it still covers the picture below it.
I've read this stems from all winform controls being windows, so by nature they aren't transparent.
...but even the 15 year old platform I'm migrating from, Borland's VCL, had several windowless controls, so it's hard to imaging winforms doesn't at least have some easy solution?
My first example above is one answer, true, but that adds a lot of work when you can only use one big panel and draw all of your "controls" inside of it. Much nicer if you can have separate controls with separate mouse events/etc. Even if not an image control, and a control I have to draw myself, that would be fine, as long as I can just put one image in each control. In VCL they called this a "paint box", just a rectangle area you could place on a form and draw whatever you want on it. Has it's own mouse events, Bounds, etc. If you don't draw anything in it, it is like it's not even there (100% transparent) other than the fact it still gets mouse events, so can be used as a "hot spot" or "target" as well.
The PictureBox control supports transparency well, just set its BackColor property to Transparent. Which will make the pixels of its Parent visible as the background.
The rub is that the designer won't let you make the 2nd picture box a child of the 1st one. All you need is a wee bit of code in the constructor to re-parent it. And give it a new Location since that is relative from the parent. Like this:
public Form1() {
InitializeComponent();
pictureBox1.Controls.Add(pictureBox2);
pictureBox2.Location = new Point(0, 0);
pictureBox2.BackColor = Color.Transparent;
}
Don't hesitate to use OnPaint() btw.
Sorry, I just found this... once I decided to Google for "winforms transparent panel" instead of the searches I was doing before, the TransPictureBox example show seems to do exactly what I need:
Transparency Problem by Overlapped PictureBox's at C#
Looks like there are 2 parts to it:
Set WS_EX_TRANSPARENT for the window style
Override the "draw background" method (or optionally could probably make the control style Opaque).
I have a panel and on that I've a picturebox. There are around 20 labels that I've to show in the panel. I want the background of Label to be transparent ie the image in picturebox is shown and the label displays only the text.
Now since labels do not exhibit true transparency I made the labels child of picturebox
this.lbl1.Parent = pictureBox1;
This has solved my immediate problem but now when the form loads, all the labels take a while to become visible and do so one at a time. I'd appreciate if you guys can give some solution for this.
Thanks in advance
The standard cure for flicker is double-buffering. But that cannot solve this kind of flicker. It is a different kind, caused by having multiple windows overlapping each other. Each label is its own window. When the form needs to paint itself, it draws its background leaving holes for the child windows. Each child window then takes a turn drawing itself. And their child windows draw themselves next. Etcetera.
This becomes noticeable when one control takes a while to draw, no doubt your picture box. Especially when it displays a large image that needs to be resized. The holes for the child windows stay unpainted while the picture box draws. They have a white background, black when you use the form's TransparencyKey or Opacity property. This can contrast badly with the image in your picture box, that effect is perceived by the user as flicker.
One immediate cure is to not use controls so you don't pay for their window. A Label is very convenient but it is a massive waste of system resources to burn up a window just to display a string. You can simply implement the picture box' Paint event and draw the strings with TextRenderer.DrawText(). PictureBox has double-buffering turned on by default so the image as well as the text is drawn completely smoothly, no more flicker. The obvious disadvantage is that you lose the convenience of point-and-click, you have to write code.
There are other fixes possible. One of them is to prevent the picture box from leaving holes for the child windows. It will draw the entire image, the labels pop on top of them. That's still flicker but not nearly as noticeable. Add a new class to your project and paste this code:
using System;
using System.Windows.Forms;
internal class MyPictureBox : PictureBox {
protected override CreateParams CreateParams {
get {
var parms = base.CreateParams;
parms.Style &= ~0x02000000; // Turn off WS_CLIPCHILDREN
return parms;
}
}
}
Compile and drop the new picture box control from the top of the toolbox onto your form.
Yet another possible workaround is to make the form and all of its children double-buffered. This doesn't speed up the painting at all but all of the windows get rendered into a memory buffer, the result is blitted to the screen. You'll notice a delay but the window suddenly pops on the screen. This is called compositing. Winforms doesn't support this directly since it can have side-effects but it is easy to enable. Paste this code into your form class:
protected override CreateParams CreateParams {
get {
CreateParams cp = base.CreateParams;
cp.ExStyle |= 0x02000000; // Turn on WS_EX_COMPOSITED
return cp;
}
}
Supported by XP and later. Watch out for painting artifacts.
or you can ditch the labels and draw the text yourself:
private void pictureBox1_Paint(object sender, PaintEventArgs e)
{
TextRenderer.DrawText(e.Graphics, "Label1", SystemFonts.DefaultFont,
new Point(10, 10), Color.Black, Color.Empty);
}
The label does not support transparency, you must create your own unique custom control, you can see these code examples.
http://www.codeproject.com/KB/dotnet/transparent_controls_net.aspx http://www.codeproject.com/KB/vb/uLabelX.aspx
Bye