Search code examples
c#wpfxamlstackpanel

Add a control (defined in xaml) to a Stack Panel from Code behind


I have a user control which I want to define as a template in XAML. In code behind, during runtime, I want to keep adding items (of type my user control) to a Stack Panel.

How would I do this?

I don't want to do this:

myUserControl myUserControl = new MyUserControl;
myStackPanel.Children.Add(myUserControl);

This is because I have set multiple properties for my UserControl in XAML and I do not want to set all bindings from code behind. I am looking for a more elegant solution.

How to I define my template for the User Control in XAML? How do I then do something like this?

myStackPanel.Children.Add(myCustomControlTemplate);

I am new to WPF, so please bear with me if this is a very trivial question.

EDIT: (As per Erno's request)

XAML file:

<someNamespace:MyUserControl x:Name="usrControl" SomeState="{Binding CachedValue}" MyUserControlEventAggregator="{Binding ParentEventAggregator}"
                                           DepedencyProperty1="{Binding Property1, Mode=TwoWay}"
                                           DepedencyProperty2="{Binding Property2}"
                                           DepedencyProperty3="{Binding Property3}"
                                           d:DataContext="{Binding Path=ParentViewModel}">

XAML.CS file:

public partial class ParentControl: Window
{
    public ParentControlViewModel ParentViewModel{ get; set; }

    public ListBuilderView(ParentViewModel viewModelPassedByPRISM)
    {
        ParentViewModel = viewModelPassedByPRISM;
        DataContext = ParentViewModel;
        InitializeComponent();
    }

    private void AddMyUserControlButton_Click(object sender, RoutedEventArgs e)
    {
        // I need to create an instance of MyUserControl here and add it to any WPF component of ParentControl as I may need it for some other user action
    }

    private void ProcessInfoButton_Click(object sender, RoutedEventArgs e)
    {
        // I need to loop through and look into each of the MyUserControls that the user adds, get some values from the MyUserControl and pass this information to do some calc and make some database calls
    }
}

ParentViewModel.cs file:

public class ParentViewModel: BaseViewModel
{
     public IEventAggregator ChildEventAggregator { get; set; }

     private AnyType _property1;
     public AnyType Property1
     {
         get { return _property1; }
         set
         {
             _property1 = value;
             NotifyPropertyChanged("Property1");
         }
     }

     private AnyType _property2;
     public AnyType Property2
     {
         get { return _property2; }
         set
         {
             _property2 = value;
             NotifyPropertyChanged("Property2");
         }
     }

     private AnyType _property3;
     public AnyType Property3
     {
         get { return _property3; }
         set
         {
             _property3 = value;
             NotifyPropertyChanged("Property3");
         }
     }

     public AnyType CachedValue { get; set; }
}

So, as you can see all the objects of type MyUserControl that I create in xaml.cs file will share common property and update themselves. Users will interact with those controls and when they request for some processing, I have to look into each MyUserControl that user adds to the Main Control, call some methods in MyUserControl to get some info, use these info from each MyUserControl for some other processing and querying the database.

I hope this helps..


Solution

  • EDIT

    Note that the ViewModel properties in the code you posted are collections! You might want to change those List's to ObservableCollection's.

    To bind to these: use an itemscontrol such as the ListBox IN THE USERCONTROL. (BTW: please use better property names!)

    Here is what it would look like:

    <StackPanel>
        <MyUserControl DataContext="{Binding Property1}"/>
        <MyUserControl DataContext="{Binding Property2}"/>
        <MyUserControl DataContext="{Binding Property3}"/>
    </StackPanel>
    

    In the MyUserControl:

    <UserControl x:Class="MyUserControl">
        <ListBox ItemsSource="{Binding}">
             <ListBox.ItemTemplate>
                 <DataTemplate>
                     <StackPanel Orientation="Horizontal">
                         <TextBlock Text="{Binding AttributeName}"/>
                         <TextBox Text="{Binding AttributeValue}"/>
                     </StackPanel>
                 </DataTemplate>
             </ListBox.ItemTemplate>
        </ListBox>
    </UserControl>
    

    Old answers- Still valid In response to your comments:

    You need a different approach that is very similar. By using an ItemsControl such as the ListBox you only need to specify the collection you want to bind to and automatically for each item in the collection an instance of the ItemTemplate (containing the usercontrol in this case) will be created and its DataContext will be set to the item so the bindings in the user control will be relative to an item. 

    There is no need to add the instances in code nor to set the DataContext of each control.  Welcome to the wonderful world of data binding.

                                                                                                                               

    My guess is that you are not looking for a Template but for a Style:

    <StackPanel x:Name="MyStackPanel">
        <StackPanel.Resources>
            <Style x:Key="ucStyle" TargetType="MyUserControl">
                <Setter Property="Background" Value="Azure" />
                <Setter Property="BorderThickness" Value="2" />
            </Style>
        </StackPanel.Resources>
    </StackPanel>
    

    In code:

    myUserControl myUserControl = new MyUserControl;
    Style style = MyStackPanel.FindResource("ucStyle") as Style;
    if(style != null)
    {
        myUserControl.Style = style;
    }
    myStackPanel.Children.Add(myUserControl);
    

    Or, shorter, but less obvious, using implicit styles:

    <StackPanel x:Name="MyStackPanel">
        <StackPanel.Resources>
            <Style TargetType="MyUserControl">
                <Setter Property="Background" Value="Azure" />
                <Setter Property="BorderThickness" Value="2" />
            </Style>
        </StackPanel.Resources>
    </StackPanel>
    

    In code:

    myUserControl myUserControl = new MyUserControl;
    myStackPanel.Children.Add(myUserControl);