I know it seems like an odd request but I have this layout. With it I'm very much enjoying and taking advantages of the FlowLayoutPanel. I disable it to disable all child controls. But there are cases I have a link in there I want enabled all the time. Is this possible?
flowLayoutPanel1.Enabled = false; // disable panel and all child controls
linkLabel1.Enabled = true; // re-enable somehow the link that was disabled inherently
I'm only asking because there maybe a way to do this, but if it's officially a bad design case then I will rethink my solution.
Previously to get around this I overlayed the control I want to enable/disable independently of the parent control. I would line it up and position the control so it appeared to be with the group. I hated this. It makes design difficult as drag and drop just puts it back into the container and also totally defeats the purpose of using a FlowLayoutPanel.
Thanks in advance.
Using the suggestion from Anthony. I've managed the following code that solves my dilemma. Using LINQ! (I always forget about using LINQ) I adjusted a bit with the extension methods and ability to just hammer in controls through a parameter array of what I do not want disabled. Very flexible to say the least.
private void button1_Click(object sender, EventArgs e)
{
// "reset"
flowLayoutPanel1.EnableAll();
}
private void button2_Click(object sender, EventArgs e)
{
// disable but the provide controls
flowLayoutPanel1.DisableBut(checkBox1);
}
public static class Extensions
{
public static void EnableAll(this FlowLayoutPanel container)
{
foreach (Control control in container.Controls.OfType<Control>())
control.Enabled = true;
}
public static void DisableBut(this FlowLayoutPanel container, params Control[] but)
{
foreach (Control control in (container.Controls.OfType<Control>().Where(x => (!but.Contains(x)))))
{
control.Enabled = false;
}
}
}
With suggested tweaks for performance, extensibility, etc.
public static class Extensions
{
public static void EnabledBut(this Control container, Boolean enabled, params Control[] but)
{
foreach (Control control in (container.Controls.OfType<Control>().Except(but)))
control.Enabled = enabled;
}
}
Related
I have many custom controls on my main form that utilize an event to signify they have done processing. They all share this same event (~100 controls or so).
The main form consumes this event but I do not have a clue how to find an efficient way at getting to the one that raised the event without having really inefficient code.
My controls are contained within a List<T> called controlList and are hosted on their own project.
My event looks like so:
public void OnTaskComplete(object sender, custom_control_project.TaskCompleteEventArgs e)
{
foreach (var control in controlList)
{
if (control.Visible) // <--- THIS IS WRONG! WHAT COULD THIS BE???
{
try
{
...// LOTS OF PROCESSING!
}
catch
{
...
}
finally
{
...
}
}
}
}
If I want to use less controls, I make them invisible and disabled, hence the control.Visible.
How can I make it so I only do work on the one control that raised the event without having to process so much unneeded iterations?
The sender parameter is the object that raised the event. You can cast this to a control.
Assuming the all of the controls are wired to the same event (which you indicate):
protected void Button1_Click(object sender, EventArgs e)
{
((Button)sender).Visible = true;
// or more generally:
((WebControl)sender).Visible = true;
}
You will need to cast the sender to a common, base type. If you go with a base type, WebControl will allow you to access the Enabled property while Control will not.
I have a tab control that only responds to changing tabs with the mouse click.
Do I need to manually code in an event handler for tab control despite having the Surface SDK? Or is there a better control handler that I could use here?
I feel like this is entirely counter productive to the point of having the SDK. Especially because I plan on having a lot of different, unique tabs in my program and don't want to be handling each tab individually with nested ifs in a button_TouchDown function. I already have custom buttons that that have button_TouchDown setup and adding individual tab controls would be a headache and hell of a mess of code.
I tried searching but came up empty handed which makes me think that perhaps I am missing something and it should work. Is it because I have a predefined button_TouchDown function?
private void TabItem_TouchDown(object sender, TouchEventArgs e)
{
TabItem tab = sender as TabItem;
TabControl control = tab.Parent as TabControl;
control.SelectedItem = tab;
e.Handled = true;
}
XAML
<TabItem x:Name="hccontactTab" Header="Phone" TouchDown="TabItem_TouchDown">
Based on the above answer, but enhanced to account for scrolling per touch. Use a ClassHandler to handle this cleanly within your application (I use AutoFac's IStartable to auto-register it during building the container):
using System.Windows;
using System.Windows.Controls;
using Autofac;
namespace ...ClassHandlers
{
public class TabItemTouchClassHandler : IStartable
{
public void Start()
{
Register();
}
public void Register()
{
EventManager.RegisterClassHandler(typeof(TabItem), UIElement.TouchDownEvent, new RoutedEventHandler(OnTabItemTouchDown));
}
//must be static! otherwise memory leaks!
private static void OnTabItemTouchDown(object ender, routedEventArgs e)
{
var tab = sender as TabItem;
var control = tab?.Parent as TabControl;
if (control != null && !Equals(tab, control.SelectedItem))
{
control.SelectedItem = tab;
e.Handled = true;
}
}
}
}
I've got these functions:
private void setupFocusControls(Control parent)
{
foreach (Control control in parent.Controls)
{
control.GotFocus += HandleFocus;
}
}
private void HandleFocus(object sender, EventArgs e)
{
Control control = (Control)sender;
thisFormName = this.Name;
thisControlName = control.Name.ToString();
if (bHelpSystemActive)
{
bHelpSystemActive = false;
if ((ModifierKeys & Keys.Control) == Keys.Control)
{
HelpSystem hs = new HelpSystem(thisFormName, thisControlName);
hs.ShowDialog();
}
else
{
showTooltipForControl(control, thisFormName);
}
return;
}
}
And I call this in the Form_Load function:
private void Labeller_Load(object sender, EventArgs e)
{
setupFocusControls(this);
fillListBox();
}
What this does is show a custom help system I've written. If no control key is clicked, then I'll display the info in a tool tip. If the control key is pressed, then I show an editor. Simple really.
Now, this code works perfectly on another form, which uses panels as containers for my form controls. The problem is, I now want to add this functionality to a separate form. I've added all the code, but none of the controls on the form are having the HandleFocus event added to them. The only difference between this form and the working one is that it uses a splitContainer as it's container.
My question is, why is the setupFocusControls function not looping through the splitContainer as it does the panels on my working form? And, how would I go about fixing it? I'd obviously rather not have several functions to perform this (what I thought) simple task...
Cheers.
Assuming that the problem is that you are not assigning the event to every single control on the form (only top-level controls), the fix should be to change your setupFocusControls(Control) method:
private void setupFocusControls(Control parent)
{
foreach (Control control in parent.Controls)
{
control.GotFocus += HandleFocus;
// add the following line to recurse throughout the control tree
setupFocusControls(control);
}
}
This will add the HandleFocus event handler to every single control, by recursing through the children of every control. I hope this works for you!
As a bonus, if you want to add the event handler to all controls, including the parent control, you could write the setupFocusControls method as follows:
private void setupFocusControls(Control parent)
{
parent.GotFocus += HandleFocus;
foreach (Control child in parent.Children)
setupFocusControls(child);
}
I have a WinForms app that contains many NumericUpDown controls. In a nutshell, if my users enter a value into the control and then delete the text, I want to restore it (the text) when the control loses focus. So I decided that I'd check .Text when the control loses focus and if it's empty, I set .Text = .Value.ToString().
I'm doing this in the Leave event handler and it works just fine. But as I said, I have many of these controls (18, to be exact). I don't like creating 18 Leave event handlers that all do the same thing so I created a generic one like this:
private void numericUpDown_GenericLeave(object sender, EventArgs e)
{
if (string.IsNullOrEmpty(((NumericUpDown)sender).Text))
((NumericUpDown)sender).Text = ((NumericUpDown)sender).Value.ToString();
}
I started to hook up all of the controls to this generic event handler but I quickly got tired of doing this:
numericUpDown1.Leave += numericUpDown_GenericLeave;
numericUpDown2.Leave += numericUpDown_GenericLeave;
numericUpDown3.Leave += numericUpDown_GenericLeave;
...
numericUpDown18.Leave += numericUpDown_GenericLeave;
So I thought I'd create a function that would return a list of all the controls of a specified type and then loop through that list and hookup the event handlers. That function looks like this:
public static List<Control> GetControlsOfSpecificType(Control container, Type type)
{
var controls = new List<Control>();
foreach (Control ctrl in container.Controls)
{
if (ctrl.GetType() == type)
controls.Add(ctrl);
controls.AddRange(GetControlsOfSpecificType(ctrl, type));
}
return controls;
}
I call the function like this:
var listOfControls = GetControlsOfSpecificType(this, typeof(NumericUpDown));
foreach (var numericUpDownControl in listOfControls)
{
numericUpDownControl.Leave += numericUpDown_GenericLeave;
}
When I run my app, however, I don't see the expected behavior that occurs when I manually hookup each control to the generic event handler. This code is currently in the constructor of my form and I've tried calling it before as well as after the call to InitializeComponent() but neither one seems to be working. I get no error of any kind, I just don't see the behavior that I was expecting. I have a breakpoint set inside the generic event handler but the debugger never breaks so it seems like the event handler isn't being hooked up correctly. Does anyone know why this might be or how I can troubleshoot it further? Thanks!
EDIT
I just realized that the call to:
var listOfControls = GetControlsOfSpecificType(this, typeof(NumericUpDown));
was happening before the call to InitializeComponent() so of course the list of controls being returned was empty. DOH! Thanks for all the replys. I apologize for wasting everyones time. :-(
You're passing this to your method, which is presumably a reference to your form. Your method will only catch the controls that are placed directly on your form. Any NumericUpDown controls that are not directly on the form (i.e. they're sitting on a panel or something) will be missed.
Why not create a user control that has a NumericUpDown control in it.
Then handle this is in the user control events.
This worked for me:
private decimal _previous = 0;
private void numericUpDown1_ValueChanged(object sender, EventArgs e)
{
if (((NumericUpDown)sender).Text.Length > 0)
{
_previous = this.numericUpDown1.Value;
}
}
private void UserControl1_Leave(object sender, EventArgs e)
{
if (this.numericUpDown1.Text == "")
{
this.numericUpDown1.Value = _previous;
this.numericUpDown1.Text = System.Convert.ToString(_previous);
}
}
Just note that the Leave event is on the user control not on the updown control itself.
Question answered. See Edit above. Thanks to bsegraves for pointing me in the right direction.
I'm trying to mimic the web functionality of having a label over a textbox that shows the type of value the textbox should contain. I can add the events individually but I'm wondering if there is a way to add 'Behavior' to a set of controls.
Please see example code:
private void labelFirstName_Click(object sender, EventArgs e)
{
HideLabelFocusTextBox(labelFirstName, textBoxFirstName);
}
private void HideLabelFocusTextBox(Label LabelToHide, TextBox TextBoxToShow)
{
LabelToHide.Visible = false;
TextBoxToShow.Focus();
}
private void textBoxFirstName_Leave(object sender, EventArgs e)
{
if (String.IsNullOrEmpty(textBoxFirstName.Text))
labelFirstName.Visible = true;
}
private void textBoxFirstName_Enter(object sender, EventArgs e)
{
labelFirstName.Visible = false;
}
You could subclass the text box control (write your own that inherits a textbox)
Btw, i have thought about this and i would take another approach:
i would override the text box's paint handler and when the textbox contains no information, draw an info string into it.
Something like:
using System;
using System.Windows.Forms;
using System.Drawing;
class MyTextBox : TextBox
{
public MyTextBox()
{
SetStyle(ControlStyles.UserPaint, true);
}
protected override void OnPaint(PaintEventArgs e)
{
if (string.IsNullOrEmpty(this.Text))
{
e.Graphics.DrawString("My info string...", this.Font, System.Drawing.Brushes.Gray, new System.Drawing.PointF(0, 0));
}
else
{
e.Graphics.DrawString(Text, this.Font, new SolidBrush(this.ForeColor) , new System.Drawing.PointF(0, 0));
}
}
protected override void OnTextChanged(EventArgs e)
{
Invalidate();
base.OnTextChanged(e);
}
}
Tie Behaviour
You can tie the feature/behaviour closer to the TextBox control by using extension methods. This simple solution might make it feel more tightly knit:
// NOTE: first parameter "this TextBox thisText"- these are all extension methods.
static public void AssignLabel(this TextBox thisText, Label companionLabel) {
thisText.Tag = companionLabel;
// HOOK UP EVENT AT THIS POINT, WHEN LABEL IS ASSIGNED (.NET 3.x)
thisText.Leave += (Object sender, EventArgs e) => {
LeaveMe(thisText); // Invoke method below.
};
}
static public void FocusText(this TextBox thisText) {
if (! ReferenceEquals(null, thisText.Tag))
(Label)thisText.Tag).Visible = false;
thisText.Focus();
}
static public void LeaveMe(this TextBox thisText) {
if (String.IsNullOrEmpty(thisText.Text))
((Label)thisText.Tag).Visible = true;
}
//etc.
and then use your textbox instances like so:
Label overlay1 = new Label(); // Place these appropriately
Label overlay2 = new Label(); // on top of the text boxes.
Label overlay3 = new Label();
TextBox myTextbox1 = new TextBox();
TextBox myTextbox2 = new TextBox();
TextBox myTextbox3 = new TextBox();
// Note: Calling our extension methods directly on the textboxes.
myTextbox1.AssignLabel(overlay1);
myTextbox1.FocusText();
myTextbox1.LeaveMe();
myTextbox2.AssignLabel(overlay2);
myTextbox2.FocusText();
myTextbox2.LeaveMe();
myTextbox3.AssignLabel(overlay3);
myTextbox3.FocusText();
myTextbox3.LeaveMe();
//etc...
How it Works
The code is cleaner and applies to all TextBoxes you instantiate.
It relies on the the .Tag property of the TextBox class to store a Label reference into (so each TextBox knows its label), and also extension methods introduced with .NET 3.x which allow us to "attach" methods onto the TextBox class itself to tie your behaviour directly to it.
I took your code and produced almost the same thing with tweaks to turn it into extension methods, and to associate a Label with your Textbox.
Variation
If you want to attach the same method to other controls (and not just the text box) then extend the base Control class itself like:
static public void LeaveMe(this Control thisControl) { //...
You could always create a user control that does this. Put both the TextBox and the Label inside the control and code up the logic inside the user control. That way every instance of that control will behave the same.
Another option might be to use an Extender Provider. These basically let you add a behavior to any control (though these can be limited if I remember right) at design time. The ToolTip is an example of an Extender Provider that is already floating around in the framework. I used these quite a bit once upon a time to do things like add support for getting the text values of controls from a resource file.
I would subclass the regular textbox and add properties which allow you to either find the associated label or set a reference to the associated label directly.
Typically in winform projects, I subclass all controls before adding them to my forms, so I can add common functionality very easily without having to change forms in the future.