Winform Textbox on top of image not printing - c#

I have a Winform application that creates signs. Everything works and looks fine except when I print. I have an image with textboxes placed on top of them. They are visible on my computer, but not when I print. I am assuming that somehow when I print the image is getting "Brought-to-front."
Below is my Print Function:
private void btnPrint_Click(object sender, EventArgs e)
{
PrintDocument pd = new PrintDocument();
pd.PrintPage += new PrintPageEventHandler(PrintImage);
pd.Print();
}
void PrintImage(object o, PrintPageEventArgs e)
{
int x = SystemInformation.WorkingArea.X;
int y = SystemInformation.WorkingArea.Y;
int width = this.Width;
int height = this.Height;
Rectangle bounds = new Rectangle(x, y, width, height);
Bitmap img = new Bitmap(width, height);
this.DrawToBitmap(img, bounds);
Point p = new Point(100, 100);
e.Graphics.DrawImage(img, p);
}
I don't know for sure that anything in the print function is a cause, but I can't think of anything else.

I don't know the answer to the question of why the TextBox contents are not being rendered, but I can tell you that you are doing it "wrong".
What you should be doing is rendering the text in your paint handler and using a single, "in-place" TextBox for allowing the user to edit text at some location on the form which is moved into position and made visible just-in-time for editing.
It requires that your "document" consist of lists of objects (like "text blocks") which you can render and which you can detect the bounds of for when the user tries to manipulate them. This is very similar to how a "paint" program works.
This is going to be a complete departure from what you are doing now. It's always a lot more work to do things "correctly". I don't want to tell you to redo your application. If this is a learning experience and not a commercial product, it's OK to hack your way through it, using what you're familiar with. But maybe next time you might try another approach.

Related

Unable to create ellipse on mouse double click event

I am trying to create a elipse in a textbox on double click. But it doesnt seem to happen.
panel.MouseClick += create_terms;
private void create_terms(object sender, EventArgs arg)
{
if (Phys_terms_check.Checked == true)
{
MouseEventArgs e = (MouseEventArgs)arg;
Graphics g = CreateGraphics();
SolidBrush p = new SolidBrush(Color.Red);
Pen erase = new Pen(Color.White);
Panel panel = (Panel)sender;
g.FillEllipse(p, e.X+panel.Left,e.Y+panel.Top,10,10);
}
}
The e.x and e.y seem to be giving relative coordinates from the sender. How to get point relative to the form.
add sender's top and left coordinates.
g.FillEllipse(p, e.X + textbox.Left, e.Y + textbox.Top, 10, 10);
but, this won't show, because textbox paint event fill fire and repaint textbox.
First of all: TextBoxes are old legacy and rather special Controls that do not support all things normal controls let you do.
Among the things that won't work are
Setting a BackgroundImage
Owner-drawing them
The latter includes any drawing in its Paint/OnPaint events.
You can code and hook-up the Paint event, but it won't get called.
You still can draw onto a TextBox using CreateGraphics, if you do it right, but as always with this function the result is non-persistent and will go away as soon as the system refreshes the TextBox itself, which is super-fast: as soon as you move your cursor over it the circle you draw may disappear..
The code to do it would have to look similar to this:
Graphics g = yourTextBox.CreateGraphics();
g.FillEllipse(Brushes.Red, yourTextBox.Width - 22, 2, 11, 11);
But as I said this will not persist, so it has little or no value.
If you want to draw onto something with a visible Text property you can use a Label:
private void yourLabel_Paint(object sender, PaintEventArgs e)
{
e.Graphics.FillEllipse(Brushes.Red, yourLabel.Width - 22, 2, 11, 11);
}
The result looks the same, but only the dot in the Label will persist, e.g. a Minimize-Maximize of the form.. In fact the dot in the TextBox didn't even survive calling my screenshot program, so I had use resort to pressing the Print-Key !
For drawing circles upon mouseclicks onto normal controls see this post!

Receipt printing in roll paper

I searched for a lot in google and did not find what i actually wanted. I got following code, which will print the variable name. I got a Epson Dot Matrix Printer and Roll Paper (Endless continuous paper).
My problem is that, after printing name paper feeds up to size of A4. I don`t want the paper feed. This application is intended to do print receipts which will have unlimited data which need to be printed flawless ( with out page break).
Can you, the smart folk out there to point me in the correct direction with these codes?
edited this code and changed scenario .. please move down further
private void pd_PrintPage(object sender, PrintPageEventArgs e)
{
Font Heading2 = new Font("Times New Roman", 13);
StringFormat sf = new StringFormat();
sf.LineAlignment = StringAlignment.Near;
sf.Alignment = StringAlignment.Center;
//e.HasMorePages = false;
PaperSize pkCustomSize1 = new PaperSize("First custom size", 100, 200);
pd.DefaultPageSettings.PaperSize = pkCustomSize1;
e.Graphics.DrawString(name.ToString(), Heading1, Brushes.Black, e.MarginBounds.Left + (e.MarginBounds.Width / 2), e.MarginBounds.Top, sf);
}
Edit 1:- #Adriano Repetti suggested this is a duplicate with Form feed in c# printing. What i learned from the above question is that he want to add the form feed. But i want to remove the form feed.
Edit 2:- I got an another hint by googling that setting the page Height equal to line height will make stop feeding which sounds promising. I am trying to figure that out too.
Edit 3:- #Adriano Repetti suggested me with Raw Printing (Directly printing binary data) with KB link. I googled around about it, and found out its c# better equivalent paste bin or pastie.org (provided because it is a handy one) . At first it sounded good and it worked nicely stopping form feeding. But eventually i struck some ice berg.
On my code i had to align some printing quotes to center or align to left`. for which i got only option of using space and tabs. But there will be no guaranty that it will be well formatted as we can not certain about built in fonts with printer.( Refer:SO Question by #syncis )
And secondly, i will have to move my application to unicode(local language support) capable one, at least with in a month or so. In that scenario, raw printing wont help and i will have to go through theses faces again. SO, For avoiding that its better for me to stay with Graphics DrawString. And for this i changed my code as.
//---------
// start button click
//---------
PrintDocument pdoc = new PrintDocument();
pdoc.DefaultPageSettings.PaperSize.Height = 300;
pdoc.Print();
//---------
// end button click
//---------
private void pd_PrintPage(object sender, PrintPageEventArgs e)
{
Font Heading2 = new Font("Times New Roman", 13);
// changed following statement to met with new **unicode** criteria
//e.Graphics.DrawString(name.ToString(), Heading1, Brushes.Black, e.MarginBounds.Left + (e.MarginBounds.Width / 2), e.MarginBounds.Top, sf);
TextRenderer.DrawText(e.Graphics, "My name in local language is വിനീത്", Heading2, new Point(0, 0), Color.Black);
}
With current problems, i am redefining the Question extending tag to unicode as.
How can print with TextRenderer.DrawText with unicode support without form feeding ? I think setting paper height to line height will solve my problem. If so how or suggest me a better way to stop paper feeding. It really eats a lot of my valuable time...
EDIT 4: Today I found out a very interesting thing about my printer. I cant even set custom paper size manually (Not by coding.. I mean control panel->printers and faxes ->Epson LX-300+ ->properties -> printing preference-> paper/quality -> advanced -> paper size -> BOOOOOM not showing my custom paper size). I am using Epson LX-300+ printer. Do guys think it wont support custom paper sizes? is that causing me problems?
I found the solution by my self ( sorry for my english ) As Hans Passant says ( PrintDocument is page based. end of story ) You must use ( e.HasMorePages = true; )
float cordenadaX;
float cordenadaY;
int totalPages;
int paginaAtual;
int indiceItem;
List<string> items;
public void ImprimeDanfeNFCe()
{
totalPages = 1;
paginaAtual = 1;
indiceItem = 0;
cordenadaX = 0;
cordenadaY = 0;
items = new List<string>();
items.Add("Item1");
items.Add("Item2");
items.Add("Item3");
(............)
PrintDocument recordDoc = new PrintDocument();
recordDoc.DocumentName = "xMarket danfe";
recordDoc.PrintPage += new PrintPageEventHandler(imprimeDanfeReceipt);
PrinterSettings ps = new PrinterSettings();
ps.PrinterName = "My printer";
recordDoc.PrinterSettings = ps;
recordDoc.Print();
recordDoc.Dispose();
}
void imprimeDanfeReceipt(PrintPageEventArgs e)
{
float pageHeight = e.MarginBounds.Height;
string text = "";
if (paginaAtual == 1)
{
text = "Cupom header";
e.Graphics.DrawString(text, drawFontDanfeTitulo, drawBrush, new
RectangleF(cordenadaX, cordenadaY, width, height),
drawFormatCenter);
cordenadaY += e.Graphics.MeasureString(text, drawFontDanfeTitulo).Height;
}
for (int i = indiceItem; i < items.Count; i++)
{
int indice = i + 1;
//items[i] Is very important to not print same items again while print next page
e.Graphics.DrawString(items[i], drawFontDanfeItems, drawBrush,
new RectangleF(cordenadaX, cordenadaY, width, height), drawFormatLeft);
cordenadaY += e.Graphics.MeasureString(text, drawFontDanfeTitulo).Height;
indiceItem++;
//cordenadaY+100 is for the size of the footer
if (cordenadaY+100 >= pageHeight)
{
paginaAtual++;
e.HasMorePages = true;
return;
}
}
e.Graphics.DrawString("page footer", drawFontDanfeItems, drawBrush,
new RectangleF(cordenadaX, cordenadaY, width, height), drawFormatLeft);
cordenadaY += e.Graphics.MeasureString(text, drawFontDanfeTitulo).Height;
}

How to call Invalidate not for the whole panel from another event/class

I have a paint event which looks like this:
private void panel1_Paint(object sender, PaintEventArgs e)
{
Rectangle rec = new Rectangle(2, 2, 820, 620);
Pen pi = new Pen(Color.Black, 2);
e.Graphics.DrawRectangle(pi, rec);
Rectangle rec2 = new Rectangle(Convert.ToInt32((410 + 2500 * GlobaleVariablen.IstWerte[0])), Convert.ToInt32(310 + 1875 * GlobaleVariablen.IstWerte[1]), 2, 2);
e.Graphics.DrawRectangle(pi,rec2);
}
I have a datastream from a serialport, and everytime I receive data I want to invalidate rec2 but not the whole form. I was able to invalidate the whole form with within my Datareceived Event with:
panel1.Invalidate();
However I do not know how I can make it happen to only invalidate my rec2, because if you invalidate the whole form all the time with a datastream it blinks like crazy and it really does not look very good.
Invalidate() has an overload version with Rectangle you want to invalidate:
panel1.Invalidate(GetRect2());
Where GetRect2() (please pick a better name) is something like:
static Rectangle GetRect2() {
int x Convert.ToInt32((410 + 2500 * GlobaleVariablen.IstWerte[0]));
int y = Convert.ToInt32(310 + 1875 * GlobaleVariablen.IstWerte[1]);
return new Rectangle(x, y, 2, 2);
}
In your paint event handler you have first to check if invalidated region intersects each object you want to write (example is simple because you're working with rectangles and you do not have slow expansive filling).
What will hurt performance more in your code is fact you're creating a new Pen for each paint operation. It's something you have to absolutely avoid: expansive native resources must be reused. Final code may be something similar to:
private Pen _pen = new Pen(Color.Black, 2);
private void panel1_Paint(object sender, PaintEventArgs e)
{
var rec = new Rectangle(2, 2, 820, 620);
if (e.ClipRectangle.IntersectsWith(rec))
e.Graphics.DrawRectangle(_pen, rec);
var rec2 = GetRect2();
if (e.ClipRectangle.IntersectsWith(rec2))
e.Graphics.DrawRectangle(pi, rec2);
}
Now your code is slightly more optimized but it may still blink. To avoid that you have to enable double buffering for your panel. Derive your own class from Panel and add in its constructor:
SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
It may also be a good chance to refactor your code and move some paint logic in a separate class (but not panel itself). Please refer to MSDN for other flags you may need to use (such as AllPaintingInWmPaint).
Final note: you hard-coded coordinates, it's not a good practice unless you have a fixed size panel (with or without scrolling) because it won't scale well with future changes and it may be broken in many circumstances (as soon as your code will become slightly more complex than a fictional example).

PictureBox.Invalidate not re-rendering correctly

I'm trying to build a little test application (and my WinForm skills have rusted somewhat) with an Image and some overlays on top of it.
My image is set to stretch in the PictureBox but my fields on the right hand side I want to be from the origin of the image. Therefore I decided to render directly on the image that the PictureBox is using to ensure that the co-ordinates are always correct. Here's the white box rendering:
private void pbImage_Paint(object sender, PaintEventArgs e)
{
try
{
if (this.rdFront.Checked)
RenderFront(pbImage.Image, true);
else
RenderBack(pbImage.Image, true);
}
catch (ArgumentNullException ex)
{ }
}
public void RenderFront(Image image, bool includeBoxes)
{
// If we have no image then we can't render
if (image == null)
throw new ArgumentNullException("image");
Graphics gfx = Graphics.FromImage(image);
// Get the top label
foreach (MessageConfiguration config in this.config.Values.Where(c => c.Front))
{
if (includeBoxes)
{
// Fill a White rectangle and then surround with a black border
gfx.FillRectangle(Brushes.White, config.X, config.Y, config.Width, config.Height);
gfx.DrawRectangle(Pens.Black, config.X - 1, config.Y - 1, config.Width + 2, config.Height + 2);
}
gfx.DrawString(config.Text, new Font(FontFamily.GenericMonospace, config.FontSize), Brushes.Black, new PointF(config.X, config.Y));
}
}
The problem that I've got is if I do this and always draw on the underlying image then when I move the white overlay, I end up with un-drawn parts of the image. So I decided to clone the image before each re-render (on the basis that I don't care about performance).
I therefore decided to clone the image whenever I need to manually invalidate it, and call this when a setting changes:
public void Refresh()
{
if (this.rdFront.Checked)
pbImage.Image = new Bitmap(front);
else
pbImage.Image = new Bitmap(back);
this.pbImage.Invalidate();
}
Now I'm sure I must be missing something obvious - if I modify one of the values my penguins render with no overlay. However if I force a resize of the application then both the penguins and the overlay suddenly appear.
Can anyone suggest what I might be doing wrong?
Edit
Here's a download link to the project as it's quite small. Paste a path to an image in the 'Front Image' box and try using the controls on the right (set 100x100 height and width). Try re-sizing to see the desired affect. https://dl.dropboxusercontent.com/u/41796243/TemplateTester.zip
Controls and Forms have a Refresh method already. Are you really calling your Refresh method? Aren't you getting a warning that you should use the new keyword? Better give your Refresh method another name (e.g RefreshImage)!
I'm really not sure why you are using a picture box but then decide to do your on painting. I suggest to draw to an image off-screen and then simply assign it to the picture box:
public void RefreshImage()
{
Bitmap bmp;
if (this.rdFront.Checked)
bmp = new Bitmap(front);
else
bmp = new Bitmap(back);
using (Graphics gfx = Graphics.FromImage(bmp)) {
foreach (MessageConfiguration config in this.config.Values.Where(c => c.Front))
{
if (includeBoxes) {
// Fill a White rectangle and then surround with a black border
gfx.FillRectangle(Brushes.White, config.X, config.Y, config.Width, config.Height);
gfx.DrawRectangle(Pens.Black, config.X - 1, config.Y - 1, config.Width + 2, config.Height + 2);
}
gfx.DrawString(config.Text, new Font(FontFamily.GenericMonospace, config.FontSize), Brushes.Black, new PointF(config.X, config.Y));
}
}
pbImage.Image = bmp;
}
and remove the pbImage_Paint method.
Another possibility is to use the pbImage_Paint event handler in another way. Call the base.Paint() handler of the picture box that draws the image but leave the image itself unchanged. Instead draw on top of it by using the Graphics object given by the PaintEventArgs e argument. This Graphics object represents the client area of the picture box. This does not alter the Bitmap assigned to the picture box, but only draws on the screen.
private void pbImage_Paint(object sender, PaintEventArgs e)
{
base.Paint(); // Paints the image
if (this.rdFront.Checked)
RenderFront(e.Graphics, true);
else
RenderBack(e.Graphics, true);
}
public void RenderFront(Graphics g, bool includeBoxes)
{
foreach (MessageConfiguration config in this.config.Values.Where(c => c.Front)) {
if (includeBoxes) {
g.FillRectangle(Brushes.White, config.X, config.Y, config.Width, config.Height);
g.DrawRectangle(Pens.Black, config.X - 1, config.Y - 1, config.Width + 2, config.Height + 2);
}
g.DrawString(config.Text, new Font(FontFamily.GenericMonospace, config.FontSize), Brushes.Black, new PointF(config.X, config.Y));
}
}

Basic tile map in winforms

I am making a program where you bassicly move from tile to tile in windows forms.
So in order to do that, I wanted to use panels each panel has a tag. To detect collision.
So I have an image of my map. and I divided into multiple tiles. However now I have to drag 900 tiles onto panels.
This isn't very effective in 2 ways. First loading 900 textures isn't really a smart idea. Also it would take ages. So i wanted to use a spritesheet or tilemap. But how would I do that in winforms. I believe I have seen some people use a grid view or whatever. However im not sure how to do what I want to do.
What would be the best solution?
Thanks in advance!
For any serious gaming project WinForms is not the best platform. Either WPF or XNA or Unity are able to deliver high performance use of DirectX.
But since you want to do it in Winforms here is a way to do it.
It creates a whopping number of 900 PictureBoxes and loads each with a fraction of an source image:
private void Form1_Load(object sender, EventArgs e)
{
int tileWidth = 30;
int tileHeight = 30;
int tileRows = 30;
int tileCols = 30;
using (Bitmap sourceBmp = new Bitmap("D:\\900x900.jpg"))
{
Size s = new Size(tileWidth, tileHeight);
Rectangle destRect = new Rectangle(Point.Empty, s);
for (int row = 0; row < tileRows; row++)
for (int col = 0; col < tileCols; col++)
{
PictureBox p = new PictureBox();
p.Size = s;
Point loc = new Point(tileWidth * col, tileHeight * row);
Rectangle srcRect = new Rectangle(loc, s);
Bitmap tile = new Bitmap(tileWidth, tileHeight);
Graphics G = Graphics.FromImage(tile);
G.DrawImage(sourceBmp, destRect, srcRect, GraphicsUnit.Pixel);
p.Image = tile;
p.Location = loc;
p.Tag = loc;
p.Name = String.Format("Col={0:00}-Row={1:00}", col, row);
// p.MouseDown += p_MouseDown;
// p.MouseUp += p_MouseUp;
// p.MouseMove += p_MouseMove;
this.Controls.Add(p);
}
}
}
When I tried it I was a bit worried about perfomance, but..
This takes under 1 second to load on my machine.
Starting the programm adds 10MB to VS memory usage. That is like nothing.
For a fun project this will do; for best performance one might use Panels but these will have to be filled and refilled in the Paint event. This solution saves you the hassle and since you don't change the tile picture all the time this works well enough.
Pleae note: I have added a Name and a Tag to each PictureBox, so you can later refer to it. These both contain info about the original position of the Picturebox. The Name looks like this: Col=23-Row=02 and the Tag is the original Location object.
Also: Dynamically added controls take a little extra to script since you can't create their method bodies in the designer. Instead you add them like above. In doing so Intellisense and the Tab key are your best friends..
I have added three event handlers for a few mouse events. When you uncomment them you will have to add the methods like e.g. this:
void p_MouseMove(object sender, MouseEventArgs e)
{
throw new NotImplementedException();
}
But maybe you want to use other events to play like Drag&Drop or keyboard events..
There are two ways to refer to these tiles. Maybe you want to try and/or use both of them: You can loop over the form's controls with a
foreach (Control ctl in this.Controls)
{ if (ctl is PictureBox ) this.Text = ((PictureBox)ctl).Name ; }
It tests for the right type and then casts to PictureBox. As an example it displays the name of the tile in the window title.
Or you can have a variable and set it in the MouseDown event:
PictureBox currentTile;
void p_MouseDown(object sender, MouseEventArgs e)
{
currentTile = (PictureBox ) sender;
}

Categories

Resources