Lars
Lars

Reputation: 45

MAUI add control element to GUI using MVVM

I use the community tool MVVM for my current MAUI project. I would like to dynamically add controls like an entry to the GUI during runtime. I would like to do that from the ViewModel. Using the toolkit, it is of course very easy to provide and interact with functions and properties. Unfortunately I haven't found a way to directly access a StackLayout or something similar.

I tried giving the VerticalStackLayout property x:name (in my xaml document) a name and then accessing it. This works from the code-behind, but not from the ViewModel itself.

I expected that with in the viewModel for example my StackLayout is displayed and then I can execute the following.

stackLayout.Add(new Label { Text = "Primary colors" }));

Furthermore I tried to provide a binding to the property x:name. x:Name="{Binding StackLayout}

In the ViewModel I then tried to provide the property.

[ObservableProperty]
VerticalStackLayout stackLayout;

Upvotes: 0

Views: 3010

Answers (4)

Lars
Lars

Reputation: 45

I have found a solution to my problem.

As you have advised me, I have put it around. I use the code-behind of my view to access the StackLayout.

1. MainPage.xaml

<ScrollView>
    <VerticalStackLayout
        Spacing="25"
        Padding="30,0"
        VerticalOptions="Center"
        x:Name="VStackLayout">
    </VerticalStackLayout>
</ScrollView>

With the property x:name I can access the VS layout from the code behind.

2. MainPage.xaml.cs

Dictionary<string, object> keyValuePairs = new Dictionary<string, object>();        
public MainPage(MainPageViewModel viewModel)
    {
    InitializeComponent();
    BindingContext = viewModel;

    foreach (var item in viewModel.KeyValues)
    {
        
        if (item.Value == "String")
        {
            keyValuePairs.Add(item.Key, "");
            var entry = new Entry {
                Placeholder = item.Key,
                ClassId = item.Key,
                Text = (String)keyValuePairs.Where(k => k.Key == item.Key).First().Value


            };
            VStackLayout.Add(entry);
        }
        else if (item.Value == "Boolean")
        {
            keyValuePairs.Add(item.Key, true);
            Label label = new Label { Text = item.Key};
            var toogle = new Switch
            {
                IsEnabled = true,
                ClassId = item.Key,
                IsToggled = (Boolean)keyValuePairs.Where(k => k.Key == item.Key).First().Value
                
            };
            HorizontalStackLayout views = new HorizontalStackLayout();

            views.HorizontalOptions = LayoutOptions.StartAndExpand;
            views.VerticalOptions = LayoutOptions.Center;
            views.Add(label);
            views.Add(toogle);

            VStackLayout.Add(views);
        }

        
    }

Here the Dic in the ViewModel is accessed and then the GUI is created from it.

Unfortunately, the access to the content of the elements (entries) does not work yet. I would like to see how to write the content in a Dictonary. The binding at this point does not work yet. Does anyone have an idea?

Upvotes: 0

H.A.H.
H.A.H.

Reputation: 3917

First of all, I want to answer that nothing is stopping you from passing a reference of your StackLayout as CommandParameter to your Command in the ViewModel. Write this:

[RelayCommand]    
void Add(StackLayout myLayout)...

And just pass the reference in the XAML.

However, there are very few situations that justify this. None of those situations are "to customize the GUI".

You need to learn how to use DataTemplates, DataTriggers, Styles, EventToCommandBehaviors, Gestures, ControlTemplates, Validators and ValueConvertors.

This will cover your basic needs for accessing the View and its elements.

Upvotes: 0

Jessie Zhang -MSFT
Jessie Zhang -MSFT

Reputation: 13889

Yes, you can use MVVM to achieve this.

A simple method is to use Bindable Layouts to achieve this.

Please refer to the following code:

1.create a viewmodel for current page

MyViewModel.cs

public class MyViewModel 
{
    public int index = 0;
    public ObservableCollection<Data> Items { get; set; }

    public ICommand AddItemCommand => new Command(addItemMethod);

    private void addItemMethod(object obj)
    {
        index++;
        Items.Add(new Data { FileName ="File " + index});
    }


    public MyViewModel()
    {
        Items = new ObservableCollection<Data>();
    }

}

Data.cs

public class Data 
{
    public string FileName { get; set; }
}

2.MainPage.xaml

<?xml version="1.0" encoding="utf-8" ?> 
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
              xmlns:mauiapp="clr-namespace:MauiAddViewApp116"
             x:Class="MauiAddViewApp116.MainPage"
              x:Name="mainpage"
             >

    <ContentPage.BindingContext>
        <mauiapp:MyViewModel></mauiapp:MyViewModel>
    </ContentPage.BindingContext>

    <ScrollView>
        <VerticalStackLayout 
            Margin="10"
            VerticalOptions="StartAndExpand">

            <Button Text="Add item" Command="{Binding AddItemCommand}"></Button>

            <StackLayout BindableLayout.ItemsSource="{Binding Items}" Orientation="Vertical">
                <BindableLayout.ItemTemplate>
                    <DataTemplate>
                        <Label   HorizontalOptions="Fill" Text="{Binding FileName}" FontSize="Large" HeightRequest="38" />
                    </DataTemplate>
                </BindableLayout.ItemTemplate>
            </StackLayout>
        </VerticalStackLayout>
    </ScrollView>

</ContentPage>

Upvotes: 0

ToolmakerSteve
ToolmakerSteve

Reputation: 21321

To clarify: the ViewModel doesn't know about the View, but the View DOES know about the ViewModel.

Thus, the view's code behind can do what is needed.

  • If the View doesn't already have a property holding the viewmodel, then add to code behind:
private MyVM VM => (MyVM)BindingContext;

That defines a VM property, so you can do VM.MyDictionary[someKey] or similar.

  • If you need to access VM in constructor BEFORE setting BindingContext, then edit question, to show how BindingContext is set currently.

Upvotes: 0

Related Questions