how to load two-way binded XAML controls from collection of controls based on a value.

guess_me_if_u_can 126 Reputation points
2020-03-21T07:15:53.287+00:00

In my scenario, i have about 30-40 two way binding xaml controls like TextBox, ComboBox etc. in my UserControl. All the XAML controls will not be shown at the same time. Instead it will be displayed only based on the value named EntityName. If the EntityName is User, then the XAML controls related for User will be shown. All the XAML controls are not in sequential order among the Entity, Hence I will not able to put all the controls in a Grid and load based on x:Load. Also I dont want to load all the XAML controls for showing only 5 controls for any entity.

i) I have tried adding these controls in UserControl resources with x:key and add the required controls to the stackpanel from code behind. But the app crashes because of two way binding in it.

ii) I have tried the same with x:Name. But using x:Name will load all the controls initially. (if i am right.... ?)

As a summary, I need a sample solution on how to load two-way binding controls to a Usercontrol based on a value without loading other non-required controls.

Universal Windows Platform (UWP)
0 comments No comments
{count} votes

3 answers

Sort by: Most helpful
  1. Peter Fleischer (former MVP) 19,311 Reputation points
    2020-03-22T05:56:54.913+00:00

    Hi,
    you can use ItemsControl to display a variable count of UI elements. try following demo.

    XAML:

    <Window x:Class="Window93"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
            xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
            xmlns:local="clr-namespace:WpfApp1"
            mc:Ignorable="d"
            Title="Window93" Height="450" Width="800">
      <Window.DataContext>
        <local:Window93VM/>
      </Window.DataContext>
      <Grid>
        <Grid.RowDefinitions>
          <RowDefinition Height="Auto"/>
          <RowDefinition/>
        </Grid.RowDefinitions>
        <ComboBox ItemsSource="{Binding SelectList}" 
                  SelectedItem="{Binding SelectedVersion}" 
                  Margin="5"/>
        <ScrollViewer Grid.Row="1">
          <ItemsControl ItemsSource="{Binding Elements}"/>
        </ScrollViewer>
      </Grid>
    </Window>
    

    And ViewModel:

    Imports System.Collections.ObjectModel
    Imports System.ComponentModel
    
    Public Class Window93VM
    
    #Region " prepare demo"
      Public Sub New()
        ' connect collection to source for displaying elements
        cvs.Source = col
        ' prepare data for demo
        For i = 1 To _data.GetUpperBound(0)
          Data(i) = $"Data {i}"
        Next
        ' prepare control list for demo
        Dim rnd As New Random
        For i = 1 To 10
          Dim l As New List(Of Integer)
          For k = 1 To rnd.Next(1, 20)
            l.Add(rnd.Next(1, 101))
          Next
          controlList.Add($"Version {i}", l)
        Next
      End Sub
    #End Region
    
    #Region " collection of framework elements for display (in ItemsSource)"
      Private cvs As New CollectionViewSource
      Private col As New ObservableCollection(Of FrameworkElement)
    
      Public ReadOnly Property Elements As ICollectionView
        Get
          Return cvs.View
        End Get
      End Property
    #End Region
    
    #Region " list for select elements to display (in ComboBox)"
      Private controlList As New Dictionary(Of String, List(Of Integer))
    
      Public ReadOnly Property SelectList As List(Of String)
        Get
          Return controlList.Keys.ToList
        End Get
      End Property
    
      Private _selectedVersion As String
      Public Property SelectedVersion As String ' selected item in SelectList
        Get
          Return Me._selectedVersion
        End Get
        Set(value As String)
          Me._selectedVersion = value
          LoadData(controlList(value))
        End Set
      End Property
    #End Region
    
    #Region " array of data for binding"
      Private _data(100) As String
      Public Property Data(index As Integer) As String
        Get
          Return Me._data(index)
        End Get
        Set(value As String)
          Me._data(index) = value
        End Set
      End Property
    #End Region
    
    #Region " load elements to display"
      Private Sub LoadData(cList As List(Of Integer))
        col.Clear() ' clear list for display in ItemsControl
        For Each item In cList
          Dim tb As New TextBox ' element to display (TextBox or other UserControl)
          Dim b As New Binding($"Data[{item}]") ' prepare binding to data
          tb.SetBinding(TextBox.TextProperty, b) ' set binding
          tb.Margin = New Thickness(5) ' set additional properties
          col.Add(tb) ' add element to display in ItemsControl
        Next
      End Sub
    #End Region
    
    End Class
    

  2. Peter Fleischer (former MVP) 19,311 Reputation points
    2020-03-22T08:16:44.297+00:00

    Hi, you can use ItemsControl to display a variable count of UI elements. Try following C# demo.

    XAML:

    <Window x:Class="WpfApp1.Window16"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
            xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
            xmlns:local="clr-namespace:WpfApp16"
            mc:Ignorable="d"
            Title="Window16" Height="450" Width="800">
      <Window.DataContext>
        <local:ViewModel/>
      </Window.DataContext>
      <Grid>
        <Grid.RowDefinitions>
          <RowDefinition Height="Auto"/>
          <RowDefinition/>
        </Grid.RowDefinitions>
        <ComboBox ItemsSource="{Binding SelectList}" 
                  SelectedItem="{Binding SelectedVersion}" 
                  Margin="5"/>
        <ScrollViewer Grid.Row="1">
          <ItemsControl ItemsSource="{Binding Elements}"/>
        </ScrollViewer>
      </Grid>
    </Window>
    

    Code:

    using System;
    using System.Collections.Generic;
    using System.Collections.ObjectModel;
    using System.ComponentModel;
    using System.Linq;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Data;
    
    namespace WpfApp16
    {
      public class ViewModel
      {
        #region prepare demo
        public ViewModel()
        {
          // connect collection to source for displaying elements
          cvs.Source = col;
          // prepare data for demo
          for (int i = 1; i <100; i++) Data[i] = $"Data {i}";
          // prepare control list for demo
          Random rnd = new Random();
          for (int i = 1; i < 10; i++)
          {
            List<int> l = new List<int>();
            for (int k = 0; k < rnd.Next(1, 20); k++) l.Add(rnd.Next(1, 101));
            controlList.Add($"Version {i}", l);
          }
        }
        #endregion
    
        #region collection of framework elements for display (in ItemsSource)
        CollectionViewSource cvs = new CollectionViewSource();
        private ObservableCollection<FrameworkElement> col = new ObservableCollection<FrameworkElement>();
        public ICollectionView Elements { get => cvs.View; }
        #endregion
    
        #region list for select elements to display (in ComboBox)
        private Dictionary<string, List<int>> controlList = new Dictionary<string, List<int>>();
        public List<string> SelectList { get => controlList.Keys.ToList(); }
        private string _selectedVersion;
        public string SelectedVersion
        {
          get { return this._selectedVersion; }
          set { this._selectedVersion = value; LoadData(controlList[value]); }
        }
        #endregion
    
        #region " array of data for binding"
        public clsData Data { get; private set; } = new clsData();
        #endregion
    
        #region load elements to display
        private void LoadData(List<int> cList)
        {
          col.Clear(); // clear list for display in ItemsControl
          foreach (var item in cList)
          {
            TextBox tb = new TextBox(); // element to display (TextBox or other UserControl)
            Binding b = new Binding($"Data[{item}]");  // prepare binding to data
            tb.SetBinding(TextBox.TextProperty, b); // set binding
            tb.Margin = new Thickness(5); // set additional properties
            col.Add(tb); // add element to display in ItemsControl
          }
        }
        #endregion
      }
      public class clsData
      {
        private string[] _data = new string[100];
        public string this[int i]
        {
          get { return this._data[i]; }
          set { this._data[i] = value; }
        }
      }
    }
    

  3. Peter Fleischer (former MVP) 19,311 Reputation points
    2020-03-22T15:26:15.58+00:00

    Hi, try this another approach with UserControls. Page03 contains a base UserControl.

    XAML Page

    <Page
        x:Class="App1.Page03"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="using:App03"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        mc:Ignorable="d"
        Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
      <Page.DataContext>
        <local:ViewModel/>
      </Page.DataContext>
      <Grid>
        <Grid.ColumnDefinitions>
          <ColumnDefinition Width="Auto"/>
          <ColumnDefinition/>
        </Grid.ColumnDefinitions>
        <ComboBox ItemsSource="{Binding SelectList}" 
                  SelectedItem="{Binding SelectedVersion, Mode=TwoWay}" 
                  Margin="5"/>
        <ScrollViewer Grid.Column="1">
          <ContentControl Content="{Binding BaseElement}"/>
        </ScrollViewer>
      </Grid>
    </Page>
    

    XAML UserControls: base UserControl (Page03UC0) with ItemsControl for UserControls Page03UC1 and Page03UC3. Base UserControls (Page03UC0) uses the DataContext from main page. Embedded UserControls use Data[n] for DataContext.

    <UserControl
        x:Class="UserControlLib1.Page03UC0"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="using:UserControlLib1"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        mc:Ignorable="d"
        d:DesignHeight="300"
        d:DesignWidth="400">
      <StackPanel>
        <ItemsControl ItemsSource="{Binding Elements}"/>
      </StackPanel>
    </UserControl>
    
    <UserControl
        x:Class="UserControlLib1.Page03UC1"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="using:UserControlLib1"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        mc:Ignorable="d"
        d:DesignHeight="300"
        d:DesignWidth="400">
      <StackPanel Orientation="Horizontal">
        <TextBlock Text="UserControl Page03UC1" Margin="5"/>
        <TextBlock Text="{Binding}" Margin="5"/>
      </StackPanel>
    </UserControl>
    
    <UserControl x:Name="uc"
        x:Class="UserControlLib1.Page03UC2"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="using:UserControlLib1"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        mc:Ignorable="d"
        d:DesignHeight="300"
        d:DesignWidth="400">
      <Grid>
        <Border BorderBrush="Red" BorderThickness="3">
          <StackPanel Orientation="Horizontal">
            <TextBlock Text="UserControl Page03UC2" Margin="10"/>
            <TextBlock Text="{Binding}" Margin="10"/>
          </StackPanel>
        </Border>
      </Grid>
    </UserControl>
    

    and classes:

    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Linq;
    using System.Runtime.CompilerServices;
    using UserControlLib1;
    using Windows.UI.Xaml;
    using Windows.UI.Xaml.Controls;
    
    namespace App03
    {
      public class ViewModel : INotifyPropertyChanged
      {
        #region prepare demo
        public ViewModel()
        {
          // prepare data for demo
          for (int i = 1; i < 100; i++) Data[i] = $"Data {i}";
          // prepare control list for demo
          Random rnd = new Random();
          for (int i = 1; i < 10; i++)
          {
            List<Szenario> l = new List<Szenario>();
            for (int k = 0; k < rnd.Next(2, 5); k++)
            {
              Szenario sc = new Szenario() { DataIndex = rnd.Next(1, 101) };
              sc.ClassType = (rnd.NextDouble() > .5) ? typeof(Page03UC1) : typeof(Page03UC2);
              l.Add(sc);
            }
            controlList.Add($"Version {i}", l);
          }
        }
        #endregion
    
        #region BaseElement and collection of framework elements for display (in ItemsSource in BaseElement)
        public FrameworkElement BaseElement { get; set; }
        private List<FrameworkElement> col = new List<FrameworkElement>();
        public List<FrameworkElement> Elements { get => col; }
        #endregion
    
        #region list for select elements to display (in ComboBox)
        private Dictionary<string, List<Szenario>> controlList = new Dictionary<string, List<Szenario>>();
        public List<string> SelectList { get => controlList.Keys.ToList(); }
        private string _selectedVersion;
        public string SelectedVersion
        {
          get { return this._selectedVersion; }
          set { this._selectedVersion = value; LoadData(controlList[value]); }
        }
        #endregion
    
        #region " array of data for binding"
        public clsData Data { get; private set; } = new clsData();
        #endregion
    
        #region load elements to display
        private void LoadData(List<Szenario> scList)
        {
          col.Clear(); // clear list for display in ItemsControl
          foreach (var item in scList)
          {
            var uc = (FrameworkElement)Activator.CreateInstance(item.ClassType);
            uc.DataContext = Data[item.DataIndex];
            col.Add(uc);
          }
          BaseElement = new Page03UC0();
          OnPropertyChanged(nameof(BaseElement));
        }
        #endregion
    
        public event PropertyChangedEventHandler PropertyChanged;
        protected void OnPropertyChanged([CallerMemberName] string propertyName = "") =>
       PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
      }
      public class clsData
      {
        private string[] _data = new string[100];
        public string this[int i]
        {
          get { return this._data[i]; }
          set { this._data[i] = value; }
        }
      }
    
      public class Szenario
      {
        public Type ClassType { get; set; }
        public int DataIndex { get; set; }
      }
    }