I am trying to make all check boxes in my C# Winform application to ignore user's DPI setting when it's bigger than 100%.
i.e I want the size of the checkbox to remain small as if it's on 100% font scale, even when the user has increased the DPI setting.
Using FlatStyle.Flat checkboxes, the size of the checkboxes in various DPI settings:
100% (96 DPI) : 11 pixel x 11 pixel (including border)
125% (120 DPI) : 13 x 13
150% (144 DPI) : 16 x 16
I have done the following but the size of my checkboxes still increases:
AutoScaleMode = System.Windows.Forms.AutoScaleMode.None;
set all Font to use GraphicsUnit.Pixel
So, is there a way for my checkbox to remain 11pixel by 11pixel regardless of DPI setting?
If it's not possible, I have also tried overriding OnPaint on my custom checkbox to paint over the checkbox without painting over the Text.
But it proves to be complicated since the starting (X,Y) coordinate to render the box is somewhat affected by CheckAlign and RightToLeft properties.
This is the code snippet.
I intended for red box to paint over the original checkbox.
And green box as the new checkbox I want to display instead.
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
// We need to override checkbox drawing for large font scaling since it will be drawn bigger than usual.
// ex: At 100% scaling, checkbox size is 11x11. At 125% scaling, checkbox size is 13x13.
if (e.Graphics.DpiX > 96)
{
// What is the x & y co-ords of the checkbox?
int x = 0;
int y = 0;
using (SolidBrush brush = new SolidBrush(Enabled ? Color.White : SystemColors.Control))
{
// Red checkbox is to override the original checkbox drawn
var scaleFactor = e.Graphics.DpiX / 96;
e.Graphics.DrawRectangle(new Pen(Color.Red), x, y, (float)Math.Round(CHECKBOX_WIDTH * scaleFactor) + 1, (float)Math.Round(CHECKBOX_HEIGHT * scaleFactor) + 1);
// Green checkbox is to draw the small checkbox that I want 11x11 (including border)
e.Graphics.FillRectangle(brush, x, y, CHECKBOX_WIDTH, CHECKBOX_HEIGHT);
e.Graphics.DrawRectangle(new Pen(Color.Green), x, y, CHECKBOX_WIDTH + 1, CHECKBOX_HEIGHT + 1); // Draw the outer border
}
}
}
Can anyone help me how to determine the correct X,Y coord for me to draw the red checkbox, please (so I can paint over the original box).
Thank you :)
Related
I am trying to create a 'jeopardy' type game and i have gotten to the point where you just have to click on a box and a question box will appear.
Im using a customPictureBox, essentially overriding text and such:
protected override void OnPaint(PaintEventArgs pe)
{
base.OnPaint(pe);
Size size = TextRenderer.MeasureText(text, font);
int x = Width / 2 - size.Width / 2;
int y = Height / 2 - size.Height / 2;
pe.Graphics.DrawString(text, font, new SolidBrush(color), x, y);
}
on the game i add its properties like this:
CustomPictureBox box = new CustomPictureBox()
{
Text = ((QuestionBox)sender).Question.Text.ToUpper(),
SizeMode = PictureBoxSizeMode.StretchImage,
Font = new Font("Comic Sans MS", 40),
Image = Properties.Resources.MainTile,
ForeColor = Color.White,
BorderStyle = BorderStyle.FixedSingle,
Dock = DockStyle.Fill
};
The problem comes when the text length exceeds certain amount of (depending on the monitor) words, essentially what happens is that the text is drawn but a majority of it goes outside of the form, adding Autosize and Maximum size
Reference, this adds the MaxSize to the whole CustomPictureBox.
This solution seems to put an offset for the words, making longer sentences fly completely off the screen.
Ideally I need to add a newline til the words reach a preset boundary so they wouldn't go off the form.
Do not use Graphics.DrawString, use TextRenderer.DrawText instead.
Specifically, use this overload since it accepts
IDeviceContext - that's your Graphics instance,
String - that's the string to draw,
Font - that's the font to use,
Rectangle - that's the rectangle to use for boundaries,
Color - that's the fore color to use,
And TextFormatFlags - that's a flags enum that allows you to specify how to wrap text - using either default or WordBreak.
So, replace this row:
pe.Graphics.DrawString(text, font, new SolidBrush(color), x, y);
With this:
var flags = TextFormatFlags.HorizontalCenter |
TextFormatFlags.VerticalCenter |
TextFormatFlags.WordBreak;
TextRenderer.DrawText(pe.Graphics, text, font, pe.ClipRectangle, ForeColor, flags);
I am trying to insert a text watermark underneath a TIFF image in my windows form and would definitely appreciate anyone's help. I have a print button that retrieves the image, scales it down, then based on my margins, places the image accordingly to print. I'd like to add an additional piece where just before the image prints, I add in a text watermark (in this case a date stamp) that is just below the image.
I've tried adjusting the margin but that just increases (or decreases depending on the number setting) the image scale but does not add the additional room I want to add the watermark. Below is code of what I have so far:
protected void PrintPage(object sender, PrintPageEventArgs e)
{
if (this.Image == null)
{
e.Cancel = true;
return;
}
//ADD TIME STAMP WATERMARK
string watermark = "DATE ISSUED: " + String.Format("{0:MM/dd/yyyy}", System.DateTime.Now.Date);
System.Drawing.Graphics gpr = Graphics.FromImage(Image);
System.Drawing.Brush brush = new SolidBrush(System.Drawing.Color.Black);
Font font = new System.Drawing.Font("Arial", 55, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Pixel);
SizeF size = e.Graphics.MeasureString(watermark, font);
float x = 0;
float y = Image.Height-size.Height;
RectangleF printArea = new RectangleF(x, y, size.Width, size.Height);
gpr.DrawString(watermark, font, brush, printArea);
e.Graphics.DrawImage(this.Image, e.MarginBounds);
}
The value of e.MarginBounds I have set in my App.config and include the following values: Left=70, Right=90, Top=190; Bottom=475. All the printouts are going to be printed portrait style on Letter 8 1/2 by 11 size paper.
I am able to display the watermark anywhere on top of the image, but I am hoping to place it underneath. When I adjust the y coordinate, and it so happens to be below the image, when I print, I assume that it is outside the print area and therefore, the watermark does not get printed on the page (it only shows the image).
I appreciate anyone's help in this as I have been racking my brain on this and have had no luck.
Aren't you printing your text beneath the image. I think you want to start printing at y=Image.Height + e.MarginBounds.Top, and x=e.MarginBounds.Left
That will print a your label left justified below the image in the margin.
Update: This works:
y=-size.Height + e.MarginBounds.Bottom;
x = e.MarginBounds.Left;
e.Graphics.DrawImage(Image, e.MarginBounds);
// note the change. Using e.graphics instead of gpr below
e.Graphics.DrawString(watermark, font, brush, printArea);
I'm attempting to make a custom control that properly draws itself to fill its current size. I was under the assumption that I should use the ClientRectangle property for sizing, but the right and bottom of the client rectangle seem to be getting clipped.
Filling the draw event handler with
Rectangle smaller = new Rectangle(5, 5, ClientRectangle.Width - 10, ClientRectangle.Height - 10);
e.Graphics.DrawRectangle(System.Drawing.Pens.Black, smaller);
e.Graphics.DrawRectangle(System.Drawing.Pens.Red, ClientRectangle);
yields this:
What should I be using to get the drawable area of the control?
You can either use:
ControlPaint.DrawBorder(g, this.ClientRectangle, _
Color.Red, ButtonBorderStyle.Solid);
where Graphics g = e.Graphics;.
Or draw it as you did but subtracting 1 from width and height (1 because width and height are inclusive but draw rectangle needs the size exclusive the last pixel - internally it calculates x + w/y + h which then ends up at the position for the next pixel after the last, hence we need to subtract one to get the position for the last pixel).
rectangle r = this.ClientRectangle;
r.Width -= 1;
r.Height -= 1;
g.DrawRectangle(System.Drawing.Pens.Red, r);
And of course this from within the OnPaint event handler.
i have no previous experience in plotting in winforms, in one form i want to plot ecg. or lets say a sin wave or any wave function in a specific area, but what i am doing is e.c.g.. rest of the form will be normal form with buttons and labels,
can anybody be nice enough to through in a tutorial
:)
You have few choices, you can write your own control, that will process data and render it. For more complicated plots, that can be a bit complicated, but the basics are always the same, setting X and Y values ranges and then just draw a line using GDI going from left to right, nothing fancy.
As this can get a bit complicated for more advanced features, you could use some charting controls, I'd read this post or check codeproject.com, I remember, that I saw few attempts to write some decent charting controls, which are open source, new articles will probably be coded in WPF, but you should find something older as well.
Edit:
Some links that you can find useful: Graph plotting lib that's main goal is to simulate ECG or another graph plotting lib
You need to create a custom control.
public class MyECGDrawer : Control{}
In it, you override the OnPaint event
protect override OnPaint(PaintEventArgs pe ){}
Then in the paint function, you draw your graphics the way you want it, let's say sin(x)
// refresh background
pe.Graphics.FillRectangle( Brushes.White, 0, 0, Width, Height );
int prevX = -1, prevY = -1;
for(int x = 0; x < Width; x++ )
{
if( prevX >= 0 )
{
pe.Graphics.DrawLine( Pens.Black, prevX, prevY, x, Math.sin(x) );
}
prevX = x;
prevY = Math.sin(x);
}
To force the ECG to redraw, you call the .Invalidate() function on the control. You should be able to drag and drop the control in your form from the designer.
In the end, the class would look like
public class MyECGDrawer : Control{}
In it, you override the OnPaint event
public class MyECGDrawer : Control
{
protect override OnPaint(PaintEventArgs pe )
{
// refresh background
pe.Graphics.FillRectangle( Brushes.White, 0, 0, Width, Height );
int prevX = -1, prevY = -1;
for(int x = 0; x < Width; x++ )
{
if( prevX >= 0 )
pe.Graphics.DrawLine( Pens.Black, prevX, prevY, x, Math.sin(x) );
prevX = x;
prevY = Math.sin(x);
}
}
}
I wrote up the following and tested it. It seems to do what you want, but note that it is simply plotting sin(x) in a loop with no delay - i.e. the plot for sin(x) streams off the left edge so fast you can hardly see it. You can, however, put a break on any line inside the loop and then step through the loop with F5 to see it work slowly - presumably your streaming ECG data will only arrive at some fixed speed so this should not be a problem in your implementation.
In the following, monitor is a PictureBox on a winforms form. Everything else is local.
private void drawStream(){
const int scaleX = 40;
const int scaleY = 40;
Point monitorTopLeft = new Point(0, 0);
Point MonitorTopLeftMinus1 = new Point(-1, 0);
int halfX = monitor.Width / 2;
int halfY = monitor.Height / 2;
Size size = new Size(halfX + 20, monitor.Height);
Graphics g = monitor.CreateGraphics();
g.TranslateTransform(halfX, halfY);
g.ScaleTransform(scaleX, scaleY);
g.Clear(Color.Black);
g.ResetClip();
float lastY = (float)Math.Sin(0);
float y = lastY;
Pen p = new Pen(Color.White, 0.01F);
float stepX = 1F / scaleX;
for (float x = 0; x < 10; x += stepX) {
g.CopyFromScreen(monitor.PointToScreen(monitorTopLeft), MonitorTopLeftMinus1, size, CopyPixelOperation.SourceCopy);
y = (float)Math.Sin(x);
g.DrawLine(p, -stepX, lastY, 0, y);
lastY = y;
}
}
Some additional info that may be helpful:
The origin in a picture box starts
out at the top left corner.
TranslateTransform allows you to
translate (i.e. move) the origin.
In the example, I translate it by
half the picture box's width and
half its height.
ScaleTransform changes the magnification of the picturebox - note that it even magnifies the width of the pen used to draw on the picturebox - this is why the pen's width is set to 0.01.
CopyFromScreen performs a bitblt. Its source point is relative to the screen, the destination is relative to the picturebox and the size of the rectangle to move disregards any transforms (like the scale and translation transforms we added).
Notice that the X coordinates in the DrawLine method are -stepx and 0. All drawing basically occurs right on the y axis (i.e. x = 0) and then CopyFromScreen moves the drawn portion to the left so that it "streams" off to the left.
Unless you are doing this as a learning experience, you may want to consider looking at the free Microsoft Chart Controls for .NET available here.
http://www.microsoft.com/downloads/details.aspx?FamilyID=130f7986-bf49-4fe5-9ca8-910ae6ea442c&displaylang=en#QuickInfoContainer
That being said, I would offer the following guidelines if you want to roll your own.
Create a user control to encapsulate the plot rendering rather than render directly on the form.
In your control, expose properties to get/set the data you wish to render and add any other properties you want to control the rendering (scaling, panning, colors, etc.)
In you control, either override the OnPaint method or create an event handler for the Paint event. These methods will have a PaintEventArgs object passed to them, which contains a Graphics object as a property. The methods of the Graphics object are used to render points, lines, etc onto the control when it needs to be painted. Most of the drawing operations require either a pen (outlines / lines) or a brush (filled areas). You can use stock objects for these operations (e.g. Pens.Black or Brushes.Red) or you can create your own (see documentation). If you create you own objects, make sure you dispose of them after using them (e.g. using the "using" statement or by calling Dispose).
There are a couple good books on GDI+. I suggest picking one up if you are going in deep.
I have a pictureBox2 and it is set to zoom, I am trying to find out how to to get a real x,y pixel location on the image by Mouse.Click on pictureBox2. but I tried 3 possible ideas I knew of: without/with PointToClient,PointToScreen but I can never get it right.
private void pictureBox2_Click(object sender, EventArgs e)
{
MouseEventArgs me = (MouseEventArgs)e;
txtpictureHeight.Text =(
(OriginalImage.GetImageHeight()*me.Location.Y)/ pictureBox2.Image.Height).ToString();
txtpictureWidth.Text = (
(OriginalImage.GetImageWidth()* me.Location.X)/ pictureBox2.Image.Width).ToString();
}
There must be some factor I need to take care of so I thought to use double result from above and I get closed but there is still 80px off for the height on my test image (1371x2221). As I use Zoom so there are 2 extra spaces on my pictureBox2
Note that with SizeMode set to Zoom, the PictureBox keeps aspect ratio, and centers the image, so on top of calculating the adjusted coordinates, you also have to take padding into account.
My advice, don't use the Click event; it is meant to detect button clicks, not to actually process interaction of the mouse with an object. Use MouseDown instead.
The first thing we need to do is get the width and height of the original image. As I noted in my comment, this is simply the object inside the Image property of the PictureBox.
Next, we need the dimensions and location of the zoomed image. For that, we can start from the dimensions of the ClientRectangle of the PictureBox. Divide those by the image width and height and you'll get the horizontal and vertical zoom values. If the SizeMode would be set to StretchImage, that'd be all we need, but since aspect ratio is conserved, you need the smallest of the two values to have the actual zoom factor.
Once we got that, multiply the original width and height by this zoom factor to get the zoomed width and height, then subtract that from the actual ClientRectangle dimensions and divide it by two to get the padding for both dimensions. This can of course be simplified by checking which of the two possible zoom factors is used, and only calculating the padding for the other one, since the dimension of which the zoom factor was used obviously has 0 padding.
Now you got the padding and zoom factor, the rest is simple: subtract the padding values from the mouse coordinates, and then divide both results by the zoom factor.
private void pictureBox1_MouseDown(object sender, MouseEventArgs e)
{
// Default check: left mouse button only
if (e.Button == MouseButtons.Left)
ShowCoords(e.X, e.Y);
}
private void pictureBox1_MouseMove(object sender, MouseEventArgs e)
{
// Allows dragging to also update the coords. Checking the button
// on a MouseMove is an easy way to detect click dragging.
if (e.Button == MouseButtons.Left)
ShowCoords(e.X, e.Y);
}
private void ShowCoords(Int32 mouseX, Int32 mouseY)
{
Int32 realW = pictureBox1.Image.Width;
Int32 realH = pictureBox1.Image.Height;
Int32 currentW = pictureBox1.ClientRectangle.Width;
Int32 currentH = pictureBox1.ClientRectangle.Height;
Double zoomW = (currentW / (Double)realW);
Double zoomH = (currentH / (Double)realH);
Double zoomActual = Math.Min(zoomW, zoomH);
Double padX = zoomActual == zoomW ? 0 : (currentW - (zoomActual * realW)) / 2;
Double padY = zoomActual == zoomH ? 0 : (currentH - (zoomActual * realH)) / 2;
Int32 realX = (Int32)((mouseX - padX) / zoomActual);
Int32 realY = (Int32)((mouseY - padY) / zoomActual);
lblPosXval.Text = realX < 0 || realX > realW ? "-" : realX.ToString();
lblPosYVal.Text = realY < 0 || realY > realH ? "-" : realY.ToString();
}
Note, I used sharp pixel zoom here to better show the effect. It's a little trick you can do by subclassing PictureBox and overriding its OnPaint method, to adjust the Graphics object from the PaintEventArgs object and set its InterpolationMode to NearestNeighbor (It's also advised to set PixelOffsetMode to Half; there's a bug where sharp zoom is shifted half a pixel unless you do that). Then you call base.OnPaint() with that adjusted event args object.
I also added some more info on it here, but that's all just stuff you can get from the in-between values of the pixel coordinates calculation process anyway.