I'm exploring graphics capabilities of WPF+C#. Drawing a line with XAML makes what expected. ok.
To draw a simple line with C# code makes something "strange". The coordinates appear not where
they should be. Why? The following code should implement a diagonal from (0, 0) to (width, height) of
the container panel of the line.
private void buttonStart_Click(object sender, RoutedEventArgs e)
{
Line line1 = new Line
{
Stroke = Brushes.Black
};
Thickness thickness = new Thickness(10, -10, 36, 250);
line1.Margin = thickness;
line1.Visibility = Visibility.Visible;
line1.StrokeThickness = 4;
line1.X1 = 0;
line1.Y1 = 0;
line1.X2 = MainGrid.ActualWidth;
line1.Y2 = MainGrid.ActualHeight;
line1.HorizontalAlignment = HorizontalAlignment.Left;
MainGrid.Children.Add(line1);
}
It doesn't matter what panel you use: canvas, dockpanel, grid, stackpanel, I observed the same
strange and annoying behavior.
Not showing the diagonal. Not beginning at origin-vertex (0,0): (left,top)
Your problem is Thickness(10, -10, 36, 250). This arbitrary margin is cutting off the rest of the line. Here is a simplified version of your code that correctly draws a line from the top-left corner, to the bottom-right:
private void buttonStart_Click(object sender, RoutedEventArgs e)
{
Line line1 = new Line
{
Stroke = Brushes.Black,
StrokeThickness = 4,
X1 = 0,
Y1 = 0,
X2 = MainGrid.ActualWidth,
Y2 = MainGrid.ActualHeight
};
MainGrid.Children.Add(line1);
}
Note that Visibility = Visibility.Visible is left because Line (like most other elements) are visible by default.
You can do this much easier with a simple path statement - use Stretch="Fill" so there's no need to worry about dimensions
<Path Stroke="Black" StrokeThickness="4" Data="M 0,0 L 1,1" Stretch="Fill" />
Related
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
I am attempting to draw a line from the center of an image on a canvas to the mouse's position when the scroll wheel is moved.
I have a function that looks like this:
// e is MouseWheelEventArgs
var position = e.GetPosition(canvas);
var x = Canvas.GetLeft(image) + image.ActualWidth / 2;
var y = Canvas.GetTop(image) + image.ActualHeight / 2;
Ellipse point = new Ellipse
{
Margin = new Thickness(x, y, 0, 0)
};
Line line = new Line
{
X1 = position.X,
Y1 = position.Y,
X2 = x,
Y2 = y
};
canvas.Children.Add(point);
canvas.Children.Add(line);
The point is drawn correctly at the pointer's location, and the line is drawn from the center of the image, but the point the line is drawn to is incorrect. Why is this?
Here is an image to show the location of the point and the line
I would suggest to implement this with Geometries.
With a XAML like this
<Canvas Background="Transparent" MouseWheel="Canvas_MouseWheel">
<Image x:Name="image" Canvas.Left="0" Canvas.Top="0" Source="..."/>
<Path x:Name="line" Stroke="Green" StrokeThickness="2"/>
<Path x:Name="point" Fill="Red"/>
</Canvas>
the event handler could look like this:
private void Canvas_MouseWheel(object sender, MouseWheelEventArgs e)
{
var position = e.GetPosition((Canvas)sender);
var center = new Point(
Canvas.GetLeft(image) + image.ActualWidth / 2,
Canvas.GetTop(image) + image.ActualHeight / 2);
line.Data = new LineGeometry(center, position);
point.Data = new EllipseGeometry(position, 3, 3);
}
I want to distribute string in rectangle.
Except each character set position
Rectangle displayRectangle = new Rectangle (new Point(40, 40), new Size (80, 80));
StringFormat format1 = new StringFormat(StringFormatFlags.NoClip);
format1.LineAlignment = StringAlignment.Center;
e.Graphics.DrawRectangle(Pens.Black, displayRectangle);
e.Graphics.DrawString("Showing Format1", this.Font,
Brushes.Black, (RectangleF)displayRectangle, format1);
But, StringFormat Alignment doesn't have distribute alignment. So I want to know a way how to distribute string in rectangle.
For the moment, I'm going to assume you can/will use the Win32 API (e.g., via. P/Invoke). .NET may have a wrapper for the function I'm going to suggest (but then again, it may not -- I'm really not sure). If it does, it'll be up to you to find and use it. Most of what I'm suggesting is more about the basic approach than the function anyway.
You can use GetTextExtentExPointI, which will compute the size of a rectangle necessary to hold a set characters you specify and (importantly) the horizontal position of each character in that rectangle.
So, what you want to do is use this to compute the size of a rectangle and position of each character in that rectangle, with it assuming normal kerning of the characters. Then, you'll divide the width of that rectangle into the width you actually want. This will give you a factor by which each position must increase to get that character to the position you want. You'll then multiply the position it returned for each character by that factor to get your desired position.
Just for example, let's assume it gave you positions of 0, 17, 35 and 44 for the characters with normal spacing. Let's also assume your target rectangle is 1.8 times as wide as the rectangle it computed for normal spacing. You'll take each of those positions and multiply by 1.8 to get the position you want to use for that character, giving 0, 31, 63, and 79 for the "corrected" positions.
Then you'll (obviously enough) go through your string and draw each character at the computed position.
Here's how to do it if you just want to literally distribute the characters evenly across the middle of the display rectangle:
private void Form1_Paint(object sender, PaintEventArgs e)
{
string text = "this is distribute";
Rectangle displayRectangle = new Rectangle(new Point(40, 40), new Size(400, 80));
e.Graphics.DrawRectangle(Pens.Black, displayRectangle);
int step = displayRectangle.Width / text.Length;
SizeF szF = e.Graphics.MeasureString(text, this.Font); // just to get the HEIGHT
int y = (displayRectangle.Y + displayRectangle.Height / 2) - (int)szF.Height / 2;
for (int i = 0; i < text.Length; i++)
{
e.Graphics.DrawString(text.Substring(i, 1), this.Font, Brushes.Black, displayRectangle.X + (i * step), y);
}
}
Here's my stab at #Jerry Coffin's algorithm using .Net managed methods:
private void Form1_Paint(object sender, PaintEventArgs e)
{
string text = "this is distribute";
Rectangle displayRectangle = new Rectangle(new Point(40, 40), new Size(400, 80));
e.Graphics.DrawRectangle(Pens.Black, displayRectangle);
StringFormat format1 = new StringFormat(StringFormatFlags.NoClip);
format1.LineAlignment = StringAlignment.Center;
format1.Alignment = StringAlignment.Near;
// SetMeasurableCharacterRanges() can only handle 32 regions max at a time!
// The below workaround simply measures each character separately:
RectangleF rcF = (RectangleF)displayRectangle;
List<Region> regions = new List<System.Drawing.Region>();
for (int i = 0; i < text.Length; i++)
{
format1.SetMeasurableCharacterRanges(new CharacterRange[] {new CharacterRange(i, 1)});
regions.AddRange(e.Graphics.MeasureCharacterRanges(text, this.Font, rcF, format1));
}
RectangleF minBounds = regions[0].GetBounds(e.Graphics);
RectangleF maxBounds = regions[regions.Count - 1].GetBounds(e.Graphics);
float ratio = (float)displayRectangle.Width / (float)((maxBounds.X + maxBounds.Width) - minBounds.X);
for(int i = 0; i < regions.Count; i++)
{
Region region = regions[i];
RectangleF boundsF = region.GetBounds(e.Graphics);
PointF ptF = new PointF(displayRectangle.X + (int)((boundsF.Left - minBounds.X) * ratio), (int)boundsF.Top);
e.Graphics.DrawString(text.Substring(i, 1), this.Font, Brushes.Black, ptF);
}
}
I have a WPF project where i had to plot some lines in the project. But when i resize the window, the lines wouldn't resize because i using the canvas coordinate to plot the line. Anyone show me how to make the line resize vary to the window size?
my code for the line:
public static void drawGridLines(MainWindow main)
{
double axisX = 10;
Line lastLine = new Line();
lastLine.X2 = axisX;
lastLine.Y2 = 15;
double y = 0;
double x = 0;
bool first = true;
int[] point = new int[10] { 1, 3, 8, 9, 9, 0, 7, 5, 4, 1 };
for (int i = 0; i < point.Length; i++) // iterate over your gridview rows
{
Line newline = new Line();
newline.X1 = lastLine.X2;
newline.Y1 = lastLine.Y2;
newline.X2 = axisX + (Point[i] * 5); // calculate X position of the current cell
newline.Y2 = lastLine.Y2 + 10; // calculate Y position of the current cell
x = newline.X2;
y = newline.Y2;
if (!first)
{
// first minimum cell should't be drawn, it is just the start point for next line
drawLine(main, newline);
}
else
{
first = false;
}
lastLine = newline;
}
public static void drawLine(MainWindow main, Line line)
{
line.HorizontalAlignment = HorizontalAlignment.Left;
line.VerticalAlignment = VerticalAlignment.Center;
line.Stroke = System.Windows.Media.Brushes.SteelBlue;
line.StrokeThickness = 1.5;
main.myLineCanvas.Children.Add(line);
}
Place your Canvas in a Viewbox:
<Viewbox>
<Canvas x:Name="myLineCanvas" />
</Viewbox>
You can change its behavior with Stretch and StretchDirection.
You should try using a ViewBox and see if it works well in your case, is as simple as surrounding your canvas with the ViewBox.
<Window ...
...>
<ViewBox>
<Canvas .....>
</Canvas>
</ViewBox>
</Window>
Window has a SizeChanged event. In your handler you can grab the new size of your window and set the endpoints of your lines accordingly.
I have my custom button where I have overridden the OnPaint() and draw the text in it only. On runtime the text looks differently - spacing between chars is lacking. Here is the image of design & runtime of the button :
The paint methods is as:
protected override void OnPaint(PaintEventArgs pevent)
{
base.OnPaint(pevent);
if (base.ContainsFocus)
{
// Draw inner dotted rectangle when button is on focus
Pen pen = new Pen(Color.Gray, 3);
Point p = base.Location;
pen.DashStyle = System.Drawing.Drawing2D.DashStyle.Dot;
Rectangle rectangle = new Rectangle(4, 4, Size.Width - 8,
Size.Height - 8);
ControlPaint.DrawFocusRectangle(pevent.Graphics, rectangle);
}
// Draw the string to screen
SizeF sf = pevent.Graphics.MeasureString(displayText, this.Font,
this.Width);
Point ThePoint = new Point();
ThePoint.X = (int)((this.Width / 2) - (sf.Width / 2));
ThePoint.Y = (int)((this.Height / 2) - (sf.Height / 2));
pevent.Graphics.DrawString(displayText, Font,
new SolidBrush(Color.FromArgb(255, 255, 254, 255)), ThePoint);
this.Text = "";
}
Any idea where am I going wrong and how to take care of the same?
You need to set the correct smoothing mode like this:
Graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality
Then, the result should look OK.
Devils Child's answer will affect the quality of lines and circles, etc.
But for text rendering, you can use:
e.Graphics.TextRenderingHint = TextRenderingHint.ClearTypeGridFit;