So I'm working on a GUI library for XNA and I've come across an issue I can't find a solution to. I'm working with rendertargets a lot, which is annoying in itself, but I have a really weird and specific issue.
In my ProgressBar class I'm drawing the individual components when the element is Validated and the object size has changed. If the object is Validated but the object size did not change, I use the filled rendertargets as textures to draw the final product onto a buffer-texture (again a rendertarget). Now, whenever I minimize the application, and tab in again, the background layer of the progressbar will have an imprint of the striped texture above it on it. The rendertargets are set to preserve content and I made sure that the correct rendertargets are set. ALSO clearing the graphicsDevice (just below the "Actual Drawing" line) does not do anything. Why?
So basically the game creates a screenshot of the area, and draws it into my texture. What the hell?
Here is the code for the ProgressBar:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
namespace PixLib
{
public class ProgressBar : UIElement
{
private float stripeThickness = 5;
private float stripeGapFactor = 3f;
private float animationSpeed = 1f;
private float at = 0;
private RenderTarget2D stripesBg, stripes;
private Coordinate2 lastSize;
protected override Coordinate2 InnerArea
{
get { return Coordinate2.Zero; }
}
protected Color color;
public Color Color
{
get
{
return color;
}
set
{
color = value;
Invalidate();
}
}
protected float value;
public float Value
{
get
{
return value;
}
set
{
float t = Math.Min(1, Math.Max(0, value));
if (t != this.value)
{
this.value = t;
Invalidate();
}
}
}
public ProgressBar(string name,Color color, Rectangle elementRect, bool localPos = true)
: base(name, elementRect, localPos)
{
lastSize = new Coordinate2();
this.color = color;
value = 0;
at = 0;
}
protected override void OnUpdate(MouseState mouseState, KeyboardState keyboardState)
{
}
protected override void OnInit()
{
}
protected override void Redraw(SpriteBatch spriteBatch)
{
if (lastSize.X != Width || lastSize.Y != Height)
{
Console.WriteLine("Redrawing Progressbar");
//redraw textures!
stripesBg = new RenderTarget2D(graphicsManager.GraphicsDevice, Width, Height, false, graphicsManager.GraphicsDevice.DisplayMode.Format, DepthFormat.Depth24, 1, RenderTargetUsage.PreserveContents);
stripes = new RenderTarget2D(graphicsManager.GraphicsDevice, Width, Height * 2, false, graphicsManager.GraphicsDevice.DisplayMode.Format, DepthFormat.Depth24, 1, RenderTargetUsage.PreserveContents);
spriteBatch.Begin();
spriteBatch.Draw(pixel, new Rectangle(0, 0, Width, Height), Color.White);
spriteBatch.End();
SetRenderTarget(stripesBg);
spriteBatch.Begin();
spriteBatch.Draw(pixel, new Rectangle(0, 0, Width, Height), Color.White);
spriteBatch.End();
/*SetRenderTarget(border);
spriteBatch.Begin();
int si = (int)(stripeThickness*0.5f + 0.5f);
DrawLine(new Coordinate2(si, 0), new Coordinate2(Width, 0), spriteBatch, Color.White, si);
DrawLine(new Coordinate2(si, Height - si), new Coordinate2(Width, Height - si), spriteBatch, Color.White, si);
DrawLine(new Coordinate2(si, 0), new Coordinate2(si, Height), spriteBatch, Color.White, si);
DrawLine(new Coordinate2(Width, 0), new Coordinate2(Width, Height - si), spriteBatch, Color.White, si);
spriteBatch.End();*/
SetRenderTarget(stripes);
spriteBatch.Begin();
int fy = (int)(stripeThickness +0.5f);
int s = (Height + fy) * 2;
int fx = -s;
at = 0;
while (fx < Width + stripeThickness * stripeGapFactor)
{
DrawLine(new Coordinate2(fx, -fy), new Coordinate2(fx+s, s-fy), spriteBatch, Color.White, stripeThickness);
fx += (int)(stripeThickness * stripeGapFactor);
}
spriteBatch.End();
SetRenderTarget(null);
lastSize.X = Width;
lastSize.Y = Height;
}
//actual drawing
spriteBatch.Begin();
spriteBatch.Draw(pixel, SizeRect, Darken(color, 2));
int cv = (int)(Value * Width + 0.5f);
spriteBatch.Draw(stripesBg, new Rectangle(cv - Width, 0, Width, Height), Darken(Color));
graphicsManager.GraphicsDevice.Clear(Color.Transparent);
spriteBatch.Draw(stripes, new Rectangle(cv - Width, -(int)(at + 0.5f), Width, Height * 2), color);
spriteBatch.End();
at += animationSpeed;
if (at >= Height)
at -= Height - (int)(stripeThickness + .5f);
}
}
}
Okay, it seems that the solution to this problem was to make the rendertargets not keep their content, and to redraw the whole damn thing whenever the content is lost.
if (lastSize.X != Width || lastSize.Y != Height || stripesBg.IsContentLost || stripes.IsContentLost)
{
Console.WriteLine("Redrawing Progressbar");
//redraw textures!
//stripesBg = new RenderTarget2D(graphicsManager.GraphicsDevice, Width, Height, false, graphicsManager.GraphicsDevice.DisplayMode.Format, DepthFormat.Depth24, 1, RenderTargetUsage.PreserveContents);
//stripes = new RenderTarget2D(graphicsManager.GraphicsDevice, Width, Height * 2, false, graphicsManager.GraphicsDevice.DisplayMode.Format, DepthFormat.Depth24, 1, RenderTargetUsage.PreserveContents);
stripesBg = new RenderTarget2D(graphicsManager.GraphicsDevice, Width, Height);
stripes = new RenderTarget2D(graphicsManager.GraphicsDevice, Width, Height * 2);
[...]
Related
I have two classes, Main and Grid. Grid simply makes a grid of square pixels. In my Main class, I want to get the list that was create in the Grid class. I managed to figure it out, but I'm wondering if there's a way to optimize the code.
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using System;
using System.Diagnostics;
using System.Collections.Generic;
namespace teeeest
{
public class Grid
{
Texture2D image;
Color color;
int rows;
int columns;
float outerThickness;
float innerThickness;
Vector2 size;
Vector2 origin;
Vector2 dotSize;
List<Pixel> pixels = new List<Pixel>(0);
public Grid(Texture2D image, int rows, int columns, float outerThickness, float innerThickness, Vector2 size, Vector2 origin, Vector2 dotSize, Color color)
{
this.dotSize = dotSize;
this.origin = origin;
this.color = color;
this.image = image;
this.rows = rows;
this.columns = columns;
this.outerThickness = outerThickness;
this.innerThickness = innerThickness;
this.size = size;
}
public void Update()
{
float sizeX = size.X / (columns - 1);
float sizeY = size.Y / (rows - 1);
for (int i = 0; i < rows; i++)
{
for (int g = 0; g < columns; g++)
{
Pixel p = new Pixel(image, 3, new Vector2((g * sizeX) + origin.X, sizeY * i + origin.Y), new Vector2(image.Width / 2, image.Height / 2), color);
pixels.Add(p);
}
}
}
public virtual void Draw(SpriteBatch hspritebatch, List<Grid> grids)
{
foreach (Pixel p in pixels)
{
hspritebatch.Draw(
texture: p.getImage(),
position: p.getPosition(),
sourceRectangle: null,
p.getColor(),
rotation: 0,
origin: new Vector2(image.Width / 2, image.Height),
scale: new Vector2(dotSize.X * .02f, dotSize.Y * .02f),
SpriteEffects.None,
0);
}
}
public Texture2D getImage()
{
return image;
}
public Vector2 getPosition()
{
return origin;
}
public Vector2 getOrigin()
{
return new Vector2(image.Width / 2, image.Height);
}
public Color getColor()
{
return color;
}
public List<Pixel> getList()
{
Update(); # This seems unnecessary. Is it?
return pixels;
}
}
}
The problem lies in the getList() function. In order to return the correct pixel list that was edited in the Update function, my solution there is to call that function right before returning the list. However, this seems costly for no reason. Is there a way around this without calling the Update function, or is this the only way?
I realize there's been posts similar to this, but I just don't understand them. I'm very much a beginner at coding. Here is my Main class.
using System;
using System.Diagnostics;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Graphics;
namespace teeeest
{
public class Game1 : Game
{
private GraphicsDeviceManager _graphics;
private SpriteBatch _spriteBatch;
private SpriteFont font;
private Texture2D ball;
private Texture2D square;
private Color color = Color.White * .1f;
private Vector2 MouseCoords;
private Vector2 winMiddle;
private Vector2 ballOrigin;
private bool leftDown;
private bool eDown;
private int winWidth;
private int winHeight;
List<Line> lines = new List<Line>(0);
List<Grid> grids = new List<Grid>(0);
List<Pixel> pixels = new List<Pixel>(0);
List<Pixel> test = new List<Pixel>(0);
public Game1()
{
_graphics = new GraphicsDeviceManager(this);
Content.RootDirectory = "Content";
IsMouseVisible = true;
_graphics.PreferredBackBufferWidth = 800;
_graphics.PreferredBackBufferHeight = 600;
}
protected override void Initialize()
{
base.Initialize();
}
protected override void LoadContent()
{
_spriteBatch = new SpriteBatch(GraphicsDevice);
ball = Content.Load<Texture2D>("ball");
square = Content.Load<Texture2D>("square");
font = Content.Load<SpriteFont>("File");
ballOrigin = new Vector2(ball.Width / 2, ball.Height / 2);
winWidth = _graphics.PreferredBackBufferWidth;
winHeight = _graphics.PreferredBackBufferHeight;
winMiddle = new Vector2(winWidth / 2, winHeight / 2);
}
protected override void Update(GameTime gameTime)
{
lines.Clear();
grids.Clear();
pixels.Clear();
if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed || Keyboard.GetState().IsKeyDown(Keys.Escape))
Exit();
if (Mouse.GetState().LeftButton == ButtonState.Released)
{
leftDown = false;
}
if (Mouse.GetState().LeftButton == ButtonState.Pressed && !leftDown)
{
Pixel g = new Pixel(ball, 5, MouseCoords, new Vector2(ball.Width / 2, ball.Height / 2), Color.Blue);
pixels.Add(g);
}
if (Keyboard.GetState().IsKeyUp(Keys.E))
{
eDown = false;
}
if (Keyboard.GetState().IsKeyDown(Keys.E) && !eDown)
{
color *= 1.1f;
eDown = true;
}
MouseCoords = new Vector2(Mouse.GetState().X, Mouse.GetState().Y);
Grid q = new Grid(ball, 10, 10, 7, 3, new Vector2(500, 500), new Vector2(30, 30), new Vector2(.2f, .2f), Color.White);
grids.Add(q);
# Here is where I'm calling the getList() function.
System.Console.WriteLine(q.getList()[7].getPosition());
# Here is where I'm calling the getList() function.
foreach (Line s in lines)
{
s.Update();
}
foreach (Grid gh in grids)
{
gh.Update();
}
base.Update(gameTime);
}
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.Black);
_spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.AlphaBlend);
foreach (Line l in lines)
{
l.Draw(_spriteBatch, lines);
}
foreach (Grid g in grids)
{
g.Draw(_spriteBatch, grids);
}
foreach (Pixel p in pixels)
{
_spriteBatch.Draw(
texture: p.getImage(),
position: p.getPosition(),
sourceRectangle: null,
p.getColor(),
rotation: 0,
origin: p.getOrigin(),
scale: new Vector2(.02f, .02f),
SpriteEffects.None,
0);
}
_spriteBatch.DrawString(font,
MouseCoords.ToString(),
new Vector2 (winWidth - 100, 10),
Color.White,
rotation: 0,
origin: new Vector2(0, 0),
scale: new Vector2(1, 1),
SpriteEffects.None,
0);
_spriteBatch.End();
base.Draw(gameTime);
}
}
}
You could only call Update() when the List is EMPTY?
public List<Pixel> getList()
{
if (pixels.Count == 0)
{
Update(); // now it only gets called when pixels is EMPTY
}
return pixels;
}
This type of check may need to be done in Update() as well if it can be called directly from other places so you don't end up with more Pixel instances in it than you were expecting.
I have a custom rounded textbox. But I couldn't add the textbox behaviors like text editing, text selection etc. Those properties take much time if I decide make myself. How can I add this properties into my textbox?
My TextBox class:
public class AltoTextBox : Control
{
public AltoTextBox()
{
SetStyle(ControlStyles.AllPaintingInWmPaint |
ControlStyles.SupportsTransparentBackColor |
ControlStyles.OptimizedDoubleBuffer |
ControlStyles.UserPaint, true);
BackColor = Color.Transparent;
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
e.Graphics.SmoothingMode = SmoothingMode.HighQuality;
RoundedRectangleF strokeRect = new RoundedRectangleF(Width, Height, 10);
RoundedRectangleF innerRect = new RoundedRectangleF(Width - 0.5f, Height - 0.5f, 10f, 0.5f, 0.5f);
e.Graphics.DrawPath(Pens.Black, strokeRect.Path);
e.Graphics.FillPath(Brushes.White, innerRect.Path);
}
}
public class RoundedRectangleF
{
Point location;
float radius;
GraphicsPath grPath;
float x, y;
float width, height;
public RoundedRectangleF(float width, float height, float radius,float x = 0,float y = 0)
{
location = new Point(0, 0);
this.radius = radius;
RectangleF upperLeftRect = new RectangleF(x, y, 2 * radius, 2 * radius);
RectangleF upperRightRect = new RectangleF(width - 2 * radius - 1, x, 2 * radius, 2 * radius);
RectangleF lowerLeftRect = new RectangleF(x, height - 2 * radius - 1, 2 * radius, 2 * radius);
RectangleF lowerRightRect = new RectangleF(width - 2 * radius - 1, height - 2 * radius - 1, 2 * radius, 2 * radius);
grPath = new GraphicsPath();
grPath.AddArc(upperLeftRect, 180, 90);
grPath.AddArc(upperRightRect, 270, 90);
grPath.AddArc(lowerRightRect, 0, 90);
grPath.AddArc(lowerLeftRect, 90, 90);
grPath.CloseAllFigures();
this.x = x;
this.y = y;
this.width = width;
this.height = height;
}
public RoundedRectangleF()
{
}
public GraphicsPath Path
{
get
{
return grPath;
}
}
public RectangleF Rect
{
get
{
return new RectangleF(x, y, width, height);
}
}
public float Radius
{
get
{
return radius;
}
set
{
radius = value;
}
}
}
I have found a solution from Hazeldev's custom controls.
In this solution we add a textbox control as our child control.
public class AltoTextBox : Control
{
int radius = 15;
public TextBox box = new TextBox();
GraphicsPath Shape;
public AltoTextBox()
{
SetStyle(ControlStyles.SupportsTransparentBackColor, true);
SetStyle(ControlStyles.UserPaint, true);
SetStyle(ControlStyles.ResizeRedraw, true);
AddTextBox();
Controls.Add(box);
BackColor = Color.Transparent;
ForeColor = Color.DimGray;
Text = null;
Font = new Font("Comic Sans MS", 11);
Size = new Size(135, 33);
DoubleBuffered = true;
}
void AddTextBox()
{
box.Size = new Size(Width - 2*radius, Height - 6);
box.Location = new Point(radius, 3);
box.BorderStyle = BorderStyle.None;
box.TextAlign = HorizontalAlignment.Left;
box.Multiline = true;
box.Font = Font;
}
protected override void OnBackColorChanged(EventArgs e)
{
base.OnBackColorChanged(e);
}
protected override void OnTextChanged(EventArgs e)
{
base.OnTextChanged(e);
box.Text = Text;
}
GraphicsPath innerRect;
protected override void OnFontChanged(EventArgs e)
{
base.OnFontChanged(e);
box.Font = Font;
}
protected override void OnResize(System.EventArgs e)
{
base.OnResize(e);
Shape = new RoundedRectangleF(Width, Height, radius).Path;
innerRect = new RoundedRectangleF(Width - 0.5f, Height - 0.5f, radius, 0.5f, 0.5f).Path;
AddTextBox();
}
protected override void OnPaint(PaintEventArgs e)
{
Bitmap bmp = new Bitmap(Width, Height);
Graphics grp = Graphics.FromImage(bmp);
grp.SmoothingMode = SmoothingMode.HighQuality;
grp.DrawPath(Pens.Gray, Shape);
grp.FillPath(Brushes.White, innerRect);
e.Graphics.DrawImage((Image)bmp.Clone(), 0, 0);
base.OnPaint(e);
}
}
I am working on my custom button in WinForm. I am already done at the work. But I have one last problem that my button doesn't Invalidate the area when a property was changed. I call the Invalidate method in set block and OnResize, but it doesn't work for the button. But it is fixed at runtime or rebuild. How can I fix it?
Here is the a picture for example:
My code:
public class AltoButton : Control
{
int radius;
RoundedRectangle roundedRect;
Color inactive1, inactive2, pressed1, pressed2;
LinearGradientBrush InactiveGB, MouseOverGB, BorderGB, currentGB;
public AltoButton()
{
inactive1 = Color.FromArgb(44, 188, 210);
inactive2 = Color.FromArgb(33, 167, 188);
pressed1 = Color.FromArgb(64, 168, 183);
pressed2 = Color.FromArgb(36, 164, 183);
radius = 10;
roundedRect = new RoundedRectangle(Width, Height, radius);
SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.OptimizedDoubleBuffer |
ControlStyles.SupportsTransparentBackColor | ControlStyles.UserPaint, true);
}
protected override void OnPaint(PaintEventArgs e)
{
e.Graphics.SmoothingMode = SmoothingMode.HighQuality;
roundedRect = new RoundedRectangle(Width, Height, radius);
InactiveGB = new LinearGradientBrush(new Rectangle(0, 0, Width, Height), inactive1, inactive2, 90f);
MouseOverGB = new LinearGradientBrush(new Rectangle(0, 0, Width, Height), pressed1, pressed2, 90f);
BorderGB = new LinearGradientBrush(new Rectangle(0, 0, Width, Height), Color.FromArgb(162, 120, 101), Color.FromArgb(162, 120, 101), 90f);
if (currentGB == null)
currentGB = InactiveGB;
e.Graphics.FillPath(currentGB, roundedRect.Path);
e.Graphics.DrawPath(new Pen(BorderGB), roundedRect.Path);
}
protected override void OnResize(EventArgs e)
{
Invalidate();
base.OnResize(e);
}
protected override void OnMouseEnter(EventArgs e)
{
currentGB = MouseOverGB;
Invalidate();
base.OnMouseEnter(e);
}
protected override void OnMouseLeave(EventArgs e)
{
base.OnMouseLeave(e);
currentGB = InactiveGB;
Invalidate();
}
public int Radius
{
get
{
return radius;
}
set
{
radius = value;
Invalidate();
}
}
}
public class RoundedRectangle
{
Point location;
int radius;
GraphicsPath grPath;
public RoundedRectangle(int width, int height, int radius)
{
location = new Point(0, 0);
this.radius = radius;
Rectangle upperLeftRect = new Rectangle(0, 0, 2 * radius, 2 * radius);
Rectangle upperRightRect = new Rectangle(width - 2 * radius - 1, 0, 2 * radius, 2 * radius);
Rectangle lowerLeftRect = new Rectangle(0, height - 2 * radius - 1, 2 * radius, 2 * radius);
Rectangle lowerRightRect = new Rectangle(width - 2 * radius - 1, height - 2 * radius - 1, 2 * radius, 2 * radius);
grPath = new GraphicsPath();
grPath.AddArc(upperLeftRect, 180, 90);
grPath.AddArc(upperRightRect, 270, 90);
grPath.AddArc(lowerRightRect, 0, 90);
grPath.AddArc(lowerLeftRect, 90, 90);
grPath.CloseAllFigures();
}
public RoundedRectangle()
{
}
public GraphicsPath Path
{
get
{
return grPath;
}
}
public Rectangle Rect
{
get
{
return new Rectangle(location.X, location.Y, 2 * radius, 2 * radius);
}
}
}
public int Radius
{
get
{
return radius;
}
set
{
radius = value;
Invalidate();
}
}
public Color Inactive1
{
get
{
return inactive1;
}
set
{
inactive1 = value;
Invalidate();
}
}
public Color Inactive2
{
get
{
return inactive2;
}
set
{
inactive2 = value;
Invalidate();
}
}
public Color Pressed1
{
get
{
return pressed1;
}
set
{
pressed1 = value;
Invalidate();
}
}
public Color Pressed2
{
get
{
return pressed2;
}
set
{
pressed2 = value;
Invalidate();
}
}
Remove the OnResize override and include ControlStyles.ResizeRedraw in the styles that you set to true.
Or alternatively set Control.ResizeRedraw property to true.
UPDATE: Actually the problem is more trivial. You have a cached brush in currentGB field which is created using a specific Width and Height.
So you can keep your code the way it is, and just set currentGB to null (you should really be disposing all these brushes, but that's another story) when the size is changed:
protected override void OnResize(EventArgs e)
{
currentGB = null;
Invalidate();
base.OnResize(e);
}
As Ivan said above, I have used using statement to dispose after used. I have added this answer as a complementary solution.
protected override void OnPaint(PaintEventArgs e)
{
e.Graphics.SmoothingMode = SmoothingMode.HighQuality;
roundedRect = new RoundedRectangle(Width, Height, radius);
if (state == MouseState.Leave)
using (LinearGradientBrush inactiveGB = new LinearGradientBrush(new Rectangle(0, 0, Width, Height), inactive1, inactive2, 90f))
e.Graphics.FillPath(inactiveGB, roundedRect.Path);
else if (state == MouseState.Over)
using (LinearGradientBrush activeGB = new LinearGradientBrush(new Rectangle(0, 0, Width, Height), active1, active2, 90f))
e.Graphics.FillPath(activeGB, roundedRect.Path);
using (LinearGradientBrush BorderGB = new LinearGradientBrush(new Rectangle(0, 0, Width, Height), Color.FromArgb(162, 120, 101), Color.FromArgb(162, 120, 101), 90f))
e.Graphics.DrawPath(new Pen(BorderGB), roundedRect.Path);
}
protected override void OnResize(EventArgs e)
{
Invalidate();
base.OnResize(e);
}
protected override void OnMouseEnter(EventArgs e)
{
state = MouseState.Over;
Invalidate();
base.OnMouseEnter(e);
}
I have a utility class for Framebuffer objects which i use to generate FBO's, bind them, render to them, and render them to screen as 2d textures.
When using this class, i can successfully make fbo textures, render to them, and render them to screen.
However, the colors seem messed up once i render them to screen.
What seems to be happening is that the color i set as drawing color ends up as the background color of the FBO (but only if i make an actual draw call after setting the color), and the drawing i made always ends up black.
What i am expecting based on my code, is a texture with a white background, and two small colored rectangles (one purple and one cyan) on them, drawn to the screen two times.
what i am getting is this :
a cyan background with two small black rectangles.
Source code follows :
FboRenderTexture.cs
public class FboRenderTexture : IDisposable
{
public int textureId = 0;
private int fboId = 0;
public int Width;
public int Height;
public FboRenderTexture(int width, int height)
{
Width = width;
Height = height;
Init();
}
//semi pseudocode
public void DrawToScreen(float xoffset = 0, float yoffset = 0)
{
if (textureId != -1)
{
GL.BindTexture(TextureTarget.Texture2D, textureId);
GL.Begin(BeginMode.Quads);
//todo : might also flip the texture since fbo's have right handed coordinate systems
GL.TexCoord2(0.0, 0.0);
GL.Vertex3(xoffset, yoffset, 0.0);
GL.TexCoord2(0.0, 1.0);
GL.Vertex3(xoffset, yoffset+Height, 0.0);
GL.TexCoord2(1.0, 1.0);
GL.Vertex3(xoffset+Width, yoffset+Height, 0.0);
GL.TexCoord2(1.0, 0.0);
GL.Vertex3(xoffset+Width, yoffset, 0.0);
GL.End();
}
}
private void Init()
{
// Generate the texture.
textureId = GL.GenTexture();
GL.BindTexture(TextureTarget.Texture2D, textureId);
GL.TexImage2D(TextureTarget.Texture2D, 0, PixelInternalFormat.Rgba, Width, Height, 0, PixelFormat.Rgba, PixelType.UnsignedByte, IntPtr.Zero);
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.Linear);
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Nearest);
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapS, (int)TextureWrapMode.ClampToBorder);
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapT, (int)TextureWrapMode.ClampToBorder);
// Create a FBO and attach the texture.
GL.Ext.GenFramebuffers(1, out fboId);
GL.Ext.BindFramebuffer(FramebufferTarget.FramebufferExt, fboId);
GL.Ext.FramebufferTexture2D(FramebufferTarget.FramebufferExt,
FramebufferAttachment.ColorAttachment0Ext, TextureTarget.Texture2D, textureId, 0);
// Disable rendering into the FBO
GL.Ext.BindFramebuffer(FramebufferTarget.FramebufferExt, 0);
}
// Track whether Dispose has been called.
private bool disposed = false;
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
private void Dispose(bool disposing)
{
if (!this.disposed)
{
if (disposing)
{
// Clean up what we allocated before exiting
if (textureId != 0)
GL.DeleteTextures(1, ref textureId);
if (fboId != 0)
GL.Ext.DeleteFramebuffers(1, ref fboId);
disposed = true;
}
}
}
public void BeginDrawing()
{
GL.BindFramebuffer(FramebufferTarget.DrawFramebuffer, fboId);
GL.DrawBuffer(DrawBufferMode.ColorAttachment0);
GL.PushAttrib(AttribMask.ViewportBit);
GL.Viewport(0, 0, Width, Height);
}
public void EndDrawing()
{
GL.PopAttrib();
GL.Ext.BindFramebuffer(FramebufferTarget.DrawFramebuffer, 0); // disable rendering into the FBO
}
}
usage
public class Buffers : CreativeSharp.core.CSInstance //inherits from OpenTK.GameWindow, adds some simple drawing stuff.
{
private FboRenderTexture buffer;
public Buffers() : base(800, 600, "buffers")
{
GL.Enable(EnableCap.Texture2D);
buffer = new FboRenderTexture(128, 128);
NoStroke();
this.Closed += Buffers_Closed;
}
private void SetupBuffer()
{
buffer.BeginDrawing();
GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit | ClearBufferMask.StencilBufferBit);
GL.ClearColor(1.0f, 1.0f, 1.0f, 1.0f);
GL.Color4(1f, 0f, 1f, 0f);
DrawRectangle(10,10, 30, 30);
GL.Color4(0f, 1f, 1f, 1f);
DrawRectangle(50, 10, 30, 30);
GL.Color4(1f, 1f, 1f);
buffer.EndDrawing();
}
void Buffers_Closed(object sender, EventArgs e)
{
buffer.Dispose();
}
public override void DrawContent()
{
SetupBuffer();
GL.ClearColor(1.0f, 1.0f, 1.0f, 1.0f);
GL.Color4(1f, 1f, 1f, 1f);
buffer.DrawToScreen(0,0);
buffer.DrawToScreen(512,0);
}
public override void Update()
{
}
private void DrawRectangle(float x, float y, float w, float h)
{
GL.Begin(PrimitiveType.Quads);
GL.Vertex3(x, y, 0);
GL.Vertex3(x + w, y, 0);
GL.Vertex3(x + w, y + h, 0);
GL.Vertex3(x, y + h, 0);
GL.End();
}
}
Somewhat embarrassingly, the cause of my problems was that i was not setting white as drawing color before drawing the fbo's texture.
This caused the colors to come out all wrong.
I like the ToolStripProfessionalRenderer style quite a lot, but I do not like the way it renders a ToolStripTextBox. Here, ToolStripSystemRenderer does a better job IMO. Now is there a way to combine both renderers' behaviour to use system style for text boxes and pro style for everything else? I have successfully managed to use pro style for buttons and system style for the rest (by deriving both classes). But text boxes in a ToolStrip don't seem to be handled by the renderer. Using .NET Reflector, those text boxes don't even seem to have a Paint event handler, although it's called by the ToolStrip.OnPaint method. I'm wondering where's the code to paint such a text box at all and how it can be configured to draw a text box like all other text boxes.
If you just want system rendering, the easiest approach is to use ToolStripControlHost instead:
class ToolStripSystemTextBox : ToolStripControlHost
{
public ToolStripSystemTextBox : base(new TextBox()) { }
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
[TypeConverter(typeof(ExpandableObjectConverter))]
public TextBox TextBox { get { return Control as TextBox; } }
}
I've taken the easy way out here and exposed the underlying TextBox directly to the form designer, instead of delegating all its properties. Obviously you can write all the property delgation code if you want.
On the other hand, if anyone wants to do truly custom rendering, I'll tell you what ToolStripTextBox does. Instead of hosting a TextBox directly, it hosts a private derived class called ToolStripTextBoxControl. This class overrides its WndProc in order to directly handle WM_NCPAINT. And then instead of delegating the actual drawing to the Renderer, it checks the Renderer's Type, and then branches to different rendering code inside of ToolStripTextBoxControl. It's pretty ugly.
It may not be necessary to dive into "WndProc" either. This was done without it:
The Question really is how do you make a "nice looking" TextBox, because as described by j__m, you can just use ToolStripControlHost, to host a custom control in your tool strip.
More here:
http://msdn.microsoft.com/en-us/library/system.windows.forms.toolstripcontrolhost.aspx
And as documented, the control you use can be a Custom Control.
Firstly, It's insanely tricky to make a custom TextBox Control. If you want to go:
public partial class TextBoxOwnerDraw : TextBox
You are in for HUGE trouble! But it doesn't have to be. Here is a little trick:
If you make a custom control as a Panel, then add the TextBox to the Panel, then set the Textbox borders to None... you can achieve the result as above, and best of all, its just a regular old TextBox, so cut copy paste all works, right click works!
Ok, here is the code for a nice looking textbox:
public partial class TextBoxOwnerDraw : Panel
{
private TextBox MyTextBox;
private int cornerRadius = 1;
private Color borderColor = Color.Black;
private int borderSize = 1;
private Size preferredSize = new Size(120, 25); // Use 25 for height, so it sits in the middle
/// <summary>
/// Access the textbox
/// </summary>
public TextBox TextBox
{
get { return MyTextBox; }
}
public int CornerRadius
{
get { return cornerRadius; }
set
{
cornerRadius = value;
RestyleTextBox();
this.Invalidate();
}
}
public Color BorderColor
{
get { return borderColor; }
set
{
borderColor = value;
RestyleTextBox();
this.Invalidate();
}
}
public int BorderSize
{
get { return borderSize; }
set
{
borderSize = value;
RestyleTextBox();
this.Invalidate();
}
}
public Size PrefSize
{
get { return preferredSize; }
set
{
preferredSize = value;
RestyleTextBox();
this.Invalidate();
}
}
public TextBoxOwnerDraw()
{
MyTextBox = new TextBox();
this.Controls.Add(MyTextBox);
RestyleTextBox();
}
private void RestyleTextBox()
{
double TopPos = Math.Floor(((double)this.preferredSize.Height / 2) - ((double)MyTextBox.Height / 2));
MyTextBox.BackColor = Color.White;
MyTextBox.BorderStyle = BorderStyle.None;
MyTextBox.Multiline = false;
MyTextBox.Top = (int)TopPos;
MyTextBox.Left = this.BorderSize;
MyTextBox.Width = preferredSize.Width - (this.BorderSize * 2);
this.Height = MyTextBox.Height + (this.BorderSize * 2); // Will be ignored, but if you use elsewhere
this.Width = preferredSize.Width;
}
protected override void OnPaint(PaintEventArgs e)
{
if (cornerRadius > 0 && borderSize > 0)
{
Graphics g = e.Graphics;
g.SmoothingMode = SmoothingMode.AntiAlias;
Rectangle cRect = this.ClientRectangle;
Rectangle safeRect = new Rectangle(cRect.X, cRect.Y, cRect.Width - this.BorderSize, cRect.Height - this.BorderSize);
// Background color
using (Brush bgBrush = new SolidBrush(MyTextBox.BackColor))
{
DrawRoundRect(g, bgBrush, safeRect, (float)this.CornerRadius);
}
// Border
using (Pen borderPen = new Pen(this.BorderColor, (float)this.BorderSize))
{
DrawRoundRect(g, borderPen, safeRect, (float)this.CornerRadius);
}
}
base.OnPaint(e);
}
#region Private Methods
private GraphicsPath getRoundRect(int x, int y, int width, int height, float radius)
{
GraphicsPath gp = new GraphicsPath();
gp.AddLine(x + radius, y, x + width - (radius * 2), y); // Line
gp.AddArc(x + width - (radius * 2), y, radius * 2, radius * 2, 270, 90); // Corner (Top Right)
gp.AddLine(x + width, y + radius, x + width, y + height - (radius * 2)); // Line
gp.AddArc(x + width - (radius * 2), y + height - (radius * 2), radius * 2, radius * 2, 0, 90); // Corner (Bottom Right)
gp.AddLine(x + width - (radius * 2), y + height, x + radius, y + height); // Line
gp.AddArc(x, y + height - (radius * 2), radius * 2, radius * 2, 90, 90); // Corner (Bottom Left)
gp.AddLine(x, y + height - (radius * 2), x, y + radius); // Line
gp.AddArc(x, y, radius * 2, radius * 2, 180, 90); // Corner (Top Left)
gp.CloseFigure();
return gp;
}
private void DrawRoundRect(Graphics g, Pen p, Rectangle rect, float radius)
{
GraphicsPath gp = getRoundRect(rect.X, rect.Y, rect.Width, rect.Height, radius);
g.DrawPath(p, gp);
gp.Dispose();
}
private void DrawRoundRect(Graphics g, Pen p, int x, int y, int width, int height, float radius)
{
GraphicsPath gp = getRoundRect(x, y, width, height, radius);
g.DrawPath(p, gp);
gp.Dispose();
}
private void DrawRoundRect(Graphics g, Brush b, int x, int y, int width, int height, float radius)
{
GraphicsPath gp = getRoundRect(x, y, width, height, radius);
g.FillPath(b, gp);
gp.Dispose();
}
private void DrawRoundRect(Graphics g, Brush b, Rectangle rect, float radius)
{
GraphicsPath gp = getRoundRect(rect.X, rect.Y, rect.Width, rect.Height, radius);
g.FillPath(b, gp);
gp.Dispose();
}
#endregion
}
Now for the ToolStripControlHost
public partial class ToolStripTextBoxOwnerDraw : ToolStripControlHost
{
private TextBoxOwnerDraw InnerTextBox
{
get { return Control as TextBoxOwnerDraw; }
}
public ToolStripTextBoxOwnerDraw() : base(new TextBoxOwnerDraw()) { }
public TextBox ToolStripTextBox
{
get { return InnerTextBox.TextBox; }
}
public int CornerRadius
{
get { return InnerTextBox.CornerRadius; }
set
{
InnerTextBox.CornerRadius = value;
InnerTextBox.Invalidate();
}
}
public Color BorderColor
{
get { return InnerTextBox.BorderColor; }
set
{
InnerTextBox.BorderColor = value;
InnerTextBox.Invalidate();
}
}
public int BorderSize
{
get { return InnerTextBox.BorderSize; }
set
{
InnerTextBox.BorderSize = value;
InnerTextBox.Invalidate();
}
}
public override Size GetPreferredSize(Size constrainingSize)
{
return InnerTextBox.PrefSize;
}
}
Then When you want to use it, just add it to the tool bar:
ToolStripTextBoxOwnerDraw tBox = new ToolStripTextBoxOwnerDraw();
this.toolStripMain.Items.Add(tBox);
or however you want to add it. If you are in Visual Studio, the preview window supports rendering this Control.
There is only one thing to remember, when accessing the TextBox with the actual text in it, its:
tBox.ToolStripTextBox.Text;