Search code examples
c#wpfmvvmdata-bindingdatacontext

Getting Items From Within ItemsControl (Understanding DataContext)


I am populating an ItemsControl with various elements, including Buttons and ComboBox elements. Accessing and populating the elements is simple, but I'm stuck on how to detect and associate which Item in the ItemsControl the ComboBox (or Button) belongs to.

To help illustrate the problem, consider the following basic UI:

Simple Example of A TextBlock, ComboBox, and RadioButton (with ToggleButton Style) inside an ItemsControl

Now, when I use the ComboBox or Button I want to be able to associate that use only with the ItemControl Item it's a part of. However, currently, if I select an item in the ComboBox every ComboBox in the ItemsControl will reflect that change.

I can capture the SelectedItem in the below ListBox, but ideally, I would like to be able to display both the SelectedItem and which ItemControl Item it came from. For instance, ComboBoxItem1, My First Property - From Item (1).

I am strictly adhering to MVVM principals, and consequently, I am not looking for any solutions using code-behind.

TL;DR

I know the code can become unwieldy. I believe the above description is adequate to state my problem, but I am including the basic boiler plate code below in case it's helpful in posting an answer. (Obviously, I have implemented INotifyProperty and ICommand elsewhere):

MainWindowView.xaml

<ItemsControl  Width="300" Height="200" ItemsSource="{Binding MyObservableCollection}">
    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <Border BorderBrush="Black" BorderThickness="2" Margin="10">
                <StackPanel Margin="0,10,0,10">
                    <TextBlock Margin="10,0,0,0" Text="{Binding MyProperty}" FontWeight="Bold"/>
                    <ComboBox Width="270" Text="myBox" ItemsSource="{Binding DataContext.ComboOptions, RelativeSource={RelativeSource AncestorType=ItemsControl}}" DisplayMemberPath="ListItem" SelectedItem="{Binding DataContext.SelectedItem, RelativeSource={RelativeSource AncestorType=Window}}"/>
                    <RadioButton Width ="270" Content="Button1" Command="{Binding DataContext.GetButtonCommand, RelativeSource={RelativeSource AncestorType=ItemsControl}}" CommandParameter="Button1" Style="{DynamicResource {x:Type ToggleButton}}"/>
                </StackPanel>
            </Border>
        </DataTemplate>
    </ItemsControl.ItemTemplate>
</ItemsControl>

MyComboBoxOptionsViewModel.cs

public class MyComboBoxOptionsViewModel : ObservableObject
{
    private MyComboBoxOptionsModel _myComboBoxOptions = new MyComboBoxOptionsModel();

    public MyComboBoxOptionsViewModel(MyComboBoxOptionsModel _myComboBoxOptions)
    {
        this._myComboBoxOptions = _myComboBoxOptions;
    }

    public string ComboBoxOption
    {
        get { return _myComboBoxOptions.ComboBoxOption; }
        set
        {
            _myComboBoxOptions.ComboBoxOption = value;
            RaisePropertyChangedEvent("ComboBoxOption");
        }
    }
}

MyComboBoxOptionsModel.cs

public class MyComboBoxOptionsModel
{
    public string ComboBoxOption { get; set; }
}

MainWindowViewModel.cs

public class MainWindowViewModel : ObservableObject
{
    private ObservableCollection<string> _messages = new ObservableCollection<string>();
    private ObservableCollection<MyViewModel> _myObservableCollection = new ObservableCollection<MyViewModel>();
    private List<MyComboBoxOptionsViewModel> _comboOptions = new List<MyComboBoxOptionsViewModel>();
    private MyComboBoxOptionsViewModel _selectedItem = new MyComboBoxOptionsViewModel(null);

    public MainWindowViewModel()
    {
        _myObservableCollection.Add(new MyViewModel(new MyModel { MyProperty = "My First Property" }));
        _myObservableCollection.Add(new MyViewModel(new MyModel { MyProperty = "My Second Property" }));

        _comboOptions.Add(new MyComboBoxOptionsViewModel(new MyComboBoxOptionsModel { ComboBoxOption = "Option1" }));
        _comboOptions.Add(new MyComboBoxOptionsViewModel(new MyComboBoxOptionsModel { ComboBoxOption = "Option2" }));
        _comboOptions.Add(new MyComboBoxOptionsViewModel(new MyComboBoxOptionsModel { ComboBoxOption = "Option3" }));
    }

    public MyComboBoxOptionsViewModel SelectedItem
    {
        get { return _selectedItem; }
        set
        {
            _selectedItem = value;
            _messages.Add(_selectedItem.ComboBoxOption);
            RaisePropertyChangedEvent("SelectedItem");
        }
    }

    public List<MyComboBoxOptionsViewModel> ComboOptions
    {
        get { return _comboOptions; }
        set
        {
            if (value != _comboOptions)
            {
                _comboOptions = value;
                RaisePropertyChangedEvent("ComboOptions");
            }
        }
    }

    public ObservableCollection<MyViewModel> MyObservableCollection
    {
        get { return _myObservableCollection; }
        set
        {
            if (value != _myObservableCollection)
            {
                _myObservableCollection = value;
                RaisePropertyChangedEvent("MyObservableCollection");
            }
        }
    }

    public ObservableCollection<string> Messages
    {
        get { return _messages; }
        set
        {
            if (value != _messages)
            {
                _messages = value;
                RaisePropertyChangedEvent("Messages");
            }
        }
    }
}

Solution

  • I'm looking at the UI you want and think you basically need a main view model with a collection of item view models.

    In that item view model create a command and a selected item property you can bind in your template to the combo box and button. That gives you a strict mvvm binding to a single instance of the combo box value and a command which is executed by the single instance of the button.

    Your bindings for combo box items will then need an explicit source as part of the binding so you can hook into one collection of values from the main view model. Or add a collection to your item view model and keep it all nice a clean and together.

    As you mention, you're code is very detailed - which is great - but I may have missed some other meaning from it.

    Apologies if this is an answer to the wrong question :)