How do I stop items in a ContextMenuStrip from treating ampersands specially? - c#

I have a ContextMenuStrip which displays items which can be named by the user; the user is allowed to give the items names containing ampersands. When the ContextMenuStrip is shown, the items' treat the ampersands as escape sequences, and underline the next character.
I could double up all the ampersands before I set the items' Text members, but that member is used elsewhere in the code so if possible, I'd like to stop the ContextMenuStrip from treating ampersands specially. Is there a way to turn that behaviour off?

use && to display a single &
Edit: Sorry, I missed the second part of your question :(
You could always use string.Replace("&", "&&") on the text when you set it, but that seems messy.
Another alternative would be to inherit from ToolStripMenuItem and override the Set of the Text Property to replace & with &&. That would be a little better as it would keep the code in once place.

I don't think that there is any built in support for that (like the UseMnemonic property of the Button control), unfortunately. One way to do it is to make a brute-force method that will walk the control tree on the form and perform a replace on all ToolStripMenuItems found:
public TheForm()
{
InitializeComponent();
FixAmpersands(this.Controls);
}
private static void FixAmpersands(Control.ControlCollection controls)
{
foreach (Control control in controls)
{
if (control is ToolStrip)
{
FixAmpersands((control as ToolStrip).Items);
}
if (control.Controls.Count > 0)
{
FixAmpersands(control.Controls);
}
}
}
private static void FixAmpersands(ToolStripItemCollection toolStripItems)
{
foreach (ToolStripItem item in toolStripItems)
{
if (item is ToolStripMenuItem)
{
ToolStripMenuItem tsmi = (ToolStripMenuItem)item;
tsmi.Text = tsmi.Text.Replace("&", "&&");
if (tsmi.DropDownItems.Count > 0)
{
FixAmpersands(tsmi.DropDownItems);
}
}
}
}
This is, of course, useful primarily if the menu structure is not build up dynamically and occasionaly during the lifetime of the form. If new items are added every now and then you will probably need to run them through some method that will perform the ampersand-doubling at on a single item.

Related

C# LoadControl() (.ascx) and add into "this" rather than sub control

I'm good with Loading the control, using the LoadControl("~/vitrualPath"), so I have:
UserControl ctrl = (UserControl)LoadControl("~/controls/someControl.ascx");
this.Controls.Add(ctrl);
//plcCtrl.Controls.Add(ctrl);
The trouble is that I wish to then loop through all the controls in the usercontrol:
foreach (Label c in this.Controls.OfType<Label>())
{
// It's a label for an input
if (c.ID.Substring(0, 8) == "lblInput")
{
// Do some stuff with the control here
}
}
However, the added controls aren't part of this, but part of ctrl
Is there a way I can add the contents of the loaded control to this or a way to loop through both this and ctrl in one hit?
If you simply want to loop through both top-level labels and labels in ctrl, try this.Controls.Concat(ctrl.Controls).OfType<Label>() in your foreach loop.
You can also move your if into a LINQ Where call:
.Where(l => l.ID.Substring(0, 8) == "lblInput")
By using a recursive function you don't need to worry about controls within sub levels/ containers. Something like this should be OK (all you need to do is to pass the top level control along with the id substring that you are interested in). So if the conditions are met it will do whatever you have intended to do with the control and at any sub level.
public void ProcessControl(Control control, string ctrlName)
{
foreach (Label c in control.Controls.OfType<Label>())
{
// It's a label for an input
if (c.ID.Substring(0, 8) == ctrlName)
{
// Do some stuff with the control here
}
}
foreach (Control ctrl in control.Controls)
{
ProcessControl(ctrl, ctrlName);
}
}
You should write a recursive method that starts looping the controls in this.Controls and goes down the tree of controls. It will then also go in your user control and find your labels.
I don't think there is a way to loop through both like you want.
You can easily create a method that receives a Control as parameter and iterate though its controls. Something like this:
void Function(Control control)
{
foreach (Label c in control.Controls.OfType<Label>())
{
// It's a label for an input
if (c.ID.Substring(0, 8) == "lblInput")
{
// Do some stuff with the control here
}
}
}
You should be able to access the controls inside the user control by accessing the this.Controls[index].Controls I think, however it kind of depends what you are trying to achieve? Their might be a cleaner way of doing what you are trying to do?

Checking Multiple textbox if they're null or whitespace

I have a form where I have lots of textboxes and all of them are required to be filled out. In C# how do I actually if check there are group of fields having a null or whitespace?
I am familiar with string.isNullOrWhiteSpace(string here) but I don't want to do multiple if statements of that, it would result in a bad code.
I am trying to avoid something like this
if(string.isNullOrWhiteSpace(string here)
|| string.isNullOrWhiteSpace(string here)
|| string.isNullOrWhiteSpace(string here))
{
// do something
}
Are there fix for this type of bad code?
You can query the controls collection of the form (or relevant container) and filter for textboxes and further query to see if any are empty (none should really have null values). Example:
var emptyTextboxes = from tb in this.Controls.OfType<TextBox>()
where string.IsNullOrEmpty(tb.Text)
select tb;
if (emptyTextboxes.Any())
{
// one or more textboxes are empty
}
You can do effectively the same thing using the fluent syntax.
bool isIncomplete = this.Controls.OfType<TextBox>().Any(tb => string.IsNullOrEmpty(tb.Text));
if (isIncomplete)
{
// do your work
}
For this code, you should be working with at least Visual Studio 2008 / C# 3 / .NET 3.5. Your project needs to have a reference to System.Core.dll (should have one by default) and you need a using System.Linq; directive in the class file.
Based upon your comments, consider another method if you are having trouble understanding or working with the linq version. You can certainly do this in an explicit loop (the Linq code will ultimately be a loop as well). Consider
bool isIncomplete = false;
foreach (Control control in this.Controls)
{
if (control is TextBox)
{
TextBox tb = control as TextBox;
if (string.IsNullOrEmpty(tb.Text))
{
isIncomplete = true;
break;
}
}
}
if (isIncomplete)
{
}
Finally, this code is written as if all of the textboxes are in a single container. That container might be the form, a panel, etc. You will need to point to the appropriate container (eg., instead of this (the form) it might be this.SomePanel). If you are working with controls that are in multiple and perhaps nested containers, you will need to do more work to find them programmatically (recursive searching, explicit concatenation, etc.) or you might just preload the references into an array or other collection. For example
var textboxes = new [] { textbox1, textbox2, textbox3, /* etc */ };
// write query against textboxes instead of this.Controls
You said you have multiple GroupBox controls. If each GroupBox is loaded onto the form and not nested in another control, this may get you started.
var emptyTextboxes = from groupBox in this.Controls.OfType<GroupBox>()
from tb in groupBox.Controls.OfType<TextBox>()
where string.IsNullOrEmpty(tb.Text)
select tb;
That depends on what you consider "bad code." Depending on your requirements what text boxes are required to be filled out can vary. Further, even if all of the fields are required all of the time you still want to give friendly error messages letting people know which field they didn't fill out. There a variety of approaches to solving this issue depending on how you are rendering your form. Since you haven't specified any here's a very direct method for doing so.
var incompleteTextBoxes = this.Controls.OfType<TextBox>()
.Where(tb => string.IsNullOrWhiteSpace(tb.Text));
foreach (var textBox in inCompleteTextBoxes)
{
// give user feedback about which text boxes they have yet to fill out
}
Yet another solution.
This will recursively travel the whole control Tree , and Check for null or empty text in all of the textboxes.
caveat -
If you have some fancy controls not inheriting from the standard Winforms textbox - check will not be performed
bool check(Control root,List<Control> nonFilled)
{
bool result =true;
if (root is TextBox && string.isNullOrEmpty(((TextBox)root).Text) )
{
nonFilled.Add(root);
return false;
}
foreach(Control c in root.Controls)
{
result|=check(c,nonFilled)
}
return result;
}
Usage :
List<Control> emptytextboxes=new List<Control>()
bool isOK=check(form, emptytextboxes);

How to disable a checkbox in a checkedlistbox?

I have some items in a CheckedListBox, I want to disable the CheckBox of first item in it.
i.e. I want to disable the first item in the CheckedListBox, because I want to tell the user visually that option is not available.
Combining 2 of the above partial answers worked great for me.
Add your items to the list with:
myCheckedListBox.Items.Add(myItem, myState);
Where myState is CheckState.Indeterminate for items that should be disabled.
Then add an event handler to keep those items from being changed:
myCheckedListBox.ItemCheck += (s, e) => { if (e.CurrentValue == CheckState.Indeterminate) e.NewValue = CheckState.Indeterminate; };
This does not allow you to use 'Indeterminate' in this list for its normal purpose but it does give a look very similar to what one would expect for a disabled item and it provides the correct behavior!
Though this post is pretty old, the last added answer has been submitted in April this year,
and I hope this will help someone.
I was after something similar : a checked list box that behaves like
a lot of installers, which offer a list of options where some features are required and
thus are both checked and disabled.
Thanks to this post (Can I use a DrawItem event handler with a CheckedListBox?)
I managed to do that, subclassing a CheckedListBox control.
As the OP in the linked post states, in the CheckedListBox control the OnDrawItem event is never fired,
so subclassing is necessary.
It's very basic, but it works.
This is what it looks like (the CheckBox above is for comparison) :
NOTE: the disabled item is really disabled : clicking on it has no effects whatsoever (as far as I can tell).
And this is the code :
public class CheckedListBoxDisabledItems : CheckedListBox {
private List<string> _checkedAndDisabledItems = new List<string>();
private List<int> _checkedAndDisabledIndexes = new List<int>();
public void CheckAndDisable(string item) {
_checkedAndDisabledItems.Add(item);
this.Refresh();
}
public void CheckAndDisable(int index) {
_checkedAndDisabledIndexes.Add(index);
this.Refresh();
}
protected override void OnDrawItem(DrawItemEventArgs e) {
string s = Items[e.Index].ToString();
if (_checkedAndDisabledItems.Contains(s) || _checkedAndDisabledIndexes.Contains(e.Index)) {
System.Windows.Forms.VisualStyles.CheckBoxState state = System.Windows.Forms.VisualStyles.CheckBoxState.CheckedDisabled;
Size glyphSize = CheckBoxRenderer.GetGlyphSize(e.Graphics, state);
CheckBoxRenderer.DrawCheckBox(
e.Graphics,
new Point(e.Bounds.X + 1, e.Bounds.Y + 1), // add one pixel to align the check gliph properly
new Rectangle(
new Point(e.Bounds.X + glyphSize.Width + 3, e.Bounds.Y), // add three pixels to align text properly
new Size(e.Bounds.Width - glyphSize.Width, e.Bounds.Height)),
s,
this.Font,
TextFormatFlags.Left, // text is centered by default
false,
state);
}
else {
base.OnDrawItem(e);
}
}
public void ClearDisabledItems() {
_checkedAndDisabledIndexes.Clear();
_checkedAndDisabledItems.Clear();
this.Refresh();
}
}
Use it like this:
checkedListBox.Items.Add("Larry");
checkedListBox.Items.Add("Curly");
checkedListBox.Items.Add("Moe");
// these lines are equivalent
checkedListBox.CheckAndDisable("Larry");
checkedListBox.CheckAndDisable(0);
Hope this can help someone.
Disabling items isn't a great idea, the user will have no good feedback that click the check box won't have any effect. You cannot use custom drawing to make it obvious. Best thing to do is to simply omit the item.
You can however easily defeat the user with the ItemCheck event:
private void checkedListBox1_ItemCheck(object sender, ItemCheckEventArgs e) {
if (e.Index == 0) e.NewValue = e.CurrentValue;
}
To disable any particular item use following:
checkedListBox1.SetItemCheckState(0, CheckState.Indeterminate);
SetItemCheckState takes index of item and CheckState Enum
Indeterminate is used to show shaded appearance
I know it has been a while, but I found this in my search for a list box and thought I would add it to the discussion.
If you have a listbox and want to disable all of the checkboxes so they cannot be clicked, but not disable the control so the user can still scroll etc. you can do this:
listbox.SelectionMode = SelectionMode.None
The CheckedListBox will not work in this way. CheckedListBox.Items is a collection of strings so they cannot be "disabled" as such.
Here are some discussions about possible solutions that might help you: here and here.
This works for me:
checkedListBox1.SelectionMode = SelectionMode.None;
Which means no items can be selected
None: No items can be selected.
For more info, you can check it here: SelectionMode Enumeration.
The solution is to use the event ItemChecking:
_myCheckedListBox.ItemChecking += (s, e) => e.Cancel = true;
This will cancel all the checking on every item, but you can always do more refined solution but testing the current .SelectedItem
Here's how I did it in a helpdesk application I wrote:
First, I made it so the check box was greyed out as I added it to the list during form load:
private void frmMain_Load(object sender, EventArgs e)
{
List<string> grpList = new List<string>();
ADSI objADSI = new ADSI();
grpList = objADSI.fetchGroups();
foreach (string group in grpList)
{
if (group == "SpecificGroupName")
{
chkLst.Items.Add(group, CheckState.Indeterminate);
}
else
{
chkLst.Items.Add(group);
}
}
Then I used an event so that when clicked it ensures it stays clicked:
private void chkLst_SelectedIndexChanged(object sender, EventArgs e)
{
if (chkLst.SelectedItem.ToString() == "SpecificGroupName")
{
chkLst.SetItemCheckState(chkLst.SelectedIndex, CheckState.Indeterminate);
}
}
The idea here is that on my form it's set so that the box checks on item click/select. This way I could kill two birds with one stone. I could keep this event from causing problems when the item is first checked and added during form load. Plus making it check on select allows me to use this event instead of the item checked event. Ultimately the idea is to keep it from messing up during the load.
You'll also notice that it doesn't matter what the index number is, that variable is unknown because in my app it's grabbing a list of groups from AD that exist in a specific OU.
As to whether this is a good idea or not, that's dependent on the situation. I have another app where the item to disable is dependent on another setting. In this app I just want the helpdesk to see that this group is required so they don't go removing them from it.
Try Below Code:
Private Sub CheckedListBox1_MouseUp(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles CheckedListBox1.MouseUp
If (Condition) Then
Me.CheckedListBox1.SelectedIndex = -1
End If
End Sub
I think an alternative solution, is using Telerik components.
A RadListControl can give you that option:

Loop through controls in TabControl

I am looking for a way to loop through controls on a particular tab of a tabcontrol. For example, I have a tabcontrol with the following tabs:
Cars,
Pets,
Admin
On each of these tabs are several controls to display/edit/save data, etc. On the "Save" button, I would like to loop through the controls for that particular tab to check whether all required fields have been filled in.
So, if I am on the Cars tab and click "Save," I want to loop ONLY through the controls on the Cars tab and NOT the Pets or Admin tabs.
How can achieve this result?
As for looping through a TabControl's controls, you need to use the Controls property.
Here's an MSDN article on the TabControl.
Example:
TabPage page = aTabControl.SelectedTab;
var controls = page.Controls;
foreach (var control in controls)
{
//do stuff
}
I feel it's important to note that, in general, you should take a more structured approach to your application. E.g., instead of having all the controls on three tab pages, include exactly one UserControl on each tabpage. A CarUserControl, PetUserControl, and AdminUserControl e.g. Then each user control knows how to create the proper respective data structure so you don't have to manually munge it all together at the same level of abstraction using inter-tab loops and whatnot.
Such a separation of concerns will make it much easier to reason about your program and is good practice for writing maintainable code for your future career.
Example where I wanted to get the DataGridView in a particular tab for an application I wrote.
TabPage pg = tabControl1.SelectedTab;
// Get all the controls here
Control.ControlCollection col = pg.Controls;
// should have only one dgv
foreach (Control myControl in col)
{
if (myControl.ToString() == "System.Windows.Forms.DataGridView")
{
DataGridView tempdgv = (DataGridView)myControl;
tempdgv.SelectAll();
}
}
The Controls property is the way to go...
foreach(Control c in currentTab.Controls)
{
if(c is TextBox)
// check for text change
if(c is CheckBox)
//check for check change
etc...
}
TabControl has a SelectedTab property, so you'd do something like this:
foreach(Control c in tabControl.SelectedTab.Controls)
{
//do checks
}
foreach (Control c in this.tabControl1.SelectedTab.Controls)
{
// Do something
}
I had the need to disable or enable controls of a tab as well. I had to go a bit more generic though. Hope it helps people and I didn't make a mistake
private void toggleControls(Control control, bool state)
{
foreach (Control c in control.Controls)
{
c.Enabled = state;
if (c is Control)
{
toggleControls(c, state);
}
}
}

Tooltips for CheckedListBox items?

Is there a straighforward way to set additional text to appear in a tooltip when a user's mouse is held over an item in a CheckedListBox?
What I would expect to be able to do in code is:
uiChkLstTables.DisplayOnHoverMember = "DisplayOnHoverProperty"; //Property contains extended details
Can anyone point me in the right direction to do this? I've already found a couple of articles that involve detecting which item the mouse is currently over and creating a new tooltip instance, but this sounds a little too contrived to be the best way.
Thanks in advance.
Add a Tooltip object to your form and then add an event handler for the CheckedListBox.MouseHover that calls a method ShowToolTip();
Add MouseMove event of your CheckedListBox which has the following code:
//Make ttIndex a global integer variable to store index of item currently showing tooltip.
//Check if current location is different from item having tooltip, if so call method
if (ttIndex != checkedListBox1.IndexFromPoint(e.Location))
ShowToolTip();
Then create the ShowToolTip method:
private void ShowToolTip()
{
ttIndex = checkedListBox1.IndexFromPoint(checkedListBox1.PointToClient(MousePosition));
if (ttIndex > -1)
{
Point p = PointToClient(MousePosition);
toolTip1.ToolTipTitle = "Tooltip Title";
toolTip1.SetToolTip(checkedListBox1, checkedListBox1.Items[ttIndex].ToString());
}
}
Alternately, you could use a ListView with checkboxes instead. This control has
builtin support for tooltips.
Contrived or not; that's what there is...
I'm not aware of an easier way than you have already described (although I'd probably re-use a tooltip instance, rather than creating new all the time). If you have articles that show this, then use them - or use a 3rd party control that supports this natively (none leap to mind).
I would like to expand upon Fermin's answer in order to perhaps make his wonderful solution slightly more clear.
In the form that you're working in (likely in the .Designer.cs file), you need to add a MouseMove event handler to your CheckedListBox (Fermin originally suggested a MouseHover event handler, but this did not work for me).
this.checkedListBox.MouseMove += new System.Windows.Forms.MouseEventHandler(this.showCheckBoxToolTip);
Next, add two class attributes to your form, a ToolTip object and an integer to keep track of the last checkbox whose tool tip was shown
private ToolTip toolTip1;
private int toolTipIndex;
Finally, you need to implement the showCheckBoxToolTip() method. This method is very similar to Fermin's answer, except that I combined the event callback method with the ShowToolTip() method. Also, notice that one of the method parameters is a MouseEventArgs. This is because the MouseMove attribute requires a MouseEventHandler, which then supplies MouseEventArgs.
private void showCheckBoxToolTip(object sender, MouseEventArgs e)
{
if (toolTipIndex != this.checkedListBox.IndexFromPoint(e.Location))
{
toolTipIndex = checkedListBox.IndexFromPoint(checkedListBox.PointToClient(MousePosition));
if (toolTipIndex > -1)
{
toolTip1.SetToolTip(checkedListBox, checkedListBox.Items[toolTipIndex].ToString());
}
}
}
Run through your ListItems in your checkbox list of items and set the appropriate text as the item 'title' attribute, and it will display on hover...
foreach (ListItem item in checkBoxList.Items)
{
//Find your item here...maybe a switch statement or
//a bunch of if()'s
if(item.Value.ToString() == "item 1")
{
item.Attributes["title"] = "This tooltip will display when I hover over item 1 now, thats it!!!";
}
if(item.Value.ToString() == "item 2")
{
item.Attributes["title"] = "This tooltip will display when I hover over item 2 now, thats it!!!";
}
}

Categories