I am using Cairo in a GTK# application for drawing. When another window covers part of the drawn content, the overlapped part of the drawn content is lost. Is there a way to make it permanent?
Here is my simplified method for drawing the content:
void UpdateConnectionLines ()
{
GdkWindow myWindow = GetGdkWindow();
myWindow.Clear ();
using (Context g = Gdk.CairoHelper.Create (myWindow))
{
g.Save ();
g.MoveTo (0, 20);
g.LineTo (100, 20);
g.Restore ();
g.Color = new Color (0, 0, 0);
g.LineWidth = 1;
g.Stroke();
}
}
If you are drawing directly on the form, then you need to do it in the Form's paint event, to ensure it is there every time the form get's painted (i.e. when another window covers it and then moves, when it is resized, ...)
Evaluating John Koerner's answer, I have found a solution, that works for every GTK# widget. I use the generic WidgetEvent ExposeEvent (thanks, ptomato) and redraw.
I append my event handler with
this.ExposeEvent += new global::Gtk.ExposeEventHandler (this.Handle_ExposeEvent);
and then the handler just calls my method:
protected virtual void Handle_ExposeEvent (object o, Gtk.ExposeEventArgs args)
{
UpdateConnectionLines();
}
EDIT:
Actually, I have not RTFM correctly, as it explicitely states:
The best place to create and use the
Context is the ExposeEvent for the
given widget. Usually you'll want to
use the Gtk.DrawingArea for this task.
An example implementation of the
Expose event method:
Related
I am looking to get the same effect as the GraphicsPath from Winforms which allows to keep some particular areas of myForm unrefreshed. f.i.:
myForm.Invalidate(new Region(graphicsPath));
My final goal is to draw things at the unrefreshed location, using a HDC (Device context handle) that I will provide to an external application. (This currently works using winforms).
I am using SFML.Net 2.4 and I create my window this way:
SFML.Graphics.RenderWindow mySfmlWindow = new RenderWindow(myForm.Handle, settings);
I can still create the HDC on myForm, however, even without calling :
mySfmlWindow.Clear(color);
, the things drawn by the external application are still instantly cleared.
Manual approach
You can draw your desired background yourself. I've got an example, where I draw a half of the window background manually, while the other half is not cleared.
The left half is "cleared" in gray just to show the point.
In the code, I use a sf::RectangleShape to clear the window, but you can use a sf::VertexArray if your shape is more complex.
Full Code
int main(){
sf::RenderWindow win(sf::VideoMode(640, 480), "SFML Test");
sf::RectangleShape r1;
r1.setOrigin(sf::Vector2f(25, 25));
r1.setPosition(50, 50);
r1.setSize(sf::Vector2f(50, 50));
r1.setFillColor(sf::Color::Red);
sf::RectangleShape r2;
r2.setOrigin(sf::Vector2f(25, 25));
r2.setPosition(400, 50);
r2.setSize(sf::Vector2f(50, 50));
r2.setFillColor(sf::Color::Blue);
sf::RectangleShape updatedRegion;
updatedRegion.setPosition(0, 0);
updatedRegion.setSize(sf::Vector2f(320, 480)); // Half window
updatedRegion.setFillColor(sf::Color(30,30,30)); // Dark gray just for the sake of the example
while (win.isOpen())
{
sf::Event event;
while (win.pollEvent(event))
{
if (event.type == sf::Event::Closed)
{
win.close();
}
}
//win.clear(); // We skip the clearing process
win.draw(updatedRegion); // And do our own "clear"
win.draw(r1);
win.draw(r2);
win.display();
// Just some movement to test the concept
r1.rotate(0.01);
r2.rotate(0.01);
}
return 0;
}
I am trying to implement a "Fillable Form" in which editable text fields appear over top of an image of a pre-preprinted form for a dot matrix printer. (using c# and Windows Forms and targeting .Net 2.0) My first idea was to use the image as the Windows Form background, but it looked horrible when scrolling and also did not scroll properly with the content.
My next attempt was to create a fixed-size window with a panel that overflows the bounds of the window (for scrolling purposes.) I added a PictureBox to the panel, and added my textboxes on top of it. This works fine, except that TextBoxes do not support transparency, so I tried several methods to make the TextBoxes transparent. One approach was to use an odd background color and a transparency key. Another, described in the following links, was to create a derived class that allows transparency:
Transparency for windows forms textbox
TextBox with a Transparent Background
Neither method works, because as I have come to find out, "transparency" in Windows Forms just means that the background of the window is painted onto the control background. Since the PictureBox is positioned between the Window background and the TextBox, it gives the appearance that the TextBox is not transparent, but simply has a background color equal to the background color of the Window. With the transparency key approach, the entire application becomes transparent so that you can see Visual Studio in the background, which is not what I want. So now I am trying to implement a class that derives from TextBox and overrides either OnPaint or OnPaintBackground to paint the appropriate part of the PictureBox image onto the control background to give the illusion of transparency as described in the following link:
How to create a transparent control which works when on top of other controls?
First of all, I can't get it working (I have tried various things, and either get a completely black control, or just a standard label background), and second of all, I get intermittent ArgumentExceptions from the DrawToBitmap method that have the cryptic message "Additional information: targetBounds." Based on the following link from MSDN, I believe that this is because the bitmap is too large - in either event it seems inefficient to capture the whole form image here because I really just want a tiny piece of it.
https://msdn.microsoft.com/en-us/library/system.windows.forms.control.drawtobitmap(v=vs.100).aspx
Here is my latest attempt. Can somebody please help me with the OnPaintBackground implementation or suggest a different approach? Thanks in advance!
public partial class TransparentTextbox : TextBox
{
public TransparentTextbox()
{
InitializeComponent();
SetStyle(ControlStyles.OptimizedDoubleBuffer |
ControlStyles.AllPaintingInWmPaint |
ControlStyles.ResizeRedraw |
ControlStyles.UserPaint, true);
}
protected override void OnPaintBackground(PaintEventArgs e)
{
//base.OnPaintBackground(e); // not sure whether I need this
if (Parent != null)
{
foreach (Control c in Parent.Controls)
{
if (c.GetType() == typeof(PictureBox))
{
PictureBox formImg = (PictureBox)c;
Bitmap bitmap = new Bitmap(formImg.Width, formImg.Height);
formImg.DrawToBitmap(bitmap, formImg.Bounds);
e.Graphics.DrawImage(bitmap, -Left, -Top);
break;
}
}
Debug.WriteLine(Name + " didn't find the PictureBox.");
}
}
}
NOTE: This has been tagged as a duplicate, but I referenced the "duplicate question" in my original post, and explained why it was not working. That solution only works if the TextBox sits directly over the Window - if another control (such as my Panel and PictureBox) sit between the window and the TextBox, then .Net draws the Window background onto the TextBox background, effectively making its background look gray, not transparent.
I think I have finally gotten to the bottom of this. I added a Bitmap variable to my class, and when I instantiate the textboxes, I am setting it to contain just the portion of the form image that sits behind the control. Then I overload OnPaintBackground to display the Bitmap, and I overload OnPaint to manually draw the text string. Here is the updated version of my TransparentTextbox class:
public partial class TransparentTextbox : TextBox
{
public Bitmap BgBitmap { get; set; }
public TransparentTextbox()
{
InitializeComponent();
SetStyle(ControlStyles.OptimizedDoubleBuffer |
ControlStyles.AllPaintingInWmPaint |
ControlStyles.ResizeRedraw |
ControlStyles.UserPaint, true);
}
protected override void OnPaint(PaintEventArgs e)
{
e.Graphics.DrawString(this.Text, this.Font, Brushes.Black, new PointF(0.0F, 0.0F));
}
protected override void OnPaintBackground(PaintEventArgs e)
{
e.Graphics.DrawImage(BgBitmap, 0, 0);
}
}
... and here is the relevant part of how I instantiate:
Bitmap bgImage = (Bitmap)Bitmap.FromStream(Document.FormImage);
PictureBox pb = new PictureBox();
pb.Image = bgImage;
pb.Size = pb.Image.Size;
pb.Top = 0;
pb.Left = 0;
panel1.Controls.Add(pb);
foreach (FormField field in Document.FormFields)
{
TransparentTextbox tb = new TransparentTextbox();
tb.Width = (int)Math.Ceiling(field.MaxLineWidth * 96.0);
tb.Height = 22;
tb.Font = new Font("Courier", 12);
tb.BorderStyle = BorderStyle.None;
tb.Text = "Super Neat!";
tb.TextChanged += tb_TextChanged;
tb.Left = (int)Math.Ceiling(field.XValue * 96.0);
tb.Top = (int)Math.Ceiling(field.YValue * 96.0);
tb.Visible = true;
Bitmap b = new Bitmap(tb.Width, tb.Height);
using (Graphics g = Graphics.FromImage(b))
{
g.DrawImage(bgImage, new Rectangle(0, 0, b.Width, b.Height), tb.Bounds, GraphicsUnit.Pixel);
tb.BgBitmap = b;
}
panel1.Controls.Add(tb);
}
I still need to work on how the text looks when I highlight it, and other things like that, but I feel like I am on the right track. +1 to Reza Aghaei and Mangist for commenting with other viable solutions!
I played with some custom Controls created by me and I wanted to draw some Graphics for them.
I achieved everything I wanted but I have a question because something seems strange to me.
I tried for example to make a new control like this:
public class Square : Control
{
public Square(Point location)
{
this.Size = new Size(100, 100);
this.Location = location;
Draw();
}
public void Draw()
{
Graphics g = this.CreateGraphics();
g.DrawRectangle(Pens.Black, 5, 5, 20, 20);
}
}
This doesn't draw anything, but, if I call the "Draw" method from another action like a button press on the form it works.
It also works if I override the OnPaint method of my control and call the "Draw" method here. (I know that I should make all the drawing in the Paint/OnPaint but I did this just to try other things)
What's the difference ? Why can't I just call my "Draw" method anywhere I want?
When you call Draw() method in constructor, the control has not yet been added to the parent, is just an object in memory, so the call has no effect.
If you make the call as you say in the OnPaint() method, then is drawed since this method is called whenever render the control is needed.
I'm trying to draw a semi-transparent background and then opaque elements on top of it.
How come I can't do something like this?
protected override void OnPaint(PaintEventArgs pe)
{
base.OnPaint(pe);
this.Opacity = 0.5;
pe.Graphics.FillRectangle(trans_black_brush, square_rect_big);
this.Opacity = 1;
pe.Graphics.FillRectangle(solid_red_brush, square_rect);
}
I'd appreciate if someone with better understanding of Form drawing could tell me why this doesn't work :)
Update:
The solution has 3 forms:
1) Main (program, buttons etc)
2) Semi-transparent background (screen size, using opacity)
3) Transparent background but solid brushes on top.
In Form2's constructor, I have this:
Foreground = new FormForeground(this);
and in Form3's constructor I have this:
private Form_FormBackground m_Parent;
public FormForeground(FormBackground parent)
{
InitializeComponent();
FormBackground m_Parent = parent;
...
}
Whenever the mouse is clicked and used to draw with in form 3,
I update the parent's rectangle like so:
private void _UpdateParent()
{
m_Parent.s_DrawArea = m_DrawArea;
m_Parent.Invalidate();
}
The parent, form 2 then does its OnPaint() where it draws the marked area.
It does work, however the drawing does lag a bit compared to drawing directly in form3 (which does not produce the desired results because the drawn area needs to be transparent across the forms).
This doesn't work because Opacity is a Property of the Form and will always make the whole form and all its content have the current Value. It is perfect for fading a form in or out, though..
You can't achieve what you want with only one form.
Instead you will need two sychronized forms.
One can be somewhat opaque and will let the desktop shine through; the other must be transparent by making use of the TransparencyKey property and you can draw onto it..
To synchronize the two forms code the Move and the ResizeEnd events.
For a first setup use code like this:
A dummy form to create the semi-transparent look:
Form form0 = new Form() { Opacity = 0.33f , BackColor = Color.Black};
In the Form1's Load event:
TransparencyKey = Color.FromArgb(255, 147, 151, 162);
BackColor = TransparencyKey;
DoubleBuffered = true;
form0.Enabled = false;
form0.BringToFront();
form0.Show();
form0.Size = Size;
form0.Location = Location;
BringToFront();
And in the Move and the ResizeEnd events maybe code like this:
private void Form1_Move(object sender, EventArgs e)
{
form0.Size = Size;
form0.Location = Location;
}
private void Form1_ResizeEnd(object sender, EventArgs e)
{
form0.Size = Size;
form0.Location = Location;
}
You also may want to study this post that also shows a way to sandwich two forms.
Note that I picked a rather random color instead of the more common named color Fuchsia or any named colors. This is because I
Don't want to accidentally use it in the drawing, thius breaking making wrong spots transparent, but also
Don't want to make the form transparent for mouse actions, aka 'click-through'. This happens when using Fuchsia (and possibly some other colors) for some weird legacy reasons..
comrades) I've found some interesting behavior of Invalidate method in multithreaded applications. I hope you could help me with a problem...
I experience troubles while trying to invalidate different controls at one time: while they're identical, one succesfully repaints itself, but another - not.
Here is an example: I have a form (MysticForm) with two panels (SlowRenderPanel) on it. Each panel has a timer and with a period of 50ms Invalidate() method is called. In OnPaint method I draw number of current OnPaint call in the centre of panel. But notice that in OnPaint method System.Threading.Thread.Sleep(50) is called to simulate long time draw procedure.
So the problem is that the panel added first repaints itself much more often than another one.
using System;
using System.Drawing;
using System.Windows.Forms;
using System.Runtime.InteropServices;
namespace WindowsFormsApplication1 {
static class Program {
[STAThread]
static void Main() {
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new MysticForm());
}
}
public class MysticForm : Form {
public SlowRenderPanel panel1;
public SlowRenderPanel panel2;
public MysticForm() {
// add 2 panels to the form
Controls.Add(new SlowRenderPanel() { Dock = DockStyle.Left, BackColor = Color.Red, Width = ClientRectangle.Width / 2 });
Controls.Add(new SlowRenderPanel() { Dock = DockStyle.Right, BackColor = Color.Blue, Width = ClientRectangle.Width / 2 });
}
}
public class SlowRenderPanel : Panel {
// synchronized timer
private System.Windows.Forms.Timer timerSafe = null;
// simple timer
private System.Threading.Timer timerUnsafe = null;
// OnPaint call counter
private int counter = 0;
// allows to use one of the above timers
bool useUnsafeTimer = true;
protected override void Dispose(bool disposing) {
// active timer disposal
(useUnsafeTimer ? timerUnsafe as IDisposable : timerSafe as IDisposable).Dispose();
base.Dispose(disposing);
}
public SlowRenderPanel() {
// anti-blink
DoubleBuffered = true;
// large font
Font = new Font(Font.FontFamily, 36);
if (useUnsafeTimer) {
// simple timer. starts in a second. calls Invalidate() with period = 50ms
timerUnsafe = new System.Threading.Timer(state => { Invalidate(); }, null, 1000, 50);
} else {
// safe timer. calls Invalidate() with period = 50ms
timerSafe = new System.Windows.Forms.Timer() { Interval = 50, Enabled = true };
timerSafe.Tick += (sender, e) => { Invalidate(); };
}
}
protected override void OnPaint(PaintEventArgs e) {
string text = counter++.ToString();
// simulate large bitmap drawing
System.Threading.Thread.Sleep(50);
SizeF size = e.Graphics.MeasureString(text, Font);
e.Graphics.DrawString(text, Font, Brushes.Black, new PointF(Width / 2f - size.Width / 2f, Height / 2f - size.Height / 2f));
base.OnPaint(e);
}
}
}
Debug info:
1) Each panel has a bool field useUnsafeTime (set to true by default) which allows using System.Windows.Forms.Timer (false) insted of System.Threading.Timer (true). In the first case (System.Windows.Forms.Timer) everything works fine. Removing System.Threading.Sleep call in OnPaint also makes execution fine.
2) Setting timer interval to 25ms or less prevents second panel repainting at all (while user doesn't resize the form).
3) Using System.Windows.Forms.Timer leads to speed increasement
4) Forcing control to enter synchronization context (Invoke) doesn't make sense. I mean that Invalidate(invalidateChildren = false) is "thread-safe" and could possibly have different behavior in diffenent contexts
5) Nothing interesting found in IL comparison of these two timers... They just use different WinAPI functions to set and remove timers (AddTimerNative, DeleteTimerNative for Threading.Timer; SetTimer, KillTimer for Windows.Forms.Timer), and Windows.Forms.Timer uses NativeWindow's WndProc method for rising Tick event
I use a similar code snippet in my application and unfortunately there is no way of using System.Windows.Forms.Timer) I use long-time multithreaded image rendering of two panels and Invalidate method is called after rendering is completed on each panel...
That would be great if someone could help me to understand what's different happening behind the scenes and how to solve the problem.
P.S. Interesting behavior isn't it?=)
Nice demonstration of what goes wrong when you use members of a control or form on a background thread. Winforms usually catches this but there's a bug in the Invalidate() method code. Change it like this:
timerUnsafe = new System.Threading.Timer(state => { Invalidate(true); }, null, 1000, 50);
to trip the exception.
The other panel is slower because lots of its Invalidate() calls are getting canceled by the paint event. Which is just slow enough to do so. Classic threading race. You cannot call Invalidate() from a worker thread, the synchronous timer is an obvious solution.
Invalidate() invalidates the client area or rectangle ( InvalidateRect() ) and "tells" Windows that next time Windows paints; refresh me, paint me. But it does not cause or invoke a paint message. To force a paint event, you must force windows to paint after an Invalidate call. This is not always needed, but sometimes it's what has to be done.
To force a paint you have to use the Update() function. "Causes the control to redraw the invalidated regions within its client area."
You have to use both in this case.
Edit: A common technique to avoid these kinds of problems is keep all your paint routines and anything related in a single (generally main) thread or timer. The logic can run elsewhere but where the actual paint calls are made should all be in one thread or timer.
This is done in games and 3D simulations.
HTH