question

guessmeifucan-8050 avatar image
0 Votes"
guessmeifucan-8050 asked ·

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

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.

windows-uwpwindows-uwp-xaml
10 |1000 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

PeterFleischer-3316 avatar image
0 Votes"
PeterFleischer-3316 answered ·

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
· 1 · Share
10 |1000 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

@PeterFleischer-3316 Kindly provide me the solution in C#

0 Votes 0 · ·
PeterFleischer-3316 avatar image
0 Votes"
PeterFleischer-3316 answered ·

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; }
     }
   }
 }


· 1 · Share
10 |1000 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

@PeterFleischer-3316 Creating XAML controls in C# code is quiet complicated compared to creating it in XAML. In my controls list there are many Custom Controls with many properties. Is it possible to create the controls in XAML code and add it to the ItemsControl from C# code ?

0 Votes 0 · ·
PeterFleischer-3316 avatar image
0 Votes"
PeterFleischer-3316 answered ·

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; }
   }
 }
· 2 · Share
10 |1000 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

@PeterFleischer-3316 Thanks for the answer. Is that we able to created two or more UserControl in same file ? or i need to create each in separate xaml file ? Also your solution is not suiting my case. In my case i have normal i) Textbox, ii) ComboBox, iii) My CustomControl named AdvancedSearchComboBox, iv) StackPanel that holds two TextBoxes and 30 more n etc. Each of them holds two way binding, I cant able to create each in a separate UserControl.

0 Votes 0 · ·

Hi,

1) Each user control consists 2 files: XAML and CodeBehind.
2) My demo shows example of basic solution.
3) For each UserControl you can use the same or separate object for binding (ViewModel, property).
4) If your UserControl contains only one simple control like TextBox you can bind a simple property from main ViewModel or like in my demo an indexed property.
5) if your UserControl requires a komplex binding like Combobox you can bind more properties from main ViewModel or bind many ViewModels.

Without knowing the details of your program concept, I can only create general Demos.

0 Votes 0 · ·