'Dynamicly adding StackLayout

So what I wanna do is have a stacklayout, with entries etc. Added when I set it in the code. For example, I have 3 entries, once they are all filled I want a set of 3 new entries to be added. And if they are emptied the entries goes away. I have not completed my code to get this to work, because I got stuck at a very crucial step.

I cannot figure out how I add an already created StackLayout:

        <StackLayout Orientation="Horizontal" x:Name="ingredientsLayout">
            <Entry x:Name="recipeIngredient"
           Placeholder="Ingredient"
           HorizontalOptions="FillAndExpand"
           Margin="20,0,0,0"
           Unfocused="StackLayout_Unfocused"/>
            <dxe1:ComboBoxEdit x:Name="recipeIngredientAmountPicker" 
            LabelText="Amount"
            Margin="10,0,10,0"
            Unfocused="StackLayout_Unfocused"/>
            <Picker x:Name="recipeIngredientMeasurePicker" 
            Title="Measure"
            HorizontalOptions="End"
            Margin="0,0,20,0"
            Unfocused="StackLayout_Unfocused"/>
        </StackLayout>

as a new StackLayout. It obviously isn't as simple as adding this line: mainStack.Children.Add(ingredientsLayout);

So how do I do it :)?

Edit:

using Plugin.Media;
using Plugin.Media.Abstractions;
using SQLite;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Food_Recipe_App.Assets.Classes;
using System.Reflection.Metadata;

using Xamarin.Forms;
using Xamarin.Forms.Xaml;
using System.Collections.ObjectModel;

namespace Food_Recipe_App
{
    [XamlCompilation(XamlCompilationOptions.Compile)]
    public partial class NewRecipePage : ContentPage
    {
        public StackLayout ingredientsLayoutTemplate = new StackLayout
        {

            Parent = mainStackLayout,
            Orientation = StackOrientation.Horizontal
        };

        Entry ingredientEntry = new Entry { Placeholder = "Ingredient", HorizontalOptions = LayoutOptions.FillAndExpand, Margin = new Thickness(20, 0, 20, 0) };
        Button ingredientAddButton = new Button { Text="+", WidthRequest = 50, HorizontalOptions = LayoutOptions.End, Margin = new Thickness(0,0,20,0) };
        Button ingredientRemoveButton = new Button { Text = "-", WidthRequest = 50, HorizontalOptions = LayoutOptions.End, Margin = new Thickness(0, 0, 20, 0), IsVisible = false };

        public List<StackLayout> ingredientsStackLayouts = new List<StackLayout>();
        public List<Entry> ingredientsEntries = new List<Entry>();
        public List<Button> ingredientsAddButtons = new List<Button>();
        public List<Button> ingredientsRemoveButtons = new List<Button>();

        public bool newStackAdded = false;

        public static string nullString = "none";

        public static StackLayout mainStackLayout;

        public NewRecipePage()
        {
            InitializeComponent();

            var assembly = typeof(NewRecipePage);

            recipeImage.Source = ImageSource.FromResource("Food_Recipe_App.Assets.Images.MainPage.crypto_ball.png", assembly);

            var measureList = Food_Recipe_App.Assets.Classes.Ingrediens.Measure;

            //recipeIngredientMeasurePicker.ItemsSource = measureList;
            //recipeIngredientAmountPicker.ItemsSource = Ingrediens.MeasureCount;

            ingredientAddButton.Clicked += recipeAddIngredient_Clicked;
            ingredientRemoveButton.Clicked += recipeRemoveIngredient_Clicked;

            ingredientsStackLayouts.Clear();
            ingredientsEntries.Clear();
            ingredientsAddButtons.Clear();
            ingredientsRemoveButtons.Clear();

            ingredientsStackLayouts.Add(this.ingredientsLayout);
            ingredientsEntries.Add(this.recipeIngredient);
            ingredientsAddButtons.Add(this.recipeAddIngredient);
            ingredientsRemoveButtons.Add(this.recipeRemoveIngredient);

            mainStackLayout = mainStack;
        }

        private void saveRecipe_Clicked(object sender, EventArgs e)
        {
            Recipe recipe = new Recipe()
            {
                title = recipeTitle.Text,
                description = recipeDesc.Text,
                //image = recipeImage
            };
            using (SQLiteConnection conn = new SQLiteConnection(App.DatabaseLocation))
            {
                conn.CreateTable<Recipe>();
                int rows = conn.Insert(recipe);

                if (rows > 0)
                {
                    DisplayAlert("Success", "Recipe successfully added", "Ok");
                }
                else
                {
                    DisplayAlert("Failure", "Recipe not added, try again", "Ok");
                }
            };

            Navigation.PopAsync();
        }

        async void recipeImage_Clicked(object sender, EventArgs e)
        {
            await CrossMedia.Current.Initialize();

            if (!CrossMedia.Current.IsPickPhotoSupported)
            {
                await DisplayAlert("Not supported", "Your device does not currently support this functionality", "Ok");
                return;
            }

            var mediaOptions = new PickMediaOptions()
            {
                PhotoSize = PhotoSize.Medium
            };

            var recipeImageFile = await CrossMedia.Current.PickPhotoAsync(mediaOptions);

            if (recipeImageFile == null)
            {
                await DisplayAlert("Error", "Could not get the image, please try again", "Ok");
                return;
            }
            else
            {
                recipeImage.Source = ImageSource.FromStream(() => recipeImageFile.GetStream());
            }
        }

        private void recipeAddIngredient_Clicked(object sender, EventArgs e)
        {
            recipeAddIngredientTemplate();

            //recipeAddIngredient.IsVisible = false;

            //recipeRemoveIngredient.IsVisible = true;
        }

        private void recipeRemoveIngredient_Clicked(object sender, EventArgs e)
        {
            recipeRemoveIngredient.IsVisible = false;

            recipeAddIngredient.IsVisible = true;

            //mainStack.Children.Remove(recipeIngredient.Parent as View);

            //mainStack.Children.Remove(ingredientsStackLayouts[ingredientsStackLayouts.Count - 1]);

            //ingredientsStackLayouts.Remove(ingredientsStackLayouts[ingredientsStackLayouts.Count - 1]);

            //ingredientsStackLayouts[ingredientsStackLayouts.Count - 1].IsVisible = false;
        }

        public void recipeAddIngredientTemplate()
        {
            ingredientsStackLayouts.Add(ingredientsLayoutTemplate);
            ingredientsEntries.Add(ingredientEntry);
            ingredientsAddButtons.Add(ingredientAddButton);
            ingredientsRemoveButtons.Add(ingredientRemoveButton);

            ingredientsStackLayouts[ingredientsStackLayouts.Count - 1].Children.Add(ingredientsEntries[ingredientsEntries.Count - 1]);
            ingredientsStackLayouts[ingredientsStackLayouts.Count - 1].Children.Add(ingredientsAddButtons[ingredientsAddButtons.Count - 1]);
            ingredientsStackLayouts[ingredientsStackLayouts.Count - 1].Children.Add(ingredientsRemoveButtons[ingredientsRemoveButtons.Count - 1]);

            mainStack.Children.Add(ingredientsStackLayouts[ingredientsStackLayouts.Count - 1]);
        }
    }
}
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" 
             xmlns:dxe1="http://schemas.devexpress.com/xamarin/2014/forms/editors"
             x:Class="Food_Recipe_App.NewRecipePage">
    <ContentPage.ToolbarItems>
        <ToolbarItem x:Name="saveRecipe" Text="Save Recipe"
                     Clicked="saveRecipe_Clicked"/>
    </ContentPage.ToolbarItems>

    <ContentPage.Content>
        <StackLayout x:Name="mainStack">
            <Entry x:Name="recipeTitle"
               Placeholder="Name"/>
            <Entry x:Name="recipeDesc"
               Placeholder="Description"/>
            <Image x:Name="recipeImage"/>
            <Button x:Name="recipeImageAddButton"
                Clicked="recipeImage_Clicked"
                Text="Upload Image"/>
            <Label x:Name="ingredientTitle"
               Text="Ingredients"
               HorizontalOptions="CenterAndExpand"
               FontAttributes="Bold"/>
            <StackLayout Orientation="Horizontal" x:Name="ingredientsLayout">
                <Entry x:Name="recipeIngredient"
               Placeholder="Ingredient"
               HorizontalOptions="FillAndExpand"
               Margin="20,0,20,0"/>
                <Button x:Name="recipeAddIngredient"
                        Text="+"
                        WidthRequest="50"
                        HorizontalOptions="End"
                        Margin="0,0,20,0"
                        Clicked="recipeAddIngredient_Clicked"/>
                <Button x:Name="recipeRemoveIngredient"
                        Text="-"
                        WidthRequest="50"
                        HorizontalOptions="End"
                        Margin="0,0,20,0"
                        Clicked="recipeRemoveIngredient_Clicked"
                        IsVisible="false"/>
            </StackLayout>
        </StackLayout>
    </ContentPage.Content>

</ContentPage>


Solution 1:[1]

At first, according to your description, you want to use the same layout in different place. So you can try the two solutions.

  1. Create a new variable which is type of StackLayout, and the add the child view into the layout. After that you can use it any where. Such as:

    StackLayout layout = new StackLayout(); layout.Children.Add(new Label...);

    You can add and remove it by the mainstack1.Children.Add(layout); the mainstack1.Children.Remove(layout);

  2. Use the ContentView to contain the StackLayout. And then you can use the contentview anywhere in the xaml. But it seems that the view cann't be reomved if you use it in the xaml. You can make it invisible by the IsVisible property.

    In addition, you can also get the stacklayout in the contentview and clear it by the layout.Children.Clear(). And then it will be a blank contentview.

Edit: This is because the button event is in the page you declare. In other words, it doesn't have the binding context in other places. So you can put the StackLayout into a static class. Such as:

public static class LayoutTest
{
    public static StackLayout stack;
}

And then set the control at the page which is the first to use it:

   public AboutPage()
    {
        InitializeComponent();
        LayoutTest.stack = new StackLayout();
        Button button = new Button() { Text = "Click Me" };
        button.Clicked += (s, e) =>
        {
            Console.WriteLine("-----------------------------------");
        };
        LayoutTest.stack.Children.Add(button);
        about.Children.Add(LayoutTest.stack);
    }

Then you can use it any where such as:

public ItemDetailPage()
    {
        InitializeComponent();
        BindingContext = new ItemDetailViewModel();
        mainstack.Children.Add(LayoutTest.stack);
    }

In addition, you can add the clicked event to every page which uses the stacklayout. But that is repetitive work.

Finally, the contentview is the best choice, because it has the xaml and the .cs file, so it has its own binding context

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1