How to add content from C# to XAML - maui

I have used a litle XamarinForms before and then i did a
naming to be able to point the c# code to it.
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="MauiTest.MainPage"
BackgroundColor="{DynamicResource SecondaryColor}">
<StackLayout x:name="_stacklayoutname">
<Label Text="" x:Name="_Lable"/>
</StackLayout>
</ContentPage>
And then i did like this in the MainPage.xaml.cs
public MainPage()
{
InitializeComponent();
_stacklayoutname.Children.Add(new Label { Text = "TEST" });
_Lable.Text = "TEST";
}
now it get this, but i can change the _Lable to "Text".
how can i point to a stacklayout.
Severity Code Description Project File Line Suppression State
Error CS0103 The name '_stacklayoutname' does not exist in the current context MauiTest (net6.0-android), MauiTest (net6.0-ios), MauiTest (net6.0-maccatalyst), MauiTest (net6.0-windows10.0.19041) *** 11 Active

this is wrong
<StackLayout x:name="_stacklayoutname">
it should be
<StackLayout x:Name="_stacklayoutname">

Related

Passing data to CommunityToolkit.Maui Popup

How do I pass data to a CommunityToolkit Popup in a .Net MAUI app?
Documentation shows how to send a result from Popup back to the page but doesn’t show how to pass data to the Popup.
I made the following Based on Gerald Versluis video:
toolkit popup Xaml:
<toolkit:Popup xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:toolkit="http://schemas.microsoft.com/dotnet/2022/maui/toolkit"
xmlns:model="modelnamespace"
x:Class="namespace.PopUpSelectService">
<VerticalStackLayout>
<CollectionView x:Name="selectService"
HorizontalOptions="Center"
VerticalOptions="Center">
<CollectionView.ItemTemplate>
<DataTemplate x:DataType="model:sal_ServiceResponse">
<Grid RowDefinitions="auto,auto,auto,auto,auto">
<Button Text="{Binding nombre_servicio}"
Grid.Row="0"
Clicked="Button_Clicked" />
</Grid>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</VerticalStackLayout>
</toolkit:Popup>
PopupSelectService .cs
public partial class PopUpSelectService
{
public PopUpSelectService(List<salServiceDTO> sal_Service)
{
InitializeComponent();
selectService.ItemsSource = sal_Service;
}
private void Button_Clicked(object sender, EventArgs e)
{
this.Close(((Button)sender).Text);
}
}
How i call the popup In my viewmodel:
var popup = new PopUpSelectService(response.sal_Service);
var result = await Shell.Current.ShowPopupAsync(popup);
and also in var result you get the value you select on the popup!

Prevent a Border control from shrinking when its child controls shrink

I have a Border control that contains a couple of Label controls. The text shown in the labels changes, and this causes the Border control to expand and shrink horizontally. To prevent flickering, I want it to be able to expand but not shrink. Is there a way to do this?
Alternatively, is there a way to specify that the Label objects should expand horizontally but never shrink - which would give the same outcome?
My XAML is:
<?xml version="1.0" encoding="utf-8" ?>
<ContentView xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="uk.andyjohnson.TakeoutExtractor.Gui.ProgressOverlay">
<Border
Stroke="#0f0f0f"
StrokeThickness="2"
StrokeShape="RoundRectangle 5,5,5,5"
HorizontalOptions="CenterAndExpand"
VerticalOptions="CenterAndExpand"
Padding="30,30">
<VerticalStackLayout
Spacing="25" >
<Label
x:Name="SourceLabel"
Text=""
HorizontalOptions="Center"/>
<Label
x:Name="DestinationLabel"
Text=""
HorizontalOptions="Center" />
</VerticalStackLayout>
</Border>
</ContentView>
Thanks!
You could play with labels WidthRequest.
When your label increase in size, you set its WidthRequest to its current Width value.
Assuming for simplicity that you're changing the label text in your code-behind and that there is only one label, something like this should do the job.
public partial class ProgressOverlay
{
private double currentWidth = 0;
// I create this method just to explain.
// It could be anything in your code that changes your label text
void ChangingText()
{
// ******
// .... SourceLabel text changes here ...
// *******
if (SourceLabel.Width > currentWidth)
{
SourceLabel.WidthRequest = SourceLabel.Width;
currentWidth = SourceLabel.Width;
}
}
}
I solved the problem using a variation on Riccardo Minato's suggestion.
Hook into the SizeChanged event on the bounding Border object.
Track its maximum width using a member variable
When the width exceeds the previous maximum, set the Border object's MinimumWidthRequest property to the new maximum width. This stops it shrinking.
New XAML:
<?xml version="1.0" encoding="utf-8" ?>
<ContentView xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="uk.andyjohnson.TakeoutExtractor.Gui.ProgressOverlay">
<Border
Stroke="#0f0f0f"
StrokeThickness="2"
StrokeShape="RoundRectangle 5,5,5,5"
HorizontalOptions="CenterAndExpand"
VerticalOptions="CenterAndExpand"
Padding="30,30"
SizeChanged="Border_SizeChanged"> <!-- ** Added this line ** -->
<VerticalStackLayout
Spacing="25" >
<ActivityIndicator
IsVisible="true"
IsRunning="true"
IsEnabled="true"
HorizontalOptions="Center"/>
<Button
x:Name="CancelButton"
Text="Cancel"
HorizontalOptions="Center"
Clicked="OnCancelButtonClicked"/>
<Label
x:Name="SourceLabel"
Text=""
HorizontalOptions="Center"/>
<Label
x:Name="DestinationLabel"
Text=""
HorizontalOptions="Center"/>
</VerticalStackLayout>
</Border>
</ContentView>
New code-behind:
private double currentMaxWidth = 0D;
private void Border_SizeChanged(object sender, EventArgs e)
{
var bdr = sender as Border;
if (bdr.Width > currentMaxWidth)
{
currentMaxWidth = bdr.Width;
bdr.MinimumWidthRequest = currentMaxWidth;
}
}

.Net MAUI data binding not carrying through to custom component

I am having trouble getting data binding to work with custom components.
I have created an IncrementValue property that gets incremented with every button click.
The changes are reflected when binded to a Label.
However they do not work when I bind it to a Bindable property in a custom component.
In the example, I have built a custom component called Card which has two bindable properties CardTitle and CardIncrement
Is there something I'm missing as I'm new to MAUI and even Xamarin.
Github link of code snippets below: https://github.com/814k31/DataBindingExample
Card.xaml.cs
namespace DataBindingExample;
public partial class Card : VerticalStackLayout
{
public static readonly BindableProperty CardTitleProperty = BindableProperty.Create(nameof(CardTitle), typeof(string), typeof(Card), string.Empty);
public static readonly BindableProperty CardIncrementProperty = BindableProperty.Create(nameof(CardIncrement), typeof(int), typeof(Card), 0);
public string CardTitle
{
get => (string)GetValue(CardTitleProperty);
set => SetValue(CardTitleProperty, value);
}
public int CardIncrement
{
get => (int)GetValue(CardIncrementProperty);
set => SetValue(CardIncrementProperty, value);
}
public Card()
{
InitializeComponent();
BindingContext = this;
}
}
Card.xaml
<?xml version="1.0" encoding="utf-8" ?>
<VerticalStackLayout
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:databindingexample="clr-namespace:DataBindingExample"
x:DataType="databindingexample:Card"
x:Class="DataBindingExample.Card"
Spacing="25"
Padding="30,0"
VerticalOptions="Center"
BackgroundColor="red"
>
<Label
Text="{Binding CardTitle}"
SemanticProperties.HeadingLevel="Level1"
FontSize="32"
HorizontalOptions="Center"
/>
<Label
Text="{Binding CardIncrement}"
SemanticProperties.HeadingLevel="Level1"
FontSize="32"
HorizontalOptions="Center"
/>
</VerticalStackLayout>
MainPage.xml
<?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"
x:Class="DataBindingExample.MainPage"
xmlns:DataBindingExample="clr-namespace:DataBindingExample"
xmlns:ViewModels="clr-namespace:DataBindingExample.ViewModels"
x:DataType="ViewModels:MainPageViewModel"
>
<ScrollView>
<VerticalStackLayout
Spacing="25"
Padding="30,0"
VerticalOptions="Center"
>
<Label
Text="{Binding IncrementedValue}"
SemanticProperties.HeadingLevel="Level2"
FontSize="18"
HorizontalOptions="Center"
/>
<!-- Why doesnt this work? -->
<DataBindingExample:Card CardIncrement="{Binding IncrementedValue}" />
<Button
x:Name="CounterBtn"
Text="Click Me"
SemanticProperties.Hint="Counts the number of times you click"
Command="{Binding IncrementValueCommand}"
HorizontalOptions="Center"
/>
</VerticalStackLayout>
</ScrollView>
</ContentPage>
When making a custom component (that includes XAML), DO NOT set BindingContext = this;.
REASON: You want the component to use the SAME BindingContext as the page it is placed in. This happens automatically, if you do NOT set a BindingContext in the custom component.
HOWEVER, removing this line breaks all your component's xaml Bindings; you'll need to add something to the xaml, to fix this.
Or to put it another way: How refer to the card's Properties from its XAML? See the next section.
ACCESS COMPONENT PROPERTIES VIA x:Name
Solution: Give the card an x:Name, and make that the "Source" of those bindings:
<VerticalStackLayout
...
x:Name="me" <-- IMPORTANT! Change name as desired.
x:Class="DataBindingExample.Card"
>
...
<Label Text={Binding CardIncrement, Source={x:Reference me}}"
...
Notice the two parts to this solution:
In component's xaml header, define x:Name="mynamehere".
In each Binding, say that the component is the source:
, Source={x:Reference mynamehere}.
OPTIONAL: If custom component has a "ViewModel":
To have a custom component be "data-driven", pass in a parameter that controls its behavior.
This parameter could be considered a "ViewModel", but above I have specified:
DO NOT set a BindingContext (so that component has easy access to the page's BindingContext).
So unlike other uses of ViewModel, in this technique, we don't set the ViewModel as the BindingContext.
How access this ViewModel?
By saving it as a property of the component; e.g.:
public partial class MyComponent : ContentView
{
private MyViewModel VM;
public void MyComponent(MyViewModel vm)
{
InitializeComponent();
VM = vm;
}
public class MyViewModel : ObservableObject
{
[ObservableProperty]
SomeType someProperty; // This is field. Property "SomeProperty" is generated.
}
Then in xaml, we access properties of VM, using . notation:
<Label Text={Binding VM.SomeProperty, Source={x:Reference me}}"

Using compiled bindings with Prism

I want to use compiled bindings in my Xamarin Forms app in combination with Prism.
I created a small xamarin forms app with a simple view, viewmodel and prism (prism:ViewModelLocator.AutowireViewModel="True"). Classic binding works as expected.
How should I implemented compiled binding without creating the binding context twice?
Classic binding with prism: HomePage.xaml
<?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:prism="clr-namespace:Prism.Mvvm;assembly=Prism.Forms"
prism:ViewModelLocator.AutowireViewModel="True"
x:Class="CompiledBinding.Views.HomePage">
<StackLayout>
<!-- Place new controls here -->
<Label Text="{Binding Name}"
HorizontalOptions="Center"
VerticalOptions="CenterAndExpand" />
</StackLayout>
</ContentPage>
HomePageViewModel.cs:
using Prism.Mvvm;
using Prism.Navigation;
using System;
using Xamarin.Forms;
namespace CompiledBinding.ViewModels
{
public class HomePageViewModel : BindableBase
{
string _name = "Compiled binding test";
public HomePageViewModel(INavigationService navigationService)
{
var nav = navigationService;
Device.StartTimer(TimeSpan.FromSeconds(1), () =>
{
// Do something
Name = DateTime.Now.ToString("yyyy MMMM dd hh:mm:ss");
return true; // True = Repeat again, False = Stop the timer
});
}
public string Name
{
get { return _name; }
set { SetProperty(ref _name, value); }
}
}
}
Adding the binding context to the xaml page again, is not an option:
<?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:prism="clr-namespace:Prism.Mvvm;assembly=Prism.Forms"
prism:ViewModelLocator.AutowireViewModel="True"
xmlns:viewModels="clr-namespace:CompiledBinding.ViewModels"
x:Class="CompiledBinding.Views.HomePage"
x:DataType="viewModels:HomePageViewModel">
<ContentPage.BindingContext>
<viewModels:HomePageViewModel />
</ContentPage.BindingContext>
<StackLayout>
<!-- Place new controls here -->
<Label Text="{Binding Name}"
HorizontalOptions="Center"
VerticalOptions="CenterAndExpand" />
</StackLayout>
</ContentPage>
Besides of defining the binding context again, it also results in the error: no public parameterless constructor.
Do I oversee something? Does anyone know how to work with compiled bindings together with prism?

Collapse/Visible UserControls on ButtonClick with MVVM - no swap mechanism -

In my scenario I have a MainView + MainViewModel, UserControl1 + UserControl 2.
In the MainView I have 2 buttons labeled: Button_ShowUserControl1 + Button_ShowUserControl2.
At the lower part of the MainView I have a "ContentGrid" which takes/should_take... every UserControl.
My goal:
When Button_ShowUserControl1 is clicked UserControl1 is Visible and UserControl2 OR any other UserControl must be set to Collapsed. Same is valid for Button_ShowUserControl2.
My problem:
1.) As the UserControls shall be loaded at application start how can I put them all together in the one "ContentGrid"? Thats actually not possible... so how can I make one UserControl visible while the other is in the same place/"ContentGrid" just collapsed ?
2.) As 1.) seems not possible how can I instantiate all UserControls at start of the application and make them only Visible/Collapsed when respective Button is clicked?
3.) As a UserControl has a property Visibility = Visible/Hidden/Collapsed, how can I bind to a property in a ViewModel return such a value like Collapsed? I only could get a boolean value like Visibility = false/true ?
My testcode:
<Grid x:Name="LayoutRoot" Background="#FFBDF5BD" ShowGridLines="False">
<Grid.RowDefinitions>
<RowDefinition Height="96*" />
<RowDefinition Height="289*" />
</Grid.RowDefinitions>
<Grid HorizontalAlignment="Stretch" Name="MenuGrid" VerticalAlignment="Stretch" Background="#FFCECEFF">
<StackPanel Name="stackPanel1" Background="#FFEDFF00" Orientation="Horizontal">
<Button Content="User Data 1" Height="35" Name="button1" Command="{Binding Path=ShowUserControl1Command}" Width="150" Margin="100,0,0,0" />
<Button Content="User Data 2" Height="35" Name="button2" Width="150" Margin="100,0,0,0" />
</StackPanel>
</Grid>
<Grid Grid.Row="1" HorizontalAlignment="Stretch" Name="ContentGrid" VerticalAlignment="Stretch" Background="#FFB15454" />
</Grid>
<UserControl x:Class="SwapUserControls.MVVM.UserControl2"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:vm="clr-namespace:SwapUserControls.MVVM.ViewModel"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300" Visibility="{Binding IsUserControl1Collapsed, Path=Value}">
<UserControl.Resources>
<vm:MainViewModel x:Key="MainViewModelID" />
</UserControl.Resources>
<UserControl.DataContext>
<Binding Source="{StaticResource MainViewModelID}" />
</UserControl.DataContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="228*" />
<RowDefinition Height="72*" />
</Grid.RowDefinitions>
<Button Content="UserControl2" Grid.Row="1" Height="23" HorizontalAlignment="Left" Margin="112,27,0,0" Name="button1" VerticalAlignment="Top" Width="75" />
<DataGrid HorizontalAlignment="Stretch" Name="dataGrid1" VerticalAlignment="Stretch" Background="#FFC046F8" />
</Grid>
public class MainViewModel : ViewModelBase
{
RelayCommand _ShowUserControl1Command;
private bool _IsUserControl1Collapsed;
public RelayCommand ShowUserControl1Command
{
get
{
if (_ShowUserControl1Command == null)
{
_ShowUserControl1Command = new RelayCommand( () => ShowUserControl1() );
}
return _ShowUserControl1Command;
}
}
public void ShowUserControl1()
{
_IsUserControl1Collapsed = true;
}
public bool IsUserControl1Collapsed
{
get
{
return _IsUserControl1Collapsed;
}
}
}
Yes the code is wrong, therefore I ask here :)
You only have 2 things wrong with this code.
1) You can't set the visibility of a usercontrol directly... you have to set it on a container:
<Grid Visibility="Collapsed">
<myControls:MyUserControl />
</Grid>
2) Visibility is not a boolean value, it is an enum. As such, you will need to use a converter to convert from boolean to Visibility. Observe:
<Window ...>
<Window.Resources>
<BooleanToVisibilityConverter x:Key="BoolToVis" />
</Window.Resources>
<Grid Visibility="{Binding ShouldShowUsercontrol1, Converter={StaticResource BoolToVis}}">
<myControls:MyUserControl />
</Grid>
</Window>
That should be it. Hope this helps.
There are other things that you are leaving clues about that might affect the ability of this to work. For example, you don't show the biggest container element... are you wrapping everything in a StackPanel? If you are wrapping everything in a Grid, for example, the controls will overlay everything in layers.
Try these changes I suggest... it should get you closer.
Edit: Another idea using data templates
Another thing you could do is make sure you have a unique ViewModel for each of these views you want to show and hide:
public class MyFirstViewModel : ViewModel
{
}
public class MySecondViewModel : ViewModel
{
}
Then from your "parent" or "main" ViewModel, you show or hide the views you want by virtue of having them in a collection of ViewModels:
public MyMainViewModel : ViewModel
{
public ObservableCollection<ViewModel> ViewsToShow
{
...
}
public void ShowFirstViewModel()
{
ViewsToShow.Add(new MyFirstViewModel());
}
}
To wire everything up in your view, you would then datatemplate these types with their user controls (but this would not cause those views to be instantiated unless they were needed:
<Window ...>
<Window.Resources>
<DataTemplate DataType="{x:Type myViewModels:MyFirstViewModel}">
<myViews:MyFirstView />
</DataTemplate>
<DataTemplate DataType="{x:Type myViewModels:MySecondViewModel}">
<myViews:MySecondView />
</DataTemplate>
</Window.Resources>
<ItemsControl ItemsSource="{Binding ViewsToShow}" />
</Window>
And for any ViewModels you put in "ViewsToShow", the view will automatically see that and template in the appropriate view. Again, without instantiating it before it's needed.
This is probably a little cleaner than putting everything single thing in the View and setting visibility, but it would be dependent on you have a unique view model type for every view, which might not be the case.
The question of saving state comes up when using the DataTemplated approach. The solution here is to tread your ViewModel as the state of the control and design both your ViewModels and your Views accordingly. Here is an example that allows you to swap out your Views using DataTemplating, but switching back and forth saves state.
Assume you have the setup from the last section w/ 2 viewmodels that have datatemplates defined. Let's change up the MainViewModel a little:
public MyMainViewModel : ViewModel
{
public RelayCommand SwapViewsCommand
{
...
}
public ViewModel View
{
...
}
private ViewModel _hiddenView;
public MyMainViewModel()
{
View = new MyFirstViewModel();
_hiddenView = new MySecondViewModel();
SwapViewsCommand = new RelayCommand(SwapViewModels);
}
public void SwapViewModels()
{
var hidden = _hiddenView;
_hiddenView = View;
View = hidden;
}
}
And a few changes to the main view. I've omitted the DataTemplates for brevity.
<Window ...>
<!-- DataTemplates Here -->
<Button Command="{Binding SwapViewsCommand}">Swap!</Button>
<ContentControl Content="{Binding View}" />
</Window>
That's it. The secret here is I'm saving the reference to the original view model. This way, let's say there is a string property in a viewmodel and an associated textbox in the DataTemplated usercontrol with a two-way binding then the state will essentially be saved.