Is there a way to create a layout with draggable items in the new .net Maui for mobile apps (Android, Ios and WinApp?
I've been searching what is possible to do in the platform and I haven't found something at the moment.
Yes there is , use CanDrag and AllowDrop.
Find it here DragdropMaui example
In MainPage.xaml
<Frame Margin="20">
<Frame.GestureRecognizers>
<DropGestureRecognizer AllowDrop="True" Drop="DropGestureRecognizer_Drop_1" />
</Frame.GestureRecognizers>
</Frame>
<Label Text="Drag Me away" HorizontalTextAlignment="Center" TextColor="Black" FontSize="36">
<Label.GestureRecognizers>
<DragGestureRecognizer CanDrag="True" DragStarting="DragGestureRecognizer_DragStarting_1" />
</Label.GestureRecognizers>
</Label>
In MainPage.cs
private void DragGestureRecognizer_DragStarting_1(object sender, DragStartingEventArgs e)
{
var label = (sender as Element)?.Parent as Label;
e.Data.Properties.Add("Text", label.Text);
}
private void DropGestureRecognizer_Drop_1(object sender, DropEventArgs e)
{
var data = e.Data.Properties["Text"].ToString();
var frame = (sender as Element)?.Parent as Frame;
frame.Content = new Label
{
Text = data
};
}
Related
I have a simple project using Community Toolkit Mvvm tool and a collectionview. The issue is that the SelectionChangeCommand of the CollectionView doesn´t fired when I select an element of the collection. I created this project because on another more complex project the error is that it can't find the binding command on the viewmodel. I know that the connection between the view and viewmodel is working because the collectionview is filling with elements on the viewmodel and also I am able to change the visibility of the border through the binded property.
Sample project: https://github.com/luis95gr/MvvmError.App
Code
LoginPage (first page with collectionview)
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="MvvmError.Views.LoginPage"
xmlns:models="clr-namespace:MvvmError.Models"
Title="LoginPage">
<RefreshView VerticalOptions="FillAndExpand">
<Border BackgroundColor="Red" IsVisible="{Binding Visible}" Stroke="Red" HorizontalOptions="FillAndExpand" Padding="0,0" Margin="0,0,0,0" VerticalOptions="FillAndExpand">
<Border.StrokeShape>
<RoundRectangle CornerRadius="40,0,0,0" />
</Border.StrokeShape>
<CollectionView Background="Transparent" ItemsSource="{Binding Messages}" SelectionMode="Single" SelectedItem="{Binding MessageSelected}" SelectionChangedCommand="{Binding MessageSelectedCommand}">
<CollectionView.ItemTemplate>
<DataTemplate x:DataType="models:MessagesMessages">
<Grid RowDefinitions="100*" ColumnDefinitions="100*">
<Label Grid.Row="0" Text="{Binding Asunto}" />
</Grid>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</Border>
</RefreshView>
LoginPageViewModel
public partial class LoginPageViewModel : ObservableObject
{
[ObservableProperty]
private MessagesMessages messageSelected;
[ObservableProperty]
private bool visible;
public ObservableCollection<MessagesMessages> Messages { get; } = new();
public LoginPageViewModel()
{
Visible = true;
GetMessages();
}
[RelayCommand]
async void MessageSelectedCommand()
{
var snackbar = Snackbar.Make("Hola");
await snackbar.Show();
}
private void GetMessages()
{
Messages.Add(new MessagesMessages
{
Asunto = "Hola"
});
Messages.Add(new MessagesMessages
{
Asunto = "Hola1"
});
Messages.Add(new MessagesMessages
{
Asunto = "Hola2"
});
Messages.Add(new MessagesMessages
{
Asunto = "Hola3"
});
}
}
LoginPage.xaml.cs
public partial class LoginPage : ContentPage
{
public LoginPage(LoginPageViewModel loginPageViewModel)
{
InitializeComponent();
BindingContext= loginPageViewModel;
}
}
MauiProgram.cs
builder.Services.AddTransient<LoginPage>();
builder.Services.AddSingleton<ViewModels.LoginPageViewModel>();
you are not following the naming convention
The name of the generated command will be created based on the method
name. The generator will use the method name and append "Command" at
the end, and it will strip the "On" prefix, if present. Additionally,
for asynchronous methods, the "Async" suffix is also stripped before
"Command" is appeneded.
to generate a command named MessageSelectedCommand your method should look like
[RelayCommand]
async void MessageSelected()
I want to play text to speech but I can't hear anything using Xamarin.essential. I 'm sure I don't have muted smartphone. The code:
using Xamarin.Essential
private void PlayTextMethod()
{
if(do sth)
{
SpeekFrommethodAsync();
}
}
private async SpeekFrommethodAsync()
{
await TextToSpeech.SpeakAsync(Tasklabel.Text, new SpeechOptions
{
Volume = 1f
});
}
If your project's Target Android version is set to Android 11 (R API 30),
Open the AndroidManifest.xml file under the Properties folder and add the following inside of the manifest node:
<queries>
<intent>
<action android:name="android.intent.action.TTS_SERVICE" />
</intent>
</queries>
And try this in your method:
public async Task SpeakNow()
{
var locales = await TextToSpeech.GetLocalesAsync();
// Grab the first locale
var locale = locales.FirstOrDefault();
var settings = new SpeechOptions()
{
Volume = .75f,
Pitch = 1.0f,
Locale = locale
};
await TextToSpeech.SpeakAsync("Hello World", settings);
}
Refer: https://learn.microsoft.com/en-us/xamarin/essentials/text-to-speech?context=xamarin%2Fandroid&tabs=android
I made a demo and I tested it on the Android 12 emulator. I found the Text-to-speech function can not speak the words which belong to another country. So I added a picker to choose the country.
Here is the code in MainPage.xaml
<StackLayout>
<Frame BackgroundColor="#2196F3" Padding="24" CornerRadius="0">
<Label Text="Text to speech sample!" HorizontalTextAlignment="Center" TextColor="White" FontSize="36"/>
</Frame>
<Picker x:Name="Languages"></Picker>
<Entry x:Name="TextToSpeak"></Entry>
<Button Text="Speak Please" Clicked="Button_Clicked"></Button>
</StackLayout>
And here is the code in MainPage.xaml.cs.
using Xamarin.Essentials;
public partial class MainPage : ContentPage
{
IEnumerable<Locale> locales;
public MainPage()
{
InitializeComponent();
}
protected async override void OnAppearing()
{
base.OnAppearing();
locales = await TextToSpeech.GetLocalesAsync();
// add the locales to the picker
foreach(var locale in locales)
{
Languages.Items.Add(locale.Name);
}
}
// realize the button
private void Button_Clicked(object sender, EventArgs e)
{
if(Languages.SelectedIndex > 0)
{
// use the TextToSpeech to read the words in the Entry.
TextToSpeech.SpeakAsync(TextToSpeak.Text, new SpeechOptions
{
Locale = locales.Single(locales=> locales.Name == Languages.SelectedItem.ToString())
});
}
}
}
This is the view of the project.
I am downloading data from an api, and displaying that data in the view. As I wait I want to display a ProgressRing, but when I bind it dosen't work
Bind the ring to the cityData property, with a two way mode and update it when the property changes
Created a new property in the VM, that is true by default and it will turn false when I get the data back
XAML
<TextBox x:Name="currentLocation"
PlaceholderText="Please wait..."
IsReadOnly="True"
Margin="20"
Width="300"
Text="{Binding cityData, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
<ListView RelativePanel.Below="currentLocation"
x:Name="ForecastList"
Margin="20"
SelectedItem="{Binding currentDay, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
ItemsSource="{Binding dailyForecasts}">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Margin="10">
<TextBlock x:Name="dateTB" Text="{Binding Date.DayOfWeek}" />
<TextBlock x:Name="highTB" Text="{Binding Temperature.Maximum.Value, Converter={StaticResource cv}}" FontSize="10" />
<TextBlock x:Name="lowTB" FontSize="10" Text="{Binding Temperature.Minimum.Value, Converter={StaticResource cv}}" />
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<ProgressRing x:Name="pRing" RelativePanel.Above="ForecastList" RelativePanel.AlignHorizontalCenterWith="currentLocation" IsActive="{Binding ring, Mode=TwoWay}" RelativePanel.Below="currentLocation" />
VM
public class WeatherVM: INotifyPropertyChanged
{
public AccuWeather accuWeather { get; set; }
private string _cityData;
public string cityData
{
get { return _cityData; }
set
{
if (value != _cityData)
{
_cityData = value;
onPropertyChanged("cityData");
GetWeatherData();
}
}
}
private DailyForecast _currentDay;
public DailyForecast currentDay
{
get { return _currentDay; }
set
{
if (value != _currentDay) \
{
_currentDay = value;
onPropertyChanged("currentDay");
}
}
}
public bool ring { get; set; } = true;
public ObservableCollection<DailyForecast> dailyForecasts { get; set; }
public WeatherVM()
{
GetCuurentLocation();
dailyForecasts = new ObservableCollection<DailyForecast>();
}
private async void GetCuurentLocation() {
cityData = await BingLocator.GetCityData();
}
public async void GetWeatherData() {
var geoposition = await LocationManager.GetGeopositionAsync();
var currentLocationKey = await WeatherAPI.GetCityDstaAsync(geoposition.Coordinate.Point.Position.Latitude, geoposition.Coordinate.Point.Position.Longitude);
var weatherData = await WeatherAPI.GetWeatherAsync(currentLocationKey.Key);
if (weatherData != null) {
foreach (var item in weatherData.DailyForecasts) {
dailyForecasts.Add(item);
}
}
currentDay = dailyForecasts[0];
ring = false;
}
public event PropertyChangedEventHandler PropertyChanged;
private void onPropertyChanged(string property) {
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property));
}
}
}
The progress ring appears when the app launch, and disappear when the data is returned
From your code, it seems to be a layout issue. First you put ListView below currentLocation, then set ProgressRing above ListView and below currentLocation, so the height of ProgressRing wil be zero. I'm not clear about your layout, you can try to only set RelativePanel.Above="ForecastList" for ProgressRing to see if it will appear.
You can use Windows Community Toolkit control for showing progress ring (Busy indicator).
<Page ...
xmlns:controls="using:Microsoft.Toolkit.Uwp.UI.Controls"/>
<controls:Loading x:Name="LoadingControl" IsLoading="{Binding IsBusy}">
<!-- Loading screen content -->
</controls:Loading>
I'm trying to create a compound view component in Xamarin Forms called FormElement which is composed of two labels and an Entry:
<?xml version="1.0" encoding="UTF-8"?>
<StackLayout xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:custom="clr-namespace:Mynamespace;assembly=Mynamespace"
x:Class="Mynamespace.Components.FormEntry">
<StackLayout Orientation="Horizontal">
<Label x:Name="formRequiredStar"
IsVisible="{Binding IsRequired}"
Text="*" TextColor="Red"
FontSize="15"
FontAttributes="Bold"
Margin="-12,0,0,0"
HorizontalOptions="Start" />
<Label x:Name="formLabel"
HorizontalOptions="Start"
Text="{Binding LabelText}"
TextColor="{Binding LabelTextColor}"
FontSize="{Binding LabelTextFontSize}"
FontAttributes="{Binding LabelTextFontStyle}" />
</StackLayout>
<Frame BorderColor="Black"
CornerRadius="7"
Padding="5,0"
Margin="0,-3,0,0"
HasShadow="false">
<Entry x:Name="mainEntry"
Keyboard="{Binding KeybdType}"
Placeholder="{Binding EntryPlaceHolder}"
TextColor="Black"
FontSize="Default"
HeightRequest="{Binding EntryHeight}" />
</Frame>
</StackLayout>
Next, I want to switch focus from the Entry to a "next" element when the user taps the DONE button, so I do:
namespace Mynamespace.Components
{
public partial class FormEntry : StackLayout
{
public VisualElement NextFocus
{
get { return (VisualElement)GetValue(NextFocusProperty); }
set { SetValue(NextFocusProperty, value); }
}
public static readonly BindableProperty NextFocusProperty =
BindableProperty.Create(nameof(NextFocus),
typeof(VisualElement),
typeof(FormEntry),
null,
Xamarin.Forms.BindingMode.OneWay);
public FormEntry()
{
InitializeComponent();
BindingContext = this;
mainEntry.Completed += (s, e) =>
{
if (NextFocus != null)
{
NextFocus.Focus();
}
};
}
}
}
Next, in order for a FormEntry to be the target of NextFocus, I tried adding
this.Focused += (s,e) => { mainEntry.Focus(); };
to the constructor, but the handler is never called, and I also tried overriding
public new void Focus() {
mainEntry.Focus();
}
but this method is never called. Layout classes are descended from VisualElement so they should inherit Focused. Is there something about Layout objects that I'm missing? I could understand that Layout objects aren't usually the target of focus, but the event handler is supposedly there so I ought to be able to use it.
Here's an example of how I utilize the FormEntry on a login screen:
<!-- Email -->
<controls:FormEntry x:Name="usernameEntry"
Margin="25,40,25,0"
IsRequired="true"
EntryHeight="40"
KeybdType="Email"
NextFocus="{x:Reference passwordEntry}"
LabelText="{il8n:Translate Emailorusername}"
EntryPlaceHolder="{il8n:Translate EnterUsername}">
</controls:FormEntry>
<!-- Password -->
<controls:FormEntry x:Name="passwordEntry"
Margin="25,0,25,0"
IsRequired="true"
EntryHeight="40"
LabelText="{il8n:Translate Password}"
EntryPlaceHolder="{il8n:Translate EnterPassword}" />
I think you have get the nextfocus element, you can get mainEntry from nextfocus, like this:
public FormEntry ()
{
InitializeComponent ();
BindingContext = this;
mainEntry.Completed += (s, e) =>
{
if (NextFocus != null)
{
FormEntry formentry = (FormEntry)NextFocus;
Entry entry = formentry.mainEntry;
entry.Focus();
}
};
}
Then you can find you will get focus.
I'm developing a UWP app and I'm facing a problem. The app uses the MVVM pattern with Template10. I have created a similar solution that recreates the problem that I'm facing. In that solution, a list of orders are displayed, the user chooses an order and then click the "Edit" button. Then a second page is displayed and pre-loaded with the previous selected order, in this second page the user can edit the order. The problem is in the second page, the data bound to comboboxes doesn't show. Maybe the problem is related to this question. In my case, the SelectedValue is set before the ItemsSource. After debugging, I have reached these lines of code in OrderEditionPage.g.cs:
private void Update_ViewModel(global::ComboApp.ViewModels.OrderEditionPageViewModel obj, int phase)
{
this.bindingsTracking.UpdateChildListeners_ViewModel(obj);
if (obj != null)
{
if ((phase & (NOT_PHASED | DATA_CHANGED | (1 << 0))) != 0)
{
this.Update_ViewModel_SelectedOrder(obj.SelectedOrder, phase);
}
if ((phase & (NOT_PHASED | (1 << 0))) != 0)
{
this.Update_ViewModel_BusinessAssociates(obj.BusinessAssociates, phase);
this.Update_ViewModel_TransactionTypes(obj.TransactionTypes, phase);
this.Update_ViewModel_OrderTypes(obj.OrderTypes, phase);
this.Update_ViewModel_ShowSelectedOrder(obj.ShowSelectedOrder, phase);
}
}
}
If I could achieve this line of code be executed at last, my problem would be solved: this.Update_ViewModel_SelectedOrder(obj.SelectedOrder, phase);
How could I achieve this? How does Visual Studio determine the order of this lines?
OrderEditionPage.xaml
<Page
x:Class="ComboApp.Views.OrderEditionPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:myconverters="using:ComboApp.Converters"
xmlns:t10converters="using:Template10.Converters"
mc:Ignorable="d">
<Page.Resources>
<t10converters:ChangeTypeConverter x:Key="TypeConverter" />
<myconverters:DateTimeConverter x:Key="DateTimeConverter" />
</Page.Resources>
<ScrollViewer VerticalScrollBarVisibility="Auto">
<StackPanel
Padding="15, 5"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<TextBox
Header="Order #"
Margin="5"
Width="150"
HorizontalAlignment="Left"
Text="{x:Bind ViewModel.SelectedOrder.ExternalId, Mode=TwoWay}" />
<ComboBox
Header="Business Associate"
Margin="5"
MinWidth="300"
SelectedValuePath="BusinessAssociateId"
DisplayMemberPath="Name1"
ItemsSource="{x:Bind ViewModel.BusinessAssociates}"
SelectedValue="{x:Bind ViewModel.SelectedOrder.BusinessAssociateId, Mode=TwoWay, Converter={StaticResource TypeConverter}}" />
<DatePicker
Header="Delivery Date"
Margin="5"
MinWidth="0"
Width="200"
Date="{x:Bind ViewModel.SelectedOrder.DeliveryDate, Mode=TwoWay, Converter={StaticResource DateTimeConverter}}" />
<ComboBox
Header="Transaction"
MinWidth="200"
Margin="5"
SelectedValuePath="Value"
DisplayMemberPath="Display"
ItemsSource="{x:Bind ViewModel.TransactionTypes}"
SelectedValue="{x:Bind ViewModel.SelectedOrder.TransactionType, Mode=TwoWay}" />
<TextBox
Header="Priority"
Margin="5"
MaxWidth="150"
HorizontalAlignment="Left"
Text="{x:Bind ViewModel.SelectedOrder.Priority}" />
<ComboBox
Header="Type"
Margin="5"
MinWidth="200"
SelectedValuePath="Value"
DisplayMemberPath="Display"
ItemsSource="{x:Bind ViewModel.OrderTypes}"
SelectedValue="{x:Bind ViewModel.SelectedOrder.OrderType, Mode=TwoWay}" />
<TextBox
Header="Information"
Margin="5"
Height="100"
AcceptsReturn="True"
TextWrapping="Wrap"
ScrollViewer.VerticalScrollBarVisibility="Auto"
Text="{x:Bind ViewModel.SelectedOrder.Information, Mode=TwoWay}" />
<Button
Margin="5"
Content="Show"
Width="100"
HorizontalAlignment="Right"
Command="{x:Bind ViewModel.ShowSelectedOrder}" />
</StackPanel>
</ScrollViewer>
</Page>
OrderEditionPage.xaml.cs
using ComboApp.ViewModels;
using Windows.UI.Xaml.Controls;
namespace ComboApp.Views
{
public sealed partial class OrderEditionPage : Page
{
public OrderEditionPageViewModel ViewModel => DataContext as OrderEditionPageViewModel;
public OrderEditionPage()
{
this.InitializeComponent();
}
}
}
OrderEditionPageViewModel.cs
using ComboApp.Models;
using ComboApp.Services;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Threading.Tasks;
using Template10.Mvvm;
using Template10.Utils;
using Windows.UI.Xaml.Navigation;
namespace ComboApp.ViewModels
{
public class OrderEditionPageViewModel
: ViewModelBase
{
private IBusinessAssociateService businessAssociateService;
private Order selectedOrder;
public Order SelectedOrder
{
get { return selectedOrder; }
set { Set(ref selectedOrder, value); }
}
public ObservableCollection<object> TransactionTypes { get; set; } = new ObservableCollection<object>();
public ObservableCollection<object> OrderTypes { get; set; } = new ObservableCollection<object>();
public ObservableCollection<BusinessAssociate> BusinessAssociates { get; set; } = new ObservableCollection<BusinessAssociate>();
public OrderEditionPageViewModel(IBusinessAssociateService businessAssociateService)
{
this.businessAssociateService = businessAssociateService;
TransactionTypes.Add(new { Value = "I", Display = "Incoming" });
TransactionTypes.Add(new { Value = "O", Display = "Outgoing" });
TransactionTypes.Add(new { Value = "T", Display = "Transfer" });
OrderTypes.Add(new { Value = "M", Display = "Manual" });
OrderTypes.Add(new { Value = "A", Display = "Automatic" });
OrderTypes.Add(new { Value = "S", Display = "Semi-automatic" });
}
public override async Task OnNavigatedToAsync(object parameter, NavigationMode mode, IDictionary<string, object> state)
{
// Loading buiness associates
var response = await businessAssociateService.GetNextPageAsync();
if (response.IsSuccessful)
{
BusinessAssociates.AddRange(response.Result.Items);
}
SelectedOrder = (Order)parameter;
await base.OnNavigatedToAsync(parameter, mode, state);
}
private DelegateCommand showSelectedOrder;
public DelegateCommand ShowSelectedOrder => showSelectedOrder ?? (showSelectedOrder = new DelegateCommand(async () =>
{
await Views.MessageBox.ShowAsync(JsonConvert.SerializeObject(SelectedOrder, Formatting.Indented));
}));
}
}
It is a known issue of x:Bind when the SelectedValue of a ComboBox is sometimes set before its ItemsSource, you can read more about it here.
As a workaround you can use Bindings instead of x:Bind, but make sure that ItemsSource binding is placed before SelectedValue binding in XAML.
Alternatively you can try calling Bindings.Update() in the Page_Loaded event of your second page.