C# Pen.DashPattern - c#

I want to draw a Line between 2 rows while using drag and drop. The function of this is simply visual, so that the user knows, where he is dropping the row. The line should look like the excel onces. Here my code:
Pen _marqueePen = new Pen(Color.Gray, 2);
float[] dashValues = {1f,1f};
_marqueePen.DashPattern = dashValues;
But this looks like that
I want to look it like that:
I'm WinForms and the C1 Flexgrid control.

You can use a Custom Pen like this:
using (Pen pen = new Pen(Color.Gray, 4f) )
{
pen.DashStyle = System.Drawing.Drawing2D.DashStyle.Custom;
pen.DashPattern = new float[] { 0.25F, 0.25F };
// now draw your stuff..
}
Note the doc on MSDN:
The elements in the dashArray array set the length of each dash
and space in the dash pattern. The first element sets the length of a dash,
the second element sets the length of a space, the third element sets
the length of a dash, and so on. Consequently, each element should be a
non-zero positive number.
The length of each dash and space in the dash pattern is the product
of the element value in the array and the width of the Pen.
You can pick any pen width and any dash&gap lengths as long as you keep their relation in mind.. So if you want the finest dashes, make sure they multiply to 1.0 pixels!
Here is the resulting line:

Some options:
You could use a PNG graphic that mimics that excel behaviour and then draw it on the control (you'll have to tile your image vertically).
Draw three lines with your code, with offset of y-axis & x-axis one pixel.

That looks to me more like a rectangle filed with HatchBrush having HatchStyle.Percent50 and height of 3.
You could try
Rectangle rect = new Rectangle(0, 0, 500, 3) //you will use the values here from your cursor but height will be 3
HatchBrush brush = new HatchBrush(HatchStyle.Percent50, Color.Black);
g.FillRectangle(brush, rect);

Related

String alignment without monospaced font

I want an aligned string across multiple rows in a right-click menu in excel. The right click menu is added like this:
CommandBar contextMenu = Globals.ThisWorkbook.Application.CommandBars["List Range Popup"];
CommandBarPopup subMenu = (CommandBarPopup)contextMenu.Controls.Add(Type: MsoControlType.msoControlPopup, Before: 1, Temporary: true);
subMenu.Caption = "Assumptions Drill Down";
I then iterate database results and add to the subMenu in the proceeding rows
Current result as below (using string padding):
As the character spaces are not even (non-monospaced font) the columns are not aligned. I've tried to do something like the below to get the pixel size of the string and then covert back to the required string padding integer but I cant get it to work:
private static double GetStringSize(string stringText)
{
Bitmap b = new Bitmap(1, 1);
Graphics g = Graphics.FromImage(b);
SizeF size = g.MeasureString(stringText,
new System.Drawing.Font("Calibri", 10, FontStyle.Regular, GraphicsUnit.Point));
return size.Width;
}
Is there a way to set font on a right click to monospaced (i dont believe so)
If I cant do (1.) how can I make sure spacing in each column is even - so for example evenly align a user of 'iiiii' vs 'QQQQQ'
?

C# fonts as 2D points

I am programming in C# and I need a way to get fonts as points in 2D. I basically want fonts converted to a mesh so that I can render it in 2D/3D. Are there any ways to do that? Or is it better to just download the meshes or something and then load them into my program.
In winforms you can use a GraphicsPath from System.Drawing.Drawing2D to
either acces the Bezier curves in the PathPoints and PathTypes data
or, after Flattening the path to acces the PathPoints array that now makes up an array of line segments.
Use one of the GraphicsPath.AddString methods to create the path..!
You may also want to look into the GraphicsPathIterator class, which..
Provides the ability to iterate through subpaths in a GraphicsPath and
test the types of shapes contained in each subpath..
Here is an example of drawing the flattened segment points:
Bitmap bmp = new Bitmap(400, 400);
GraphicsPath gp = new GraphicsPath();
using (Graphics g = Graphics.FromImage(bmp))
using (Font f = new Font("Tahoma", 40f))
{
g.ScaleTransform(4,4);
gp.AddString("Y?", f.FontFamily, 0, 40f, new Point(0, 0), StringFormat.GenericDefault);
g.DrawPath(Pens.Gray, gp);
gp.Flatten(new Matrix(), 0.2f); // <<== *
g.DrawPath(Pens.DarkSlateBlue, gp);
for (int i = 0; i < gp.PathPoints.Length; i++)
{
PointF p = gp.PathPoints[i];
g.FillEllipse(Brushes.DarkOrange, p.X-1, p.Y - 1, 2, 2);
}
pictureBox1.Image = bmp;
}
Note the 2nd Flatten parameter that lets you control how tight i.e. how closely the curve is approximated by the lines. The smaller the value the more 2d points are created..
To use the unflattended path you need to combine the PathPoints with their respective PathTypes; this is basically the same as creating Bezier curves: Two control points go between each pair of points. The types tell you where a figure starts/ends and where a line starts or a curve..
You can use the GlyphTypeface.GetGlyphOutline method to return the glyphs which make up the curves of the lettering. Note this is part of WPF.
MSDN:
Returns a Geometry value describing the path for a single glyph in the font
Under the hood I suspect it is calling the native function GetGlyphOutline
...which you could p-invoke from a WinForms/XNA app.

Why do I see this jagged curves?

I have a canvas and draw curve with this code:
using (Graphics g = Graphics.FromImage(canvas.BackgroundImage))
{
g.DrawCurve(pen, points);
points is array that I fill that by mouse location points.
In the result I see some jagged lines that I didn't draw.
You can see them here(in red rectangles):
What can i do about this?
What you is see is the somewhat unlucky combination of the default for Linejoin, which is Miter and the default for MiterLimit, which is 10.
Instead you have a choice of either picking one of the other LineJoin options or reducing the MiterLimit to say less than half the Pen.Width..
using (Pen myPen = new Pen(Color.Blue, 24f))
{
// either another LineJoine;
myPen.LineJoin = System.Drawing.Drawing2D.LineJoin.Round;
// or a reduced MiterLimit:
myPen.MiterLimit = 1+ myPen.Width / 5f;
}

Using matrices to normalize transformed images in C#

Consider the two images below (original and transformed respectively). The three blue squares (markers) are used for orientation.
Original Image:
We know the width, height
We know the (x,y) coordinates of all three markers.
Transformed Image:
We can detect the (x,y) coordinates of all three markers.
As a result, we can calculate the angle of rotation, the amount of (x,y) translation and the (x,y) scaling factor.
I now want to use the System.Drawing.Graphics object to perform RotateTransform, TranslateTransform and ScaleTransform. The trouble is, the resulting image is NEVER like the original.
I've been told on stack overflow that the order of applying transformations does not matter but my observation is different. Below is some code that generates an original image and attempts to draw it on a new canvas after introducing some transformations. You can change the order of the transformations to see different results.
public static void GenerateImages ()
{
int width = 200;
int height = 200;
string filename = "";
System.Drawing.Bitmap original = null; // Original image.
System.Drawing.Bitmap transformed = null; // Transformed image.
System.Drawing.Graphics graphics = null; // Drawing context.
// Generate original image.
original = new System.Drawing.Bitmap(width, height);
graphics = System.Drawing.Graphics.FromImage(original);
graphics.Clear(System.Drawing.Color.MintCream);
graphics.DrawRectangle(System.Drawing.Pens.Red, 0, 0, original.Width - 1, original.Height - 1);
graphics.FillRectangle(System.Drawing.Brushes.Blue, 10, 10, 20, 20);
graphics.FillRectangle(System.Drawing.Brushes.Blue, original.Width - 31, 10, 20, 20);
graphics.FillRectangle(System.Drawing.Brushes.Blue, original.Width - 31, original.Height - 31, 20, 20);
filename = System.IO.Path.Combine(System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location), "Original.png");
original.Save(filename, System.Drawing.Imaging.ImageFormat.Png);
graphics.Dispose();
// Generate transformed images.
transformed = new System.Drawing.Bitmap(width, height);
graphics = System.Drawing.Graphics.FromImage(transformed);
graphics.Clear(System.Drawing.Color.LightBlue);
graphics.ScaleTransform(0.5F, 0.7F); // Add arbitrary transformation.
graphics.RotateTransform(8); // Add arbitrary transformation.
graphics.TranslateTransform(100, 50); // Add arbitrary transformation.
graphics.DrawImage(original, 0, 0);
filename = System.IO.Path.Combine(System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location), "Transformed.png");
transformed.Save(filename, System.Drawing.Imaging.ImageFormat.Png);
graphics.Dispose();
transformed.Dispose();
original.Dispose();
System.Diagnostics.Process.Start(filename);
}
I can see two potential issues here:
Since the transformations are being applies one after another, they render the originally calculated values useless.
The graphics object applies rotation at the (0, 0) coordinate where as I should be doing something different. Not sure what.
From what I understand from here, here, and here, the Graphics.Drawing transformations are performed by multiplying matrices together in the order in which you apply the transformations.
With integers, a*b*c = b*a*c
However, with matricies, ABC almost never equals BAC.
So, it appears the order of transformations does matter, since matrix multiplication is not commutative.
Put another way, it seems that if I do the following on your picture:
case 1:
translate (100,50)
scale (0.5,0.7)
picture ends up with top-left corner at: (100,50)
and bottom-right corner at: (200,190)
case 2:
scale (0.5,0.7)
translate (100,50)
picture ends up with top-left corner at: (50,35)
and bottom-right corner at: (150,174)
This means that by scaling first, and then translating, that the scaling will also scale the amount of translation, that is why in case two the the picture ended up at (50,35) for the top left corner, half of the translated X and .7 of the translated Y.

How do I correctly space multiple string fragments within a single bounding rectangle when priting with c#?

I am writing a function that applies special formatting to predetermined keywords when printing a string. For example, in the string - "Why won't this work?" I might need to print the word "Why" underlined and in blue.
I've had to implement this in pieces, printing each segment of a string with a separate call to print. This approach works with one problem - I cannot get the spacing correct when printing the strings. My keywords print over top of previous default text and are overlapped in turn by text printed afterward.
I am using bounding rectangles to place my strings on the printed page.
RectangleF rectKeywordBounds = new RectangleF( 60.0, 60.0, 550.0, 1200.0);
Once I've printed a segment of the string, I modify the size of the rectangle by the number of characters drawn and I print the next segment of the string.
EArgs.Graphics.DrawString(strFragment, fontBlueItalics, Brushes.Blue, rectKeywordBounds );
iLastPrintIndex = strFragment.Length + iLastPrintIndex;
I've used this method to change the print position of the new string segment:
rectKeywordBounds = new Rectangle(rectKeywordBounds .X + iLastPrintIndex, rectKeywordBounds .Y, rectKeywordBounds .Width, rectKeywordBounds .Height);
And I've used this one:
properSpacing = new SizeF(-((float)iLastPrintIndex), 0.0f);
rectKeywordBounds .Inflate(properSpacing);
Both methods result in the same overlapping of segments. The following code advances a bounding rectangle in the fashion I expect, so why doesn't the concept work when printing text within the rectangle?
Rectangle rectKeywordBounds = new Rectangle(90, 90, 800, 100);
for (int x = 0; x < 6; x++)
{
EventArgs.Graphics.DrawRectangle(Pens.BlueViolet, rectKeywordBounds );
rectKeywordBounds = new Rectangle(rectKeywordBounds .X + 15, rectKeywordBounds .Y + 200, rectKeywordBounds .Width, rectKeywordBounds .Height);
}
You don't say how you are calculating how far to advance the rectangle -- you seem to be using the length of the string, which won't work as this is a character count rather than a coordinate value.
Instead, you should use the Graphics.MeasureString method to calculate how much space is required to print the current segment, and advance the rectangle that much. You may also be able to do this all in one go using Graphics.MeasureCharacterRanges if you are using the same font for all segments.
By the way, if you have the option to use WPF instead of GDI+, then the TextBlock class will take care of all measurement and layout for you.

Categories

Resources