0

I am using MVVMLight and need to be able to make programmatic edits to the properties about 12 toggle boxes during the initialization of a View. Since there are so many of them and I would like to iterate through the linked properties I am trying to use an ObservableCollection of an ObservableObject containing the values to be bound to the properties of the toggle buttons. I am unsure if this is a binding issue or incorrect implementation of the INotifyPropertyChanged interface inherited from the ObservableObject.

Here is the class containing the properties I wish to bind to:

public class CavitySelect : ObservableObject
{
    private string _Text;
    public string Text
    {
        get { return _Text; }
        set
        {
            _Text = value;
            RaisePropertyChanged("Text");
        }
    }
    private bool _Visible;
    public bool Visible
    {
        get { return _Visible; }
        set
        {
            _Visible = value;
            RaisePropertyChanged("Visible");
        }
    }
    private bool _Toggle;

    public bool Toggle
    {
        get { return _Toggle; }
        set
        {
            _Toggle = value;
            RaisePropertyChanged("Toggle");
        }
    }

    public CavitySelect()
    {
        Text = "";
        Visible = false;
        Toggle = false;
    }
}

Here is the instantiation of my ObservableCollection:

private ObservableCollection<CavitySelect> _CavTogglesProperties;
public ObservableCollection<CavitySelect> CavTogglesProperties
{
    get { return _CavTogglesProperties; }
    set
    {
        _CavTogglesProperties = value;
        RaisePropertyChanged("CavTogglesProperties");
    }
}

public MyViewModel()
{
    this.CavTogglesProperties = GetCavities();
}    

public ObservableCollection<CavitySelect> GetCavities()
{
    CavitySelect t11 = new CavitySelect();
    CavitySelect t12 = new CavitySelect();
    CavitySelect t13 = new CavitySelect();
    CavitySelect t14 = new CavitySelect();
    CavitySelect t15 = new CavitySelect();
    CavitySelect t16 = new CavitySelect();
    CavitySelect t26 = new CavitySelect();
    CavitySelect t21 = new CavitySelect();
    CavitySelect t22 = new CavitySelect();
    CavitySelect t23 = new CavitySelect();
    CavitySelect t24 = new CavitySelect();
    CavitySelect t25 = new CavitySelect();
    ObservableCollection<CavitySelect> temp = new ObservableCollection<CavitySelect>() {t11,t12,t13,t14,t15,t16,t21,t22,t23,t24,t25,t26};
    return temp;
}

And here is how I am attempting to bind it:

<Window.Resources>
    <BooleanToVisibilityConverter x:Key="BoolToVisibilty"/>
</Window.Resources>
<Grid Background="#FFF4F4F5" Margin="8,165,8,8"  DataContext="{Binding CavTogglesProperties}">
    <ToggleButton DataContext="{Binding t11}" Content="{Binding Text}" IsChecked="{Binding Toggle}" Visibility="{Binding Visible,Converter={StaticResource BoolToVisibilty}}"/> 
</Grid>

I have confirmed the binding of the View to the ViewModel class is working properly. I have also tried binding without setting the DataContext of the containing Grid first, like:

<ToggleButton DataContext="{Binding CavTogglesProperties[t11]}" ... />

For clarification: Each one of CavitySelect items is related to a toggle button in a GridView, and the properties will be initialized based on an input not shown.

chodge
  • 11
  • 4
  • Do you want to show a list of ToggleButtons, one for each item in CavTogglesProperties? Or just show a single item from CavTogglesProperties? – Colin Williams Feb 21 '20 at 01:47
  • Answered with both explanations instead. Hopefully that helps! – Colin Williams Feb 21 '20 at 02:10
  • I want to be able to change which ToggleButtons are visible and their text programmatically. – chodge Feb 21 '20 at 02:15
  • Awesome. In that case, take a look at the first half of my answer - Displaying a collection of items. That shows how to use a ListView, and points to another question explaining how to add a filter to the collection powering your ListView. Then, you can filter based on the "Visible" property as desired. – Colin Williams Feb 21 '20 at 02:16

1 Answers1

2

Displaying a collection of items

Your question doesn't make it perfectly clear, but I believe you want to show a list of items in your UI; however, as currently implemented, your XAML is really structured to show a single item.

To show a list, you'll want to look at any of the variety of collection views (e.g. ListView, GridView, etc.).

For example:

<Window.Resources>
    <BooleanToVisibilityConverter x:Key="BoolToVisibilty"/>
</Window.Resources>
<Grid Background="#FFF4F4F5" Margin="8,165,8,8">
  <ListView ItemsSource="{Binding CavTogglesProperties}">
    <ListView.ItemTemplate>
      <DataTemplate>
        <ToggleButton Content="{Binding Text}" IsChecked="{Binding Toggle}" />
      </DataTemplate>
    </ListView.ItemTemplate>
  </ListView>
</Grid>

To only show specific items, you should filter the list in the ViewModel, rather than binding the visibility. If the items visible doesn't change while being displayed, simply filter the list when you're populating it.

If, however, the visibilities will change and you want the view to reflect those changes, look at implementing a filter for your items source as explained in this question: How do I Filter ListView in WPF?.

As you're already using an ObservableCollection and ObservableObject, everything should automatically update.

Showing a single item from a collection

If I read your question wrong and you want to know how to show a single item from a collection, there's a couple of different ways of approaching this:

  1. Expose the individual item as a property on the ViewModel so the View doesn't have to dig into the list.
  2. Create a converter that takes in the collection and index, then pulls out the correct item.

I would highly recommend going with option 1, though, as it most conforms with MVVM, producing the cleanest and most testable code.

For example:

private ObservableCollection<CavitySelect> _CavTogglesProperties;
public ObservableCollection<CavitySelect> CavTogglesProperties
{
    get { return _CavTogglesProperties; }
    set
    {
        _CavTogglesProperties = value;
        RaisePropertyChanged("CavTogglesProperties");
    }
}

private CavitySelect _SpecificCavToggle;
public CavitySelect SpecificCavToggle
{
    get { return _SpecificCavToggle; }
    set
    {
        _SpecificCavToggle= value;
        RaisePropertyChanged("SpecificCavToggle");
    }
}

public MyViewModel()
{
    this.CavTogglesProperties = GetCavities();
    this.SpecificCavToggle = this.CavTogglesProperties[0];
}    

public ObservableCollection<CavitySelect> GetCavities()
{
    CavitySelect t11 = new CavitySelect();
    CavitySelect t12 = new CavitySelect();
    CavitySelect t13 = new CavitySelect();
    CavitySelect t14 = new CavitySelect();
    CavitySelect t15 = new CavitySelect();
    CavitySelect t16 = new CavitySelect();
    CavitySelect t26 = new CavitySelect();
    CavitySelect t21 = new CavitySelect();
    CavitySelect t22 = new CavitySelect();
    CavitySelect t23 = new CavitySelect();
    CavitySelect t24 = new CavitySelect();
    CavitySelect t25 = new CavitySelect();
    ObservableCollection<CavitySelect> temp = new ObservableCollection<CavitySelect>() {t11,t12,t13,t14,t15,t16,t21,t22,t23,t24,t25,t26};
    return temp;
}
Colin Williams
  • 173
  • 1
  • 10
  • I apologise for the misunderstanding, I should have mentioned that I took a large chunk of code out to ease readability. I believe the second half of your answer is what I am looking for. Either approach seems to suggest that the view will be unable to communicate directly with individual elements in the Observable Collection. Is this correct? – chodge Feb 21 '20 at 02:55
  • My answer was based around presentation. So depending on whether you want to present the whole collection or a single item, refer to the separate halves of my answer. Regardless of which presentation technique you use, you can use {Binding Property, Mode=TwoWay} to create what is called a two-way binding. This will allow the view to set a property on the ViewModel. For example, if you changed your “IsChecked={Binding IsToggled}” binding to be two-way, the ViewModel’s IsToggled property would be set to true if that control set it as such. Is that more so what you’re looking for? – Colin Williams Feb 21 '20 at 03:12
  • Here’s a question with a bunch more detail on two-way bindings: https://stackoverflow.com/questions/320028/two-way-binding-in-wpf – Colin Williams Feb 21 '20 at 03:14
  • Continuing with the IsToggled example, if you want some other property (e.g. text) to be updated in response to IsToggled changing, you can do that from the setter of IsToggled. When the other property changes, View will update thanks to INotifyPropertyChanged. – Colin Williams Feb 21 '20 at 03:16
  • The second part of your answer is what I was originally asking, I wanted to try to understand why the binding broke down with an extra layer added. – chodge Feb 21 '20 at 03:29
  • Ahhh I see. The reason your bindings broke down is that they were invalid. For your first binding (Binding t11), the t11 property doesn’t exist anywhere, really. It was a local variable in the GetProperties method. For your second variant (Binding CavToggleProperties[t11]), once again t11 doesn’t exist. If you change t11 to a valid index (e.g. 0), then it should work. Though to me it’s still better code to expose them specifically as properties in the ViewModel. If that feels cumbersome because you have so many to display, then you should be using a ListView or similar. – Colin Williams Feb 21 '20 at 03:47
  • To help with debugging binding failures, since they fail silently, I recommend doing 2 things: 1. Use x:Bind where possible - it’s evaluated at compile time, which means errors are exposed during compilation and it’s also more performant; 2. In your App.xaml.cs constructor, subscribe to DebugSettings.BindingFailed and set DebugSettings.IsBindingTracingEnabled = true, then Debug.Fail() in the event handler. This way if you have to use a regular binding and it does fail, at least you’ll be told about it and the event args will contain extra details about what, exactly, it’s looking for. – Colin Williams Feb 21 '20 at 03:50
  • 1
    To wrap up: This is the answer I was looking for, and I went with a hybrid of the two approaches by adding a method that exposes the individual Observable objects contained in the Observable Collection (using the second example above) that is called in the set method of the observable collection. – chodge Feb 21 '20 at 22:20