Draw a static line between characters in a TextBox - c#

I have a TextBox. I want the user to be able to click inside it; when he does, a red marker line should appear between the two characters closest to the click position, and remain here. The user could do that multiple times.
I'm new to WPF, but I guess, as in Winforms, I will have to hack a messy OnRender method. So far, it's okay.
What I'd really like to know is: how to get the two closest characters to the click position?
I was about to do a pixel check but it seems pretty heavy.

You could try:
textBox.GetCharacterIndexFromPoint(point, true);

Well I found what I wanted to do, and it's way simpler than I thought (even though FINDING the actual way was a pain).
Just add this handler to your textbox's SelectionChanged event:
private void placeMarker(object sender, RoutedEventArgs e)
{
// txt_mark is your TextBox
int index = txt_mark.CaretIndex;
// I don't want the user to be able to place a marker at index = 0 or after the last character
if (txt_mark.Text.Length > first && first > 0)
{
Rect rect = txt_mark.GetRectFromCharacterIndex(first);
Line line = new Line();
// If by any chance, your textbox is in a scroll view,
// use the scroll view's margin instead
line.X1 = line.X2 = rect.Location.X + txt_mark.Margin.Left;
line.Y1 = rect.Location.Y + txt_mark.Margin.Top;
line.Y2 = rect.Bottom + txt_mark.Margin.Top;
line.HorizontalAlignment = HorizontalAlignment.Left;
line.VerticalAlignment = VerticalAlignment.Top;
line.StrokeThickness = 1;
line.Stroke = Brushes.Red;
// grid1 or any panel you have
grid1.Children.Add(line);
// set the grid position of the line to txt_mark's (or the scrollview's if there is one)
Grid.SetRow(line, Grid.GetRow(txt_mark));
Grid.SetColumn(line, Grid.GetColumn(txt_mark));
}
}
You might want to add some kerning or simply increase the font size for the markers not to ruin readability.

Related

Display header text after CellPainting event in winforms datagridview

I have a datagridview control where I want to put a 3px line at the bottom of each header cell to look something like
I have put code in CellPainting even for the datagridview like:
if (e.RowIndex < 0) // headers
{
Rectangle newRect = new Rectangle(e.CellBounds.X, e.CellBounds.Y - 1 + e.CellBounds.Height, e.CellBounds.Width, 2);
using (Brush gridBrush = new SolidBrush(Color.Red))
{
e.Graphics.FillRectangle(gridBrush, newRect);
}e.Handled = true;
}
The red line appears correctly (I will add the 3px later). However, the header text now is missing.
I am assuming that setting the e.Handled = true; tells to not continue to draw the original header text. If I set it to false, then the red line disappears. There is no base.CellPainting type concept for this control (apparently).
I know I can draw the text myself, but then I have to worry about alignment, font...
Is there now way to tell the system to do both the line AND draw original header text?
I am willing to try other approaches if necessary.
There is no base.CellPainting type concept for this control
Indeed; the DGV has more options than just calling the base event.
Instead you can let it draw the parts separately and in the order you want:
if (e.RowIndex < 0) // headers
{
e.PaintBackground(e.CellBounds, true); // draw the default background
Rectangle newRect =
new Rectangle(e.CellBounds.X, e.CellBounds.Bottom - 2, e.CellBounds.Width, 2);
e.Graphics.FillRectangle(Brushes.Red, newRect); // now draw the red line
e.PaintContent(e.CellBounds); // finally draw the text in the default way
e.Handled = true; // done
}
If you disable dgv.EnableHeadersVisualStyles you can also set many other properties to be used when drawing the column headers..
For even finer-tuned options you may want to look into MSDN.

Get width directly after Button.AutoResize is set

I'm adding buttons dynamically to a Form and am trying to lay the out side by side. I'm perfectly content with using the latest Button.Right as a starting point for the next button (with a margin left between of course), but the buttons have to adjust to fit the text.
So, what I'm doing is setting the AutoResize property to true and then storing the Right property, which however does not work because I guess the resizing doesn't happen until the button is drawn (I think). I tried Invalidate(), Refresh(), Update() and I think a couple more functions, and of course all together, but to no avail, I still get the old position and the next button starts beneath this one.
So the question is, after setting AutoResize to true on a Forms component, how do I force it to resize so I can grab the new Width/Right without waiting for the window to be redrawn?
Thanks in advance!
Note: If all else fails I'll do a rough approximation of the width of the buttons based on the string's length, so don't bother with something too fancy as a solution, it's not a requirement that it is perfect
You can use the Control's GetPreferredSize Method to obtain the final auto-sized dimensions. The Font property must either be explicitly set or the Control must be parented to a displayed control such that it can inherit the Font to use in the layout. In the following example, the control's Parent property is set so that it inherits the parent control's Font.
private Random rnd = new Random(1000);
private void button1_Click(object sender, EventArgs e)
{
const Int32 xDelta = 5; // the horizontal distance between the added Buttons
Int32 y = button1.Location.Y + 5 + button1.Height;
Int32 x = button1.Location.X;
Point loc = new Point(x, y);
this.SuspendLayout(); // this is Form that is the Parent container of the Buttons
for (Int32 i = 1; i <= 10; i++)
{
Button btn = new Button { Parent = this, AutoSize = true, AutoSizeMode = AutoSizeMode.GrowAndShrink };
btn.Text = new string('A', rnd.Next(1, 21));
btn.Location = loc;
Size sz = btn.GetPreferredSize(Size.Empty); // the size of btn based on Font and Text
loc.Offset(sz.Width + xDelta, 0);
}
this.ResumeLayout(true);
}

Auto sized multi line text in a button

I have a button that needs the font size of the text to be changed based on how much text is in the button. This is changed dynamically during run-time.
Basically, the text needs to start fairly large, then it needs to test how much text is in the button and decide whether to go down in size or not. It could do this in a loop by decreasing the size by one, repainting, and testing each time to see if the text fits now. If not, loop again until it does.
But the problem is with testing it. The button has multi line text, and that's the way I want it. So I can't simply test the width of the text with the width of the button using something like TextRenderer.MeasureText, because that assumes the text will only be one line. It never measures based on if it can fit on two or more lines.
So if one line of text at a certain font is 40 pixels in height, even if its on 3 lines of text on the button, TextRenderer.MeasureText.Height will show 40 pixels. And with margins and padding on the button, and space between lines of text, I can't just do 40 * 3 to get how much 3 lines would be, it's not that simple.
So... how do I test whether or not the text in the button is too big for the button?
I could set Auto Ellipsis property to true, but there's no way to test to see if the Auto Ellipsis has been used or not. So that doesn't help.
I could set Auto Size to true and test to see if the button size changed, then bring it back to the right size and lower the text size, but, it resizes based on width, before it moves text to the second line, so it's always on one line. So that doesn't help.
Any ideas? I just want to auto size multi line text. It seems so simple.
Here is solutions for this for Windows Forms:
Set the starting size on TextBox than the size will be changed by this event handler.
private void textBox1_TextChanged(object sender, EventArgs e)
{
var textBox = sender as TextBox;
if(!string.IsNullOrEmpty(textBox.Text))
{
Graphics g = textBox.CreateGraphics();
var width = g.MeasureString("a", textBox.Font).Width;//Get One Symbol Width
var totalWidth = width * textBox.Text.Length; // Calculate the width of the full text
if(totalWidth<width*10) // do what you want depending on conditions
{
textBox.Font=new Font(textBox.Font.Name,20f);
}
else if(totalWidth < width * 20)
{
textBox.Font = new Font(textBox.Font.Name, 16f);
}
else if(totalWidth < width * 30)
{
textBox.Font = new Font(textBox.Font.Name, 14f);
}
else if (totalWidth < width * 40)
{
textBox.Font = new Font(textBox.Font.Name, 12f);
}
else if (totalWidth < width * 50)
{
textBox.Font = new Font(textBox.Font.Name, 10f);
}
}
}

C# ListView, View=List - How to get rid of unused space at the bottom?

In a C# ListView with View=List, there is empty space at the bottom, reserved for a scrollbar. Weirdly, setting Scrollable=false will only increase the size of this unused space.
How can I get rid of this space or make sure it's used to display items?
Edit:
I'm having this issue in a Windows Form Application.
Edit 2: The issue seems to be coupled with the font size somehow. I need the font size to be 9 pt. With 11 pt this problem doesn't appear.
Edit 3: I also tried Item spacing in ListView where View=List but that didn't help either.
Edit 4: It happens under Win7 with the Win7 Theme. However, at least with Scrollable = false, it doesn't happen with the Classic Theme.
Still hoping for a more elegant solution, but for now, I found this workaround:
One can use a Panel to get rid of the extra space. I got the idea from TaW's answer to Add padding to last ListView item in WinForms
Remember, this worked for me, because I don't want or need a Scrollbar.
listView1.Scrollable = true;
int itemHeight = listView1.GetItemRect(0).Height;
int numItemsPerColumn = 10;
//One needs to add 21 to the height, because even if no Scrollbar
//is needed, that space will stay reserved.
listView1.Size = new Size(500, itemHeight * numItemsPerColumn + 21);
Panel P = new Panel();
P.BackColor = listView1.BackColor;
P.Location = listView1.Location;
//The height you actually want
P.Size = new Size(500, itemHeight * numItemsPerColumn + 4);
P.BorderStyle = listView1.BorderStyle;
listView1.BorderStyle = BorderStyle.None;
listView1.Parent = P;
listView1.Location = new Point(0, 0);
this.Controls.Add(P);

How can I keep the RadioButton text and an additional Label in alignment?

I'm working on a C# project using .NET 3.5 and Windows Forms. I need to design a decision step with multiple options that require a bit of explanatory text. For this, I want to have a set of RadioButtons to choose an option, followed by an additional Label each that contains the explanation.
I want to keep the label of the radio buttons and the label containing the explanatory text aligned - I've added red lines to the image to illustrate this. I could probably tweak some margins or other settings on the second label, but that would probably start to look weird as soon as the user chooses a different theme or changes some other settings. What is the canonical (and most robust) way to do this?
Your question boils down to two partial problems:
How large is the RadioButton (or the CheckBox when thinking ahead)..
How large is the gap between the glyph and the Text.
The first question is trivial:
Size s = RadioButtonRenderer.GetGlyphSize(graphics,
System.Windows.Forms.VisualStyles.RadioButtonState.CheckedNormal);
..using a suitable Graphics object. Note that I use the RadioButtonState CheckedNormal as I don't you want the Lables to align differently when the Buttons are checked or unchecked..
The second one is anything but trivial. The gap may or may not be constant and there is another gap to the left of the glyph! If I really wanted to get it right I guess I would write a routine to measure the text offset at startup:
public Form1()
{
InitializeComponent();
int gapRB = getXOffset(radioButton1);
int gapLB = getXOffset(label1);
label1.Left = radioButton1.Left + gapRB - gapLB;
}
Here is the measurement function. Note that is doesn't even use the Glyph measurement. Also note that it isn't enough to measure the text offset of the RadioButton. You also need to measure the offset of the Label!
int getXOffset(Control ctl)
{
int offset = -1;
string save = ctl.Text; Color saveC = ctl.ForeColor; Size saveSize = ctl.Size;
ContentAlignment saveCA = ContentAlignment.MiddleLeft;
if (ctl is Label)
{
saveCA = ((Label)ctl).TextAlign;
((Label)ctl).TextAlign = ContentAlignment.BottomLeft;
}
using (Bitmap bmp = new Bitmap(ctl.ClientSize.Width, ctl.ClientSize.Height))
using (Graphics G = ctl.CreateGraphics() )
{
ctl.Text = "_";
ctl.ForeColor = Color.Red;
ctl.DrawToBitmap(bmp, ctl.ClientRectangle);
int x = 0;
while (offset < 0 && x < bmp.Width - 1)
{
for (int y = bmp.Height-1; y > bmp.Height / 2; y--)
{
Color c = bmp.GetPixel(x, y);
if (c.R > 128 && c.G == 0) { offset = x; break; }
}
x++;
}
}
ctl.Text = save; ctl.ForeColor = saveC; ctl.Size = saveSize;
if (ctl is Label) { ((Label)ctl).TextAlign = saveCA; }
return offset;
}
Now the Texts do align pixel perfect..:
Note that I use two original controls from my form. Therefore much of the code is simply storing and restoring the properties I need to manipulate for the measurement; you can save a few lines by using two dummies.. Also note that I wrote the routine so that it can measure RadioButtons and Labels and probably CheckBoxes as well..
Is it worth it? You decide..!
PS: You could also owner-draw the RadioButton and the Label text in one.. this would have the interesting side-effect, that the whole text would be clickable..:
Here is a quick and dirty implementation of owner drawing a CheckBox: Prepare it by setting AutoSize = false and by adding the real text together with the extra text into the Tag, separated by a e.g. "§". Feel free to change this setup, maybe using the Label control..
I clear the Text to prevent it from drawing it and I decide on an offset. To measure it, you could use the GetGlyphSize from above.. Note how the DrawString method honors embedded '\n' characters.
The Tag contained this string:
A Rose is a Rose is a Rose..§A Rose is a rose is a rose is a rose is /
A rose is what Moses supposes his toes is / Couldn't be a lily or a
taffy daphi dilli / It's gotta be a rose cuz it rhymes with mose!
And I for the screenshot I actually used this line:
e.Graphics.DrawString(texts[1].Replace("/ ", "\n"), ...
Here is the Paint event:
private void checkBox1_Paint(object sender, PaintEventArgs e)
{
checkBox1.Text = "";
string[] texts = checkBox1.Tag.ToString().Split('§');
Font font1 = new Font(checkBox1.Font, FontStyle.Regular);
e.Graphics.DrawString(texts[0], checkBox1.Font, Brushes.Black, 25, 3);
if (texts.Length > 0)
{
SizeF s = e.Graphics.MeasureString(texts[1], checkBox1.Font, checkBox1.Width - 25);
checkBox1.Height = (int) s.Height + 30;
e.Graphics.DrawString(texts[1], font1, Brushes.Black,
new RectangleF(new PointF(25, 25), s));
}
}
The simplest out-of-the-box solution (it seems to me) would be to use 3 controls instead of 2: a radio button (with the text set to ""), a label (to go beside the radio button) and another label (to go below them). This would allow you easier configuration in designer, but (far more importantly) simpler run-time evaluation and adjustment, if necessary, to keep them in alignment should styles change.
I do understand that this takes away the benefit of clicking the label to select the radio button, but you could add that behavior in the label's Click event if you need it.
Alternatively, you could create a UserControl containing the text-free radio button and the label, and handle the behavior within that UserControl while exposing the label's location.
If you don't care about the radiobutton's text being bold, you could set it's label to a multiline string, and set CheckAlign to TopLeft:
radioButton2.CheckAlign = ContentAlignment.TopLeft;
radioButton2.Text = #"Radiobutton
Explanation text";
Don't know why I didn't think of this earlier, but the following approach seems to work:
Use a TableLayoutPanel with two columns that are set to adjust their width automatically.
Place all RadioButtons in the first column and set them to span both columns.
Place all Labels in the second column, setting all margins to 0.
Add a disabled, but visible (!) "spacer" RadioButton without text in an additional row at the end of the layout.
When displaying the form, convert the first column to a fixed size and hide the "spacer".
The key point seems to be that the "spacer" has to be visible initially - otherwise the column will get a size of 0.
This is my test form in the designer:
To change the layout, I used the following Load handler:
private void TestForm_Load(object sender, EventArgs e)
{
// find the column with the spacer and back up its width
int column = tableLayoutPanel.GetColumn(radioButtonSpacer);
int width = tableLayoutPanel.GetColumnWidths()[column];
// hide the spacer
radioButtonSpacer.Visible = false;
// set the column to the fixed width retrieved before
tableLayoutPanel.ColumnStyles[column].SizeType = SizeType.Absolute;
tableLayoutPanel.ColumnStyles[column].Width = width;
}
And this is the result at runtime:
You could add an invisible dummy label having the same text as the radiobutton. Then, get the length of that label and calculate the correct position of the explanation label.
labelDummy.Text = radioButton1.Text;
labelExplanation.Left = radioButton1.Right - labelDummy.Width;
However, this still appears to be some pixels off, even though I the label's margin to 0, maybe some additional tweaking can fix this. Here's a screenshot to show what I mean. The label's background is green to be able to see the extra margin.

Categories

Resources