Better way to programmatically Lock/Disable multiple UI controls on Ribbon bar - c#

I have been wondering, what would be the better way to implement Locking/Disabling a bunch of Ribbon UI controls by just a click?
Currently, my approach is kinda simple and basic:
private void tbtnLock_Click(object sender, RibbonControlEventArgs e)
{
if (tbtnLock.Checked)
{
control1.Enabled = false;
control2.Enabled = false;
control3.Enabled = false;
//...
//controlN.Enabled = false;
}
else
{
control1.Enabled = true;
control2.Enabled = true;
control3.Enabled = true;
//...
//controlN.Enabled = true;
}
}
I think it's okay if we only have just a few controls but once we add more and more controls onto the Ribbon bar, I don't think it'd be a good coding practice to do things like above.
Is there any cleaner and neater approach to this? Am I able to get the collection of all the controls on the Ribbon bar? Hopefully someone can give me some pointers here? Thanks very much.
EDIT:
Revised code below:
private void tbtnLock_Click(object sender, RibbonControlEventArgs e)
{
toggleUILockState();
}
private void toggleUILockState()
{
if (group1.Items != null)
{
foreach (RibbonControl c in group1.Items)
{
if (c.Name != "tbtnLock")
{
c.Enabled = !tbtnLock.Checked;
}
}
}
}
I think it looks a lot better than the previous version. Thanks everyone for the help.

Well certainly the first step to improve the code would be to remove the if statement and assigned the enabled state of the control directly with the checked state of the tbtnLock control like...
control1.Enabled = !tbtnLock.Checked;
control2.Enabled = !tbtnLock.Checked;
that would cut your code if half straight away. You may want to assign that to a bool first incase you want to do additional processing on it later (maybe some other object helps determining lock state for example)
bool isEnabled = !tbtnLock.Checked;
control1.Enabled = isEnabled;
control2.Enabled = isEnabled;
Further than that I would need to know what "ribbon" control you are using. Do you have a link?
But as you have hinted, I would want to look at trying to find a colleciton of controls, loop through them, check if the control is not the tbtnLock control and disable/enable as needed.
Also, I would recommend move all this code to a function outside of the event handle, in case you need to call this method from other code. Something like...
private void tbtnLock_Click(object sender, RibbonControlEventArgs e)
{
UpdateRibbonState();
}
private void UpdateRibbonState(){
//Code goes here
}
EDIT: Making assumtion that a "group" (as described in comments) has a collection of controls...
foreach(Control c in group.Controls)
{
if(c.Name != "tbtnLock")
{
c.Enabled = !tbtnLock.Checked;
}
}
I am not familiar with any built-in .Net ribbon controls and as there is no link to a 3rd party set) I am making a best guess at the properties available for a "group"

Seems like you could put all your UI controls into one list and then iterate it to enable/disable them all at once. Something like (untested/pseudocode):
List<RibbonBase> listMyControls = new List<RibbonBase>()
{
control1, control2, control3, ... , controlN
};
foreach (var control in listMyControls)
{
control.Enabled = !tbtnLock.Checked;
}

Here is some code to extend this to an entire tab.
/// <summary>
/// Enable or Disable all buttons in all groups of the RibbonTab to match toggleButtonActive
/// toggleButtonActive remains enabled
/// </summary>
/// <param name="enabled"></param>
private void SetUILockState(bool enabled)
{
foreach (RibbonGroup group in myRibbonTab.Groups)
{
if (group.Items != null)
{
foreach (RibbonControl c in group.Items)
{
if (c.Name != "toggleButtonActive")
{
c.Enabled = enabled;
}
}
}
}
// TODO handle right click menus as well
}

Related

Add a default user control everytime any other user control is closed in a panel

I have been trying to load a default user control every time any other user control on the same panel is closed by the user. I have a panel named MainContainer and when the main form loads I am calling the following method to load that default user control named welcome.
public void AddUserControlWelcome()
{
MainContainer.Controls.Clear();
welcome.Dock = DockStyle.Fill;
MainContainer.Controls.Add(welcome);
}
I have a menustrip button which calls the following method,
private void sellItemsToolStripMenuItem_Click(object sender, EventArgs e)
{
AddUserControlSellManager();
}
And it is defined as,
public void AddUserControlSellManager()
{
MainContainer.Controls.Clear();
sellManager.Dock = DockStyle.Fill;
MainContainer.Controls.Add(sellManager);
}
So, there is a button on sellManager user control which actually closes sellManager. And after that I am invoking AddUserControlWelcome() again from MainContainer_ControlRemoved(object sender, ControlEventArgs e) but the application is crashing and I don't know why.
I think, it is clear why you having this issue. MainContainer_ControlRemoved called not only when you remove your "sell" but "welcome" too. So, the culprit I believe is the fact that you do add control on such event as MainContainer_ControlRemoved, which you shouldn't do. As good as .Net is, sometimes you have to stay away from using certain events for certain purposes , or you run into issues.
Try to do something like this. Considering that your surface can host only one control at the time
class SurfaceManager
{
private Control _defaultCtrl;
private bool _currentDefault;
private Control _surface;
void SurfaceManager(Control _surface, Control defaultCtrl)
{
_surface = surface;
_defaultCtrl = defaultCtrl;
_surface = surface.Controls.Add(_defaultCtrl);
_currentDefault = true;
}
public Control Add(Control ctrl)
{
Control c = null; // Returning removed control so you can do something else with it
if (_surface.Controls.Count > 0)
{
if (!_currentDefault)
c = _surface.Controls[0];
_surface.Controls.Clear();
}
_surface = surface.Controls.Add(ctrl);
_currentDefault = false;
Return c;
}
public Control Remove()
{
if (_currentDefault) Return // Current is default - do nothing
Control c = null; // Returning removed control so you can do something else with it
if (_surface.Controls.Count > 0)
{
c = _surface.Controls[0];
_surface.Controls.Clear();
}
_surface = surface.Controls.Add(_defaultCtrl);
_currentDefault = true;
Return c;
}
}
Now, in your class create instance of this manager and use Add or Remove. Remove will automatically bring on the Welcome screen

Is there a way to cast an object and access its properties in the same line?

I am talking in the context of event handler in a C# windows forms, but I'm assuming the answer could be used anywhere in C#.
To give an example, I have a form that has many check boxes that each activate a button. The CheckedChanged event is handled by a function that is very similar for each CheckBox and it looks something like this right now:
private void acheckbox_CheckedChanged(object sender, EventArgs e)
{
int uniquetocheckbox = 12345;
if(acheckbox.CheckedChanged)
{
ThisFunction(uniquetocheckbox, true);
AssociatedButton.Enabled = true;
}
else
{
ThisFunction(uniquetocheckbox, false);
AssociatedButton.Enabled = false;
}
}
There are a lot of these check boxes and I'm trying to cut and past the code for each and make as few changes as possible so I want to do something like this :
private void acheckbox_CheckedChanged(object sender, EventArgs e)
{
int uniquetocheckbox = 12345;
if((CheckBox)sender.Checked) //CHANGE HERE
{
ThisFunction(uniquetocheckbox, true);
AssociatedButton.Enabled = true;
}
else
{
ThisFunction(uniquetocheckbox, false);
AssociatedButton.Enabled = false;
}
}
This does not work. The easy work around is this :
private void acheckbox_CheckedChanged(object sender, EventArgs e)
{
int uniquetocheckbox = 12345;
CheckBox cb = (CheckBox)sender;
if(cb.Checked) //CHANGE HERE
{
ThisFunction(uniquetocheckbox, true);
AssociatedButton.Enabled = true;
}
else
{
ThisFunction(uniquetocheckbox, false);
AssociatedButton.Enabled = false;
}
}
But out of pure curiosity I am wondering if there is a way to do it in one line like the second example I gave. I would like to know because I think it looks better and is obviously 1 line shorter.
I think you're just missing a set of parenthesis. You want to cast to Checkbox, then get the properties of that:
if (((CheckBox)sender).Checked)
This will force the order of operations to cast first, then get the property from the cast result.
Sure, it's possible. You just missed another set of brackets:
if(((CheckBox)sender).Checked)
However, I wouldn't do this. Why? You don't want to cast again if you want to access the sender as a textbox again if you did it your way.
You can. For example:
object o;
o = new SomeType();
var prop = ((SomeType)o).SomeProperty;
It needs to be this:
if(((CheckBox)sender).Checked) //CHANGE HERE
But personally I like the way you've shown better. That way if it needs to be casted again, it's already been done.
Don't know any C# but ((CheckBox)sender).Checked) should work. In java the "." (member access) has higher priority than casting so putting the parenthesis like this should force the casting to happen first.
You just need a couple of more parenthesis in your if statement:
if (((CheckBox)sender).Checked)
{
...
}

Disable a button

I want to prevent the user clicking two times on a button when it has been already executing and the process is not finished.
I am using compact framework 3.5 and I have problems when the user clicks two times on a button that is already executing or some other button. I want to disable all buttons when the program is executing and enable them again when the process is done.
OS: Windows mobile 6.1
Framework: .NET 3.5 CF
Try adding this.Enabled = false first thing (this being the form in question) in the scope of your Click handler. Be sure to set it back to true when done. You may need to Application.DoEvents() or Update() to display visible progress if this all in the scope of the handler. Probably the preferred way to do any extended processing though would be to spawn a background thread and update your UI from it using Invoke and BeginInvoke.
I found that I needed to do this quite often when building a windows mobile application so made a simple utility class.
public static class FormUtility
{
/// <summary>
/// Lock the form whilst processing
/// </summary>
/// <param name="controlCollection"></param>
/// <param name="enabled"></param>
public static void FormState(Control.ControlCollection controlCollection, bool enabled)
{
foreach (Control c in controlCollection)
{
c.Enabled = enabled;
c.Invalidate();
c.Refresh();
}
}
}
All I need to do was then call one line to lock the form down.
FormUtility.FormState(this.Controls, false);
You should end up with something like
private void btnSave_Click(object sender, EventArgs e)
{
FormUtility.FormState(this.Controls, false);
//Do your work
if (!SaveSuccessful())
//Renable if your validation failed
FormUtility.FormState(this.Controls, true);
}
EDIT : I think what #tcarvin is suggesting is that you do not need to call refresh on every control but simply invalidate the controls and then refresh the container which will cause all the invalidated controls to redraw at once. I haven't tested this but a small change to something like...
public static void FormState(Form form, bool enabled)
{
foreach (Control c in form.Controls)
{
c.Enabled = enabled;
c.Invalidate();
}
form.Refresh();
}
Then use
FormUtility.FormState(this, true);
This is the easiest way, for a button called button1:
void button1_Clicked(object sender, EventArgs e) {
button1.Enabled = false;
try {
// put your code here
} finally {
button1.Enabled = true;
}
}

ASP.NET dynamic data: Table is not shown anymore after inserting data if FormView.Controls is accessed on Page.Initialized event

Lately, I received a bug report for Ninject.Web that it is not working properly together with ASP.NET dynamic data. The problem is that on postback (e.g. when Inserting, Deleting, Editing a record) the table is not shown anymore.
Some debuging showed that the problem is caused by a IHttpModule that recursively iterates through all controls of a page after it is initialized. As soon as this module accesses the Controls property get accessor of FormView or GridView the problem occurs. If this type of controls is skiped everything is fine. The following code shows the module:
public class NinjectHttpModule : DisposableObject, IHttpModule
{
private HttpApplication httpApplication;
public void Init(HttpApplication context)
{
this.httpApplication = context;
this.httpApplication.PreRequestHandlerExecute += this.OnPreRequestHandlerExecute;
}
private static void InjectUserControls(Control parent)
{
if (parent == null)
{
return;
}
foreach (Control control in parent.Controls)
{
if (control is UserControl)
{
// KernelContainer.Inject(control); This is irrelevant for the question.
}
InjectUserControls(control);
}
}
private void OnPreRequestHandlerExecute(object sender, EventArgs e)
{
var page = this.httpApplication.Context.CurrentHandler as Page;
if (page == null)
{
return;
}
KernelContainer.Inject(page);
page.InitComplete += (src, args) => InjectUserControls(page);
}
}
If this code is changed so that the iteration through the child controls of DataBoundControls is delayed to the DataBound event everything is fine. Shown by the next code snippet:
private static void InjectUserControls(Control parent, bool skipDataBoundControls)
{
if (parent == null)
{
return;
}
if (skipDataBoundControls)
{
var dataBoundControl = parent as DataBoundControl;
if (dataBoundControl != null)
{
dataBoundControl.DataBound += InjectDataBoundControl;
return;
}
}
foreach (Control control in parent.Controls)
{
if (control is UserControl)
{
KernelContainer.Inject(control);
}
InjectUserControls(control, skipDataBoundControls);
}
}
private static void InjectDataBoundControl(object sender, EventArgs e)
{
var dataBoundControl = sender as DataBoundControl;
if (dataBoundControl != null)
{
dataBoundControl.DataBound -= InjectDataBoundControl;
InjectUserControls(dataBoundControl, false);
}
}
Because I'm completely unfamiliar with System.Web.DynamicData I'd like to know some things to get a better feeling about how to fix this bug:
Why does this problem occur? I mean it's only a simple read access to the Controls property.
What side effects can the change above have?
Is it still early enough to inject the controls after the data bound event?
Do you think this is a valid bug fix for this problem?
Certainly puzzling behavior, as can sometimes happen in WebForms with the many phases of execution.
Even though it's just a simple read access to the Controls property, this property can actually do a lot of work to return the child controls. In particular, it can't return the child controls unless they have been created, and that creation normally does not occur until later in the page life cycle. So by accessing it in InitComplete, the children end up getting created prematurely, before some important Dynamic Data hookups have happened, causing some controls to be missing. Yes, I realize that the end result behavior seems to make little sense, which is why some people favor the straightforwardness of MVC :)
As an alternate possible workaround, could you try moving your injection from InitComplete to PreLoad? e.g.
page.PreLoad += (src, args) => InjectUserControls(page);
I'm pretty sure that'll address the problem, though I'm less sure whether this will cause issues with your KernelContainer.Inject logic. Give it a try, since it's simpler than your workaround.
If that doesn't work, I think your workaround is ok, as it delays the enumeration until the children are created. As for 'Is it still early enough to inject the controls after the data bound event', I think that depends in exactly what KernelContainer.Inject does, and what expectations it has on the state of the control.

How to create a control and manage its events in one method?

I'm still stuck.
Assume that I've got a user control with a button. And an event called damnIt_ButtonClicked.
In the main window I want to emulate the control's lifetime like it is a modal dialog, although it's not.
I want to wrap everything into one method, it returns true if the Button on the control clicked.
public bool Show() {
var control = new ControlWithSingleButton();
bool result;
control.damnIt_ButtonClicked += (object sender, EventArgs args) =>
{
result = true;
};
MainWindowGrid.Children.Add(control);
MainWindowGrid.Visibility = Visibility.Visible;
return result;
}
Now. As you see the problem is this method will return always false;
But I need to return a result only when damnIt_ButtonClicked event fires. It means I have to put the thread on wait, till the user clicks button.
Right? Or how it should be done. Help me please....
You're going to need to re-architect your solution. Without knowing a broader scope of what you're trying to do, here's a possible solution.
private bool buttonResult;
public void Show() {
var control = new ControlWithSingleButton();
bool result;
control.damnIt_ButtonClicked += (object sender, EventArgs args) =>
{
this.ProcessButtonClick();
};
MainWindowGrid.Children.Add(control);
MainWindowGrid.Visibility = Visibility.Visible;
}
private void ProcessButtonClick()
{
this.buttonResult = true;
//do whatever you would have before if Show had returned true
}
You know what? I give up!
I decided to make the control a window, although it was strictly prohibited in given specifications to use any other windows but the Main. Anyway it's gonna be a chromeless, borderless transparent window, so nobody can see the difference.
Thank you so much.

Categories