Graphics DrawImage, FillRectangle, and DrawCell on cells in C1FlexGrid - c#

I'm having a difficult time correctly rendering cells in a C1FlexGrid when I need to set a background color, draw an image, and fill a rectangle for a wide border. I can't seem to get the right combination of DrawCell, DrawImage, and FillRectangle for each cell to draw properly.
The "OwnerDrawCell" event is where I am drawing the contents, border, and image.
First I am setting the cell backcolor of each cell to something like this:
e.Style.BackColor = lockedBackColor;
Then for some cells I am drawing an image and text.
// CENTER TEXT IN CELL; IMAGE IS RIGHT JUSTIFIED, CENTERED VERTICALLY
// Must draw cell first - background color, borders, etc..
e.DrawCell(DrawCellFlags.Background | DrawCellFlags.Border);
// Draw cell text
int textWidth = (int)e.Graphics.MeasureString(e.Text, e.Style.Font).Width;
int textHeight = (int)e.Graphics.MeasureString(e.Text, e.Style.Font).Height;
float textCenterX = e.Bounds.Left + ((e.Bounds.Width - textWidth) / 2);
float textCenterY = e.Bounds.Top + ((e.Bounds.Height - textHeight) / 2);
e.Graphics.DrawString(e.Text, e.Style.Font, brushColorForString, textCenterX, textCenterY);
if (e.Row == 8 || PlantHasBins())
{
// Draw cell image
int cellImageX = e.Bounds.Right - _cellImage.Width;
int cellImageY = e.Bounds.Top + ((e.Bounds.Height - _cellImage.Height) / 2);
var cellImagePoint = new Point(cellImageX, cellImageY);
e.Graphics.DrawImage(_cellImage, cellImagePoint);
}
e.Handled = true;
Then for some columns I want to draw a heavy right border to give a visual separation between groups of columns.
e.DrawCell(DrawCellFlags.Border);
Rectangle rc;
Margins m = new Margins(0, 0, 0, 0);
m.Right = 3;
CellRange rg;
rg = PlantAlleyBinGrid.GetCellRange(e.Row, e.Col);
rc = e.Bounds;
rg.c1 = rg.c2 = 2 + 1;
rg.c1 = rg.c2 = 2;
rc.X = rc.Right - m.Right;
rc.Width = m.Right;
e.Graphics.FillRectangle(new SolidBrush(Color.Black), rc);
e.Handled = true;
The code as written is almost there. I've tried many alternatives and flows but it ends up not drawing the cell image, the cell border, the cell contents, or any combination thereof.
I need help on how to draw everything on a cell.

I resolved this by calling DrawCell and DrawString for cells that do not draw an image.

Related

C# DrawString with StringFormatFlags: How to Flip Vertical Orientation?

I'm drawing strings on an image and saving it as a TIFF. I need to orient the text like this:
desired output
I'm using this code to create the string format:
formatFlags = (StringFormatFlags.NoClip | StringFormatFlags.DirectionVertical | StringFormatFlags.DirectionRightToLeft);
And this is the output:
actual output
How can I simply flip the orientation around?
Update: Based on the comment, I tried this, but it is place my text way up in the corner instead of where it would be if I didn't add in that sample code. I commented out my original DrawString.
protected virtual void AddTextToBackground(Graphics backgroundGfx, FeatureLocation featureLocation, TextFeature textFeature, int equator) {
Font font = CreateFont(textFeature);
Color fontColor = ColorTranslator.FromHtml(textFeature.FontColor);
Brush textBrush = new SolidBrush(fontColor);
// Determine postion of text box
int xPos = featureLocation.XPos - TEXT_RECT_WIDTH / 2;
int adjustedEquatorOffset = CalculateTextEquatorOffset(featureLocation.EquatorOffset);
//int adjustedEquatorOffset = featureLocation.EquatorOffset;
int yPos = CalculateYPos(equator, adjustedEquatorOffset, TEXT_RECT_WIDTH);
// Rectangle is necessary to create centered text using StringFormat in the DrawString call
Rectangle rect = new Rectangle(xPos, yPos, TEXT_RECT_WIDTH, TEXT_RECT_WIDTH);
// Set up alignment
StringFormat stringFormat = new StringFormat {
Alignment = StringAlignment.Center,
LineAlignment = FindStringAlignment(featureLocation.EquatorOffset)
};
// Determine rotation and set format flags
stringFormat.FormatFlags = GetTextFormatFlags(featureLocation.DefaultRotation, textFeature.Rotation);
// Draw text
SizeF sz = backgroundGfx.VisibleClipBounds.Size;
backgroundGfx.TranslateTransform(sz.Width / 2, sz.Height / 2);
backgroundGfx.RotateTransform(45);
//backgroundGfx.DrawString(textFeature.Text, font, textBrush, rect, stringFormat);
backgroundGfx.DrawString(textFeature.Text, font, textBrush, -(sz.Width/2), -(sz.Height/2), stringFormat);
backgroundGfx.ResetTransform();
}

How can I resize the width and height of a UserControl according to a child Label?

I'm coding a clone messaging app. I have a user control called "Bubble" that I use as a message bubble. And its user control layout is like this:
It contains only lblMessage, lblTime and pictureBox. lblMessage's AutoSize property off and Anchor is top-left-right. My goal is to size or wrap this according to the content. I am able to resize vertically using this code.
void SetHeight()
{
//SizeF maxSize = new Size(500, int.MaxValue);
Graphics g = CreateGraphics();
SizeF size = g.MeasureString(lblMessage.Text, lblMessage.Font, lblMessage.Width);
lblMessage.Height = int.Parse(Math.Round(size.Height + 2, 0).ToString());
lblTime.Top= lblMessage.Bottom +10;
this.Height = lblTime.Bottom + 10;
}
My result is like this:
I can resize vertically, but not horizontally at the same time. The way I resize it vertically is to use width property of lblMessage as the limit size. I want to resize user control according to size of short text. I thought about creating a maxSize for MeasureString but I can't get it to work. I'm trying to find a function that continues from a bottom line when it reaches a certain width value. I look forward to your help.
I searched all stackowerflow questions in depth but none of them worked in my case.
**Edit after Jimi comments
I tried to apply his solution to my project. I don't think I did everything right, but with a little bit of comment, something came up that worked for me at least.
private void Bubble_Paint(object sender, PaintEventArgs e)
{
float minimumWidth = lblTime.Width + pictureBox1.Width + 35;
float maximumWidth = 500;
string measureString = lblMessage.Text;
Font stringFont = new Font("Helvetica", 12.0F);
CharacterRange[] characterRanges = { new CharacterRange(0, measureString.Length) };
float layautWidth = maximumWidth - minimumWidth;
RectangleF layoutRect = new RectangleF(10, 6, layautWidth, this.Height);
StringFormat stringFormat = new StringFormat();
stringFormat.SetMeasurableCharacterRanges(characterRanges);
Color myColor = Color.FromArgb(211, 212, 212);
SolidBrush myBrush = new SolidBrush(myColor);
Region[] stringRegions = e.Graphics.MeasureCharacterRanges(measureString, stringFont, layoutRect, stringFormat);
RectangleF measureRect1 = stringRegions[0].GetBounds(e.Graphics);
//When I gave Rectangle to the DrawString, some last letter was truncated, so I used plain text printing until I got to the bottom line.
if (measureRect1.Height < 30)
e.Graphics.DrawString(measureString, stringFont, myBrush, 10, 6, stringFormat);
else e.Graphics.DrawString(measureString, stringFont, myBrush, measureRect1, stringFormat);
e.Graphics.DrawRectangle(new Pen(Color.Transparent, 0), Rectangle.Round(measureRect1));
this.Width = Convert.ToInt32(measureRect1.Width + minimumWidth);
lblTime.Top = Convert.ToInt32(measureRect1.Height) + 10;
this.Height = lblTime.Bottom + 10;
}
Result in container preview:
user control that goes down one line from the specified limit
and for one line
dynamically result inside the application

ImageSharp and Font Height

I have a task to create an image that will be printed. On the picture, I need to put a single uppercase letter (Upper case, [A-Z]).
The printed image size can vary between 15cm height, and 30cm height (including any size in between).
The letter needs to span the full height of the printed image.
When setting the font size, I see you can get the size of the text.
using (Image<Rgba32> img = new Image<Rgba32>(imageWidth, imageHeight))
{
img.Mutate(x => x.Fill(Rgba32.White));
img.MetaData.HorizontalResolution = 96;
img.MetaData.VerticalResolution = 96;
var fo = SystemFonts.Find("Arial");
var font = new Font(fo, 1350, FontStyle.Regular);
I can get the size of my text here:
SizeF size = TextMeasurer.Measure(group.Text, new RendererOptions(font));
However, as you can see, I hard coded the size for my font here. The height needs to be matched to the height of the image.
Is there any way to specify this, without stretching and losing quality? Is there a way I can specify the height, in pixels? Maybe there's coloration to the font size that I can use safely?
When I set the Font size to the pixel height of my Image, I am seeing this:
I'm not sure why the circled parts have gaps. I am setting my top left position of the left hand text, to 0,0.... and the top right hand point of the 'QWW' group to the width of the image, and 0 as Y. But I'd expect them to be flush against the size, and the bottom.
TextMeasurer is designed for measurer text in the context of line and words not on individual characters because it doesn't look at individual glyph forms instead looks at the font as a whole to measure against line spacing etc.
Instead you will want to render the glyph directly to a vector using the nuget package SixLabors.Shapes.Text. This will allow you to accurately measure the final glyph + apply scaling and transforms to guarantee the glyph lines up with the edges of your image. It also saves you having to perform any expensive pixel level operations except the final drawing of the glyph to the image.
/// <param name="text">one or more characters to scale to fill as much of the target image size as required.</param>
/// <param name="targetSize">the size in pixels to generate the image</param>
/// <param name="outputFileName">path/filename where to save the image to</param>
private static void GenerateImage(string text, Primitives.Size targetSize, string outputFileName)
{
FontFamily fam = SystemFonts.Find("Arial");
Font font = new Font(fam, 100); // size doesn't matter too much as we will be scaling shortly anyway
RendererOptions style = new RendererOptions(font, 72); // again dpi doesn't overlay matter as this code genreates a vector
// this is the important line, where we render the glyphs to a vector instead of directly to the image
// this allows further vector manipulation (scaling, translating) etc without the expensive pixel operations.
IPathCollection glyphs = SixLabors.Shapes.TextBuilder.GenerateGlyphs(text, style);
var widthScale = (targetSize.Width / glyphs.Bounds.Width);
var heightScale = (targetSize.Height / glyphs.Bounds.Height);
var minScale = Math.Min(widthScale, heightScale);
// scale so that it will fit exactly in image shape once rendered
glyphs = glyphs.Scale(minScale);
// move the vectorised glyph so that it touchs top and left edges
// could be tweeked to center horizontaly & vertically here
glyphs = glyphs.Translate(-glyphs.Bounds.Location);
using (Image<Rgba32> img = new Image<Rgba32>(targetSize.Width, targetSize.Height))
{
img.Mutate(i => i.Fill(new GraphicsOptions(true), Rgba32.Black, glyphs));
img.Save(outputFileName);
}
}
I split up your question into 3 parts:
dynamic font size, rather than hard coded font size
the glyph should use the full height of the image
the glyph should be aligned to the left
Dynamically scale the text to fill the height of the image
After measuring the text size, calculate the factor by which the font needs to be scaled up or down to match the height of the image:
SizeF size = TextMeasurer.Measure(text, new RendererOptions(font));
float scalingFactor = finalImage.Height / size.Height;
var scaledFont = new Font(font, scalingFactor * font.Size);
This way the initially set font size is largely ignored. Now we can draw the text with the dynamically scaled font, depending on the height of the image:
Inflate text to use the entire height of the image
Depending on each glyph, we might now have a gap in between the top/bottom side of the image and the top/bottom side of the text. How a glyph is rendered or drawn depends heavily on the font in use. I am not an expert in typography, but AFAIK every font has it's own margin/padding, and has custom heights around the baseline.
In order for our glyph to align with the top and bottom of the image, we have to further scale up the font. To calculate this factor, we can determine the top and bottom edge of the currently drawn text by searching for the height (y) of the top-most and bottom-most pixels, and scale up the font with this difference. Additionally, we need to offset the glyph by the distance from the top of the image to the top edge of the glyph:
int top = GetTopPixel(initialImage, Rgba32.White);
int bottom = GetBottomPixel(initialImage, Rgba32.White);
int offset = top + (initialImage.Height - bottom);
SizeF inflatedSize = TextMeasurer.Measure(text, new RendererOptions(scaledFont));
float inflatingFactor = (inflatedSize.Height + offset) / inflatedSize.Height;
var inflatedFont = new Font(font, inflatingFactor * scaledFont.Size);
location.Offset(0.0f, -top);
Now we can draw the text with the top and the bottom snapping to the top and bottom edges of the image:
Move glyph to the very left
Lastly, depending on the glyph, the left side of the glyph might not snap with the left side of the image. Similar to the previous step, we can determine the left-most pixel of the text within the current image containing the inflated glyph, and move the text accordingly to the left to remove the gap in between:
int left = GetLeftPixel(intermediateImage, Rgba32.White);
location.Offset(-left, 0.0f);
Now we can draw the text aligning with the left side of the image:
This final image now has the font dynamically scaled depending on the size of the image, has been further scaled up and moved to fill up the entire height of the image, and has been further moved to have no gap to the left.
Note
When drawing the text, the DPI of the TextGraphicsOptions should match the DPI of the image:
var textGraphicOptions = new TextGraphicsOptions(true)
{
HorizontalAlignment = HorizontalAlignment.Left,
VerticalAlignment = VerticalAlignment.Top,
DpiX = (float)finalImage.MetaData.HorizontalResolution,
DpiY = (float)finalImage.MetaData.VerticalResolution
};
Code
private static void CreateImageFiles()
{
Directory.CreateDirectory("output");
string text = "J";
Rgba32 backgroundColor = Rgba32.White;
Rgba32 foregroundColor = Rgba32.Black;
int imageWidth = 256;
int imageHeight = 256;
using (var finalImage = new Image<Rgba32>(imageWidth, imageHeight))
{
finalImage.Mutate(context => context.Fill(backgroundColor));
finalImage.MetaData.HorizontalResolution = 96;
finalImage.MetaData.VerticalResolution = 96;
FontFamily fontFamily = SystemFonts.Find("Arial");
var font = new Font(fontFamily, 10, FontStyle.Regular);
var textGraphicOptions = new TextGraphicsOptions(true)
{
HorizontalAlignment = HorizontalAlignment.Left,
VerticalAlignment = VerticalAlignment.Top,
DpiX = (float)finalImage.MetaData.HorizontalResolution,
DpiY = (float)finalImage.MetaData.VerticalResolution
};
SizeF size = TextMeasurer.Measure(text, new RendererOptions(font));
float scalingFactor = finalImage.Height / size.Height;
var scaledFont = new Font(font, scalingFactor * font.Size);
PointF location = new PointF();
using (Image<Rgba32> initialImage = finalImage.Clone(context => context.DrawText(textGraphicOptions, text, scaledFont, foregroundColor, location)))
{
initialImage.Save("output/initial.png");
int top = GetTopPixel(initialImage, backgroundColor);
int bottom = GetBottomPixel(initialImage, backgroundColor);
int offset = top + (initialImage.Height - bottom);
SizeF inflatedSize = TextMeasurer.Measure(text, new RendererOptions(scaledFont));
float inflatingFactor = (inflatedSize.Height + offset) / inflatedSize.Height;
var inflatedFont = new Font(font, inflatingFactor * scaledFont.Size);
location.Offset(0.0f, -top);
using (Image<Rgba32> intermediateImage = finalImage.Clone(context => context.DrawText(textGraphicOptions, text, inflatedFont, foregroundColor, location)))
{
intermediateImage.Save("output/intermediate.png");
int left = GetLeftPixel(intermediateImage, backgroundColor);
location.Offset(-left, 0.0f);
finalImage.Mutate(context => context.DrawText(textGraphicOptions, text, inflatedFont, foregroundColor, location));
finalImage.Save("output/final.png");
}
}
}
}
private static int GetTopPixel(Image<Rgba32> image, Rgba32 backgroundColor)
{
for (int y = 0; y < image.Height; y++)
{
for (int x = 0; x < image.Width; x++)
{
Rgba32 pixel = image[x, y];
if (pixel != backgroundColor)
{
return y;
}
}
}
throw new InvalidOperationException("Top pixel not found.");
}
private static int GetBottomPixel(Image<Rgba32> image, Rgba32 backgroundColor)
{
for (int y = image.Height - 1; y >= 0; y--)
{
for (int x = image.Width - 1; x >= 0; x--)
{
Rgba32 pixel = image[x, y];
if (pixel != backgroundColor)
{
return y;
}
}
}
throw new InvalidOperationException("Bottom pixel not found.");
}
private static int GetLeftPixel(Image<Rgba32> image, Rgba32 backgroundColor)
{
for (int x = 0; x < image.Width; x++)
{
for (int y = 0; y < image.Height; y++)
{
Rgba32 pixel = image[x, y];
if (pixel != backgroundColor)
{
return x;
}
}
}
throw new InvalidOperationException("Left pixel not found.");
}
We don't need to save all 3 images, however we do need to create all 3 images and inflate and move the text step by step in order to fill up the entire height of the image and start at the very left of the image.
This solution works independently of the used font. Also, for a production application avoid finding a font via SystemFonts, because the font in question might not be available at the target machine. To have an stable stand-alone solution, deploy a TTF font with the application and install the font via FontCollection manually.

Align middle text string inside the button

I'm making some custom control for a small project. I have created a TP1CustomFlatButton like this:
It was easy for me if I added a label with text to bottom of my TP1CustomFlatButton. I didn't want to handle events for that label so I used event onPaint to draw the text. I followed the turtorial of Microsoft and I got the custom flat button like the picture I attached. What I'm trying to get is to make the text align center at the bottom of my TP1CustomFlatButton.
This is my code for TP1CustomFlatButton:
// constructor
public TP1CustomFlatButton()
{
this.FlatStyle = FlatStyle.Flat;
this.FlatAppearance.BorderSize = 0;
this.BackColor = Color.MediumSeaGreen;
this.ForeColor = Color.White;
this.Text = "TP1CustomButton";
}
protected override void OnPaint(PaintEventArgs pevent)
{
pevent.Graphics.FillRectangle(new SolidBrush(this.BackColor), new Rectangle(0, 0, this.Width, this.Height));
TextFormatFlags flags = TextFormatFlags.Bottom;
//render text
TextRenderer.DrawText(pevent.Graphics, this.Text, this.Font, new Point((int)(this.Width - Text.Length)/2,this.Height), this.ForeColor, flags);
//draw image
Image img = this.BackgroundImage;
//create rectangle to display image
Rectangle imgRec = new Rectangle(this.Width - 32 /3, this.Height - 32/ 3, 32, 32);
if(img!=null)
pevent.Graphics.DrawImage(img, imgRec);
}
I'm really confused with the coordinate X and Y. As you can see the code I tried to make the "SETTINGS" text string to align center at bottom of my TP1CustomFlatButton. I spent 5 hours to read more information about coordinate and location of controls in Windows Form. But now I'm really tired.
Hope someone can give me any advice or any solution for my custom control.
You need to use the MeasureString()method in order to calculate the middle.
also see the changes i have madein order to find the middle (calculated in drawPoint field).
see my example based on your code:
public TP1CustomFlatButton()
{
this.FlatStyle = FlatStyle.Flat;
this.FlatAppearance.BorderSize = 0;
this.BackColor = Color.MediumSeaGreen;
this.ForeColor = Color.White;
this.Text = "middle";
}
protected override void OnPaint(PaintEventArgs pevent)
{
pevent.Graphics.FillRectangle(new SolidBrush(this.BackColor), new Rectangle(0, 0, this.Width, this.Height));
TextFormatFlags flags = TextFormatFlags.Bottom;
//render text
String drawString = this.Text;
SizeF size = pevent.Graphics.MeasureString(drawString, this.Font);
Point drawPoint = new Point((int)this.Size.Width / 2 - (int)size.Width / 2,this.Height);
TextRenderer.DrawText(pevent.Graphics, this.Text, this.Font, drawPoint, this.ForeColor, flags);
//draw image
Image img = this.BackgroundImage;
//create rectangle to display image
Rectangle imgRec = new Rectangle(this.Width - 32 / 3, this.Height - 32 / 3, 32, 32);
if (img != null)
pevent.Graphics.DrawImage(img, imgRec);
}
Output:
Try Changing the TextFormatFlags as below:
TextFormatFlags flags = TextFormatFlags.Bottom | TextFormatFlags.VerticalCenter;
Also, checkout this link

Changing DataGridViewCheckBoxCell backcolor in CellPainting

I am trying to override the default DataGridViewCheckBoxCell with a colored rectangle.
I found the following post but it doesn't behave as expected:
Drawing a filled circle or rectangle inside a DataGridViewCell in C# Winforms
Here is my code:
private void OrdersComponentsDGV_CellPainting(object sender, DataGridViewCellPaintingEventArgs e)
{
if (e.RowIndex >= 0 && e.ColumnIndex > 0)
{
const float size = 20;
var datagridview = (sender as DataGridView);
var cell = datagridview.Rows[e.RowIndex].Cells[e.ColumnIndex];
if (cell.Value != DBNull.Value && (bool)cell.Value)
{
// center of the cell
var x = e.CellBounds.X + e.CellBounds.Width / 2 - size/2;
var y = e.CellBounds.Y + e.CellBounds.Height / 2 - size/2;
RectangleF rectangle = new RectangleF(x, y, size, size);
e.Graphics.FillRectangle(Brushes.Yellow, rectangle);
e.PaintContent(e.CellBounds);
e.Handled = true;
}
}
}
After loading for the first time, all checked cells have a Gray backcolor. It disappear after scrolling the datagridview down and up back.
Illustration:
In addition, I set the DataGridView SelectionMode to FullRowSelect:
OrdersComponentsDGV.SelectionMode = DataGridViewSelectionMode.FullRowSelect;
But after I implement CellPainting as described above, the blue selection backcolor is gone (see in the image) where the checkbox is checked.
you can use base background painting method before drawing your own rectangle (just like documentation for DataGridView.CellPainting Event suggests)
if (cell.Value is bool && (bool)cell.Value)
{
e.PaintBackground(e.ClipBounds, cell.Selected);
// center of the cell
var x = e.CellBounds.X + e.CellBounds.Width / 2 - size / 2;
var y = e.CellBounds.Y + e.CellBounds.Height / 2 - size / 2;
RectangleF rectangle = new RectangleF(x, y, size, size);
e.Graphics.FillRectangle(Brushes.Yellow, rectangle);
e.PaintContent(e.ClipBounds);
e.Handled = true;
}

Categories