Is there a way to make a control dependent on another control? I have a combo box and a button, and I need the button to be enabled if and only if there is an item selected in the combo box.
I know I can set the Enabled property of the button inside the SelectedIndexChanged callback, but then it will require some code, and besides there's an issue with what initial state the button would have. So I'm looking for something that wouldn't require manually handing events, is this possible?
Thanks!
No, there is no way in winforms to do this without code. What I usually do is to collect all such state-setting code into one specific method:
private void SetControlStates()
{
theButton.Enabled = theComboBox.SelectedIndex >= 0;
// code for other controls follow here
}
Then I trigger this method from all over the place, as soon as there is an interaction that may lead to the state changing (including the last thing I do when the form has finished loading; that takes care of initial state). If you want to avoid unnecessary assignments, just add code to check the value first:
private void SetControlStates()
{
bool buttonEnabled = theComboBox.SelectedIndex >= 0;
if (theButton.Enabled != buttonEnabled) theButton.Enabled = buttonEnabled;
// code for other controls follow here
}
Related
So in my project I have a few textBoxes that hold the coordinates of two corners (Latitude & Longitude). The textBoxes are updated by a timer (which gets a value from a server and sets the textBoxes if the value received is different from the current value). Problem is, I want the textBoxes to be available for manual editing; However if I'm in the middle of typing the number and the timer checks the current value he sees that it's a different thing from what the server returned and changes it immediately. Is there a way to check if the textBox is being edited at the moment, or a better way to solve this solution?
code (samples, the code is the same for the two corners):
if (northEastLatitude != double.Parse(neLatTB.Text)) //neLatTB is the textBox
neLatTB.Text = northEastLatitude.ToString();
else //No answer returned from the server so we need to reset the textBoxes
{
northEastLatitude = 0;
northEastLongitude = 0;
if(neLatTB.Text != "0")
neLatTB.Text = northEastLatitude.ToString();
if(neLngTB.Text != "0")
neLngTB.Text = northEastLongitude.ToString();
}
in addition, I have functions for TextChanged events for all of the textBoxes (so that when I set the coordinates manually it uploads them to the server). Is there any way to prevent this function from being called whenever I press the dot key? apparently it calls the event too (marks the ending of the text entering).
It really depends on your design but if you want to use TextBox to show updatable values and also make it possible to edit you have to suppress code from your timer to execute. WinForms TextBox doesn't have an option to show you if text is changing programmatically or by user interaction. You have to somehow make it by yourself.
There is plenty of ways to do that of courde. One way is to use Enter/Leave events to detect when your TextBox gets or loses focus. But there will be a need to click somwhere out from the control after edit.
Another one, and probably desired by you would be using TextChanged event preventing your timer from updating field until text in TextBox will be typed in full. I would do it something like that:
Fisrtly I would declare two bool variables for blocking parts of code from execution:
private bool _isDirty; // used when user types text directly
private bool _suppresTextChanged; // used when timer updates value programmatically
After that I would write TextBox.TextChanged event listener:
private void neLatTBTextChanged(object sender, EventArgs args)
{
if(_suppressTextChanged)
return;
_isDirty = true; // toggle dirty state
if(/* text has good format */)
{
// Upload changes to server
_isDirty = false; // end manual edit mode
}
}
And inside timer method i would set:
_suppresTextChanged = true; // on the beginning
if (northEastLatitude != double.Parse(neLatTB.Text)) //neLatTB is the textBox
neLatTB.Text = northEastLatitude.ToString();
else //No answer returned from the server so we need to reset the textBoxes
{
northEastLatitude = 0;
northEastLongitude = 0;
if(neLatTB.Text != "0")
neLatTB.Text = northEastLatitude.ToString();
if(neLngTB.Text != "0")
neLngTB.Text = northEastLongitude.ToString();
}
_suppresTextChanged = false; // after edit was made
Personally i think this design can lead to a lot of problems (consider what to do when user stops typing and leave the TextBox in _isDirty state etc...). Instead of using just TextBox I would add a Label to store data from timer (and probably data the user will type) and left TextBox just for entering user specific values.
I have an ASP.NET checkbox that based on certian logic I need to set its checked value to true server-side. This is easy enough, but it fires the wired up MyCheckBox_CheckedChanged(object sender, EventArgs e) event as well.
That event's code is 100% ok, as long as it is called when the user checks the check box from the client. However, when I'm just trying to set that checkbox to be Checked = true; server-side I don't want the code in the event to run.
The cleanest way I would like to approach this was to inspect the sender object on the event and see if this event was really called by the client or not (like: http://msdn.microsoft.com/en-us/library/aa457091.aspx). However this did not yield any distinguishing information. It still looked like the checkbox being selected called it.
My 2nd thought is to set some session value and inspect this to basically return if not being called by the user. I can easily do this, but I don't like using 'public flags' of sorts unless absolutely necessary. I'd rather (if at all possible) inspect the sender or arguments on that event to determine if that property was set server side on reload of data from the database, or if the user actually selected it.
I saw this Prevent postback on server-side property change and it was not a direct solution to my question, but rather a work around not applicable to my situation.
Is there a way to determine this was set solely server side so that I can bypass running this code?
Add a private boolean variable dontFire to the top of your class with a default value = false.
Then use the following:
dontFire = True;
myCheckbox.Checked = True;
dontFire = False;
Then in the myCheckBox.CheckedChanged event put this at the top:
if (dontFire) return;
Problem solved.
If I am correct you are trying to set the checked property of check box true on load/initialize of the page.
Here are a couple of approaches you may follow:
Another approach to deal with the situation you mentioned could be to dynamically bind the controls (your checkbox's) event on the page load only if you are not setting the checkbox's checked property to true.
Use a simple page property as a flag to signify if the checked property had been set server side, and use it inside the check box's change event handler. You do not need to use session, as you may not need to persist this value across post back.
If possible put the logic to set the checked property of the check box to true in the check box's change event handler, and based on whether or not you are setting the property you can allow/disallow the handler code.
This would be my recommended solution:
public bool Check_CheckBox
{
set
{
checkbox1.Checked = value;
if (value)
{
checkbox1.CheckedChanged -= new EventHandler(this.Check_Clicked);
}
}
get
{
return checkbox1.Checked;
}
}
set the Check_CheckBox property to true based on your logic and it will do the trick.
Hope this helps.
My form looks something like a three-pane email client. Left side is a grid with a list of people. Top right is the current person's detail record. Bottom right is a custom control with many checkboxes displaying the current person's areas of expertise:
[x] cooking [x] window cleaning [x] brain surgery
[x] massage-therapy [x] singing [ ] random acts of vandalism
When the form is opened, the focus goes to the first person listed in the grid on the left-side of the form,, and the grid's focused_row_changed event fires. In the handler for this event I get the current person's id, then fetch detail data for that person from the database and populate the detail record, and also fetch the person's areas-of-expertise rows and set the checkboxes. All of this is working fine except when the form is first opened, because then the custom control with its many checkboxes is not yet initialized. At that point MyCustomControl is null.
if (null != MyCustomControl)
{
MyCustomControl.SetCheckedValues( datasource);
}
What is the best practice design-pattern for handling this situation? What do I do here when my control isn't fully initialized yet?
if (null != MyCustomControl)
{
MyCustomControl.SetCheckedValues( datasource);
}
else
{
// ?? Wait around for a bit and keep trying every 100ms?
}
The way I have solved this in my controls when they have had this problem is to implement ISupportInitialize.
In your control, you would put something like:
public class MyCustomControl: ISupportInitialize
{
private bool _initializing = false;
private void BeginInit()
{
_initializing = true;
}
private void EndInit()
{
_initializing = false;
}
private void SomeMethodThatWouldRaiseAnEventDuringInit()
{
if (_initializing) return;
//...
}
}
The windows forms designer checks for your control implementing the interface, and produces this code in the .Designer.cs file:
((System.ComponentModel.ISupportInitialize)(this.customControl1)).BeginInit();
///
/// customControl1
///
this.customControl1.SelectedIndex = 0; //this would normally raise the event
((System.ComponentModel.ISupportInitialize)(this.customControl1)).EndInit();
From what I understand, you are setting MyCustomControl.SetCheckedValues( datasource); when focused_row_changed event fires.
This also tends to happen when the form is just loading, which is generally not desired, because you end up with events telling things to load when, for example, a selected index is still -1.
The way I have been working around this is I have a global boolean in the form called doneLoading. It starts off false and becomes true when the Form_Shown() event gets called.
From there I just put an if(doneLoading) around any piece of code that needs to wait until the form is actually done loading before it is allowed to execute. In your case, I would do:
if(doneLoading)
{
MyCustomControl.SetCheckedValues( datasource);
}
Do your UI initialization functions in a subroutine that isn't called until after all the other UI elements are initialized, or base your calculations on the back-end values instead of the UI.
in response to comments and other posts, if you can't get anything else to work, you can add a 'refresh' button to the UI
The WinForms CheckedListBox control has 2 default behaviors when clicking with a mouse:
In order to check/uncheck an item you're required to click an item twice. The first click selects the item, and the second toggles the check state.
In addition, one subsequent click of the same item will toggle that item's checked state.
As a convenience feature I needed to allow users to toggle the selection in one click. I have achieved this, so now default behavior #1 above is achieved in one click. The problem is behavior #2 no longer works correctly when clicking the same (i.e., currently selected) item. It works fine when jumping between items, which is desired, but it requires up to 4 clicks on the same item.
My workaround for this is to call the toggling logic twice if the user selects the same item repeatedly. So on to my questions:
This works, but why? What's the real underlying issue?
Is there a better way to achieve this so I can get it working like default behavior #2 without calling the method twice and keeping track of my last selection?
Oddly enough debugging the code reveals that the checked state has changed but it doesn't appear on the UI side till it's called twice. I thought it might be threading related but it's not a re-entrant event being triggered that might need BeginInvoke usage.
Here's my code:
using System.Linq;
using System.Windows.Forms;
namespace ToggleCheckedListBoxSelection
{
public partial class Form1 : Form
{
// default value of -1 since first item index is always 0
private int lastIndex = -1;
public Form1()
{
InitializeComponent();
CheckedListBox clb = new CheckedListBox();
clb.Items.AddRange(Enumerable.Range(1, 10).Cast<object>().ToArray());
clb.MouseClick += clb_MouseClick;
this.Controls.Add(clb);
}
private void clb_MouseClick(object sender, MouseEventArgs e)
{
var clb = (CheckedListBox)sender;
Toggle(clb);
// call toggle method again if user is trying to toggle the same item they were last on
// this solves the issue where calling it once leaves it unchecked
// comment these 2 lines out to reproduce issue (use a single click, not a double click)
if (lastIndex == clb.SelectedIndex)
Toggle(clb);
lastIndex = clb.SelectedIndex;
}
private void Toggle(CheckedListBox clb)
{
clb.SetItemChecked(clb.SelectedIndex, !clb.GetItemChecked(clb.SelectedIndex));
}
}
}
To reproduce my problem comment out the lines mentioned in the code comments and follow these steps:
Click the item at index 2 - state changes to checked.
With the current item selected, click it again - state does not change. Expected: unchecked. Click it a few times and it finally switches.
Thanks for reading!
As a convenience feature I needed to allow users to toggle the selection in one click.
I'm not sure what's happening with the code, but setting CheckOnClick to true will do this:
CheckOnClick indicates whether the check box should be toggled whenever an item is selected. The default behavior is to change the selection on the first click, and then have the user click again to apply the check mark. In some instances, however, you might prefer have the item checked as soon as it is clicked.
I have a dialog with loads of control in it. Each and evey control will be populated during the loading sequence and it might take a while for it to get completely filled. Mean while, I don't wanna allow the user to click on any of the controls. In other words, I wanna disable the control from receiving the events and I don't wanna disable the controls (as it might look odd).Also, I don't wanna subscribe and unsubscribe for the events regular intervals. Is there any way to stop the controls from listening to the events for a brief time ??
Sudarsan Srinivasan
The whole point of disabling controls is to communicate to the user that the control cannot be used at a particular time. This is a convention that users have learned and are used to, so I would advice to follow that. Not doing that may confuse the users.
The easiest way is to disable the container in which the controls are located in, rather than disabling each and every control. A better way (or at least the way that I prefer) is to have a method that will control the Visible and Enabled properties of controls based on which state the UI is in.
The easiest way is to move the control population out of the load event (if possible). Then in Load do something like:
private bool _LoadComplete;
void OnFormLoad(Object sender, EventArgs e)
{
_LoadComplete = true;
InitializeControls();
_LoadComplete = false;
}
void InitializeControls()
{
// Populate Controls
}
void OnSomeControlEvent()
{
if (_LoadComplete)
{
// Handle the event
}
}
Edit A Couple other Ideas:
Set the Application.Cursor = WaitCursor (typically will disallow clicking, but not a 100% guarantee)
Create a "Spinner" control to let the user know that the screen is busy. When loading bring it to the front so it sits on top and covers all other controls. Once you're done loading set it to visible = false and show your other controls
Unfortunately the only way i know of is to have a class variable (called something like _loading) and in each control handler do something like:
If (! _loading )
{
...
}
And in your loading code set _loading = true; once you have finished loading.
If you just want to disable user input, then you can set the form's Enabled property to false.
This has the effect of blocking user input to any of the form's controls, without changing the appearance of the controls; it's the technique used internally by the ShowDialog method.