TabControl AddingTab event - c#

I have a TabControl in which I want to prevent adding existing TabPage (they are identified by a name) and instead set the SelectedTabPage to this precise tab.
I wish to know if there are an event that triggers right before a page is being added to the TabControl. If not, would using the event CollectionChanged of the TabPages (list) be a correct alternative ?

I believe the event you're looking for is the Control.ControlAdded event:
http://msdn.microsoft.com/en-us/library/system.windows.forms.control.controladded.aspx
If that also detects when things inside the tab pages themselves are added, you should be able to filter out everything but TabPage controls using the ControlEventArgs.Control property in your event handler.
To reject adding a control will be a little more complicated. Since this event seems to only be raised after the control gets added, you'll need to do something like this:
void onControlAdded(object sender, ControlEventArgs e) {
var tab = e as TabPage;
if (tab == null)
return;
this.myTabControlObject.TabPages.Remove(tab);
}
This should remove the tab, but it will likely slow the tab adding process considerably.

Try something like this, I am checking the TabControl page Collection for a page with the same name as the Page that is trying to be added, if it exists I am setting focus to the existing instance, otherwise adding the new page to the TabControl. See if something like this works for you.
private void button1_Click(object sender, EventArgs e)
{
TabPage tp = new TabPage();
tp.Name = tabPage1.Name;
var temp =tabControl1.Controls.Find(tp.Name,true);
if( temp.Length > 0)
{
tabControl1.SelectedTab = (TabPage) temp[0];
}
else
tabControl1.Controls.Add(tp);
}
Anything having to do with the ControlCollection will most likely be triggered after the control has been added.
From above link:
You can determine if a Control is a member of the collection by passing the control into the Contains method. To get the index value of the location of a Control in the collection, pass the control into the IndexOf method. The collection can be copied into an array by calling the CopyTo method.
If you want you could cleanup your code some by adding an ExtensionMethod to your TabControl Check for an existing page, set focus or add from there.
Example:
namespace ExtensionMethods
{
public static class MyExtensions
{
public static bool AddPage(this TabControl tc, TabPage tp)
{
var matchedPages = tc.Controls.Find(tp.Name, false);
if ( matchedPages.Length > 0)
{
tc.SelectedTab = (TabPage)matchedPages[0];
return true;
}
else
{
tc.TabPages.Add(tp);
tc.SelectedTab = tp;
return false;
}
}
}
}
Usage:
tabControl1.AddPage(tp);

Related

How do I clear a user control from a winform?

This is probably a basic question, but I can't find answers because the terms are generic.
I am building a WinForm aplication. Its purpose is to set up memory in a certain chip. I think the best way to organize the application is to have a user control for each chip type, derived from a generic parent class. Think of the children as "iphone," "android" and "blackberry," derived from a parent class "phone".
VS2017 Designer has a Panel where I want the control to be. On startup, I generate an object of the base class and add it to the panel. When I press a button, the old object is deleted and replaced with a new one. Each class has just one control, a label with distinctive text.
The problem is, after I press the button, I see both texts. The panel's Controls collection has just one element, but I see the text from both objects. I have tried Refresh, Update and Invalidate withe the same results.
What do I have to do to make the old text "go away" so the only thing I see is the latest object?
private ChipMemBase ChipMemControl = new ChipMemBase();
public Form1()
{
InitializeComponent();
//tbFeedback.Text = string.Format(fmtString, 0, 1, 2, 3, 4, 5);
cbChipName.SelectedIndex = 0;
tbVersion.Text = Version;
OriginalWindowColor = tbFeedback.BackColor;
ShowChipMemControl();
PrintToFeedback(Version);
}
private void ShowChipMemControl()
{
var ctl = pnlChipMem.GetChildAtPoint(new Point(5,5));
if (null != ctl)
{
if (ctl != ChipMemControl)
{
pnlChipMem.Controls.Remove(ctl);
ctl.Dispose();
pnlChipMem.Update();
Refresh();
}
}
if (null != ChipMemControl)
{
pnlChipMem.Controls.Add(ChipMemControl);
}
}
private void btnMakeChipMemory_Click(object sender, EventArgs e)
{
ChipMemControl = new ChipMemGen2();
ShowChipMemControl();
}
Screenshots before and after clicking Create
Your ShowChipMemControl gets the control at point 5,5 and checks if it's a ChipMemControl then removes it.
I'm guessing that the reason it's not getting removed is that the control at point 5,5 is not a ChipMemControl.
You can use:
pnlChipMem.Controls.Clear()
to remove all the controls
Or:
ChipMemControl cmc = pnlChipMem.Controls.OfType<ChipMemBase>().FirstOrDefault();
if (cmc != null)
{
pnlChipMem.Controls.Remove(cmc);
cmc.Dispose();
}
To only remove the first instance of ChipMemBase on your pnlChipMem panel.
Got it. The problem was from inheritance, not window behavior. Control lblDefault in the base class, carrying the inconvenient text, was still present in the child class. I had to make it Public in the base class and remove it in the child class constructor:
InitializeComponent();
Controls.Remove(lblDefault);
lblDefault.Dispose();
lblDefault = null;
The clue was this article and project:
dynamically-and-remove-a-user-control

WinForm TabControl tab page contents does not update until the new tab is shown

I have a tabcontrol which allows the creation of new tabs. Each new tab has a web browser control CEFSharp on it. When the new tab is created it is not shown the previously opened tab is shown; which is what we want.
However, the browser on the newly created tab is only added to the tab page, and only partially runs... it does not go to the loading state until the tab page is shown.
Here is the Tabpage creation code:
private void AddNewBrowser()
{
Log("Adding New Tab and Browser");
UiBrowser browser = new UiBrowser();
TabPage tp = new TabPage();
tp.Controls.Add(browser);
customTabControl1.TabPages.Add(tp);
}
The UiBrowser is a UserControl which contains the CEFSharp Browser Control plus some extra UI.
And here is the Startup code for the Browser itself.
private void UiBrowser_Load(object sender, EventArgs e)
{
Execute();
}
private void Execute()
{
webBrowser = new ChromiumWebBrowser("http://google.co.uk")
{
Dock = DockStyle.Fill,
Text = "Loading...",
Tag = Tag
};
webBrowser.TitleChanged += Browser_TitleChanged;
webBrowser.AddressChanged += Browser_AddressChanged;
webBrowser.ConsoleMessage += Browser_ConsoleMessage;
webBrowser.LoadingStateChanged += Browser_LoadingStateChanged;
webBrowser.StatusMessage += Browser_StatusMessage;
browserPanel.Controls.Add(webBrowser);
Application.DoEvents();
}
The code has been simplified for clarity and I have not found a solution on SO or elsewhere for this problem.
Question:
How do I get the browser control to load the webpage whilst remaining in the background? That is while the TabPage that the control is on is NOT shown to the user.
The Load event will only happen when the control becomes visible the first time:
Occurs before the control becomes visible for the first time.
so try moving your Execute method into the UserControl's constructor code.
There is no "official" way of doing that.
But if you really need it and don't afraid using internals, you may take a look at my answer to WinForms: Respond to BindingSource being applied.
The solution (or hack) is encapsulated in this little helper
public static class ControlUtils
{
static readonly Action<Control, bool> CreateControlFunc = (Action<Control, bool>)Delegate.CreateDelegate(typeof(Action<Control, bool>),
typeof(Control).GetMethod("CreateControl", BindingFlags.Instance | BindingFlags.NonPublic, null, new[] { typeof(bool) }, null));
public static void CreateControls(this Control target)
{
if (!target.Created)
CreateControlFunc(target, true);
else
for (int i = 0; i < target.Controls.Count; i++)
target.Controls[i].CreateControls();
}
}
At the end of your form load event, add the following
this.CreateControls();
or
customTabControl1.CreateControls();
and also here
private void AddNewBrowser()
{
Log("Adding New Tab and Browser");
UiBrowser browser = new UiBrowser();
TabPage tp = new TabPage();
tp.Controls.Add(browser);
customTabControl1.TabPages.Add(tp);
if (customTabControl1.Created)
tp.CreateControls();
}

Jumping to next tab

I have a tab control in my WPF application with multiple tabs. Each tab gives access to several buttons, text boxes, drop downs. Now before moving to the next tab valid entries in each of the controls in the tab is to be checked or jumping to the next tab should not be allowed. How can this be done?
I was able to use IsEnable property to do this. But I want it like, when I click on the next tab it should, without entering the next tab, display a warning that such and such entry in the present tab is not valid.
If you adhere to the Selected event you can do something like this:
// Keep a global variable for the previous index
int prevIndex = 0;
private void tabControl_Selected(object sender, TabControlEventArgs e)
{
TabControl tc = sender as TabControl;
if (tc != null)
{
bool letSwitchHappen = validateTabControls(tc.SelectedIndex);
if (!letSwitchHappen)
{
tc.SelectedIndex = prevIndex;
}
prevIndex = tc.SelectedIndex;
}
}
Where validateTabControls is something like:
private bool validateTabControls(int tabIndex)
{
bool validEntries = false;
// Some code here to set validEntries according to the control at tabIndex
return validEntries;
}
Take a look at this example from Josh Smith.
It shows explicitly how to do this, and Josh is well-known (and respected) in the WPF world.

How to update listview after Form.Show

I have a Form with a listview. After calling Form.Show I need to update my listview. However, after Form.Show is called regardless of my listview code, it comes up empty, no columns, no data. If I move the Form.Show till after my listview code, the listview shows correctly.
Here is my listview code :
private void InitializeListView()
{
_snapshotList.BeginUpdate();
_snapshotList.Items.Clear();
foreach (ISnapshot snapshot in _snapshots)
{
string comment = InstanceFactory<ProjectRecoveryService>.Instance.RetrieveCommentsforSnapshot(snapshot);
string[] sub = new string[] { snapshot.Name, snapshot.Version.ToString(), snapshot.CreatedDate.ToString(), comment };
ListViewItem item = new ListViewItem(sub);
item.Tag = snapshot;
this._snapshotList.Items.Add(item);
}
_snapshotList.EndUpdate();
this._snapshotList.Refresh();
}
A side note, I have another Form that is very similar but has a TreeView that someone else has extended which works as desired.
Any thoughts?
EDIT 1
This form needs be a single instance. After reading this post, my Form.Show code is structured like this :
public static RestoreSnapshotDialog GetInstance()
{
if (_dialog == null)
{
_dialog = new RestoreSnapshotDialog();
_dialog.Show(Control.FromHandle(System.Diagnostics.Process.GetCurrentProcess().MainWindowHandle));
}
else
{
_dialog.BringToFront();
}
return _dialog;
}
On a FormClosed event I set _dialog = null.
You have to handle Form.Shown event to update the listview.
The only solution I could find was to call the Form.Show() after my listview was fully populated. So I create my own Form.Show by overriding Form.Show.
public new void Show()
{
if (_showdialog)
{
_dialog.Show(Control.FromHandle(System.Diagnostics.Process.GetCurrentProcess().MainWindowHandle));
}
else
{
_dialog.BringToFront();
}
}
Calling this method after my listview solves my problem. However, all my other dialogs (not using listview) work as expected with the code from the original post. Thanks to Hans Passant for leading me to this solution.

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:

Categories