8

How can I make a boolean property like a RadioButton? You know, like a RadioButton, only one can be selected?

Like example below.

When I set one Employee IsResponsiblePerson to true, it should set all others to false. Without using a loop.

var list = new ObservableCollection<Employee>();

public class Employee
{
    public string Name{get;set;}
    public string Surname{get;set;}
    public bool IsResponsiblePerson{get;set;}
}
Massimiliano Kraus
  • 3,638
  • 5
  • 27
  • 47
user1702369
  • 1,089
  • 2
  • 13
  • 31
  • 5
    Use a loop..... **Or**, have another object which contains a property referring to the currently responsible person instead. Your data model tells a story indicating multiple employees can be the responsible one at the same time, if you don't want that, don't design it like that. – Lasse V. Karlsen Sep 07 '17 at 10:50
  • Show us how you are binding to UI in XAML or C# – Siva Gopal Sep 07 '17 at 10:53
  • 4
    The question is...why without using a loop? What's the problem with that? – Pikoh Sep 07 '17 at 10:55
  • 2
    @SivaGopal I don't think this has anything to do with the UI. I believe the OP only referred to `RadioButton` to try to explain the idea of what they need. – 41686d6564 stands w. Palestine Sep 07 '17 at 10:55
  • @AhmedAbdelhameed Though OP didn't mentioned, how about the `ObservableCollection` bind to radio group in XAML, checking one radio button would not update remaining properties as expected, with a correct binding? – Siva Gopal Sep 07 '17 at 10:57
  • looping is the best way. Since you asked if it is possible, there is a dirty way to set every Employee's property to false.: `list.All(a => { a.IsResponsiblePerson = false; return true; })` (This is not the intended use of `All` and I don't recommend it. Like, at all ) – adiga Sep 07 '17 at 11:01
  • @SivaGopal Maybe you're right. However the OP started the question with *"How can I make a boolean property.."*. Anyway, let's not turn this into a debate, you probably have a point there :) – 41686d6564 stands w. Palestine Sep 07 '17 at 11:02
  • 2
    Create a `class EmployeeCollection : ObservableCollection` that encapsulates this logic and has a property `Employee Responsible`. This class handles the custom event `ResponsiblePersonChanged` for every added `Employee`-instance. This event is invoked in `Employee` when `IsResponsiblePerson` is set to `true`. Then the property `Responsible` can be changed to the new employee-instance. You still need to modify the old responsible by setting `IsResponsiblePerson` to `false` before you assign the new. – Tim Schmelter Sep 07 '17 at 14:05
  • .... like this: https://stackoverflow.com/a/46098825/284240 – Tim Schmelter Sep 07 '17 at 14:52

3 Answers3

5

What i'd probably do if I didn`t want to use loops, is, as Lasse V. Karlsen says in a comment, store instead the name of the "ResponsiblePerson" in another property:

static string ResponsiblePerson {get;set;}

And change the IsResponsiblePerson property to something like this:

public bool IsResponsiblePerson 
{ 
    get 
    { 
        return this.Name == ResponsiblePerson; 
    }
    set 
    {   
        if (value)
        {
             ResponsiblePerson = this.Name;
        }
        else
        {
            if (this.Name == ResponsiblePerson)
            {
                ResponsiblePerson = "";
            }
        }
    }
}

Sample code:

List<Employee> employees = new List<Employee>() { new Employee() { Name = "name1" },
                                                    new Employee() { Name = "name2" },
                                                    new Employee() { Name = "name3" } };

Employee emp1 = employees.Where(x => x.Name == "name1").First();
emp1.IsResponsiblePerson = true;

Employee emp2 = employees.Where(x => x.Name == "name2").First();
emp2.IsResponsiblePerson = true;

foreach (Employee e in employees) 
{ 
     Console.WriteLine(e.IsResponsiblePerson); //false true false
}

I've made a DotNetFiddle sample here

Pikoh
  • 7,582
  • 28
  • 53
  • 1
    But how does this update the old responsible? _" it should set all others to false"_ – Tim Schmelter Sep 07 '17 at 13:15
  • Well @TimSchmelter, it does not need to update the others. As all the other `Employee.IsResponsiblePerson` return `this.Name == ResponsiblePerson`, in the moment you set `IsResponsiblePerson` for one Employee, all the others would return `false` when asked `IsResponsiblePerson` – Pikoh Sep 07 '17 at 13:18
  • But it doesn't change if another Employee.IsResponsiblePerson is set to true. So if you have a list of 10 Employee and then you set `IsResponsiblePerson` to `true` for every one, you will end up with 10 objects which all have their name as the ResponsiblePerson – Tim Schmelter Sep 07 '17 at 13:21
  • @TimSchmelter i didn't saw your edited comment. No, if you set true for all 10 employees, you'll end with the last one being the Responsible person. I added a DotNetFiddle link in my answer so you can check and try it yourself – Pikoh Sep 07 '17 at 13:39
  • @TimSchmelter Of course the ideal way would be a loop, but as OP wanted it without one this is the only way I can think of. Anyway, I don't think it's ugly, it's more or less the same as having a variable for the actual index of an array :) – Pikoh Sep 07 '17 at 14:01
  • @Pikoh What happens when you want two independent collections? Or worse, an employee not even part of a collection? – Rob Sep 07 '17 at 14:03
  • @Rob I didn't think that much ahead. But you could have a `Dictionary` to store the collections and the `ResponsiblePerson` for example – Pikoh Sep 07 '17 at 14:05
  • @Pikoh: i have tried to provide an approach which encapsulates the logic in my [comment above](https://stackoverflow.com/questions/46094457/only-set-property-on-one-object#comment79158870_46094457) – Tim Schmelter Sep 07 '17 at 14:06
  • @TimSchmelter of course that makes a lot of sense! That's probably the way to go, I just made a quick answer to show a possible way to manage the OP problem, but of course that this approach could be greatly improved :) – Pikoh Sep 07 '17 at 14:07
  • @Rob Anyway,I just tried to throw a possible approach to OP question. I'd probably never implement something like this and i'll use LINQ or a loop. The thing is that, for the OP requirements, I think this answer works. Of course,as I already commented to Tim, this could be greatly improved, but I don't think this is the place to explore this more in deep. Thank you for your comments :) – Pikoh Sep 07 '17 at 14:12
4

This is the ideal use case for a new collection type that inherits from List<Employee> or ObservableCollection<Employee>. There you can encapsulate the whole logic.

Before you should make your Employee class more intelligent by a custom event, the collection class wants to know when the property IsResponsiblePerson was set to true because then it has to change the old responsible. For example like this:

public class Employee
{
    public string Name { get; set; }
    public string Surname { get; set; }

    public event EventHandler ResponsiblePersonChanged;

    private bool _isResponsiblePerson;
    public bool IsResponsiblePerson
    {
        get => _isResponsiblePerson;
        set
        {
            _isResponsiblePerson = value;
            if (_isResponsiblePerson)
            {
                ResponsiblePersonChanged?.Invoke(this, EventArgs.Empty);
            }
        }
    }
}

Now the collection class could be implemented like following, it handles every Employee's ResponsiblePersonChanged event:

public class EmployeeCollection : ObservableCollection<Employee>
{
    public Employee Responsible { get; private set; }

    public EmployeeCollection():base(){}
    public EmployeeCollection(IEnumerable<Employee> employees) : base()
    {
        foreach (Employee e in employees)
        {
            if (e.IsResponsiblePerson)
            {
                if(Responsible != null)
                    throw new ArgumentException("Multiple responsible employees aren't allowed", nameof(employees));
                Responsible = e;
            }
            base.Add(e);
        }
    }

    public new void Add(Employee emp)
    {
        base.Add(emp);
        if (emp.IsResponsiblePerson)
        {
            MakeResponsible(emp);
        }
        emp.ResponsiblePersonChanged -= ResponsibleChanged;
        emp.ResponsiblePersonChanged += ResponsibleChanged;
    }

    private void ResponsibleChanged(Object sender, EventArgs e)
    {
        MakeResponsible(sender as Employee);
    }

    private void MakeResponsible(Employee employee)
    {
        if(!employee.IsResponsiblePerson)
            throw new ArgumentException("Employee is not responsible but should be", nameof(employee));

        if (Responsible != null && !Responsible.Equals(employee))
            Responsible.IsResponsiblePerson = false;
        Responsible = employee;
    }
}

An example:

var list = new EmployeeCollection();
list.Add(new Employee { Name = "1", IsResponsiblePerson = true });
list.Add(new Employee { Name = "2", IsResponsiblePerson = false });
list.Add(new Employee { Name = "3", IsResponsiblePerson = false });
list.Add(new Employee { Name = "4", IsResponsiblePerson = false });
list.Add(new Employee { Name = "5", IsResponsiblePerson = false });

list.Last().IsResponsiblePerson = true;  // now the first employee's IsResponsiblePerson is set to false
Tim Schmelter
  • 450,073
  • 74
  • 686
  • 939
1

Unfortunately, your example gets close to being able to handle the "changed" event for the boolean, but not quite. Your example only allows handling of changes to the collection rather than changes to one of its internal elements.

To do that, you'd need to implement INotifyPropertyChanged, here's an example. Though bare in mind there's a million Notification mechanisms so do some research on it.

To answer your question ("How do I update other members of a collection when an element's property changes") you'd need to handle the changed event for the specific property, in your case IsResponsiblePerson. Then, in that handler, you can use Linq (which internally uses loops), but whatever happens, you will ALWAYS have to perform an operation on the entire list (maybe could break the loop early) in order to achieve what you're asking.

Another concept to look into is ObservableObject, very closely related to INotifyPropertyChanged.Here's the doc on it

Dan Rayson
  • 1,315
  • 1
  • 14
  • 37