TextBox.LineCount always -1 WPF - c#

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;
}
}

Related

Groupbox created in code doesn't dock correctly to parent TableLayoutPanel

In my C# WinForm application I have a tableLayoutPanel with only one row and two columns. The left column contains a TreeView, the right one another tableLayoutPanel which was set to Dock: Fill in the designer. I want to create several GroupBoxes programmatically that should use the full width of this second column also when the form is resized.
In the designer of Visual Studio it looks like this:
But when the application runs, the GroupBoxes don't use the complete width and it looks like this:
I'm using two methods to create the GroupBoxes (you can ignore the int values):
private void addDashboardRow(int i, int s)
{
// Add one row to dashboardTableLayoutPanel
sensorTableLayoutPanel.RowCount++;
// Maybe this one is wrong, but SizeType.AutoSize doesn't work either
sensorTableLayoutPanel.RowStyles.Add(new RowStyle(SizeType.Absolute, 75f));
sensorTableLayoutPanel.Controls.Add(createSensorGroupbox(i, s));
}
private GroupBox createSensorGroupbox(int i, int sen)
{
GroupBox g = new GroupBox();
//g.Width = 500; // I don't want fixed sizes, height of 75px is sufficient
//g.Height = 75;
g.Dock = DockStyle.Fill;
g.Text = "Sensor " + sen.ToString();
// just one label as example...
Label temp = new Label();
temp.Text = "Temperature: ";
temp.Location = new Point(5, 20);
Label tempVal = new Label();
tempVal.Text = "°C";
tempVal.Location = new Point(5, 50);
tempVal.Name = "Sensor" + sen.ToString() + "_temp";
//[... more labels]
g.Controls.Add(temp);
g.Controls.Add(tempVal);
return g;
}
My questions are:
How can I fit the GroupBox into the complete width of the parent container?
How can I delete all rows? E.g. when looking up the sensors during run time all GroupBoxes are dublicated, but everything should be created newly.
Layout of the question: how should I format terms like TableLayoutPanel correctly in this forum? It's my second question here and I haven't found out, yet.
For the second question. Currently I'm using this method that doesn't do what I'm expecting:
private void clearDashboardTable()
{
if (sensorTableLayoutPanel.Controls.Count == sensorList.Count) {
//MessageBox.Show(sensorTableLayoutPanel.Controls.Count.ToString());
foreach (Control con in sensorTableLayoutPanel.Controls)
{
sensorTableLayoutPanel.Controls.Remove(con);
con.Dispose();
}
//sensorTableLayoutPanel.Dispose();
sensorList.Clear();
}
}
I commented out what I've tried so far in the above code or added additional comments. I used this topic to try to understand the basics of dynamic groupbox creation: Add Row Dynamically in TableLayoutPanel
One way to achieve the outcome you describe is to make a custom UserControl containing a docked GroupBox that contains a docked TableLayoutPanel.
To adjust the width when the container changes, attach to the SizeChanged event of the parent.
public partial class CustomGroupBox : UserControl
{
public CustomGroupBox() => InitializeComponent();
protected override void OnHandleCreated(EventArgs e)
{
base.OnHandleCreated(e);
if((Parent != null) &&!DesignMode)
{
Parent.SizeChanged += onParentSizeChanged;
}
}
private void onParentSizeChanged(object? sender, EventArgs e)
{
if(sender is FlowLayoutPanel flowLayoutPanel)
{
Debug.WriteLine($"{flowLayoutPanel.Width}");
Width =
flowLayoutPanel.Width -
SystemInformation.VerticalScrollBarWidth;
}
}
public new string Text
{
get=>groupBox.Text;
set=>groupBox.Text = value;
}
}
Main Form
The main form is laid out similar to your image except to substitute a FlowLayoutPanel on the right side.
Now test it out with this minimal code in the method that loads the Main Form:
public partial class MainForm : Form
{
int _id = 0;
public MainForm()
{
InitializeComponent();
flowLayoutPanel.AutoScroll= true;
for (int i = 0; i < 5; i++)
{
var customGroupBox = new CustomGroupBox
{
Text = $"Sensor {++_id}",
Width = flowLayoutPanel.Width - SystemInformation.VerticalScrollBarWidth,
Padding = new Padding(),
Margin = new Padding(),
};
flowLayoutPanel.Controls.Add(customGroupBox);
}
}
}
Clear
An example of clearing the sensors would be to add a button and set a handler in the same Load method:
buttonClear.Click += (sender, e) => flowLayoutPanel.Controls.Clear();

How to check if a space on the form has an object

I'm working on a mind-map project. I'm trying to get the "New Bubble" button to create a new textbox into a FREE space on the form. So I want to check if there is another bubble in the place where it's getting created. If it already has a textbox then I want it to find a new place and repeat the process.
How can I do this?
public partial class frmMap : Form
{
private void btnProperties_Click(object sender, EventArgs e)
{
new frmProperties().Show();
}
private void btnNewBubble_Click(object sender, EventArgs e)
{
var tb = new TextBox();
tb.Multiline = true;
tb.BorderStyle = BorderStyle.FixedSingle;
tb.Top = 100;
tb.Left = 200;
tb.Size = new Size(100, 100);
this.Controls.Add(tb);
}
}
You can check "collision" with other controls like so:
foreach (Control checkControl in Controls)
{
if (tb.Bounds.IntersectsWith(checkControl.Bounds))
...
}
Of course, thats a lot of checking to do! If you are just going to "grid" the controls, it would be faster/easier to just layout a bool array that holds the state of each "cell" (filled/empty) then pick the first empty one you find.
Create dynamic textbox:
var tb = new TextBox();
tb.Multiline = true;
tb.BorderStyle = BorderStyle.FixedSingle;
tb.Top = 100;
tb.Left = 200;
tb.Size = new Size(100, 100);
Then use Rectangle.IntersectWith to check if new textbox intersects with other already added texboxes (you can remove control type filter, if you have other type of controls to check):
while(Controls.OfType<TextBox>().Any(x => tb.Bounds.IntersectsWith(x.Bounds))
{
// adjust tb size or position here
}
And last step - add textbox to form:
Controls.Add(tb);

Get PropertyGrid TextBox

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).

Is there anyway to set a value of a hidden control?

I have a hidden control which contains a textbox control and I want to set the text property of the textbox but i get an NullReferenceException. However if I show the control, set the value and then hide it then i get no exception.
miStatus1.Show();
miStatus1.ioItem1.popIoItem(caseState);
miStatus1.Hide();
However this feels like a really unclean and not very elegant way to do it. And i'm seeing some flickering because i have to do this to 4 controls with up to 8 textboxes on each.
Is there any way to set the text propery of the textboxes while the control is hidden? Or is it perhaps a better idea to populate my textboxes when showing the control? And will this slow down my application as it needs to populate everytime the control is shown?
popIoItem - code
public void popIoItem(object obj){
if (ioType == 1)
{
tb.Text = (string)obj;
}
}
My interface
I'm trying to create the menu to the right and on each pressing of the categories the menus slide up/down and i hide/show the proper user control with the textboxes and other io-elements.
More code
When one of the boxes to the left is click'ed the following method is run:
public void openMenu(int caseNum)
{
caseDB.casesDataTable chosenCase;
chosenCase = _casesAdapter.GetDataByID(caseNum);
string caseName = "";
int caseOwner = -1;
DateTime caseDate = DateTime.Today;
string caseDesc = "";
int caseState = -1;
foreach (caseDB.casesRow casesRow in chosenCase)
{
if (!casesRow.IscaseNameNull())
caseName = casesRow.caseName;
if (!casesRow.IscaseCreatedByNull())
caseOwner = casesRow.caseCreatedBy;
if (!casesRow.IscaseCreatedNull())
caseDate = casesRow.caseCreated;
if (!casesRow.IscaseDescNull())
caseDesc = casesRow.caseDesc;
if (!casesRow.IscaseStateNull())
caseState = casesRow.caseState;
}
int caseJobs = (int)_jobsAdapter.JobCount(caseNum);
string caseStateStr = Enum.GetName(typeof(caseState), caseState);
caseInfoMenu1.popMenu(caseName, caseOwner, caseDate, caseDesc,caseJobs,caseStateStr);
}
The caseInfoMenu is the right menu. It consists of some drawing and mouse logic that draws the menu and handles hit-detection. Besides this it contains 4 user controls, one for each of the vertical tabs.
public void popMenu(string caseName, int caseOwner ,DateTime caseDate, string caseDesc, int caseJobs, string caseState)
{
marked = 0;
miGeneral1.Show();
miEconomy1.Hide();
miStatus1.Hide();
miHistory1.Hide();
miGeneral1.ioItem1.popIoItem(caseName);
miGeneral1.ioItem2.popIoItem(caseOwner.ToString());
miGeneral1.ioItem3.popIoItem(caseDate.ToShortDateString());
miGeneral1.ioItem4.popIoItem(caseJobs.ToString());
miGeneral1.ioItem5.popIoItem(caseDesc.ToString());
//miStatus1.ioItem1.popIoItem(caseState);
//This is commented out because it makes the application crash. However if I show miStatus1, set the value and hide it, it does not crash.
this.Invalidate();
}
Inside each of these user controls I have io-items user controls which essentially draws a blue box and puts a control in front of if ie. the textbox.
public partial class ioItem : UserControl
{
public int ioType { get; set; }
public int ioPadding { get; set; }
RichTextBox tb;
public ioItem()
{
InitializeComponent();
}
public void popIoItem(object obj){
if (ioType == 1)
{
tb.Text = (string)obj;
}
}
private void ioItem_Load(object sender, EventArgs e)
{
switch (ioType)
{
case 1:
tb = new RichTextBox();
tb.Location = new System.Drawing.Point(ioPadding, ioPadding);
tb.Name = "textbox";
tb.Size = new Size(this.Size.Width - (ioPadding * 2), this.Size.Height - (ioPadding * 2));
tb.BorderStyle = BorderStyle.None;
tb.Visible = true;
tb.BackColor = Color.FromArgb(255, 184, 198, 208);
tb.Font = new Font("Microsoft Sans Serif", 7);
this.Controls.Add(tb);
break;
case 2:
historyCtrl hiCtrl = new historyCtrl();
hiCtrl.Location = new Point(0,0);
hiCtrl.Size = new Size(this.Width, this.Height);
hiCtrl.Name = "history";
hiCtrl.Visible = true;
hiCtrl.BackColor = Color.FromArgb(255, 184, 198, 208);
this.Controls.Add(hiCtrl);
break;
default:
goto case 1;
}
}
}
Try checking the code with debugger..May be there is something other going on there? NullReference means that you try to do something with object that doesn't exist. Show/Hide just set Visible property of the control to true/false in normal situation(without custom overload/changes of Control class).
So i figured out what was wrong. The problem was something else than what I initially expected. The reason why it didn't work was because I created my textbox controls in the Load_event. When I tried to set the value of the text property of the textbox controls they hadn't been created yet. The reason why I had created the user controls containing the textboxes this way was in order to make it easy to drag them into the screen in the Designer. I found this discussion 'UserControl' constructor with parameters in C#, which showed me another way of doing it and now it works.

C# Why does the TextChanged event takes the previous width?

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.

Categories

Resources