What is "[Xamarin.Forms.ContentProperty("Contents")]" above a class actually doing? - c#

I understand how this code works but can someone explain what the first line does? Is this translated some place into some C# code. How about if I wanted to code this manually, how could I go about that?
[Xamarin.Forms.ContentProperty("Contents")]
class PopupFrame : Frame
{
StackLayout contentStack { get; } = new StackLayout();
public IList<View> Contents { get => contentStack.Children; }
public PopupFrame()
{
Content = contentStack;
HasShadow = true;
HorizontalOptions = LayoutOptions.FillAndExpand;
Padding = 0;
VerticalOptions = LayoutOptions.Center;
}
}

This attribute tells the XAML processor that if should use the Frame's Content property as the default basically. So, in practice it allows you to write this
<ContentView>
<Label Text="Hello, Forms"/>
</ContentView>
Instead of
<ContentView>
<ContentView.Content>
<Label Text="Hello, Forms"/>
</ContentView.Content>
</ContentView>
Examples taken from the Docs page.
In regard to your question "how do I write this in C#?" you don't. This is something specific to XAML and nothing more than syntactic sugar. In C# you would simply assign something to the Content property. I.e.:
var frame = new Frame();
Frame.Content = new Label() { Text = "Hello, Forms" };

Related

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;
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.
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>
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?
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.

How to show a Content Dialog with multiple input fields from viewmodel

I am building an C# UWP application where onclick of a button in the view a command (using System.Windows.Input.IComand) raises in viewmodel(not code behind) which builds a form of input fields like Name:____ phone:____ etc and shows it on the UI/view.
I dont want to keep the <ContentDialog></ContentDialog> in the
view.
What have I done so far is
Button in my view:
<CommandBar>
<AppBarButton x:Name="buttonNew" Command="{Binding AddClick}" />
</CommandBar>
ViewModel object in the code behind is set as the DataContext of the
View
In the view model:
public ICommand AddClick=> new RelayCommand(Add);
private async void Add()
{
TextBox input = new TextBox()
{
PlaceholderText = "Name",
};
var contentDialog = new ContentDialog
{
Title = "Add a Person",
Content = input,
FullSizeDesired = true,
PrimaryButtonText = "Add",
CloseButtonText = "Cancel"
};
await contentDialog.ShowAsync();
}
As you can see I am able to show/trigger a Content Dialog box with one input field and 2 buttons.
My problem is:
How to add more input fields like we add in a
<StackPanel></StackPanel> in the code of view model and assign it
to the content of the Content Dialog?
How to size it accordingly that all the fields show up properly and does not show up haphazard since am not coding this in the Xaml?
From the general application construction practice, it is recommended to create a UI using XAML to create a custom ContentDialog.
You don’t have to write the XAML code of ContentDialog in the View, you can add new item in Visual Studio, select the Content Dialog template, and create a custom dialog derived from ContentDialog.
Then use code similar to the following:
MyCustomDialog.xaml
<ContentDialog
...
Title="Add a Person"
FullSizeDesired="True"
PrimaryButtonText="Add"
CloseButtonText="Cancel"
PrimaryButtonClick="ContentDialog_PrimaryButtonClick">
<StackPanel>
<TextBox Header="Name" x:Name="NameBox" PlaceholderText="Name"
HorizontalAlignment="Stretch"/>
<TextBox Header="Phone" x:Name="PhoneBox" PlaceholderText="Phone"
Margin="0,15,0,0" HorizontalAlignment="Stretch"
InputScope="Number"/>
</StackPanel>
</ContentDialog>
ViewModel.cs
private async void Add()
{
var dialog = new MyCustomDialog();
await dialog.ShowAsync();
}
If you insist on using C# code to create ContentDialog, you need to convert the tags in XAML into corresponding classes, but this method is not easy to debug.
private async void Add()
{
var container = new StackPanel();
TextBox nameBox = new TextBox()
{
PlaceholderText = "Name",
Header = "Name",
HorizontalAlignment = HorizontalAlignment.Stretch
};
TextBox phoneBox = new TextBox()
{
PlaceholderText = "Phone",
Header = "Phone",
HorizontalAlignment = HorizontalAlignment.Stretch,
Margin = new Thickness(0, 15, 0, 0)
};
container.Children.Add(nameBox);
container.Children.Add(phoneBox);
var contentDialog = new ContentDialog
{
Title = "Add a Person",
Content = container,
FullSizeDesired = true,
PrimaryButtonText = "Add",
CloseButtonText = "Cancel"
};
await contentDialog.ShowAsync();
}
The tags in XAML are actually the corresponding classes in C#, from the code you provided, the ContentDialog is displayed in full screen. You mentioned that you want the fields to be displayed correctly, if you mean that the string entered in the TextBox is too long and the text is not displayed completely, you can set the TextBox.TextWrapping property to True.

Xamarin Forms - Recognize Postition

I want to make a comment entry that is only visible if the user is at the bottom of the article.
So the app has to recognize when the user has scrolled enough, then a method should make the entryfield visible.
I can't find something like this on the Internet, so maybe you guys can help me.
This one is without the entryfield and when the user scrolls down ...
... the entryfield becomes visible
If you are using a ScollView, there is a Scrolled event that fires whenever the view is scrolled and the ScrolledEventArgs contain ScrollX and ScrollY properties that allow you to know where the ScrollView currently is. If you compare ScrollY to the height of the ContentSize property of the ScrollView, e.g.:
XAML:
<StackLayout>
<ScrollView x:Name="scrollView" Scrolled="Handle_Scrolled">
<StackLayout>
<Label Text="{Binding Article}" HorizontalOptions="StartAndExpand" VerticalOptions="StartAndExpand" />
</StackLayout>
</ScrollView>
<Entry IsVisible="{Binding AtEnd}" Placeholder="End reached!" />
</StackLayout>
Code behind (MainPage is a ContentPage subclass):
string _article;
public string Article
{
get
{
return _article;
}
set
{
if (_article != value)
{
_article = value;
OnPropertyChanged("Article");
}
}
}
bool atEnd;
public bool AtEnd
{
get
{
return atEnd;
}
set
{
if (atEnd != value)
{
atEnd = value;
OnPropertyChanged("AtEnd");
}
}
}
public MainPage()
{
Article = "<put in enough text here to force scrolling>";
AtEnd = false;
InitializeComponent();
BindingContext = this;
}
void Handle_Scrolled(object sender, Xamarin.Forms.ScrolledEventArgs e)
{
if (e.ScrollY + scrollView.Height >= scrollView.ContentSize.Height)
AtEnd = true;
else
AtEnd = false;
}
That said, why not just put the entry below the article using the same scroll view? IOW just put the Entry element after the Label above in the same StackLayout and the Entry will just be there at the end always, but the user won't see it until they scroll down. Seems that that would be a simpler solution. Of course you may not be using a Label but the same applies, just put the Entry at the bottom of the layout that the ScrollView is scrolling.

Xamarin.Forms set focus from mvvm ViewModel

I'm working on a chat application using Xamarin.Forms.
And I want to avoid to hide the keyboard when the Entry loses focus and button Send is clicked.
How can I do it on Android and iOS?
I use XF, full Mvvm without XAML(only C#)
Updated:
In page class:
private EntrySetBorder _newMessageEntry;
...
_newMessageEntry = new EntrySetBorder
{
TextColor = Color.Black,
HorizontalOptions = LayoutOptions.FillAndExpand,
VerticalOptions = LayoutOptions.End,
Margin = new Thickness(0, 0, 5, 0)
};
In model class:
var entry = CurrentPage.FindByName<EntrySetBorder>("_newMessageEntry");
entry.Focus();
}
This can be achieved easily by using the FindByName<>() function inside the PCL.
This is one way of doing that:
Entry myEntry = CurrentPage.FindByName<Entry>("YourEntryName");
myEntry.Focus();
You can add that at the end of the click handler of your send button.
Edit:
In your case I think your problem is that your entry is set to private, so I would suggest either expose it as public or expose it using another public property. Two solutions that might work:
public EntrySetBorder _newMessageEntry;
...
_newMessageEntry = new EntrySetBorder
{
TextColor = Color.Black,
HorizontalOptions = LayoutOptions.FillAndExpand,
VerticalOptions = LayoutOptions.End,
Margin = new Thickness(0, 0, 5, 0)
};
And:
EntrySetBorder entry = CurrentPage.FindByName<EntrySetBorder>("_newMessageEntry");
entry.Focus();
Or you go with this:
private EntrySetBorder _newMessageEntry;
...
_newMessageEntry = new EntrySetBorder
{
TextColor = Color.Black,
HorizontalOptions = LayoutOptions.FillAndExpand,
VerticalOptions = LayoutOptions.End,
Margin = new Thickness(0, 0, 5, 0)
};
public EntrySetBorder NewMessageEntry => _newMessageEntry;
and :
EntrySetBorder entry = CurrentPage.FindByName<EntrySetBorder>("NewMessageEntry");
entry.Focus();
Please try that :)
Edit 2:
After reviewing your code, and testing it, the final way to fix it was by sending the Entry as a parameter in the command you're using, example:
Inside the page you're creating:
sendButton.CommandParameter = NewMessageEntry; // We're adding the Entry we want to focus as a command parameter.
And inside your PageModel and the command we want to use:
public Command SendCommand
{
get
{
return new Command<Entry>((obj) => //obj here means the parameters we're sending I.E: the entry we set it in the page.
{
//The code you want to execute
Entry entry = obj;
entry.Focus();
});
}
}
Note that I used Entry because I didn't have all the implementation of your custom entry.
This is an example of how I do, before this I used to do using MessagingCenter
in xaml , you need to give an x:Name to the obj you want to make focus.
<!-- PICKER's DEFINITION -->
<DatePicker
x:Name="Datepicker"
Date="{Binding SelectedDate, Mode=TwoWay}"
IsEnabled="true"
IsVisible="false">
</DatePicker>
then you have to make reference to that control in your command parameter on a button or for example in this case I use a toolbar item.
<!-- MENU TOOLBAR -->
<ContentPage.ToolbarItems>
<ToolbarItem
Command="{Binding ShowCalendarCommand}"
Icon="Calendar"
CommandParameter="{x:Reference Datepicker}" />
</ContentPage.ToolbarItems>
then in your vm command :
#region toolbar commands
public ICommand ShowCalendarCommand => new RelayCommand<Object>(ShowCalendar);
#endregion
private void ShowCalendar(Object obj)
{
var calendar = (DatePicker)obj;
calendar.Focus();
// MessagingCenter.Send(this, "Calendar");
}

Add StackPanel and more than one TextBlock inside Button

I want to add a number of TextBlocks inside a Button. How can I add a number of them, along with StackPanels or Canvases, in C#, as shown below in XAMAL
<Button>
<StackPanel>
<TextBlock Text="ABC"/>
<TextBlock Text="DEF"/>
</StackPanel>
</Button>
It's easy:
public partial class MainWindow : Window {
public MainWindow() {
InitializeComponent();
var tb1 = new TextBlock() { Text = "TextBlock 1" };
var tb2 = new TextBlock() { Text = "TextBlock 2" };
var stackPanel = new StackPanel();
stackPanel.Children.Add(tb1);
stackPanel.Children.Add(tb2);
var button = new Button() { Content = stackPanel };
this.Content = button;
}
}
Maybe you should think about an enclosing control of csharpfolk's answer. This would help to get a reuseable control.
The text strings are good to use as a dependency property. :)
Regards,
- Tobbo

Categories