FloppyDisk
FloppyDisk

Reputation: 97

Binding multiple unique UserControls in a ListView to an ObservableCollection

I want the user to be able to select between 10 or more unique UserControls and display them in a list in any order with any number of multiple instances of any UserControl. So, the list might look like this UC1, UC1, UC3, UC3, UC 10, UC8, etc. It is important to note that the two UC1s and two UC3s in the previous example are different instances with different values associated with them. Essentially, I’m creating a sequence of commands that I’ll parse into a real-world actions.

Problem: I cannot bind my UserControls displayed in a ListView to the data in an ObservableCollection. At the very least, my bindings in the UserControls are just wrong and I am not sure how to get them to work.

I’m using MVVMLight. I have a Window control containing only a content control that has a UserControl loaded called CommandView, which is my main display. CommandView currently has several buttons with properties that bind to relay commands in the CommandViewModel that add unique class instances to an ObservableCollection called CommandsCollection. The ListView binds to a property that encapsulates the CommandsCollection.

I use a DataTemplate and a DataTemplateSelector in the code behind to select the correct UserControl (UC1, UC2, etc) based on the type of the class in that position in the collection. I have that working successfully, but I’m not binding the displayed UserControl textbox back to the ObservableCollection so no data is displayed in the textbox of the UserControl. I do set the default values of the class properties in the ObservableCollection and write the collection to a text file and see the values are the default. So the ObservableCollection is seeing the values.

How do I setup the binding in the user controls? Here’s what I have in UC1 User control:

    <lightext:UserControl
DataContext="{Binding VelocityCommandStatic, Source={StaticResource Locator}}">

<TextBox Text="{Binding VelocityCommandProperty.VelocityKinematic, 
         UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"/> 

VelocityCommandStatic – I’m actually a little confused by this as well, but this is the object (?) of the usercontrol created in the viewmodellocator.cs.

What is the best way to bind? Can the xaml binding point directly back to the ObservableCollection? One issue, each UserControl has different properties and I place a different class (instance of) for each user control in the collection. I would know the properties, but need correctly set them up in the xaml. Maybe this approach is not ideal. Can I have properties in the viewmodel for the UserControl point back to the correct position in the observablecolleciton in the CommandViewViewModel?

Upvotes: 1

Views: 1660

Answers (1)

Fede
Fede

Reputation: 44038

Look, you're overcomplicating it too much. All you need is an ObservableCollection that can hold all your items and a proper DataTemplate for each. There's no need for DataTemplateSelectors or any other stuff like that. Also, there's no need to point directly back to the ObservableCollection, whatever that means:

MainWindow:

<Window x:Class="WpfApplication5.Window3"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:WpfApplication5"
        Title="Window3" Height="300" Width="300">
    <Window.Resources>
        <DataTemplate DataType="{x:Type local:Item1}">
            <local:UserControl1/>
        </DataTemplate>
        <DataTemplate DataType="{x:Type local:Item2}">
            <local:UserControl2/>
        </DataTemplate>
        <DataTemplate DataType="{x:Type local:Item3}">
            <local:UserControl3/>
        </DataTemplate>
    </Window.Resources>
    <ListBox ItemsSource="{Binding}"/>
</Window>

Code Behind:

using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Windows;
using System.ComponentModel;

namespace WpfApplication5
{
    public partial class Window3 : Window
    {
        public Window3()
        {
            InitializeComponent();

            DataContext = new ObservableCollection<ItemBase>
                {
                    new Item1() {MyText1 = "This is MyText1 inside an Item1"},
                    new Item2() {MyText2 = "This is MyText2 inside an Item2"},
                    new Item3() {MyText3 = "This is MyText3 inside an Item3", MyBool = true}
                };
        }
    }

    public class ItemBase: INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        protected virtual void NotifyPropertyChange(string propertyName)
        {
            PropertyChangedEventHandler handler = PropertyChanged;
            if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
        }
    }

    public class Item1: ItemBase
    {
        private string _myText1;
        public string MyText1
        {
            get { return _myText1; }
            set
            {
                _myText1 = value;
                NotifyPropertyChange("MyText1");
            }
        }
    }

    public class Item2: ItemBase
    {
        private string _myText2;
        public string MyText2
        {
            get { return _myText2; }
            set
            {
                _myText2 = value;
                NotifyPropertyChange("MyText2");
            }
        }

        private ObservableCollection<string> _options;
        public ObservableCollection<string> Options
        {
            get { return _options ?? (_options = new ObservableCollection<string>()); }
        }

        public Item2()
        {
            Options.Add("Option1");
            Options.Add("Option2");
            Options.Add("Option3");
            Options.Add("Option4");
        }
    }

    public class Item3: ItemBase
    {
        private string _myText3;
        public string MyText3
        {
            get { return _myText3; }
            set
            {
                _myText3 = value;
                NotifyPropertyChange("MyText3");
            }
        }

        private bool _myBool;
        public bool MyBool
        {
            get { return _myBool; }
            set
            {
                _myBool = value;
                NotifyPropertyChange("MyBool");
            }
        }
    }
}

UserControl1:

<UserControl x:Class="WpfApplication5.UserControl1"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             mc:Ignorable="d" 
             d:DesignHeight="300" d:DesignWidth="300">
    <Border BorderBrush="Black" BorderThickness="1">
        <StackPanel>
            <TextBlock Text="This is UserControl1"/>
            <TextBlock Text="{Binding MyText1}"/>
        </StackPanel>
    </Border>
</UserControl>

UserControl2:

<UserControl x:Class="WpfApplication5.UserControl2"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             mc:Ignorable="d" 
             d:DesignHeight="300" d:DesignWidth="300">
    <Border BorderBrush="Black" BorderThickness="1">
        <StackPanel>
            <TextBlock Text="This is UserControl2"/>
            <TextBox Text="{Binding MyText2}"/>
            <ComboBox ItemsSource="{Binding Options}" SelectedItem="{Binding MyText2}"/>
        </StackPanel>
    </Border>
</UserControl>

UserControl3:

<UserControl x:Class="WpfApplication5.UserControl3"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             mc:Ignorable="d" 
             d:DesignHeight="300" d:DesignWidth="300">
    <Border BorderBrush="Black" BorderThickness="1">
        <StackPanel>
            <TextBlock Text="This is UserControl3"/>
            <TextBlock Text="{Binding MyText3}"/>
            <CheckBox Content="This is the MyBool Property" IsChecked="{Binding MyBool}"/>
        </StackPanel>
    </Border>
</UserControl>

Just Copy and paste My Code in a File - New - WPF Application and see the results for yourself. It looks like this:

enter image description here

Upvotes: 2

Related Questions