This question already has answers here:
Watermark TextBox in WinForms
(11 answers)
Closed 4 years ago.
I'm currently making a Windows Forms Application on Visual Studio in C# and I'm trying to find a way to have a real hint.
I've found a lot of answers online on how to have some text preset there, Some examples even show how to grey out the text to look like a placeholder, but that's not what I'm looking for.
I want a grayed out text that you don't have to backspace to type something there. So I want it to behave like an HTML placeholder like the "Search Q&A" search bar on stack Overflow.
Is there an easy way to do this, like configuring a property of the textbox in the designer on Visual Studio?
This might be the ugliest code but I think you can improve it.
This following class is merely an extension of the standard TextBox
class PHTextBox : System.Windows.Forms.TextBox
{
System.Drawing.Color DefaultColor;
public string PlaceHolderText {get;set;}
public PHTextBox(string placeholdertext)
{
// get default color of text
DefaultColor = this.ForeColor;
// Add event handler for when the control gets focus
this.GotFocus += (object sender, EventArgs e) =>
{
this.Text = String.Empty;
this.ForeColor = DefaultColor;
};
// add event handling when focus is lost
this.LostFocus += (Object sender, EventArgs e) => {
if (String.IsNullOrEmpty(this.Text) || this.Text == PlaceHolderText)
{
this.ForeColor = System.Drawing.Color.Gray;
this.Text = PlaceHolderText;
}
else
{
this.ForeColor = DefaultColor;
}
};
if (!string.IsNullOrEmpty(placeholdertext))
{
// change style
this.ForeColor = System.Drawing.Color.Gray;
// Add text
PlaceHolderText = placeholdertext;
this.Text = placeholdertext;
}
}
}
Copy/paste to new cs file entitled PHTextBox.cs.
Go to your graphic designer and add a TextBox.
Go to the designer and change the instiantion line for the textbox as follow:
Now compile but before you do, just make sure the textbox is not the first element to get the focus. Add button for that matter.
Have you tried overlapping a label on the textbox?
On the textbox keypress event you can check the length of the textbox.text and set the label.
On the keypress event..
MyLabel.Visible = String.IsNullOrEmpty(MyTextBox.Text);
Of course you might want to set the default text of the label as well as grey it out too.
Issue with this is if your form is re sizable.
What you want to achieve is not native to windows forms.
I know that this is an old question; However I was searching for a way and I found my answer to be best answer to display a prompt text in a TextBox:
1) Create a class .cs file called for example MyExtensions.cs having a namespace called for example 'Extensions'.
2) Create a method in the TextBox called Init(string prompt) that takes the prompt text you want to display inside the TextBox.
3) Let me stop talking and give you the rest of the code for MyExtensions.cs (The entire code):
MyExtensions.cs
using System.Drawing;
using System.Windows.Forms;
namespace Extensions
{
public static class MyExtensions
{
public static void Init(this TextBox textBox, string prompt)
{
textBox.Text = prompt;
bool wma = true;
textBox.ForeColor = Color.Gray;
textBox.GotFocus += (source, ex) =>
{
if (((TextBox)source).ForeColor == Color.Black)
return;
if (wma)
{
wma = false;
textBox.Text = "";
textBox.ForeColor = Color.Black;
}
};
textBox.LostFocus += (source, ex) =>
{
TextBox t = ((TextBox)source);
if (t.Text.Length == 0)
{
t.Text = prompt;
t.ForeColor = Color.Gray;
return;
}
if (!wma && string.IsNullOrEmpty(textBox.Text))
{
wma = true;
textBox.Text = prompt;
textBox.ForeColor = Color.Gray;
}
};
textBox.TextChanged += (source, ex) =>
{
if (((TextBox)source).Text.Length > 0)
{
textBox.ForeColor = Color.Black;
}
};
}
}
}
Now Assume that you have three TextBox's : tbUsername, tbPassword, tbConfirm:
In your Form_Load(object sender, EventArgs e) method initialize your three TextBox's to have their appropriate Prompt Text Messages:
using Extensions;
namespace MyApp{
public partial class Form1 : Form{
private void Form1_Load(object sender,
EventArgs e){
tbUsername.Init("Type a username");
tbPassword.Init("Type a password");
tbConfirm.Init("Confirm your password");
}
}
}
Enjoy! :)
What about this
private bool hasTextBeenTyped;
private void Form1_Load(object sender, EventArgs e)
{
this.ActiveControl = label1;
textBox1.ForeColor = Color.LightGray;
}
private void textBox1_TextChanged(object sender, EventArgs e)
{
hasTextBeenTyped = !String.IsNullOrEmpty(textBox1.Text);
if (hasTextBeenTyped)
{
textBox1.ForeColor = Color.Black;
}
}
private void textBox1_Click(object sender, EventArgs e)
{
if (!hasTextBeenTyped)
{
textBox1.Text = "";
}
}
private void textBox1_KeyPress(object sender, KeyPressEventArgs e)
{
hasTextBeenTyped = true;
}
the this.ActiveControl = label1; is just to take focus away from the text box initially. If something else already does, don't worry about that line.
Please, take a look at my ControlHintManager class, ControlHintInfo type and ControlHintType enumeration. They are written in Vb.Net, but you can get the idea analyzing the source-code, or compile the library to use it in your C# project without any more effort.
My solution has even better behavior than the StackOverflows hint that you mentioned, taking into account that when the control leave focus, the hint-text should be restored if the string remains empty.
Usage is so friendlly:
ControlHintInfo hint1 =
new ControlHintInfo("I'm a hint text.", font (or nul), Color.Gray,
ControlHintType.Persistent);
ControlHintManager.SetHint(TextBox1, hint1);
To acchieve this by your own, one way is calling the Win32 SendMessage function with the EM_SETCUEBANNER message, however, that will produce a too basic hint with poor behavior, not recommendable,
so a proper way to acchieve this is by taking control of the edit-control text by yourself, handling the Control.HandleCreated, Control.Enter, Control.Leave, Control.MouseDown, Control.KeyDown and Control.Disposed events (as you can see in my linked source-code).
Just use a object to keep track of the control's state (forecolor, text, and optionally the font), then use properlly the event-handlers mentioned to set or restore the text and color.
This is a online C# translation of the most-important code of the linked urls if this help you understand it better:
private static void Control_HandleCreated(object sender, EventArgs e) {
InstanceControlHintFields();
Control ctrl = (Control)sender;
ControlHintInfo hintInfo = controlHintsB(ctrl);
SetProperties(ctrl, hintInfo);
}
private static void Control_Enter(object sender, EventArgs e) {
InstanceControlHintFields();
Control ctrl = (Control)sender;
string ctrlText = ctrl.Text;
ControlHintInfo ctrlDefaults = controlHintsDefaults(ctrl);
ControlHintInfo hintInfo = controlHintsB(ctrl);
switch (hintInfo.HintType) {
case ControlHintType.Normal:
if ((ctrlText.Equals(hintInfo.Text, StringComparison.OrdinalIgnoreCase))) {
RestoreProperties(ctrl, ctrlDefaults);
}
break;
}
}
private static void Control_Leave(object sender, EventArgs e) {
InstanceControlHintFields();
Control ctrl = (Control)sender;
string ctrlText = ctrl.Text;
ControlHintInfo ctrlDefaults = controlHintsDefaults(ctrl);
ControlHintInfo hintInfo = controlHintsB(ctrl);
switch (hintInfo.HintType) {
case ControlHintType.Normal:
if ((ctrlText.Equals(hintInfo.Text, StringComparison.OrdinalIgnoreCase))) {
RestoreProperties(ctrl, ctrlDefaults);
} else if (string.IsNullOrEmpty(ctrlText)) {
SetProperties(ctrl, hintInfo);
}
break;
case ControlHintType.Persistent:
if (string.IsNullOrEmpty(ctrlText)) {
SetProperties(ctrl, hintInfo);
}
break;
}
}
private static void Control_MouseDown(object sender, MouseEventArgs e) {
InstanceControlHintFields();
Control ctrl = (Control)sender;
string ctrlText = ctrl.Text;
ControlHintInfo hintInfo = controlHintsB(ctrl);
switch (hintInfo.HintType) {
case ControlHintType.Persistent:
if ((ctrlText.Equals(hintInfo.Text, StringComparison.OrdinalIgnoreCase))) {
// Get the 'Select' control's method (if exist).
MethodInfo method = sender.GetType.GetMethod("Select", BindingFlags.Public | BindingFlags.Instance | BindingFlags.InvokeMethod, null, {
typeof(int),
typeof(int)
}, null);
if ((method != null)) {
// Select the zero length.
method.Invoke(ctrl, new object[] {
0,
0
});
}
}
break;
}
}
private static void Control_KeyDown(object sender, KeyEventArgs e) {
Control ctrl = (Control)sender;
string ctrlText = ctrl.Text;
ControlHintInfo ctrlDefaults = controlHintsDefaults(ctrl);
ControlHintInfo hintInfo = controlHintsB(ctrl);
switch (hintInfo.HintType) {
case ControlHintType.Persistent:
if ((ctrlText.Equals(hintInfo.Text, StringComparison.OrdinalIgnoreCase))) {
RestoreProperties(ctrl, ctrlDefaults);
} else if (string.IsNullOrEmpty(ctrlText)) {
RestoreProperties(ctrl, ctrlDefaults, skipProperties: { "Text" });
}
break;
case ControlHintType.Normal:
if (string.IsNullOrEmpty(ctrlText)) {
RestoreProperties(ctrl, ctrlDefaults);
}
break;
}
}
private static void Control_Disposed(object sender, EventArgs e) {
RemoveHint((Control)sender);
}
PS: It is Reflection based to support more variety of controls.
Related
What i want to do is: I have a "+" button, that creates a new tab every click, and one textbox. I want to do every tab create a text array for the same textbox (1 textbox, having different values according to the selected tabcontrol tab.) i have a maximum value of 5 tabs that i set, how can i do that?
i searched alot on how to create it, but didnt found a specific one for what i need
code of tabcontrols
private void label1_Click(object sender, EventArgs e)
{
if(tabControl1.TabPages.Count != 5)
{
page++;
string title = "Script " + page.ToString();
TabPage tabipage = new TabPage(title);
tabControl1.TabPages.Add(tabipage);
}
else
{
MessageBox.Show("You cant add more tabs!");
}
}
also, doesnt need to be exactly an array, just need to save and restore values between tabs
This is a pretty open ended question, and you'll have to forgive me if I misunderstood. What I'm guessing is you have a tab control, and OUTSIDE the tab control you have a single textbox that should change based on which tab is selected? If so you could do something similar to the below:
class tabWithScript : TabPage
{
public tabWithScript(string title) : base(title)
{ }
public string myscript { get; set; }
}
private void button1_Click(object sender, EventArgs e)
{
if (tabControl1.TabPages.Count < 5)
{
string title = "Script " + (tabControl1.TabPages.Count +1 ).ToString();
tabWithScript tabipage = new tabWithScript(title);
tabipage.myscript = $"Oh man, this is my script {tabControl1.TabPages.Count +1}";
tabControl1.TabPages.Add(tabipage);
}
else
{
MessageBox.Show("You cant add more tabs!");
}
}
private void tabControl1_SelectedIndexChanged(object sender, EventArgs e)
{
try
{
tabWithScript myTab = (tabWithScript)tabControl1.SelectedTab;
textBox1.Text = myTab.myscript;
}
catch (Exception ex) { }
}
}
Instead of using the default TabPage object you can create your own class that inherits TabPage, then store whatever information you want in there. (You'd likely have to delete the default TabPages the tabcontrol creates).
Alternatively, you can just create a string array and index it appropriately e.g.
string[] myList = new string[5];
private void button1_Click(object sender, EventArgs e)
{
if (tabControl1.TabPages.Count < 5)
{
string title = "Script " + (tabControl1.TabPages.Count +1 ).ToString();
TabPage tabipage = new TabPage(title);
myList[tabControl1.TabPages.Count] = $"Oh man, this is my script {tabControl1.TabPages.Count +1}";
tabControl1.TabPages.Add(tabipage);
}
else
{
MessageBox.Show("You cant add more tabs!");
}
}
private void tabControl1_SelectedIndexChanged(object sender, EventArgs e)
{
textBox1.Text = myList[tabControl1.SelectedIndex];
}
`
An alternative of #Devon Page's solution is using the Tag property, existing in almost all controls, to store value.
private void label1_Click(object sender, EventArgs e)
{
if (tabControl1.TabPages.Count < 5)
{
page++;
string title = "Script " + page.ToString();
TabPage tabipage = new TabPage(title);
tabipage.Tag = title; //Store whatever you want.
tabControl1.TabPages.Add(tabipage);
}
else
{
MessageBox.Show("You cant add more tabs!");
}
}
private void tabControl1_SelectedIndexChanged(object sender, EventArgs e)
{
textBox1.Text = tabControl1.SelectedTab.Tag.ToString(); // Cast to restore it
}
When I click a check box on a Windows Form, it enables a text box and sets the cursor in it ready for input. Code is relatively simple:
private void chkLatte_CheckedChanged(object sender, EventArgs e)
{
if(chkLatte.Checked)
{
txtLatte.Enabled = true;
txtLatte.Focus();
}
else
{
txtLatte.Enabled = false;
txtLatte.Text = "0";
}
}
Now, here's the rub. I have lots of these check boxes so what I want is something like this:
public void setCheckBox(string chkName, string txtName)
{
if (chkName.Checked)
{
txtName.Enabled = true;
txtName.Focus();
}
else
{
txtName.Enabled = false;
txtName.Text = "0";
}
}
Now, I can just call the method and pass the appropriate parameters like this:
private void chkMocha_CheckedChanged(object sender, EventArgs e)
{
setCheckBox(chkMocha,txtMocha);
}
Of course, this won't work: .Checked .Enabled .Focus() etc only work with a check box object and I define chkName as a string
How should I re-write the procedure setCheckBox to overcome this problem?
And why don't you pass the object sender as it is?
I mean something like this:
public void setCheckBox(CheckBox chk, TextBox txt)
{
if (chk.Checked)
{
txt.Enabled = true;
txt.Focus();
}
else
{
txt.Enabled = false;
txt.Text = "0";
}
}
And casting of course:
In the designer you have something like:
private System.Windows.Forms.TextBox txtMocha;
And by this reason you will solve a lot problems.
private void chkMocha_CheckedChanged(object sender, EventArgs e)
{
setCheckBox((CheckBox)sender, txtMocha);
}
Also, I have to say, that the code you give doesn't work... You have supposed it.
If you want pass the parameters as strings, use this:
Get a Windows Forms control by name in C#
One way to solve this is to assign the same handler to all checkboxes even
checkbox1.Check += chk_CheckedChanged;
checkbox2.Check += chk_CheckedChanged;
private void chk_CheckedChanged(object sender, EventArgs e)
{
// do your logic here
}
I have an editable ComboBox that should contain a path. The user can select several default paths (or enter his own) from a dropdown list, such as %ProgramData%\\Microsoft\\Windows\\Start Menu\\Programs\\ (All Users). The items in the dropdown list contain a short explanation, like the (All Users) part in the former example. Upon selection of such an item, I want to remove this explanation, so that a valid path is displayed in the ComboBox.
I currently strip the explanation out of the string and try to change the text via setting the Text property of the ComboBox. But this doesn't work, the string is parsed correctly, but the displayed text won't update (it stays the same as in the dropdown list, with the explanation).
private void combobox_TextChanged(object sender, EventArgs e) {
//..
string destPath = combobox.GetItemText(combobox.SelectedItem);
destPath = destPath.Replace("(All Users)", "");
destPath.Trim();
combobox.Text = destPath;
//..
}
I found the solution in a similar question, by using BeginInvoke()
Using Nikolay's solution, my method now looks like this:
private void combobox_SelectedIndexChanged(object sender, EventArgs e) {
if (combobox.SelectedIndex != -1) {
//Workaround (see below)
var x = this.Handle;
this.BeginInvoke((MethodInvoker)delegate { combobox.Text = combobox.SelectedValue.ToString(); });
}
}
The workaround is required, since BeginInvoke requires the control to be loaded or shown, which isn't necessarily the case if the program just started. Got that from here.
I suggest you to create PathEntry class to store both Path and its Description.
public sealed class PathEntry
{
public string Path { get; private set; }
public string Description { get; private set; }
public PathEntry(string path)
: this(path, path)
{
}
public PathEntry(string path, string description)
{
this.Path = path;
this.Description = description;
}
}
Then create an instance of BindingList<PathEntry> to store all the known paths and descriptions. Later you can add user-defined paths to it.
private readonly BindingList<PathEntry> m_knownPaths =
new BindingList<PathEntry>();
And update your Form's constructor as follows:
public YourForm()
{
InitializeComponent();
m_knownPaths.Add(new PathEntry("%ProgramData%\\Microsoft\\Windows\\Start Menu\\Programs",
"(All Users)"));
// TODO: add other known paths here
combobox.ValueMember = "Path";
combobox.DisplayMember = "Description";
combobox.DataSource = m_knownPaths;
}
private void combobox_DropDown(object sender, EventArgs e)
{
combobox.DisplayMember = "Description";
}
private void combobox_DropDownClosed(object sender, EventArgs e)
{
combobox.DisplayMember = "Path";
}
You might want to learn more abount DataSource, DisplayMember and ValueMember from MSDN.
this is probably not the most elegant solution, but a simple one for beginners - like me - who don't want to use those InvokeMethods before they understand them a little better.
The boolean is required because when assign a new string(object) to the position in the Items array, the handler is fired again - recursively.
I figured this out just now, since i was working on the exact same problem.
Just sharing :)
bool preventDoubleChange = false;
private void comboBox1_SelectedIndexChanged(object sender, EventArgs e)
{
if (preventDoubleChange){
preventDoubleChange = false;
return;
}
preventDoubleChange = true;
switch (comboBox1.SelectedIndex)
{
case 0:
comboBox1.Items[0] = "ChangeToThisText";
break;
case 1:
comboBox1.Items[1] = "ChangeToThisText";
break;
default:
preventDoubleChange = false;
break;
}
}
...or if you are comfortable using the "Tag" field, you can avoid the whole mess with the boolean. This second variation is also cleaner in my opinion.
public Form1()
{
InitializeComponent();
comboBox1.Items.Add("Item One"); //Index 0
comboBox1.Items.Add("Item Two"); //Index 1
comboBox1.Items.Add("Item Three"); //Index 2
comboBox1.Items.Add("Item Four"); //Index 3
}
private void comboBox1_SelectedIndexChanged(object sender, EventArgs e)
{
if (comboBox1.Tag != null && (int)comboBox1.Tag == comboBox1.SelectedIndex)
return;
switch (comboBox1.SelectedIndex)
{
case 0:
break;
case 1:
comboBox1.Tag = comboBox1.SelectedIndex;
comboBox1.Items[comboBox1.SelectedIndex] = "changed item 2";
break;
case 2:
comboBox1.Tag = comboBox1.SelectedIndex;
comboBox1.Items[comboBox1.SelectedIndex] = "changed item 3";
break;
case 3:
break;
default:
break;
}
}
redfalcon,
You can't change the text this way. What you need to do is get the selectedindex value and then set the Text property. Something like that:
protected void DropDownList1_SelectedIndexChanged(object sender, EventArgs e)
{
var index = DropDownList1.SelectedIndex;
DropDownList1.Items[index].Text = "changed";
}
`
private void combobox_TextChanged(object sender, EventArgs e)
{
//..
string destPath = combobox.SelectedItem.Text;
destPath = destPath.Replace("(All Users)", "");
destPath.Trim();
combobox.SelectedItem.Text = destPath;
//..
}
`
How to create a Textbox which displays "search" in grey color when it is empty and standard behavior when user starts typing text into it?
Do it via TextBox Events Enter and Leave and Attributes:
private void textBox1_Leave(object sender, EventArgs e)
{
if(textBox1.Text.Trim().Length == 0)
{
textBox1.Text = "Search";
textBox1.ForeColor = Color.LightGray;
}
}
private void textBox1_Enter(object sender, EventArgs e)
{
textBox1.Text = string.Empty;
}
See this thread at MSDN for a possible solution: http://social.msdn.microsoft.com/Forums/en/csharpgeneral/thread/93a67793-6426-4d4f-be9d-a9b79725efc8
I am working in C# windows forms application in which I am adding 3 different controls having same name (a button, a textBox & a Label) to my form.
Why there is error in button4_Click?
CODE:
private void button1_Click(object sender, EventArgs e)
{
TextBox myControl = new TextBox();
myControl.Name = "myControl";
this.Controls.Add(myControl);
}
private void button2_Click(object sender, EventArgs e)
{
Button myControl = new Button();
myControl.Name = "myControl";
this.Controls.Add(myControl);
}
private void button3_Click(object sender, EventArgs e)
{
Label myControl = new Label();
myControl.Name = "myControl";
this.Controls.Add(myControl);
}
private void button4_Click(object sender, EventArgs e)
{
((ComboBox)this.Controls["myControl"]).Text = "myCombo"; // works
((TextBox)this.Controls["myControl"]).Text = "myText"; // error
((Label)this.Controls["myControl"]).Text = "myLabel"; // error
}
The Controls[string] indexer returns the first control whose name matches the string. It will be hit and miss with your code but you probably have a ComboBox already added to the form with that same name. The next statements go kaboom because you cannot cast a ComboBox to a TextBox.
Of course, do try to do the sane thing, give these controls different names.
this.Controls["myControl"] returns the first control named myControl.
This is a TextBox, not a Label.
Instead of accessing them through the Controls collection, you should store your controls in fields in the form class (perhaps using List<T>s).
Here is one idea that might help you:
void SetControlText(Type controlType, string controlName, string text) {
foreach (var ctl in this.Controls.OfType<Control>()) {
if (ctl.GetType() == controlType && ctl.Name == controlName) {
ctl.Text = text;
break;
}
}
}
Or with LINQ only:
var item = this.Controls.OfType<Control>().Where(j => j.GetType() == controlType && j.Name == controlName).FirstOrDefault();
if (item != null)
item.Text = text;
Simply call the above function like so:
SetControlText(typeof(Button), "myButton", "Text was set!");
This function will iterate through all of the controls on the form, and when it finds the control type you specify with the name you specify, it will update the controls .Text field.