C# WinForms Radio Buttons - c#

I loaded 4 radios buttons and when I run the program and click on them, if i click one, and click another the other ones goes away. What if I wanted to ahve two radio buttons but they did different things?

Group the different choice sets in separate group boxes (or panels, or other container controls, but group boxes are probably what you're after).
MSDN:
Windows Forms RadioButton controls are designed to give users a choice among two or more settings, of which only one can be assigned to a procedure or object. For example, a group of RadioButton controls may display a choice of package carriers for an order, but only one of the carriers will be used. Therefore only one RadioButton at a time can be selected, even if it is a part of a functional group.
You group radio buttons by drawing them inside a container such as a Panel control, a GroupBox control, or a form.
This assumes you have four or more radio buttons on your form. If you have only two, say, and you want to allow the user the possibility of selecting both, use a set of checkboxes.

You should use GroupBox control.
All radio buttons in a GroupBox are mutually exclusive - put 2 radio buttons in one GroupBox, and the other 2 in another GroupBox.
If you don't like your container to be visible - use Panel control instead of GroupBox.
(Just drag and drop)
As you can probably guess, the upper one is a GroupBox, the lower one (which is invisble, but allows only one readioButton within it to be selected) is a panel.
HTH.

I like the concept of grouping RadioButtons in WPF. There is a property GroupName that specifies which RadioButton controls are mutually exclusive (http://msdn.microsoft.com/de-de/library/system.windows.controls.radiobutton.aspx).
So I wrote a derived class for WinForms that supports this feature:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Diagnostics;
using System.Windows.Forms.VisualStyles;
using System.Drawing;
using System.ComponentModel;
namespace Use.your.own
{
public class AdvancedRadioButton : CheckBox
{
public enum Level { Parent, Form };
[Category("AdvancedRadioButton"),
Description("Gets or sets the level that specifies which RadioButton controls are affected."),
DefaultValue(Level.Parent)]
public Level GroupNameLevel { get; set; }
[Category("AdvancedRadioButton"),
Description("Gets or sets the name that specifies which RadioButton controls are mutually exclusive.")]
public string GroupName { get; set; }
protected override void OnCheckedChanged(EventArgs e)
{
base.OnCheckedChanged(e);
if (Checked)
{
var arbControls = (dynamic)null;
switch (GroupNameLevel)
{
case Level.Parent:
if (this.Parent != null)
arbControls = GetAll(this.Parent, typeof(AdvancedRadioButton));
break;
case Level.Form:
Form form = this.FindForm();
if (form != null)
arbControls = GetAll(this.FindForm(), typeof(AdvancedRadioButton));
break;
}
if (arbControls != null)
foreach (Control control in arbControls)
if (control != this &&
(control as AdvancedRadioButton).GroupName == this.GroupName)
(control as AdvancedRadioButton).Checked = false;
}
}
protected override void OnClick(EventArgs e)
{
if (!Checked)
base.OnClick(e);
}
protected override void OnPaint(PaintEventArgs pevent)
{
CheckBoxRenderer.DrawParentBackground(pevent.Graphics, pevent.ClipRectangle, this);
RadioButtonState radioButtonState;
if (Checked)
{
radioButtonState = RadioButtonState.CheckedNormal;
if (Focused)
radioButtonState = RadioButtonState.CheckedHot;
if (!Enabled)
radioButtonState = RadioButtonState.CheckedDisabled;
}
else
{
radioButtonState = RadioButtonState.UncheckedNormal;
if (Focused)
radioButtonState = RadioButtonState.UncheckedHot;
if (!Enabled)
radioButtonState = RadioButtonState.UncheckedDisabled;
}
Size glyphSize = RadioButtonRenderer.GetGlyphSize(pevent.Graphics, radioButtonState);
Rectangle rect = pevent.ClipRectangle;
rect.Width -= glyphSize.Width;
rect.Location = new Point(rect.Left + glyphSize.Width, rect.Top);
RadioButtonRenderer.DrawRadioButton(pevent.Graphics, new System.Drawing.Point(0, rect.Height / 2 - glyphSize.Height / 2), rect, this.Text, this.Font, this.Focused, radioButtonState);
}
private IEnumerable<Control> GetAll(Control control, Type type)
{
var controls = control.Controls.Cast<Control>();
return controls.SelectMany(ctrl => GetAll(ctrl, type))
.Concat(controls)
.Where(c => c.GetType() == type);
}
}
}

Try placing similar options in a container, like GroupBox

Typically a group of radio buttons is used when only one option applies. If it is valid to select multiple options simultaneously, use Checkboxes instead.

Put all radio buttons into a group box.
Now, on all radio button Properties, set Auto Check to false.
Alternatively, you can set
radioButton1->AutoCheck = false;
Inside the radioButton1_Click function, handle as you needed. Example:
Void radioButton1_Click(Object^ sender, EventArgs^ e) {
radioButton1->Enabled = true;
radioButton2->Enabled = true;
radioButton3->Enabled = true;
radioButton1->Checked = true;
radioButton2->Checked = true;
radioButton3->Checked = true;
}
This should make those selected radio buttons to check.

Related

Hide ListBox Scrollbar & Synchronize Scrolling

I'm using 3 ListBox Controls and I want to remove their Scrollbars, so they're appearance may looking cleaner.
I have it so when I select an item in any, it selects the same in the rest. Only problem is that I have no idea how to make them scroll together.
E.g., if I scrolled down in the first Listbox, the position of the other two should match the position of the the first one.
I'd also like to know how to remove the Scrollbar, since there is no property for this.
Here's an example of a ListBox stripped of its Vertical ScrollBar that can handle Mouse Wheel messages and scroll itself.
The Vertical ScrollBar is removed by default, unless the ScrollAlwaysVisible property is set to true or the custom public VerticalScrollBar property is set to true.
The LisBox is scrolled setting its TopIndex property. There's a Min/Max check in WndProc where WM_MOUSEWHEEL is handled that ensures that the list is not scrolled beyond its limits.
It's kind of a redundant check, but may come in handy if you needs to be perform a more complex calculation to determine the current offset.
When the ListBox is scrolled, it raises the custom public Scroll event. You could create a custom EventArgs class to pass specific values to the Event Handler, if required. Here, I'm just synchronizing all ListBox Controls using the TopIndex property.
Note that the Mouse Wheel scrolls the ListBox by 1 Item, while also pressing SHIFT sets the scroll to 3 Items. Modify this behavior as required.
using System.ComponentModel;
using System.Windows.Forms;
[DesignerCategory("code")]
public class ListBoxEx : ListBox
{
public event EventHandler<EventArgs> Scroll;
private const int WS_VSCROLL = 0x200000;
private const int WM_MOUSEWHEEL = 0x020A;
private const int MK_SHIFT = 0x0004;
private bool m_VerticalScrollBar = false;
public ListBoxEx() { }
protected override CreateParams CreateParams {
get {
CreateParams cp = base.CreateParams;
if (!ScrollAlwaysVisible && !m_VerticalScrollBar) {
cp.Style &=~WS_VSCROLL;
}
return cp;
}
}
[DefaultValue(false)]
public bool VerticalScrollBar {
get => m_VerticalScrollBar;
set {
if (value != m_VerticalScrollBar) {
m_VerticalScrollBar = value;
RecreateHandle();
}
}
}
protected override void WndProc(ref Message m)
{
base.WndProc(ref m);
switch (m.Msg) {
case WM_MOUSEWHEEL:
var wparm = m.WParam.ToInt64();
int button = (short)wparm;
int delta = (int)(wparm >> 16);
int direction = Math.Sign(delta);
int steps = button == MK_SHIFT ? 3 : 1;
TopIndex = Math.Max(Math.Min(Items.Count - 1, TopIndex - (steps * direction)), 0);
Scroll?.Invoke(this, EventArgs.Empty);
m.Result = IntPtr.Zero;
break;
}
}
}
Add three instances of this Custom Control to a Form and subscribe to the SelectedIndexChanged event of the first one (for example). E.g.,
private void listBoxEx1_SelectedIndexChanged(object sender, EventArgs e)
{
var lb = (sender as ListBox);
if (listBoxEx2.Items.Count > lb.SelectedIndex) {
listBoxEx2.SelectedIndex = lb.SelectedIndex;
}
if (listBoxEx3.Items.Count > lb.SelectedIndex) {
listBoxEx3.SelectedIndex = lb.SelectedIndex;
}
}
Now, if you want to sync-scroll the three Controls, subscribe to the custom Scroll event of the first:
private void listBoxEx1_Scroll(object sender, EventArgs e)
{
var lb = sender as ListBox;
listBoxEx2.TopIndex = lb.TopIndex;
listBoxEx3.TopIndex = lb.TopIndex;
}
This is how it works:
The code sample also handles ListBox Controls with different number of Items
Edit:
How to use:
ListBoxEx is a Custom Control class.
To create this Control:
Add a new Class file to the Project, name it ListBoxEx.
Overwrite the class definition in that file with the class content you find here.
Add on top the 2 using directives you find in this code.
Build the Project.
Now, in the ToolBox, you can find the new ListBoxEx Control.
To replicate what is shown here:
Drop 3 instances of it onto a Form.
In the designer, select the first object (listBoxEx1).
In the PropertyGrid, switch to the events (⚡) view. Find the Scroll and SelectedIndexChanged events and double-click each. It will create the event handlers for you.
Copy the content of the event handler you find here inside the new event handlers just created.
Or, subscribe to the events in code:
Copy the listBoxEx1_Scroll and listBoxEx1_SelectedIndexChanged handlers you find here (including their content) and paste them inside the Form that contains the ListBoxEx Controls.
Add this to the Form Constructor, after InitializeComponent(), to subscribe to the Scroll and SelectedIndexChanged events of listBoxEx1:
listBoxEx1.Scroll += this.listBoxEx1_Scroll;
listBoxEx1.SelectedIndexChanged += this.listBoxEx1_SelectedIndexChanged;

List<Button> as properties. Not adding buttons to panel

I am trying to create a custom panel where I can change the properties using Properties -> Buttons -> [(Collection) ...]:
The following is what I have tried
public class CustomPanel : Panel
{
private List<Button> buttons = new List<Button>();
public List<Button> Buttons
{
get { return buttons; }
set
{
buttons = value;
this.Controls.Clear();
foreach (var button in buttons)
{
button.Size = new Size(200, 30);
this.Controls.Add(button);
}
}
}
}
But when I add a new button using Properties -> Buttons -> [(Collection) ...]: it is not adding it to my panel.
Assuming a CustomPanel class has been added as a custom control, and its base class changed to Panel, you can try the following code.
using System.ComponentModel;
using System.ComponentModel.Design;
using System.Drawing;
using System.Drawing.Design;
using System.Linq;
using System.Windows.Forms;
namespace TEST
{
public partial class CustomPanel : Panel
{
public CustomPanel()
{
InitializeComponent();
}
[Editor(typeof(ArrayEditor), typeof(UITypeEditor))]
public Button[] Buttons
{
get { return this.Controls.OfType<Button>().ToArray(); }
set
{
SuspendLayout();
try
{
this.Controls.Clear();
foreach (var button in value)
{
button.Size = new Size(200, 30);
this.Controls.Add(button);
}
}
finally { ResumeLayout(); }
}
}
}
}
Make sure the project has a reference to System.Design.dll, for ArrayEditor to be recognized.
Keep in mind that:
Every Button control added by the designer through the Buttons property will be serialized like it was added in the usual design way.
Adding other (non-Button) controls is still possibile in the usual design way, or programmatically.
this.Controls.Clear will remove any control, even the ones added without using Buttons; you can delete this instruction if you want to preserve them, but you will need a way to recognize the previously existing buttons and replace or keep them as needed.
All properties of any Button object can be edited inside the ArrayEditor form.
You need to handle positioning of the Button controls (both newly added and existing ones) in order to avoid overlapping.
I suggest to use a child FlowLayoutPanel for automatic positioning of the buttons and to prevent anyone to mess directly with the collection of buttons. With this approach, this.Controls will become flowLayoutPanel1.Controls (or similar).
Otherwise, you should override OnControlAdded and OnControlRemoved to be notified when any control is added or removed.

Allow a ListBox to overlap a TableLayoutPanel (C# .NET)

I have a form that contains a TableLayoutPanel with various controls and labels in it. One of them is a custom control that inherits from ComboBox that has extra auto-complete behavior (auto-completes on any text rather than just left to right). I didn't write the code for this control, so I'm not super familiar with how it works, but essentially upon clicking on the Combobox, it adds a ListBox below the ComboBox, within the same Panel of the TableLayoutPanel, that covers the normal drop down.
Unfortunately, the TableLayoutPanel prevents the ListBox from being fully visible when added, and only one item is shown. The goal is to get it to look like a normal ComboBox which would drop down to cover any controls below it.
Is there any way to allow a control that is in a TableLayoutPanel to overlap the TableLayoutPanel to get this to work as I want? I want to avoid any controls moving around due to the TableLayoutPanel growing to accommodate the ListBox.
Relevant code from the control:
void InitListControl()
{
if (listBoxChild == null)
{
// Find parent - or keep going up until you find the parent form
ComboParentForm = this.Parent;
if (ComboParentForm != null)
{
// Setup a messaage filter so we can listen to the keyboard
if (!MsgFilterActive)
{
Application.AddMessageFilter(this);
MsgFilterActive = true;
}
listBoxChild = listBoxChild = new ListBox();
listBoxChild.Visible = false;
listBoxChild.Click += listBox1_Click;
ComboParentForm.Controls.Add(listBoxChild);
ComboParentForm.Controls.SetChildIndex(listBoxChild, 0); // Put it at the front
}
}
}
void ComboListMatcher_TextChanged(object sender, EventArgs e)
{
if (IgnoreTextChange > 0)
{
IgnoreTextChange = 0;
return;
}
InitListControl();
if (listBoxChild == null)
return;
string SearchText = this.Text;
listBoxChild.Items.Clear();
// Don't show the list when nothing has been typed
if (!string.IsNullOrEmpty(SearchText))
{
foreach (string Item in this.Items)
{
if (Item != null && Item.ToLower().Contains(SearchText.ToLower()))
{
listBoxChild.Items.Add(Item);
listBoxChild.SelectedIndex = 0;
}
}
}
if (listBoxChild.Items.Count > 0)
{
Point PutItHere = new Point(this.Left, this.Bottom);
Control TheControlToMove = this;
PutItHere = this.Parent.PointToScreen(PutItHere);
TheControlToMove = listBoxChild;
PutItHere = ComboParentForm.PointToClient(PutItHere);
TheControlToMove.Anchor = ((System.Windows.Forms.AnchorStyles)
((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right)));
TheControlToMove.BringToFront();
TheControlToMove.Show();
TheControlToMove.Left = PutItHere.X;
TheControlToMove.Top = PutItHere.Y;
TheControlToMove.Width = this.Width;
int TotalItemHeight = listBoxChild.ItemHeight * (listBoxChild.Items.Count + 1);
TheControlToMove.Height = Math.Min(ComboParentForm.ClientSize.Height - TheControlToMove.Top, TotalItemHeight);
}
else
HideTheList();
}
Images:
Desired behavior
Current behavior
Going on the suggestion from TaW, I came up with a tentative solution. This form isn't re-sizable but does auto-size so that it looks ok if the user changes their DPI in Windows.
To resolve this, I moved the control out of the TableLayoutPanel to an arbitrary position in the Parent of the TableLayoutPanel. On form loading, I summed the coordinates of the TableLayoutPanel and an empty panel in the cell that I wanted the control to be located on top of. This worked for my needs but it feels like a kludge.
The better solution is probably to use Control.PointToScreen and Control.PointToClient methods, however I wasn't able to get these methods to give me the correct coordinates.

How to use ctrl key + mouse click to select multiple controls?

Probably this question has already an answer here but I was not able to find it..
I have a tabControl with a flowlayoutpanel in each tab page where I can add controls at run time. I can rearrange them, move them across tab pages.. How can I select multiple controls to be able to move them around using ctrl key + mouse click?
This is my drag event so far:
private void control_DragDrop(object sender, DragEventArgs e)
{
Control target = new Control();
target.Parent = sender as Control;
if (target != null)
{
int targetIndex = FindCSTIndex(target.Parent);
if (targetIndex != -1)
{
string cst_ctrl = typeof(CustomControl).FullName;
if (e.Data.GetDataPresent(cst_ctrl))
{
Button source = new Button();
source.Parent = e.Data.GetData(cst_ctrl) as CustomControl;
if (targetIndex != -1)
fl_panel = (FlowLayoutPanel)tabControl1.SelectedTab.Controls[0];
if (source.Parent.Parent.Name == target.Parent.Parent.Parent.Name)
{
this.fl_panel.Controls.SetChildIndex(source.Parent, targetIndex);
}
else
{
target.Parent.Parent.Parent.Controls.Add(source.Parent);
this.fl_panel.Controls.SetChildIndex(source.Parent, targetIndex);
}
}
}
}
}
private int FindCSTIndex(Control cst_ctr)
{
fl_panel = (FlowLayoutPanel)tabControl1.SelectedTab.Controls[0];
for (int i = 0; i < this.fl_panel.Controls.Count; i++)
{
CustomControl target = this.fl_panel.Controls[i] as CustomControl;
if (cst_ctr.Parent == target)
return i;
}
return -1;
}
This is not an easy, nor a common task. But surely doable and depending on preconditions could become trivial without need to spend multi-man-year effort on it ^^.
You have many options:
controls support selection;
container control support children controls selection;
overlay.
Handling selection is pretty easy: have a dictionary (or a control property, possibly using Tag) to store if control is selected or not, show selection somehow, when control is Ctrl-clicked invert selection. You can even provide Shift-key selection.
As #Hans Passant commented, you can use overlay window (invisible window on top of everything) to draw selection reticle there as well as handle selection and dragging itself. Or it could be a custom control with property IsSelected, setting which will draw something (border?) to indicate selection.
Easiest option would be to create SelectionPanel control, which can host any other controls inside, has IsSelected indication and is draggable. When children is added subscribe to MouseUp/MouseDown events or you can only allow to drag if special area of SelectionPanel is clicked. To example, you could have option Enable dragging in your software, when set all SelectionPanels will display special area (header?) which you can drag or Ctrl-click.

AutoTab to Next TabPage in TabControl

C# .Net fw 3.5, in winform in TabControl,
when the user tab out of the last control on a TabPage, then focus should moves to the next page, and focuses the first control in that page, how can i do it?
this is necessary for me because, in a master entry form, there is some compulsory questions which is placed out side of tabcontrol, and some controls which is not necessary all in tabcontrol,
if user visiting each control sequentially then focus should automatically move to next pages, if user want to fill only neccessory infos, then he can submit by clicking save button.
is any suggestion about this.
your question is not accurate
"C# .Net fw 3.5, in winform in TabControl, when the user tab out of the last control on a TabPage, then focus should moves to the next page, and focuses the first control in that page?"
is this a statement or question. I didnt understand. And what is the goal you need ?
If you want the user consequently visit the controls inside the consequent tabs by pressing tab key you can do it by keypressed event in tab control. In the keypressed event you can change the tab programatically.
hope it helps.
The code should be something like this.
Generate keypress event for your tabcontrol and monitor the press of TAB key.
private void tabControl1_KeyPress(object sender, KeyPressEventArgs e)
{
if(e.ToString().Equals("TAB") // I dont know what tab key returns. But is hould be something like this
{
tabControl1.SelectedTab = tabControl1.TabPages[1] ;
// now tabpage 2 has the focus
// You can also focus any control you want in here as follows:
tabControl1.TabPages[1].Control["control key"].Focus();
}
}
hope its clear enough
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.ComponentModel;
namespace CSBSSWControls
{
// Class inhertis TabControl
public class bssTabControl : TabControl
{
private bool AutoTab_;
[DefaultValue(false)]
public bool AutoTab { get { return AutoTab_; } set { AutoTab_ = value; } }
protected override bool ProcessCmdKey(ref Message msg, Keys keyData)
{
//property which determines auto change tabpages
if (AutoTab)
{
switch (keyData)
{
case Keys.Tab | Keys.Shift:
{
return SetNextTab(false);
}
case Keys.Tab:
{
return SetNextTab(true);
}
}
}
return base.ProcessCmdKey(ref msg, keyData);
}
private bool SetNextTab(bool Forward)
{
// getting cuurent active control
ContainerControl CC = this.FindForm();
Control ActC = null;
while (CC != null)
{
ActC = CC.ActiveControl;
CC = ActC as ContainerControl;
}
//checking, current control should not be tabcontrol or tabpage
if (ActC != null && !(ActC is TabPage) && !(ActC is bssTabControl))
{
//getting current controls next control if it is tab page then current control is surely that last control on that tab page
//if shift+tab pressed then checked its previous control, if it is tab page then current control is first control on the current tab page.
TabPage NC = ActC.FindForm().GetNextControl(ActC, Forward) as TabPage;
if (NC != null)
if (this.TabPages.Contains(NC))
if (Forward)
{
//selecting next tab page
this.SelectedTab = NC;
return true;
}
else
{
if (this.TabPages.IndexOf(NC) > 0)
{
//selecting pervious tab page
this.SelectedIndex = this.TabPages.IndexOf(NC) - 1;
return true;
}
}
}
return false;
}
}
}

Categories

Resources