I am trying to make the points in a C# Chart show up as letters (e.g. 'A' and 'B') to separate sets of points, instead of coloring them with green/red.
The Label property of the point collection does not satisfy this request, since it only places labels beside the point, and I want the label to replace the point.
Here is what I have:
while (reader.Read())
{
if (reader[2].ToString() == "Kupi 002")
chart3.Series["Good_Group"].Points[pointsCounter].Label = "A";
else
chart3.Series["Good_Group"].Points[pointsCounter].Label = "B";
pointsCounter = pointsCounter + 1;
}
What property, instead of Label, should I use to achieve my goal?
Thanks in advance.
One option would be to use the MarkerImage property of either the series or point to apply custom bitmap labels of each letter:
chart.Series[0].MarkerImage = "a.png";
chart.Series[0].Points[2].MarkerImage = "b.png";
chart.Series[0].Points[4].MarkerImage = "b.png";
The bitmap images could be created in a paint program and distributed with your program. They could also be generated dynamically, as in the following (highly simplified) example:
private void CreateLetterBitmap(char letter, string path)
{
using (var bmp = new Bitmap(13, 13))
using (var gfx = Graphics.FromImage(bmp))
using (var font = new Font(FontFamily.GenericSansSerif, 12.0f, FontStyle.Bold, GraphicsUnit.Pixel))
{
gfx.TextRenderingHint = TextRenderingHint.AntiAlias;
gfx.DrawString(letter.ToString(), font, Brushes.Black, new Point(0, 0));
bmp.Save(path, ImageFormat.Png);
}
}
private void PrepareChart()
{
CreateLetterBitmap('A', "a.png");
CreateLetterBitmap('B', "b.png");
chart.Series[0].MarkerImage = "a.png";
chart.Series[0].Points[2].MarkerImage = "b.png";
chart.Series[0].Points[4].MarkerImage = "b.png";
}
Related
I save some line object in a list and try to draw them once over a picture box, but every time it draw only one line at the middle of the picture box , is there any solution for this problem.
Thanks in advance ...
public class Lines
{
public System.Drawing.Point startPoint = new System.Drawing.Point();
public System.Drawing.Point endPoint = new System.Drawing.Point();
}
Lines b = new Lines();
List<Lines> alllines = new List<Lines>();
//------------inside button click i wrote the following code----------
b.startPoint.X = rectlist[i].X;
b.startPoint.Y = (rectlist[i].Y + rectlist[i].Bottom) / 2;
b.endPoint.X = rectlist[i].Right;
b.endPoint.Y = (rectlist[i].Top + rectlist[i].Bottom) / 2;
alllines.Add(b);
this.OrignalimgPIcBX.Invalidate();
and inside the paint event of the picture box i wrote this code
Graphics g = e.Graphics;
using (var pen = new Pen(Color.Black, 2))
{
foreach (var lines in alllines)
{
g.DrawLine(pen, lines);
}
}
what is the problem ??!
the list of line in now correct
but know the line objects does not drawn in the correct position
i make the picture box size mode as stretch image , is that make any change!
this is the paint event
private void OrignalimgPIcBX_Paint(object sender, PaintEventArgs e)
{
Graphics g = e.Graphics;
using (var pen = new Pen(Color.Black, 2))
{
foreach (var lines in alllines)
{
g.DrawLine(pen, lines.startPoint, lines.endPoint);
}
}
}
The is only 3 possible explanations,
All your lines are the same
All but one line is drawing out of the view-able area
Or you are eating up an exception
You need to use your debugger and break point the lines to make sure they are drawing with the correct metrics
Update
for (int i = 0; i < rectlist.Count; i++)
{
var b = new Lines(); // <-- you need to do this
b.startPoint.X = rectlist[i].X;
b.startPoint.Y = (rectlist[i].Y + rectlist[i].Bottom) / 2;
b.endPoint.X = rectlist[i].Right;
b.endPoint.Y = (rectlist[i].Top + rectlist[i].Bottom) / 2;
alllines.Add(b);
}
Your problem is you are just changing the same line and adding it to the list
I.e your list ends up with the same line over and over again
I need to highlight the particular character in a control using the fill rect.
I can get the location of the text when it's not wrapped by using the Graphics.MeasureString() method like below,
var size = g.MeasureString(tempSearchText, style.Font, 0, StringFormat.GenericTypographic);
If the text is wrapped then I'm not able to find the exact bounds of the character to highlight the text.
I need to get the exact bounds of the given character in the text which is wrapped. Provide your suggestion to achieve this scenario.
There is no clear specification of which controls to target, so I'm testing 3 different:
TextBox, RichTextbox and ListBox.
TextBox and RichTextbox have the same behavior and share the same tools, so there's no need to define two different methods to achieve the same result.
Of course RichTextbox offers many more options, including RTF.
Also, I'm testing both Graphics.DrawString() and TextRenderer.DrawText().
This is the result of this test, so it's more clear what the code does.
Warning:
For this example I'm using Control.CreateGraphics(), because TextBox and RichTextBox controls don't provide a Paint() event. For a real world application, you should create a Custom Control derived from TextBox or RichTextBox, override WndPrc and handle WM_PAINT.
1) Highlight all t in a multiline TextBox control.
TextRenderer->DrawText():
//Define some useful flags for TextRenderer
TextFormatFlags flags = TextFormatFlags.Left | TextFormatFlags.Top |
TextFormatFlags.NoPadding | TextFormatFlags.WordBreak |
TextFormatFlags.TextBoxControl;
//The char to look for
char TheChar = 't';
//Find all 't' chars indexes in the text
List<int> TheIndexList = textBox1.Text.Select((chr, idx) => chr == TheChar ? idx : -1)
.Where(idx => idx != -1).ToList();
//Or with Regex - same thing, pick the one you prefer
List<int> TheIndexList = Regex.Matches(textBox1.Text, TheChar.ToString())
.Cast<Match>()
.Select(chr => chr.Index).ToList();
//Using .GetPositionFromCharIndex(), define the Point [p] where the highlighted text is drawn
if (TheIndexList.Count > 0)
{
foreach (int Position in TheIndexList)
{
Point p = textBox1.GetPositionFromCharIndex(Position);
using (Graphics g = textBox1.CreateGraphics())
TextRenderer.DrawText(g, TheChar.ToString(), textBox1.Font, p,
textBox1.ForeColor, Color.LightGreen, flags);
}
}
The same operation using Graphics.FillRectangle() and Graphics.DrawString():
if (TheIndexList.Count > 0)
{
using (Graphics g = textBox1.CreateGraphics())
{
foreach (int Position in TheIndexList)
{
PointF pF = textBox1.GetPositionFromCharIndex(Position);
SizeF sF = g.MeasureString(TheChar.ToString(), textBox1.Font, 0,
StringFormat.GenericTypographic);
g.FillRectangle(Brushes.LightGreen, new RectangleF(pF, sF));
using (SolidBrush brush = new SolidBrush(textBox1.ForeColor))
{
g.TextRenderingHint = TextRenderingHint.ClearTypeGridFit;
g.DrawString(TheChar.ToString(), textBox1.Font, brush, pF, StringFormat.GenericTypographic);
}
}
}
}
There is no notable difference in behavior: TextRenderer.DrawText()
and Graphics.DrawString() do the exact same thing here.
Setting Application.SetCompatibleTextRenderingDefault() to true or
false does not seem to have any affect (in the current context, at least).
2) Highlight some string patterns ("Words") in a TextBox control and a multiline RichTextbox control.
Using TextRenderer only, since there's no difference in behavior.
I'm simply letting IndexOf() find the the first occurrence of the
strings, but the same search pattern used before can take it's place. Regex works better.
string[] TheStrings = {"for", "s"};
foreach (string pattern in TheStrings)
{
Point p = TextBox2.GetPositionFromCharIndex(TextBox2.Text.IndexOf(pattern));
using (var g = TextBox2.CreateGraphics()) {
TextRenderer.DrawText(g, pattern, TextBox2.Font, p,
TextBox2.ForeColor, Color.LightSkyBlue, flags);
}
}
TheStrings = new string []{"m", "more"};
foreach (string pattern in TheStrings)
{
Point p = richTextBox1.GetPositionFromCharIndex(richTextBox1.Text.IndexOf(pattern));
using (Graphics g = richTextBox1.CreateGraphics())
TextRenderer.DrawText(g, pattern, richTextBox1.Font, p,
richTextBox1.ForeColor, Color.LightSteelBlue, flags);
}
3) Highlight all s in all the ListItems of a ListBox control (of course it can be any other string :)
The ListBox.DrawMode is set to Normal and changed "on the fly" to OwnerDrawVariable to evaluate whether TextRenderer and Graphics behave differently here.
There is a small difference: a different offset, relative to the left
margin of the ListBox, compared to the standard implementation.
TextRenderer, with TextFormatFlags.NoPadding renders 2 pixels to the
left (the opposite without the flag). Graphics renders 1 pixel to the
right. Of course if OwnerDrawVariable is set in design mode,
this will not be noticed.
string HighLightString = "s";
int GraphicsPaddingOffset = 1;
int TextRendererPaddingOffset = 2;
private void button1_Click(object sender, EventArgs e)
{
listBox1.DrawMode = DrawMode.OwnerDrawVariable;
}
How the following code works:
Get all the positions in the ListItem text where the pattern (string HighLightString) appears.
Define an array of CharacterRange structures with the position and length of the pattern.
Fill a StringFormat with all the CharacterRange structs using .SetMeasurableCharacterRanges()
Define an array of Regions using Graphics.MeasureCharacterRanges() passing the initialized StringFormat.
Define an array of Rectangles sized using Region.GetBounds()
Fill all the Rectangles with the highlight color using Graphics.FillRectangles()
Draw the ListItem text.
TextRenderer.DrawText() implementation:
private void listBox1_DrawItem(object sender, DrawItemEventArgs e)
{
e.DrawBackground();
TextFormatFlags flags = TextFormatFlags.Left | TextFormatFlags.Top | TextFormatFlags.NoPadding |
TextFormatFlags.WordBreak | TextFormatFlags.TextBoxControl;
Rectangle bounds = new Rectangle(e.Bounds.X + TextRendererPaddingOffset,
e.Bounds.Y, e.Bounds.Width, e.Bounds.Height);
string ItemString = listBox1.GetItemText(listBox1.Items[e.Index]);
List<int> TheIndexList = Regex.Matches(ItemString, HighLightString)
.Cast<Match>()
.Select(s => s.Index).ToList();
if (TheIndexList.Count > 0)
{
CharacterRange[] CharRanges = new CharacterRange[TheIndexList.Count];
for (int CharX = 0; CharX < TheIndexList.Count; CharX++)
CharRanges[CharX] = new CharacterRange(TheIndexList[CharX], HighLightString.Length);
StringFormat format = new StringFormat(StringFormat.GenericDefault);
format.SetMeasurableCharacterRanges(CharRanges);
Region[] regions = e.Graphics.MeasureCharacterRanges(ItemString, e.Font, e.Bounds, format);
RectangleF[] rectsF = new RectangleF[regions.Length];
for (int RFx = 0; RFx < regions.Length; RFx++)
rectsF[RFx] = regions[RFx].GetBounds(e.Graphics);
e.Graphics.FillRectangles(Brushes.LightGreen, rectsF);
}
TextRenderer.DrawText(e.Graphics, ItemString, e.Font, bounds, e.ForeColor, flags);
}
`Graphics.DrawString()` implementation
private void listBox1_DrawItem(object sender, DrawItemEventArgs e)
{
e.DrawBackground();
Rectangle bounds = new Rectangle(e.Bounds.X - GraphicsPaddingOffset,
e.Bounds.Y, e.Bounds.Width, e.Bounds.Height);
string ItemString = listBox1.GetItemText(listBox1.Items[e.Index]);
List<int> TheIndexList = Regex.Matches(ItemString, HighLightString)
.Cast<Match>()
.Select(s => s.Index).ToList();
StringFormat format = new StringFormat(StringFormat.GenericDefault);
if (TheIndexList.Count > 0)
{
CharacterRange[] CharRanges = new CharacterRange[TheIndexList.Count];
for (int CharX = 0; CharX < TheIndexList.Count; CharX++)
CharRanges[CharX] = new CharacterRange(TheIndexList[CharX], HighLightString.Length);
format.SetMeasurableCharacterRanges(CharRanges);
Region[] regions = e.Graphics.MeasureCharacterRanges(ItemString, e.Font, e.Bounds, format);
RectangleF[] rectsF = new RectangleF[regions.Length];
for (int RFx = 0; RFx < regions.Length; RFx++)
rectsF[RFx] = regions[RFx].GetBounds(e.Graphics);
e.Graphics.FillRectangles(Brushes.LightGreen, rectsF);
}
using (SolidBrush brush = new SolidBrush(e.ForeColor))
e.Graphics.DrawString(ItemString, e.Font, brush, bounds, format);
}
Note: Depending on the ListBox.DrawMode, it may become necessary to
subscribe the ListBox.MeasureItem() event or set the .ItemHeight
property to the corrent value.
private void listBox1_MeasureItem(object sender, MeasureItemEventArgs e)
{
e.ItemHeight = listBox1.Font.Height;
}
My travails in trying to use both "generic" textbox-type controls and multiline ones (that have greater height/are taller) are detailed here
What can I do to use an iTextSharp Textfield (or its basic equivalent), with the only difference being that it is multiline (covers more vertical space on the form)?
This gives me back the "textboxes" that I want:
public class DynamicTextbox : IPdfPCellEvent
{
private string fieldname;
public DynamicTextbox(string name)
{
fieldname = name;
}
public void CellLayout(PdfPCell cell, Rectangle rectangle, PdfContentByte[] canvases)
{
PdfWriter writer = canvases[0].PdfWriter;
iTextSharp.text.pdf.TextField text = new iTextSharp.text.pdf.TextField(writer, rectangle, fieldname);
//Microsoft.SharePoint.WebControls.TextField text = new TextField(writer, rectangle, fieldname);
PdfFormField field = text.GetTextField();
writer.AddAnnotation(field);
}
}
...but this derivation on that theme, attempting to generate a "taller" version of that, fails:
public class DynamicMultilineTextbox : IPdfPCellEvent
{
private string fieldname;
public DynamicMultilineTextbox(string name)
{
fieldname = name;
}
public void CellLayout(PdfPCell cell, Rectangle rectangle, PdfContentByte[] canvases)
{
Rectangle wreckTangle = new Rectangle(30, 60); // changed from 300, 600
PdfWriter writer = canvases[0].PdfWriter;
iTextSharp.text.pdf.TextField text = new iTextSharp.text.pdf.TextField(writer, wreckTangle, fieldname);
PdfFormField field = text.GetTextField();
writer.AddAnnotation(field);
}
}
The failure is in that, if I use a rectangle of 300, 600, it produces a monstrous Blob that threatens to cover this county and the next. If I use 30,60 it shows nothing - and in either case, where I expect the "tall textboxes" to be just sports horizontal lines, like so:
Am I barking up the wrong tree? How is what I'm trying to accomplish (add multline controls to a PDF file) possible?
UPDATE
Awedly enough, if I just give the textboxes any old literal string, it works:
The code for that is this:
PdfPCell cellNotesMultilineTextBox = new PdfPCell()
{
CellEvent = new DynamicTextbox("multilineTextboxNotes"),
Phrase = new Phrase("I will be darned like a sock", timesRoman9Font)
};
tblMultilineTextAreas.AddCell(cellNotesMultilineTextBox);
Phrase blankPhrase = new Phrase();
PdfPCell blankCell = new PdfPCell(blankPhrase);
blankCell.BorderWidth = 0;
tblMultilineTextAreas.AddCell(blankCell);
PdfPCell cellAccountCodesMultilineTextBox = new PdfPCell()
{
CellEvent = new DynamicTextbox("multilineTextboxAccountCodes"),
Phrase = new Phrase("I will be dammed like a reservoir", timesRoman9Font)
};
tblMultilineTextAreas.AddCell(cellAccountCodesMultilineTextBox);
Phrase blankPhrase2 = new Phrase();
PdfPCell blankCell2 = new PdfPCell(blankPhrase2);
blankCell2.BorderWidth = 0;
tblMultilineTextAreas.AddCell(blankCell2);
PdfPCell cell1099TaxReportableMultilineTextBox = new PdfPCell()
{
CellEvent = new DynamicTextbox("multilineTextbox1099TaxReportable"),
Phrase = new Phrase("I will be the uncle of a monkey", timesRoman9Font)
};
tblMultilineTextAreas.AddCell(cell1099TaxReportableMultilineTextBox);
doc.Add(tblMultilineTextAreas);
...but, of couse, that won't do. Accessing the InnerText of the HtmlTextArea controls on the WebPart, which are coded up like this:
HtmlTextArea txtarNotes = null;
. . .
txtarNotes = new HtmlTextArea();
txtarNotes.Cols = 40;
txtarNotes.Rows = 6;
...results in the "no-usable-height" controls as shown in the first scream shot.
And referencing a Textbox control on the WebPart (instead of a HtmlTextArea control) also results in the "horizontal lines" only. Is this a result of the value being empty (if the string is empty, no space is allotted for it)?
UPDATE 2
Apparently so, because doing this:
String s = txtarAccountCodes.InnerText;
if (String.IsNullOrEmpty(s))
{
s = " ";
}
PdfPCell cellNotesMultilineTextBox = new PdfPCell()
{
CellEvent = new DynamicTextbox("multilineTextboxNotes"),
Phrase = new Phrase(s, timesRoman9Font)
};
...works (the textboxes, albeit empty, display in their expected sizes/heights).
UPDATE 3
It dawned on me that maybe I needed to incrase the size of the cell first (prior to worrying about the size of the "multiline textbox" within the cell). SO I changed the code to this:
PdfPCell cellNotesMultilineTextBox = new PdfPCell()
{
CellEvent = new DynamicTextbox("multilineTextboxNotes"),
Phrase = new Phrase(notes, timesRoman9Font),
MinimumHeight = 120,
NoWrap = false,
Rowspan = 40
};
(the MinimumHeight, NoWrap, and Rowspan values are new). This, though, does not solve the "won't wrap" problem that I've got - the area is large enough, but I can only enter one line of text; the more text I enter, the smaller the text gets:
Is there a solution to this conundrum? It seems what I need is a way to tell the "Textbox" being created to "be multiline" (increase its height, or "rowcount", or make it wrappable, as in the case with the Cell itself above) but there seems to be no such capability "on the surface" - is there something beneath the covers that exposes this functionality?
I have a similar requirement and achieved with the following code. Let me know if I misunderstood something or your requirement is something else.
TextField tf = new TextField(stamper.Writer, new Rectangle(llx, lly, urx, ury), property.ControlId)
{
Options = TextField.MULTILINE | TextField.READ_ONLY,
FontSize = 11
};
Screenshot from Adobe Acrobat:
A clear omission seems to be that after applying this approach:
Vertical Tab Control with horizontal text in Winforms
Which is also recommended by Microsoft:
How to: Display Side-Aligned Tabs with TabControl
There is no text on tabs at design time, so further development and support becomes a nightmare.
Is there a way to make tab text also display at design time?
Just create your own control so the custom drawing also works at design time. Add a new class to your project and paste the code shown below. Compile. Drop the new control from the top of the toolbox onto your form. I tweaked it a bit to make it no so garish.
using System;
using System.Drawing;
using System.Windows.Forms;
class VerticalTabControl : TabControl {
public VerticalTabControl() {
this.Alignment = TabAlignment.Right;
this.DrawMode = TabDrawMode.OwnerDrawFixed;
this.SizeMode = TabSizeMode.Fixed;
this.ItemSize = new Size(this.Font.Height * 3 / 2, 75);
}
public override Font Font {
get { return base.Font; }
set {
base.Font = value;
this.ItemSize = new Size(value.Height * 3 / 2, base.ItemSize.Height);
}
}
protected override void OnDrawItem(DrawItemEventArgs e) {
using (var _textBrush = new SolidBrush(this.ForeColor)) {
TabPage _tabPage = this.TabPages[e.Index];
Rectangle _tabBounds = this.GetTabRect(e.Index);
if (e.State != DrawItemState.Selected) e.DrawBackground();
else {
using (var brush = new System.Drawing.Drawing2D.LinearGradientBrush(e.Bounds, Color.White, Color.LightGray, 90f)) {
e.Graphics.FillRectangle(brush, e.Bounds);
}
}
StringFormat _stringFlags = new StringFormat();
_stringFlags.Alignment = StringAlignment.Center;
_stringFlags.LineAlignment = StringAlignment.Center;
e.Graphics.DrawString(_tabPage.Text, this.Font, _textBrush, _tabBounds, new StringFormat(_stringFlags));
}
}
}
You need to subclass the TabControl and override OnDrawItem. Here's an example:
Public Class UITabControl
Inherits TabControl
Protected Overrides Sub OnDrawItem(e As DrawItemEventArgs)
Using brush As New SolidBrush(Me.ForeColor)
Using format As New StringFormat() With {.LineAlignment = StringAlignment.Center}
Select Case Me.Alignment
Case TabAlignment.Left
format.Alignment = StringAlignment.Near
Case TabAlignment.Top
format.Alignment = StringAlignment.Far
End Select
format.FormatFlags = (format.FormatFlags Or StringFormatFlags.NoWrap)
Dim rect As Rectangle = e.Bounds
rect.X += 3
rect.Width -= 6
e.Graphics.DrawString(Me.TabPages(e.Index).Text, Me.Font, brush, rect, format)
End Using
End Using
MyBase.OnDrawItem(e)
End Sub
End Class
Since you linked to my question, I thought it expedient to inform you of updates to my question thread.
I have, in an answer to my question, uploaded my code for the control in the interest of the programming community.
This is a screenshot of the control at runtime.
It features full design time support, automatic resizing of tabs (up to 128px wide) and tab icons as well.
The code can be downloaded from here.
I have a Windows Forms app where I add different figures(rectangles, circles, etc.) to the main form. The figure is a UserControl and it's shape I define with GraphicsPath.
Method for adding new figure:
void AddElement(ShapeType shape, string guid)
{
Shape newShape = new Shape();
newShape.Name = guid;
newShape.Size = new Size(100, 100);
newShape.Type = shape;
newShape.Location = new Point(100, 100);
newShape.MouseDown += new MouseEventHandler(Shape_MouseDown);
newShape.MouseMove += new MouseEventHandler(Shape_MouseMove);
newShape.MouseUp += new MouseEventHandler(Shape_MouseUp);
newShape.BackColor = this.BackColor;
this.Controls.Add(newShape);
}
In Shape (Figure) class:
private ShapeType shape;
private GraphicsPath path = null;
public ShapeType Type
{
get { return shape; }
set
{
shape = value;
DrawElement();
}
}
private void DrawElement()
{
path = new GraphicsPath();
switch (shape)
{
case ShapeType.Rectangle:
path.AddRectangle(this.ClientRectangle);
break;
case ShapeType.Circle:
path.AddEllipse(this.ClientRectangle);
break;
case ShapeType.Line:
path.AddLine(10,10,20,20);
break;
}
this.Region = new Region(path);
}
protected override void OnPaint(System.Windows.Forms.PaintEventArgs e)
{
if (path != null)
{
e.Graphics.DrawPath(new Pen(Color.Black, 4), path);
}
}
When resizing the figure, I redraw It:
protected override void OnResize(System.EventArgs e)
{
DrawElement();
this.Invalidate();
}
Everything works fine when I add shapes like rectangle and circle. But when I choose Line, nothing appears on my form. The breakpoint shows that the programs steps in all the methods and this.Controls.Add(newShape); as well.
I do not understand why this is not working.
I'd appreciate any advice.
You can draw an open GraphicsPath with a thin or a thick Pen. But a region must be set from a closed shape or else there is no place where your pixels could show up. This will help to keep your region intact; but you need to know, just what you want it to be:
if (shape != ShapeType.Line) this.Region = new Region(path);
If you want it to be something like a thick line you must create a polygon or a series of lines to outline the shape you want. And if you want your line to be inside that region you will need two paths: one closed polygon path to set the region and one open line path to draw the line inside the region.
Edit:
The best way to create the closed path is probably to use the Widen() method with the Pen you are using like this:
GraphicsPath path2 = path.Widen(yourPen);
This would get the thickness right as well as the line caps and also work for more complicated polylines; I haven't tried it though..
Maybe it's because the line has no area. Try to replace it with a very thin shape having a positive area. For instance:
const int thickness = 1;
path.AddLines(new[]
{
new Point(10, 10),
new Point(20, 20),
new Point(20 + thickness, 20 + thickness),
new Point(10 + thickness, 10 + thickness)
});