Home > Software engineering >  Binding Nested Observable Collection in xaml using c# WPF
Binding Nested Observable Collection in xaml using c# WPF

Time:09-14

I am having a hard time trying to figure out how to bind in xaml a nested Observable Collection. The PLC class contains Tags. This might be familiar if you work in Automation. I have marked the areas of code I am having trouble with by saying "!Can't Figure this out". I am new to xaml and trying to do the binding in the xaml. If it can't be done in the xaml, a code behind solution would be helpful.

PLC Class

public class PLC
{
    public string Name { get; set; }
    public ObservableCollection<Tag> Tags { get; set; }
    public PLC(string name)
    {
        Name = name;
        Tags = new ObservableCollection<Tag>();
    }
    public override string ToString()
    {
        return Name;
    }
}

Tag Class The PLC's tags when you click on a PLC the ListView to the right will get the tags associated with that PLC.

public class Tag
{
    public Tag(string name, int value)
    {
        Name = name;
        Value = value;
    }
    public string Name { get; set; }
    public int Value { get; set; }
}

xaml - note this is a user control binded to the parent's viewmodel.

<UserControl x:Class="Test.UserControls.RuntimeControl"
             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="450" d:DesignWidth="800">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="20"></RowDefinition>
            <RowDefinition Height="*"></RowDefinition>
        </Grid.RowDefinitions>
        <Grid Grid.Row="0">
            <TextBlock Foreground="Red" Margin="10,0,0,0" >Runtime</TextBlock>
        </Grid>
        <Grid Grid.Row="1">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="*"></ColumnDefinition>
                <ColumnDefinition Width="*"></ColumnDefinition>
            </Grid.ColumnDefinitions>
            <ListView x:Name="PLCLV" Grid.Column="0" Margin="10" FontSize="25" SelectionMode="Single" ScrollViewer.VerticalScrollBarVisibility="Visible"
                      BorderThickness="0" ItemsSource="{Binding PLCs}">
                <ListView.ItemTemplate>
                    <DataTemplate>
                        <TextBlock Text="{Binding Name}" Foreground="{Binding ForegroundColor}"></TextBlock>
                    </DataTemplate>
                </ListView.ItemTemplate>
            </ListView>
            <ListView Grid.Column="1" Margin="10" FontSize="25" AlternationCount="2" ScrollViewer.VerticalScrollBarVisibility="Visible"
                      BorderThickness="0 " ItemsSource=***!Can't Figure this out!***>
                <ListView.ItemContainerStyle>
                    <Style TargetType="ListViewItem">
                        <Setter Property="Focusable" Value="false"/>
                    </Style>
                </ListView.ItemContainerStyle>
                <ListView.ItemTemplate>
                    <DataTemplate>
                        <TextBlock>
                            <Run Text = "Name: "/>
                            <Run Text =***!Can't Figure this out!***
                            <Run Text ="Value: "/>
                            <Run Text =***!Can't Figure this out!***
                        </TextBlock>
                    </DataTemplate>
                </ListView.ItemTemplate>
            </ListView>
        </Grid>
    </Grid>
</UserControl>

Trying Solutions Now.

CodePudding user response:

Assuming the DataContext of the UserControl is set correctly (i.e. the binding of PLCs is correct)

Add this property in code behind under PLCs collection property (probably you will need it later in code behind)

private PLC _selectedPlc;
public PLC SelectedPlc
{
    get => _selectedPlc;
    set 
    {
        _selectedPlc = value;
        OnPropertyChanged(nameof(SelectedPlc)); // this will update the second ListView
    }
}
public ObservableCollection PLCs {set; get;} // you have this already 

The first ListView can see all PLCs.. Let's bind the DataContext of the second ListView with SelectedPlc property, this way, the second ListView will only see one PLC.

<ListView x:Name="PLCLV" Grid.Column="0" Margin="10" FontSize="25" SelectionMode="Single" ScrollViewer.VerticalScrollBarVisibility="Visible"
          BorderThickness="0" ItemsSource="{Binding PLCs}"
          SelectedItem="{Binding SelectedPlc}">
    <ListView.ItemTemplate>
        <DataTemplate>
            <TextBlock Text="{Binding Name}" Foreground="{Binding ForegroundColor}"></TextBlock>
        </DataTemplate>
    </ListView.ItemTemplate>
</ListView>

<ListView Grid.Column="1" Margin="10" FontSize="25" AlternationCount="2" ScrollViewer.VerticalScrollBarVisibility="Visible"
          BorderThickness="0" 
          DataContext="{Binding SelectedPlc}"
          ItemsSource="{Binding Tags}">
    <ListView.ItemContainerStyle>
        <Style TargetType="ListViewItem">
            <Setter Property="Focusable" Value="false"/>
        </Style>
    </ListView.ItemContainerStyle>
    <ListView.ItemTemplate>
        <DataTemplate>
            <TextBlock>
                <Run Text= "Name: "/>
                <Run Text="{Binding Name}"
                <Run Text="Value: "/>
                <Run Text="{Binding Value}"
            </TextBlock>
        </DataTemplate>
    </ListView.ItemTemplate>
</ListView>

Do not forget to fill PLCs (and Tags inside each PLC) collections with some data!

Note: there is ForegroundColor property missing in your PLC class!

CodePudding user response:

First: you set the ItemsSource by list of PLC on "PLCLV" named ListView , so the type of object in SelectedItem of this ListView must be PLC type (or null), you can make this SelectedItem be the source of Tag list.

            <ListView Grid.Column="1" Margin="10" FontSize="25" AlternationCount="2" ScrollViewer.VerticalScrollBarVisibility="Visible"
                      BorderThickness="0 " ItemsSource="{Binding SelectedItem.Tags, ElementName=PLCLV}">
            <!-- Or SelectedItem.(local:PLC.Tags), the "(local:PLC.Tags)" means "the speicified property of specified type on unspecified type boxed property" -->
            <!-- you can use Binding.ElementName to find the sepcified "Name/x:Name" named element in visual tree to be the Binding source -->
                <ListView.ItemContainerStyle>
                    <Style TargetType="ListViewItem">
                        <Setter Property="Focusable" Value="false"/>
                    </Style>
                </ListView.ItemContainerStyle>
                <ListView.ItemTemplate>
                    <!-- "x:Type" is a speicial markup for return a specific "Type" object -->
                    <!-- Set DataType of DataTemplate can help XAML editor show IntelliSense to help you -->
                    <!-- "local:" is a prefix of namespace for the type in xml, defined by "xmlns:", here make the editor know the this DataTemplate is apply on the "Tag" type -->
                    <DataTemplate DataType="{x:Type local:Tag}">
                        <TextBlock>
                            <Run Text="Name: "/>
                            <Run Text="{Binding Name}" />
                            <Run Text="Value: "/>
                            <Run Text="{Binding Value}" />
                        </TextBlock>
                    </DataTemplate>
                </ListView.ItemTemplate>
            </ListView>

Or, use DataContext and ItemsSource:

<ListView Grid.Column="1" Margin="10" FontSize="25" AlternationCount="2" ScrollViewer.VerticalScrollBarVisibility="Visible"
          BorderThickness="0"
          DataContext="{Binding SelectedItem, ElementName=PLCLV}"
          ItemsSource="{Binding Tags}">
          <!-- Or ItemsSource="{Binding Path=(local:PLC.Tags)}" -->
...
  • Related