2

I want to enable/disable controls in a Windows Forms application according to the user privileges.

Initially I thought of writing a method in each form class that would check the user credentials and then enable/disable its controls. But then I realized I could (maybe) create a static class method which would take the form as a parameter and do the job.

So I started writing it, presuming that sometimes I would like to enable the controls of just one or two panels, instead of the whole form. So, I need the parameters to be:

  • a varying number of panels and/or
  • a form class.

My difficulties with this task is that I'm getting an error trying to make the panels argument varying, and I have no idea how to set a parameter that could take any form class. All my form classes obviously inherits from Form generic class, but I don't know how to apply this.

Here's what I got:

public static void Enable(TableLayoutPanel[] containers = null)
    {
        if (MyOF.isEnabled)
        {
            return;
        }
        else
        {
            try
            {
                foreach (TableLayoutPanel table in containers)
                {
                    foreach (Control control in table.Controls)
                    {
                        control.Enabled = false;
                    }
                }
            }
            catch (NullReferenceException)
            {
            }
        }
    }
Charley R.
  • 187
  • 1
  • 8
  • 2
    There is actually a feature in C# called "generic types" which may solve your problem. The way you are using the term "generic" is not correct in this sense. `Form` is the base class of all forms. You can make a method like this `public static void DoStuff(T someForm) where T : Form { // implement here }` that can accept any specific form as a parameter and access members specific to that form type. – JamesFaix Apr 07 '20 at 18:06
  • Thanks James. I ran into this in my previous search, but I was missing the `where T : Form ` part. I'll try that. – Charley R. Apr 07 '20 at 18:10
  • Also, how can I make the TableLayoutPanel argument vary in quantity? When I try to call the method passing just one panel I get an error "cannot convert from ...Panel to ...Panel[ ]" – Charley R. Apr 07 '20 at 18:13
  • 1
    Look up the `params` keyword – JamesFaix Apr 07 '20 at 18:17
  • @JamesFaix this - `public static void DoStuff(T someForm) where T : Form` I call a heresy. :-) Total misuse of generic use. This will suffice - `public static void DoStuff(Form someForm)` What is the purpose to make it generic if you restrict it to `Form`? – T.S. Apr 07 '20 at 21:28
  • You can get access to members on a specific subtype, not just members on the base class. That is the reason that generic type constraints were introduced to the language, rather than only supporting using base class parameters. – JamesFaix Apr 11 '20 at 00:42

3 Answers3

1

If we remember that the Form class derives from Control (indirectly, by deriving from ContainerControl which derives from ScrollableControl, which derives from Control), and the Enabled property belongs to the Control class, we can write a method that will enable any control's children (including the Form or TableLayoutPanel controls), since the Controls collection also belongs to the Control class:

public static void EnableChildren(Control control, bool enabled = true)
{
    foreach (Control child in control.Controls)
    {
        child.Enabled = enabled;
    }
}

And then if we also want to be able to use this with a collection of controls (as in your example), we can write an overload that takes a collection:

public static void EnableChildren(IEnumerable<Control> controls = null, 
    bool enabled = true)
{
    if (controls == null) return;

    foreach (var control in controls)
    {
        EnableChildren(control, enabled);
    }
}

Now we can use this with a Form or a collection of TableLayoutPanel controls (or any control that has controls in it's Controls collection).

Examples of usage:

var myForm = new Form1();

EnableChildren(this);     // 'this' is the current form
EnableChildren(myForm);   // a separate instance of a form control
EnableChildren(tableLayoutPanel1, false);  // A single TableLayoutPanel control

var tableLayoutPanels = new [] {tableLayoutPanel1, tableLayoutPanel2, tableLayoutPanel3};
EnableChildren(tableLayoutPanels);  // An array of tableLayoutPanel controls
Rufus L
  • 36,127
  • 5
  • 30
  • 43
  • Thanks for answering. I tried your solution, but got the error "cannot convert from 'System.Windows.Forms.TableLayoutPanel' to 'System.Collections.Generic.IEnumerable" when trying to pass a TableLayoutPanel as a parameter to the method. – Charley R. Apr 07 '20 at 18:36
  • @CharleyR. message is clear - you can't convert a single control into enumerable of controls. method needs to be recursive – T.S. Apr 07 '20 at 18:41
  • 1
    I added some sample usage that works fine for me. How are you calling the method? – Rufus L Apr 07 '20 at 18:57
  • Translating to your example, I was calling something like this: `EnableChildren(tableLayoutPanel1);`. I appreciate all the answers, as all of them were useful at some point. I am going to upvote everything and post what worked out for me. Thanks again. – Charley R. Apr 07 '20 at 19:03
  • Strange, that code you tried works fine for me. Did you modify the samples? – Rufus L Apr 07 '20 at 23:09
  • @RufusL I studied a little bit more and found that your solution does solve my problem, and it is fairly simple as well. I'm taking yours as an answer to my question. Thanks for your patience and willingness to teach. – Charley R. Apr 08 '20 at 12:05
1

One of the simple ways I can think about what you are trying to do, is this. Let me get away for a sec here. I worked on projects where all form controls were built from Metadata. And meta came with licensing info. So, when control was placed where it should, it also was disabled or set read-only based on Metadata but the whole feature would be hidden if licensing info was restricting access to it. Coming back to your approach, this is not a bad approach and I see that this is can be done. And it can be done in 2 ways, (quickly from my head).

  1. Use user controls as surface for the components you want to enable/disable. Create an interface
public interface IDisableableControl // make your fine name, no methods needed - marker interface
 . .  . . . 
public class MyFineUserControl : UserControl, IDisableableControl 

And in your static method that you're going to write pass the form, and find all controls that implement this interface and work them the way you want.

2. Similarly, you can use property Tag, which is available on each control. With that, you can actually set your complex security object that can come from DB-stored metadata and then you evaluate this object stored in Tag to apply your configuration

Your method needs to be recursive

internal static void SetAllControls(Control parent)
{
    // Do something with control, for example parent.Enabled = false
    if (parent is IDisableableControl)
    {
       // here you use your logic, evaluate your parent you're dialing with and
       // enable/disable correspondingly 
       parent.Enabled = false;
       return;
    }
    foreach(var c in parent.Controls)
        SetAllControls(c);
} 

In real life, your TOP parent will be a form and will not need to be disabled, but it's certain children will. In fact, most of the time, once you found a UserControl which implements IDisableableControl that should be end of line, means, you don't need to go into children controls as they all sit on this parent and all will be disabled

T.S.
  • 18,195
  • 11
  • 58
  • 78
  • Thanks for your answer mate. It seems really clever. But as I am not a professional programmer myself, I lack background knowledge to apply your solution. I would have to study a bit to follow what you did, which would be nice, but I managed to do it in a different way, and my employer wants the program ASAP, so I will stick to the quicker solution. But thanks once again. – Charley R. Apr 07 '20 at 19:18
  • @CharleyR. hmmm. Are you afraid of my solution? In fact, my solution is simple! All you do, create an empty interface and a user control -> `add new-user control`. Then in user control, only add the interface. compile your app. Now you can drop your custom user control on a form or a panel. Drop it where you need a disableable surface. Now, on top of it drop your panel. This is all. Your solution is to actually list all your containers every time on every form. My solution is - 1 line of code with no specifying controls/containers. – T.S. Apr 07 '20 at 20:35
  • Sure, but mind you that I never went to "programming school", so to speak, and I am learning c# features at the time I need them. Before your comment, I had never heard of `interface` or ´user control`. I'd have to study that, which I surely will at some point. – Charley R. Apr 08 '20 at 11:15
  • @CharleyR. It would take less time to learn interface than write this comment. If cars were made by interface `ICar`, it would have properties like `Color, Doors, wheels, hood, trunk, seats..` and methods like `Stop, go, turn`. Then when you create your car you can make big wheels or small, red color or blue... But the consumer of your class will use `ICar` without knowing what exact car is there. Same in my suggestion to you, if you mark your control with it, consumer of this control will not need to know the control, only if the control marked by interface – T.S. Apr 08 '20 at 14:51
  • @CharleyR. And user control is also easy. Instead of placing your controls on the form, place it on user control and then place the user control on the form. Don't like user control, you know the panel, how about custom panel - `public class MyPanel : Panel, IDisableablePanel`. And work with your custom control/panel just like you do with .net-provided ones – T.S. Apr 08 '20 at 14:54
  • Although I already managed to do what I was trying to initially, I spent some time studying your solution as I couldn't stand the fact I wasn't able to understand it. I ran into this question: https://stackoverflow.com/questions/15741274/how-to-find-all-objects-that-implement-a-interface-and-invoke-a-method and I think I am finally able to apply your solution. Not in this problem, as I already solved it, but for sure in future ones. My deepest thanks. – Charley R. Apr 09 '20 at 18:01
  • @CharleyR.yep. This is very similar. you don't need these days to do `x=b as ISome`. Newer frameworks have **pattern matching** - `if (b is ISome x) x.DoSomething();` Looks like you're on the right track – T.S. Apr 09 '20 at 18:42
0

I manage to accomplish what I was trying to do with the code below, which is pretty much a blend of all the helpful answers I got:

public static void EnableContainer(params Control[] containers)
    {
        if(containers.Count() == 0) { return; }
        if (MyOF.isEnabled)
        {
            return;
        }
        else
        {
            try
            {
                foreach (var container in containers)
                {
                    foreach (Control control in container.Controls)
                    {
                        control.Enabled = false;
                    }
                }
            }
            catch (NullReferenceException)
            {
            }
        }
    }
    public static void EnableForm<form>(form f) where form : Form
    {
        if (MyOF.isEnabled)
        {
            return;
        }
        else
        {
            foreach(Control control in f.Controls)
            {
                control.Enabled = false;
            }
        }
    }

The community is welcome to suggest improvements as I am far from being a professional programmer. Thanks everyone once again.

Charley R.
  • 187
  • 1
  • 8
  • 2
    This is really redundant here `public static void EnableForm
    (form f) where form : Form`. First, don't do this `EnableForm
    ` simply do `EnableForm`, this is globally accepted norm. Now, what is the purpose? You already have `(form f)`. Generic here is out of place. The whole idea of generic is to be able to work with anything - `public static void EnableForm(T f)` , Now, you added `. where form : Form` - another restriction to form. So, just do `public static void EnableForm(form f)` and be done with this!
    – T.S. Apr 07 '20 at 21:19
  • 1
    You also don't need to check if containers.count == 0, since the foreach will simply not run. But you should replace it with `if (containers == null) return;` to avoid a `NullReferenceException`. You don't need an `else` since the `if` block returns. You don't need to catch a `NullReferenceException` because there's no code that will throw one. Also the name is misleading - calling `EnableContainers` will *disable* all the controls in it. – Rufus L Apr 07 '20 at 23:07
  • 1
    @T.S. yeah... reading your comment I found that I am still a bit confused about the whole idea of generic method, and even about the way C# handles objects and variables. But your comment helped things start to make sense in my head. Thanks a lot. – Charley R. Apr 08 '20 at 11:21
  • @RufusL you're completely right. I appreciate all of your suggestions. – Charley R. Apr 08 '20 at 11:23