In my winforms application I have some code that needs to be executed after the text is changed. On the label I have a textchanged event with the following code:
string value = lblText.Text;
int labelWidth = lblText.Width;
int controlWidth = groupPanel1.Width;
int difference = controlWidth - labelWidth;
lblText.Left = difference / 2;
When I set a breakpoint at string value = lblText.Text; I see the correct value. But the width property returns the width of the previous value of the text property.
For example:
The first time: text = "hello world!" width: 0
Second time: text = "h" width: 60
Third time: text = "hi" width: 13
How is that possible?
If that is a label with an autosize property on, then it will refit after the paint event. Looks like you are changing the text and asking for the new width, but not asking after the paint, so it still has the last width.
You should use something like this (your code moved to delegate):
private void label1_TextChanged(object sender, EventArgs e) {
this.BeginInvoke(new Action(delegate {
string value = lblText.Text;
int labelWidth = lblText.Width;
int controlWidth = groupPanel1.Width;
int difference = controlWidth - labelWidth;
lblText.Left = difference / 2;
this.Text = label1.Width.ToString();
}));
}
Place this code to TextChanged handler.
Related
I'm trying to set the Height of the form while I'm resizing it, if a condition is met. I have it set to only allow the width of the form to be altered manually using the code provided by in this answer.
I have a FlowLayoutPanel displaying a collection of PictureBox controls, each with a fixed Height of 50 pixels. Initially, the form's Height is 38 (Size.Height - ClientSize.Height) + 50 + 6 (Margin.Top + Margin.Bottom of an image) = 94.
If the controls overflow, by default the FlowLayoutPanel pushes them down onto a new line. What I want to do is resize the form when this happens, or when the form width is manually changed which might cause the controls to jump to the next line.
The following code works, and is called whenever a new control is added to the FlowLayoutPanel (itemPanel):
private void ResizeForm()
{
if (itemPanel.Controls.Count < 1) return;
var lastElement = itemPanel.Controls[itemPanel.Controls.Count - 1];
// The Form is the correct size, no need to resize it:
if (lastElement.Bottom + lastElement.Margin.Bottom == itemPanel.Height) return;
Height = 38 + lastElement.Bottom + lastElement.Margin.Bottom;
}
However, when called within my SizeChange event, this method causes the Form to "flash" between the initial Height and the new Height:
private void MainForm_SizeChanged(object sender, EventArgs e)
{
ResizeForm();
}
I'm guessing the reason is because setting Height will fire the SizeChange event again, but I don't know how to resolve this issue. When I print out the values of lastElement.Bottom + lastElement.Margin.Bottom and itemPanel.Height after setting the Height, they are identical, but the code is still somehow reaching that point.
In a nutshell, I want only the form Width to be manually altered, but the Height of the form to change when items are added or the Width is changed, so that all controls inside the FlowLayoutPanel can be viewed.
However, when called within my SizeChange event, this method causes
the Form to "flash" between the initial Height and the new Height
Basically any of the stock "resize" events for your Form will occur too late for you to change the size without it being noticeable.
You'll want to trap the WM_SIZING message:
Sent to a window that the user is resizing. By processing this
message, an application can monitor the size and position of the drag
rectangle and, if needed, change its size or position.
This will allow you to change the size of the Form before it has actually been updated on the screen.
It would look something like this:
public partial class Form1 : Form
{
private struct RECT
{
public int Left;
public int Top;
public int Right;
public int Bottom;
}
enum HitTest
{
Caption = 2,
Transparent = -1,
Nowhere = 0,
Client = 1,
Left = 10,
Right = 11,
Top = 12,
TopLeft = 13,
TopRight = 14,
Bottom = 15,
BottomLeft = 16,
BottomRight = 17,
Border = 18
}
private const int WM_SIZING = 0x214;
private const int WM_NCHITTEST = 0x84;
public Form1()
{
InitializeComponent();
}
protected override void WndProc(ref Message m)
{
base.WndProc(ref m);
switch (m.Msg)
{
case WM_NCHITTEST:
var result = (HitTest)m.Result.ToInt32();
if (result == HitTest.Top || result == HitTest.Bottom)
m.Result = new IntPtr((int)HitTest.Caption);
if (result == HitTest.TopLeft || result == HitTest.BottomLeft)
m.Result = new IntPtr((int)HitTest.Left);
if (result == HitTest.TopRight || result == HitTest.BottomRight)
m.Result = new IntPtr((int)HitTest.Right);
break;
case WM_SIZING:
// Retrieve the "proposed" size of the Form in "rc":
RECT rc = (RECT)Marshal.PtrToStructure(m.LParam, typeof(RECT));
// ... do something with "rc" ...
// this is your code (slightly modified):
if (itemPanel.Controls.Count > 0)
{
var lastElement = itemPanel.Controls[itemPanel.Controls.Count - 1];
if (lastElement.Bottom + lastElement.Margin.Bottom != itemPanel.Height)
{
int Height = 38 + lastElement.Bottom + lastElement.Margin.Bottom;
rc.Bottom = rc.Top + Height; // <--- use "Height" to update the "rc" struct
}
}
// Put the updated "rc" back into message structure:
Marshal.StructureToPtr(rc, m.LParam, true);
break;
}
}
}
Give this a try:
private void ResizeForm()
{
this.SuspendLayout(); // Suspends the layout logic until ResumeLayout() is called (below)
if (itemPanel.Controls.Count < 1) return;
var lastElement = itemPanel.Controls[itemPanel.Controls.Count - 1];
// The Form is the correct size, no need to resize it:
if (lastElement.Bottom + lastElement.Margin.Bottom == itemPanel.Height) return;
Height = 38 + lastElement.Bottom + lastElement.Margin.Bottom;
this.ResumeLayout(); // ADD THIS AS WELL
}
I created an textbox from code behind like this:
TextBox txtPlainTxt = new TextBox();
txtPlainTxt.Height = 200;
txtPlainTxt.Width = 300;
txtPlainTxt.TextWrapping = TextWrapping.Wrap;
txtPlainTxt.Text = text;
int lineCount = txtPlainTxt.LineCount;
I'm trying to get the LineCount property of a textbox but problem is that it always has the value of "-1". I guess it have something to do with that I created it from code behind and not in my xaml becouse when I create the exact same textbox in xaml, everything works fine and I get the correct number of lines in it.
I tried to call UpdateLayout() method and also tried to call Focus() method like this:
txtPlainTxt.Focus();
txtPlainTxt.UpdateLayout();
txtPlainTxt.Focus();
txtPlainTxt.UpdateLayout();
But I still get the value of -1. How can I solve this problem?
That is happening because until your layout gets measured, you don't have ActualHeight and ActualWidth, so LineCount can't get calculated until that happens.
That means that you can only use LineCount property after your layout got measured & arranged.
(UpdateLayout() only notifies the layout engine that the layout should be updated and immediately returns.)
public partial class Window1 : Window
{
TextBox txtPlainTxt = new TextBox();
public Window1()
{
txtPlainTxt.Height = 200;
txtPlainTxt.Width = 300;
txtPlainTxt.TextWrapping = TextWrapping.Wrap;
txtPlainTxt.Text = "some text some text some text some text some text";
Grid.SetRow(txtPlainTxt, 0);
Grid.SetColumn(txtPlainTxt, 0);
gridMain.Children.Add(txtPlainTxt);
// here it will be -1
int lineCount = txtPlainTxt.LineCount;
gridMain.LayoutUpdated += new EventHandler(gridMain_LayoutUpdated);
txtPlainTxt.LayoutUpdated += new EventHandler(txtPlainTxt_LayoutUpdated);
}
void txtPlainTxt_LayoutUpdated(object sender, EventArgs e)
{
// the layout was updated, LineCount will have a value
int lineCount = txtPlainTxt.LineCount;
}
void gridMain_LayoutUpdated(object sender, EventArgs e)
{
// here it will be correct too
int lineCount = txtPlainTxt.LineCount;
}
}
I'm working on a code-editor and want to auto-adjust the width of a label as the number increases. For example, for 1-9 (1 digit) there's a specific width. Then when it gets to 10-99 (2 digits), width of label increases. Then again for then 100-999 (3 digits), etc.
The result should be something like this:
Here is my code:
private void timer_countline_Tick(object sender, EventArgs e)
{
updateNumberLabel();
}
private void updateNumberLabel()
{
// we get index of first visible char and number of first visible line
Point pos = new Point(0, 0);
int firstIndex = rtb.GetCharIndexFromPosition(pos);
int firstLine = rtb.GetLineFromCharIndex(firstIndex);
// now we get index of last visible char and number of last visible line
pos.X = ClientRectangle.Width;
pos.Y = ClientRectangle.Height;
int lastIndex = rtb.GetCharIndexFromPosition(pos);
int lastLine = rtb.GetLineFromCharIndex(lastIndex);
// this is point position of last visible char, we'll use its Y value for calculating numberLabel size
pos = rtb.GetPositionFromCharIndex(lastIndex);
// finally, renumber label
numberLabel.Text = "";
for (int i = firstLine; i <= lastLine + 1; i++)
numberLabel.Text += i + 1 + "\n";
}
You can use TextRenderer for doing what you want. Please check the following code lines (You should add the code lines to TextChanged event of your label):
Please remember that the AutoSize property of your controls must set to False.
This is for changing Width of your control to fit with width of its contents.
yourLabelName.Width = TextRenderer.MeasureText(yourLabelName.Text, yourLabelName.Font).Width;
This is for changing Height of your control to fit with height of its contents.
yourLabelName.Height = TextRenderer.MeasureText(yourLabelName.Text, yourLabelName.Font).Height;
Update 1:
For changing your panel Width for showing all contents in it horizontally, you can use the followng code line:
yourPanelName.Width = yourLabelName.Left + yourLabelName.Width;
For changing your panel Height for showing all contents in it vartically, you can use the followng code line:
yourPanelName.Height = yourLabelName.Top + yourLabelName.Height;
Update 2:
If you are used SplitContainer control, you must change the properties of your SplitContainer as follows:
FixedPanel = none
IsSplitterFixed = False
Then you can use the following lines of code for achieve to what you want:
For changing your SplitContainer panel Width for showing all contents in it horizontally, you can use the followng code line:
int yourLabelNameWidth = TextRenderer.MeasureText(yourLabelName.Text, yourLabelName.Font).Width;
yourSplitContainerName.SplitterDistance = yourLabelName.Left + yourLabelNameWidth;
yourLabelName.Width = yourLabelName.Left + yourLabelNameWidth;
How can I get PropertyGrid's TextBox from specified field?
I need this TextBox to set Pointer to the end of text.
var num = txtBox.GetCharIndexFromPosition(Cursor.Position);
txtBox.SelectionStart = num + 1;
txtBox.SelectionLength = 0;
So how can I get this TextBox from PropertyGrid?
Also, property in PropertyGrid is read-only.
If what you want is the cursor to be located right after the last character written in the textbox, you can rely on the following code (triggered by the TextChanged Event of the TextBox):
private void txtBox_TextChanged(object sender, EventArgs e)
{
int newX = txtBox.Location.X + TextRenderer.MeasureText(txtBox.Text, txtBox.Font).Width;
int newY = txtBox.Bottom - txtBox.Height / 2;
if (newX > txtBox.Location.X + txtBox.Width)
{
newX = txtBox.Location.X + txtBox.Width;
}
Cursor.Position = this.PointToScreen(new Point(newX, newY));
}
Bear in mind that its Y position is always in the middle.
----- UPDATE AFTER THE KINGKING COMMENT
As far as the code in the question was referred to the TextBox, I focused my answer on the TextBox. Nonetheless, KingKing is right and the PropertyGrid has to be brought into consideration. Below these lines I adapted the code you can find in MSDN for PropertyGrid:
private void Form1_Load(object sender, EventArgs e)
{
PropertyGrid propertyGrid1 = new PropertyGrid();
propertyGrid1.CommandsVisibleIfAvailable = true;
propertyGrid1.Location = new Point(10, 20);
propertyGrid1.Size = new System.Drawing.Size(400, 300);
propertyGrid1.TabIndex = 1;
propertyGrid1.Text = "Property Grid";
this.Controls.Add(propertyGrid1);
propertyGrid1.SelectedObject = txtBox;
}
After txtBox is added to propertyGrid1, its position is updated and thus the original code can be used without any problem.
In summary, the idea is not looking for the TextBox inside the PropertyGrid, but accessing directly the TextBox control (which is added at runtime to the PropertyGrid).
I am trying to create trackbars and labels dynamically. In which if a user inputs a number like 4, it creates 4 trackbars and 4 labels. Then if the user moves any of the dynamically created trackbar moves it and updates its associated label. Then adds the numbers in all the labels and stores it in another label call total label.
Here is another way of explaining it. The user enters the number 3. The system creates 3 trackbars and 3 labels (next to the trackbars). The user moves first track bar to 5, the first label is automatically updated with 5. The user moves the second track bar to 3, the second label is automatically updated with 3. finally the user moves the third track bar to position 9 and the label is automatically updated with 9.
On the right side there is another label that shows 17 = (5+3+9).
I found a few websites ons creating dynamically controls but I don't know how to link a dynamically created trackbar to the dynamically created label. Then adding those dynamically added labels.
ALL this in C# on a windows form.
I did something very simliar to the below when creating my labels and trackbars.
for (int i = 0; i < 10; i++)
{
Label label = new Label();
label.Text = i.ToString();
PlaceHolder1.Controls.Add(label);
}
Thanks in advances
------------------------Update-----------------
void CreateLabeledTrackBars(Control host, int n)
how do I use this, I was hoping that when I start a new form
that all I would have to is this..that way the form already has the in n, but it seems not to work..i am confused on how the control works. can you please explain
namespace WindowsFormsApplication1
{
public partial class Form3 : Form
{
public static Form2 myNewForm = new Form2();
private TrackBar[] _trackBars;
private Label[] _labels;
public Form3(int n)
{
CreateLabeledTrackBars(new Label (), n);
}
//Then the rest of the code
You need to handle ValueChanged event of each TrackBar you create. To calculate sum of all values, store created controls in arrays.
private TrackBar[] _trackBars;
private Label[] _labels;
void CreateLabeledTrackBars(Control host, int n)
{
const int trackBarWidth = 150;
const int minValue = 0;
const int maxValue = 10;
const int defaultValue = 0;
_trackBars = new TrackBar[n];
_labels = new Label[n];
int y = 0;
for(int i = 0; i < n; ++i)
{
var label = new Label()
{
Top = y,
Left = trackBarWidth,
Text = defaultValue.ToString(),
Parent = host,
};
var trackBar = new TrackBar()
{
Top = y,
Width = trackBarWidth,
// save associated label
Tag = label,
Minimum = minValue,
Maximum = maxValue,
Value = defaultValue,
Parent = host,
};
// handle ValueChangedEvent
trackBar.ValueChanged += OnTrackBarValueChanged;
// apply vertical offset for next trackbar
y += trackBar.Height;
_trackBars[i] = trackBar;
_labels[i] = label;
}
}
void OnTrackBarValueChanged(object sender, EventArgs e)
{
// get trackbar, which generated event
var trackBar = (TrackBar)sender;
// get associated label
var associatedLabel = (Label)trackBar.Tag;
associatedLabel.Text = trackBar.Value.ToString();
// recalculate sum of all values and update other label here
}
Now, when you have an array of trackbars, getting sum of all values should be trivial.