Select UserControl from FlowLayoutPanel - c#

I have set up UserControls in a FlowPanelLayout with the help in this question:
For Each DataTable Add UserControl to FlowLayoutPanel
I am now trying to implement a click event which allows me to put a border around the UserControl that has been selected. I have done this:
private void User_Load(object sender, EventArgs e)
{
flowlayoutpanelUsers.HorizontalScroll.Visible = false;
// Load and Sort Users DataTable
DataTable datatableUsers = UserMethods.GetUsers().Tables["Users"];
datatableUsers.DefaultView.Sort = "Name";
DataView dataviewUsers = datatableUsers.DefaultView;
// Loop Through Rows and Add UsersGrid to FlowLayoutPael
foreach (DataRowView datarowviewUsers in dataviewUsers)
{
var UsersGrid = new UsersGrid
{
Username = datarowviewUsers["Username"].ToString(),
User = datarowviewUsers["Name"].ToString(),
Admin = datarowviewUsers["Administrator"].ToString(),
};
flowlayoutpanelUsers.Controls.Add(UsersGrid);
UsersGrid.MouseClick += new MouseEventHandler(user_click);
}
}
private UsersGrid selectedUser;
void user_click(object sender, EventArgs e)
{
if (selectedUser != null)
selectedUser.BorderStyle = BorderStyle.None;
selectedUser = (UsersGrid)sender;
selectedUser.BorderStyle = BorderStyle.FixedSingle;
}
My issue is that it only works when I click in a white space in the UserControl but not when the user clicks on the two labels or image. How do I make it work for all child objects too?
Also, how can I then use the selected UserControl to do other things like open a form which shows all the details for that selected user?

I have a few suggestions for you. At the bottom of my response I included code that demonstrates my suggestions.
Suggestion 1: Fixing MouseClick in your UC
When you register the MouseClick event for a UserControl (UC) you're doing so for the UserControl itself, not for any controls that you place on the UserControl such as your Labels, etc. If you click one of these child controls the click won't be 'seen' by the underlying UC.
To fix this register the MouseClick event for all your child controls; you can even register the same MouseClick event handler you have for the UserControl itself.
Suggestion 2: Setting your UC's BorderStyle
I'd move your code for setting the UC's BorderStyle into the UC itself. Create public property IsSelected that's set to true when the UC is selected. In the property's setter update the UC's BorderStyle property depending on the value of the property.
Exposing an IsSelected property for your UC can be handy: you can query a group of these UCs to see which ones are selected rather than trying to track this status outside of the control like through a Form-level variable.
Edit in response to your comment:
Here's an example of how you might query the UCs in a FlowLayoutPanel to see if any are selected and if one is found how you might take some action. In this case the action is to call an EditUser method that takes as parameters values you get from properties in the selected UC:
var selectedUC = flowLayoutPanel.Controls.Cast<UserControl1>().FirstOrDefault(uc => uc.IsSelected);
if (selectedUC != null) {
// Use the properties of the UC found to be selected as parameters is method EditUser.
EditUser(selectedUC.Name, selectedUC.Username, selectedUC.Administrator);
}
Suggestion 3: Managing selection in a group of your UCs
If you want to unselect all UCs in a group except for the one that the user clicks (i.e. selects) you'll need to create an event in your UC that fires when a UC is clicked. The handler for this event explicitly sets IsSelected to false for all UCs in a set (such as in a container type control such as a Form, FlowLayoutPanel, etc.), the MouseClick handler in the UC that was clicked will then set the clicked UC's IsSelected to true.
It's worth considering creating another UserControl type that manages a group of your UCs. This new UserControl can encapsulate the code for the creation and state management of sets of your UC and would faciliate using your UCs in other projects as well as keeping the code of Forms hosting your UCs a bit cleaner.
I figured that rather than include a series of disjointed code snippets for each of my suggestions I'd include what I'm hoping is the minimum amount of code to allow you to reproduce what I'm talking about.
Create a new Visual Studio Winform project and use the following for class Form1:
public partial class Form1 : Form
{
public Form1() {
InitializeComponent();
flowLayoutPanel = new FlowLayoutPanel {
Dock = DockStyle.Fill,
};
this.Controls.Add(flowLayoutPanel);
// Add several sample UCs.
for (int i = 0; i < 10; i++) {
var uc = new UserControl1();
uc.WasClicked += UsersGrid_WasClicked;
flowLayoutPanel.Controls.Add(uc);
}
}
FlowLayoutPanel flowLayoutPanel;
// Event handler for when MouseClick is raised in a UserControl.
void UsersGrid_WasClicked(object sender, EventArgs e) {
// Set IsSelected for all UCs in the FlowLayoutPanel to false.
foreach (Control c in flowLayoutPanel.Controls) {
if (c is UserControl1) {
((UserControl1)c).IsSelected = false;
}
}
}
}
Next add a UserControl to the project. Keep the name UserControl1 and add a couple Labels and a PictureBox. Use this code for class UserControl1:
public partial class UserControl1 : UserControl
{
public UserControl1() {
InitializeComponent();
this.Load += UsersGrid_Load;
}
// Event fires when the MouseClick event fires for the UC or any of its child controls.
public event EventHandler<EventArgs> WasClicked;
private void UsersGrid_Load(object sender, EventArgs e) {
// Register the MouseClick event with the UC's surface.
this.MouseClick += Control_MouseClick;
// Register MouseClick with all child controls.
foreach (Control control in Controls) {
control.MouseClick += Control_MouseClick;
}
}
private void Control_MouseClick(object sender, MouseEventArgs e) {
var wasClicked = WasClicked;
if (wasClicked != null) {
WasClicked(this, EventArgs.Empty);
}
// Select this UC on click.
IsSelected = true;
}
private bool _isSelected;
public bool IsSelected {
get { return _isSelected; }
set {
_isSelected = value;
this.BorderStyle = IsSelected ? BorderStyle.Fixed3D : BorderStyle.None;
}
}
}

I know this is old, but I landed here looking for some pointers on how to do UC selection in a Container.
Jay's answer works well.
Just one update: The UsersGrid_Load method will only engage the top level controls, children of containers will not participate in the WasClicked event.
private void UsersGrid_Load(object sender, EventArgs e) {
// Register the MouseClick event with the UC's surface.
this.MouseClick += Control_MouseClick;
// Register MouseClick with all child controls.
RegisterMouseEvents(Controls);
}
Add recursive method RegisterMouseEvents to the UserControl
private void RegisterMouseEvents(ControlCollection controls)
{
foreach (Control control in controls)
{
// Subscribe the control to the
control.Click += Control_MouseClick;
if (control.HasChildren) RegisterMouseEvents(control.Controls);
}
}

You could possibly try subscribing to the GotFocus event on the UserControl.
Caveat:
Because this event uses bubbling routing, the element that receives focus might be a child element instead of the element where the event handler is actually attached. Check the Source in the event data to determine the actual element that gained focus.
UIElement.GotFocus Event
UPDATE
This question may be appropriate: Click event for .Net (Windows Forms) user control

Related

How to handle MouseEnter and MouseLeave events in WPF if Adorner shows directly on top of WPF control?

I have a ListView in a WPF application where I want to add a Grid with some Buttons to each ListViewItem by way of an custom Adorner. The adorner is currently being added when the MouseEnter event fires for the ListViewItem, e.g. when I move the mouse into the ListViewItem, and removed when the MouseLeave event happens on the ListViewItem. This causes a loop, however, of MouseEnter/MouseLeave events because when the Adorner is added it causes the Adorner to become the focus of the mouse and not the ListViewItem. In other words, as soon as the Adorner is added, it is immediately removed because of the MouseLeave event that is fired from the ListViewItem.
I have seen some suggestions about adding the "IsHitTestVisible = false" attribute to the Adorner, but I need to be able capture click events on the buttons in the Adorner.
Abbreviated Adorner Class
private readonly VisualCollection visualChildren;
private Grid adornerGrid;
public ListViewItemOptionsAdorner(UIElement adornedElement): base(adornedElement)
{
visualChildren = new VisualCollection(this);
adornerGrid = new Grid();
Button editButton = new Button();
Button deleteButton = new Button();
adornerGrid.Children.Add(editButton);
adornerGrid.Children.Add(deleteButton);
visualChildren.Add(adornerGrid);
MouseEnter += ListViewItemOptionsAdorner_MouseEnter;
MouseLeave += ListViewItemOptionsAdorner_MouseLeave;
}
private void ListViewItemOptionsAdorner_MouseEnter { }
private void ListViewItemOptionsAdorner_MouseLeave { }
Main window functions for ListView
private void List_MouseEnter(object sender, MouseEventArgs e)
{
ListViewItem listItem = sender as ListViewItem;
AdornerLayer myAdornerLayer = AdornerLayer.GetAdornerLayer(listItem);
myAdornerLayer.Add(new ListViewItemOptionsAdorner(listItem));
e.Handled = true;
}
private void List_MouseLeave(object sender, MouseEventArgs e)
{
ListViewItem listItem = sender as ListViewItem;
if (!listItem.IsMouseOver)
{
AdornerLayer myAdornerLayer = AdornerLayer.GetAdornerLayer(listItem);
Adorner[] adorners = myAdornerLayer.GetAdorners(listItem);
for (int i = 0; adorners.Length; i++)
myAdornerLayer.Remove(adorners[i]);
e.Hanlded = true;
}
}
What happens is just a continuous loop where the adorner flickers on and off but I would like to just have it appear when the mouse is over the ListViewItem and hidden when not on the ListViewItem.

TextBox AutoCompleteStringCollection Suggest

I have created a form in C# with a CustomSource for a textbox:
public partial class FormLookup : Form
{
AutoCompleteStringCollection source = new AutoCompleteStringCollection();
public FormLookup()
{
InitializeComponent();
source.Add("Test");
source.Add("TestItem");
source.Add("TestValue");
this.textBox1.AutoCompleteCustomSource = source;
}
private void textBox1_TextChanged(object sender, EventArgs e)
{
}
}
The result looks like this:
The purpose of what I am looking for is to select multiple values from the auto suggestion list. When the user selected the first value, a seperator like ';' should trigger the auto suggestion again.
It should look like this:
Maybe some code/idea in the _TextChanged method?
Is it possible in C# to highlight the selected value like in pic2?
Your ideas are welcome!
You need to create your custom control for this requirement.
I have created similar one. posting logic and code - hope it may help you in getting basic Idea.
You need to create two user controls.
Custom Control (container for second user control + text box which you show us in question)
Tag control (will contain label to show Text of tag and link label 'x' to remove it)
Together it will create a visualization of single unit custom control. where you can type (with you drop down suggestion) and once you press ',' , it will create a tag and store it within.
It will look like below,
Here is code for that.
Custom Control will have following cnntrols by default
private System.Windows.Forms.FlowLayoutPanel flayoutCustomControlContainer;
public System.Windows.Forms.TextBox textBox1; //Marking this public so it can be directly accessed by external code.
here flayoutCustomControlContainer is containing textBox1 by default.
textBox1 will have Key Up event, which will be like below.
private void textBox1_KeyUp(object sender, KeyEventArgs e)
{
if (e.KeyCode == Keys.Oemcomma) //we will perform this on every ',' press
{
flayoutCustomControlContainer.Controls.Remove(textBox1);
TagControl tag = new TagControl(); //creating new Tag control
tag.lblTagName.Text = textBox1.Text.TrimEnd(",".ToCharArray());
tag.Remvoed += tag_Remvoed; //subscribing "Removed" event of Tag
tag.Width = tag.lblTagName.Width + 50;
tag.Height = tag.lblTagName.Height + 5;
flayoutCustomControlContainer.Controls.Add(tag);
textBox1.Text = "";
flayoutCustomControlContainer.Controls.Add(textBox1);
textBox1.Focus();
}
}
void tag_Remvoed(object sender, EventArgs e)
{
this.flayoutCustomControlContainer.Controls.Remove((Control)sender);
textBox1.Focus();
}
Constructor of Custom Control, as you shown in question,
public CustomControl()
{
InitializeComponent();
source.Add("Test");
source.Add("TestItem");
source.Add("TestValue");
this.textBox1.AutoCompleteMode = System.Windows.Forms.AutoCompleteMode.SuggestAppend;
this.textBox1.AutoCompleteSource = System.Windows.Forms.AutoCompleteSource.CustomSource;
this.textBox1.AutoCompleteCustomSource = source;
}
Now, Tag Control.
private System.Windows.Forms.FlowLayoutPanel flowLayoutPanel1;
public System.Windows.Forms.Label lblTagName;
private System.Windows.Forms.LinkLabel llblRemove;
link label, llblRemove will have link lable click event which will raise an event, which Custom control is listing.
private void llblRemove_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
{
if (Remvoed != null)
Remvoed(this, EventArgs.Empty);
}
Now, use this Custom Control anywhere you want to use.
I have uploaded working example project on GitHub.
Find a Link
You need create your own component.
The structure can be be:
Panel (the main container with white background and border)
|-> FlowLayoutPanel (the container for already added tags); Dock = Left
| |-> TagControl (you custom control for tag label)
| |-> ... (more tags if required)
|-> TextBox (the one with autocompletion with no borders); Dock = Fill;
You can encapsulate to your own Control/UserControl and use that event from Toolbox in designer.

Are there multiple ways to group radio buttons in Windows Forms?

I have a few radio buttons in my form that should all be connected, but I want one of them in a separate container to group it specifically with some other related inputs. Here is the general look:
Is there a way to group the fourth radio button with the other 3 even though it is in its own group box?
Fact that radio buttons are mutually exclusive only when they are within same parent. Being said that I can think of two options.
Let them be in same parent, handle the Paint event of the parent and draw yourself manually giving the impression that they are different groups.
You have to manually manage the mutual exclusion :(
RadioButtons are no magic, they just set the Checked property of other radio's to false behind the scenes.
private List<RadioButton> radioButtons = new List<RadioButton>();
//Populate this with radios interested, then call HookUpEvents and that should work
private void HookUpEvents()
{
foreach(var radio in radioButtons)
{
radio.CheckedChanged -= PerformMutualExclusion;
radio.CheckedChanged += PerformMutualExclusion;
}
}
private void PerformMutualExclusion(object sender, EventArgs e)
{
Radio senderRadio = (RadioButton)sender;
if(!senderRadio.Checked)
{
return;
}
foreach(var radio in radioButtons)
{
if(radio == sender || !radio.Checked)
{
continue;
}
radio.Checked = false;
}
}
They're in different containers, so you're not able to. I think this is a dupe of this question: Radiobuttons as a group in different panels
This question talks about it:
VB.NET Group Radio Buttons across different Panels
You can handle the CheckedChanged event of your radio buttons to accomplish what you need:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
this.radioButton1.CheckedChanged += new System.EventHandler(this.HandleCheckedChanged);
this.radioButton2.CheckedChanged += new System.EventHandler(this.HandleCheckedChanged);
this.radioButton3.CheckedChanged += new System.EventHandler(this.HandleCheckedChanged);
}
private bool changing = false;
private void HandleCheckedChanged(object sender, EventArgs e)
{
if (!changing)
{
changing = true;
if (sender == this.radioButton1)
{
this.radioButton2.Checked = !this.radioButton1.Checked;
this.radioButton3.Checked = !this.radioButton1.Checked;
}
else if (sender == this.radioButton2)
{
this.radioButton1.Checked = !this.radioButton2.Checked;
this.radioButton3.Checked = !this.radioButton2.Checked;
}
else if (sender == this.radioButton3)
{
this.radioButton1.Checked = !this.radioButton3.Checked;
this.radioButton2.Checked = !this.radioButton3.Checked;
}
changing = false;
}
}
}
All the radio buttons can be in the same parent container. You just need the radio button in question to draw over the top of the group box.
You can put all the radio buttons in the same parent container (group box would go into the parent as well), but configure the location and the z-order of the radio button in question so it's located over the group box and draws on top of it. That will give you the behaviour you want.
To do this in the designer, to get the radio button on top of the group box (you can't drag/drop it or the designer will put it in the group box), first add the group box, then add the radio button to the parent contjust drop it in the parent container, select it, then move it into position using the arrow keys on your keyboard.
Make sure to right click on it and "bring to front" and/or send the group box to the back, so they draw in the correct order.
** a more hacky way is just to edit the designer generated code - delete the line of code that adds the radio button to the group box's control collection, add line of code to add it to the parent container's control collection instead, and handle location and z-oder with similar lines of code.

detect keypress event for all textboxes inside a nested TableLayoutPanel

I have a series of nested TableLayoutPanelcontrols which each of them contains lots of TextBox controls.
I think it is insane to make a keypress event for each of the textboxes, So what I am trying to do is to have a common event method and then apply the event for all textboxes on FormLoad event. What I want to do is to see if the user has pressed Enter key in any of those textboxes.
This is my common method (I hope nothing is wrong with it!):
private void ApplyFiltersOnEnterKey(object sender, KeyPressEventArgs e)
{
if (e.KeyChar == (char)13)
{
tsApplyFilters_Click(this, null);
}
}
And I have the following code in Load event of my form:
//Applying common event for all textboxes in filter options!
foreach (var control in tableCriterias.Controls)
{
var textBox = control as TextBox;
if (textBox != null)
textBox.KeyPress += new KeyPressEventHandler(this.ApplyFiltersOnEnterKey);
}
Well, maybe you can guess already, the codes above does not work! I can list the problems I can think of:
tableCriterias which is the parent TableLayoutPanel and all the other layout panels are inside it, is itself inside a series of Panel SplitContainer and....Do I need to point this in my loop?
Or do I recursively loop over each layoutpanel inside the main layoutpanel?
Or the whole idea is wrong?!!?
Thanks.
private void Recursive(TableLayoutPanel tableCriterias)
{
foreach (var control in tableCriterias.Controls)
{
var textBox = control as TextBox;
if (textBox != null)
textBox.KeyPress += new KeyPressEventHandler(this.ApplyFiltersOnEnterKey);
else if(control is TableLayoutPanel)
Recursive(control as TableLayoutPanel);
}
}
And call this method for parent TableLayoutPanel

How to make usercontrol to behave as radiobutton

i am developing tab button as user control, how can i make it to behave as radiobutton. "When the user selects one option button (also known as a radio button) within a group, the others clear automatically". thank you.
Say your custom Tab button Control is named MyTabButton,
Override and implement Equals, and
then in the Click event handler in your custom control class,
if (this.Checked)
foreach(Control myBut in Parent.Controls)
if (myBut is MyTabButton && !myBut.Equals(this))
myBut.Checked = false;
If you want the behavior of radio buttons, use radio buttons.
Use javascript to hide the radio buttons and create your tab buttons in place of the original radio buttons. Feed the click events from your tab button to the original radio button. You might also want to have a common event to deselect the other tab buttons.
Your buttons will also degrade nicely if javascript is disabled.
Since you have mentioned you are using winforms, instead of using Javascript, you can override the paint method a derived RadioButton class to paint your Radiobutton as a tab. Here is a basic example
public class ButtonRadioButton : RadioButton {
protected override void OnPaint(PaintEventArgs e) {
PushButtonState state;
if (this.Checked)
state = PushButtonState.Pressed;
else
state = PushButtonState.Normal;
ButtonRenderer.DrawButton(e.Graphics, e.ClipRectangle, state);
}
}
obviously you need a container for the button, and whenever a usercontrol is selected, fire the event to container, and container deselect the other usercontrol
Need more information as to if this is WindowsForms, WPF, ASP.NET etc.. but
If it is WPF I have written a post that explains my approach to solving this problem:
Grouping and Checkboxes in WPF
Edited to included the answer
You can achieve it by overriding OnClick or OnMouseClick event. I don't get what you mean by "clearing a button", so I just change the Backcolor of it. You can easily adapt it to your Property or other needs.
using System;
using System.Linq;
using System.Windows.Forms;
namespace StackOverflow
{
public partial class FormMain : Form
{
public FormMain()
{
InitializeComponent();
}
}
public partial class MyRadioButton : Button
{
//Override OnClick event. - THIS IS WHERE ALL THE WORK IS DONE
protected override void OnClick(EventArgs e)
{
do
{
/*
This is where you select current MyRadioButton.
I'm changing the BackColor for simplicity.
*/
this.BackColor = System.Drawing.Color.Green;
/*
If parent of current MyRadioButton is null,
then it doesn't belong in a group.
*/
if (this.Parent == null)
break;
/*
Else loop through all other MyRadioButton of the same group and clear them.
Include System.Linq for this part.
*/
foreach (MyRadioButton button in this.Parent.Controls.OfType<MyRadioButton>())
{
//If button equals to current MyRadioButton, continue to the next RadioButton
if (button == this)
continue;
//This is where you clear other MyRadioButton
button.BackColor = System.Drawing.Color.Red;
}
}
while (false);
//Continue with the regular OnClick event.
base.OnClick(e);
}
}
}

Categories

Resources