Is there a way how to provide a nested view it's own viewmodel?
Example:
Master view of type TabbedView has multiple tabs.
<mvx:MvxTabbedPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Foo.Core.Pages.Access.MainPage"
xmlns:res="clr-namespace:Foo.Core.Resources;assembly=Foo.Core"
xmlns:mvx="clr-namespace:MvvmCross.Forms.Views;assembly=MvvmCross.Forms"
xmlns:views="clr-namespace:Foo.Core.Pages.Access">
<TabbedPage.Children>
<views:LoginPage></views:LoginPage>
<views:RegisterPage></views:RegisterPage>
</TabbedPage.Children>
</mvx:MvxTabbedPage>
The LoginPage and RegisterPage are in separate views. But all the binding must be in the MainViewModel and I want the bindings to be separately in the LoginViewModel and RegisterViewModel.
Is there a way how to setup the binding to the properties to appropriate viewmodel? Preferably in XAML.
In order that to work you need to let the NavigationService (and therefore the Presenter) to load the children pages:
Xamarin.Forms View Presenter -> MvxTabbedPagePresentationAttribute
In your case it should be something like:
ViewModels
public class MyTabsContainerViewModel : MvxViewModel
{
private readonly IMvxNavigationService _navigationService;
public MyTabsContainerViewModel(IMvxNavigationService navigationService)
{
_navigationService = navigationService ?? throw new ArgumentNullException(nameof(navigationService));
}
public override async void ViewAppearing()
{
await ShowInitialViewModels();
base.ViewAppearing();
}
private async Task ShowInitialViewModels()
{
var tasks = new List<Task>();
tasks.Add(_navigationService.Navigate<LoginViewModel>());
tasks.Add(_navigationService.Navigate<RegisterViewModel>());
await Task.WhenAll(tasks);
}
}
public class LoginViewModel : MvxViewModel
{
}
public class RegisterViewModel : MvxViewModel
{
}
Views
MyTabsContainerPage.xaml
<?xml version="1.0" encoding="utf-8"?>
<views:MvxTabbedPage x:TypeArguments="viewModels:MyTabsContainerViewModel" xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:viewModels="clr-namespace:Foo.Core.ViewModels;assembly=Foo.Core"
xmlns:views="clr-namespace:MvvmCross.Forms.Views;assembly=MvvmCross.Forms"
x:Class="Foo.Core.Pages.Access.MyTabsContainerPage">
</views:MvxTabbedPage>
MyTabsContainerPage.xaml.cs
[XamlCompilation(XamlCompilationOptions.Compile)]
[MvxMasterDetailPagePresentation(Position = MasterDetailPosition.Detail, NoHistory = true)]
public partial class MyTabsContainerPage : MvxTabbedPage<MyTabsContainerViewModel>
{
public MyTabsContainerPage()
{
InitializeComponent();
}
}
LoginPage.xaml
<?xml version="1.0" encoding="utf-8" ?>
<views:MvxContentPage x:TypeArguments="viewModels:LoginViewModel" xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:viewModels="clr-namespace:Foo.Core.ViewModels;assembly=Foo.Core"
xmlns:views="clr-namespace:MvvmCross.Forms.Views;assembly=MvvmCross.Forms"
x:Class="Foo.Core.Pages.Access.MixedNavTab1Page">
<StackLayout>
<Label Text="This is Tab 1" />
</StackLayout>
</views:MvxContentPage>
LoginPage.xaml.cs
[XamlCompilation(XamlCompilationOptions.Compile)]
[MvxTabbedPagePresentation(WrapInNavigationPage = false, Title = "LoginTab1")]
public partial class LoginPage : MvxContentPage<LoginViewModel>
{
public LoginPage()
{
InitializeComponent();
}
}
RegisterPage.xaml
<?xml version="1.0" encoding="utf-8" ?>
<views:MvxContentPage x:TypeArguments="viewModels:RegisterViewModel" xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:viewModels="clr-namespace:Foo.Core.ViewModels;assembly=Foo.Core"
xmlns:views="clr-namespace:MvvmCross.Forms.Views;assembly=MvvmCross.Forms"
x:Class="Foo.Core.Pages.Access.RegisterPage">
<StackLayout>
<Label Text="This is Tab 2" />
</StackLayout>
</views:MvxContentPage>
RegisterPage.xaml.cs
[XamlCompilation(XamlCompilationOptions.Compile)]
[MvxTabbedPagePresentation(WrapInNavigationPage = false, Title = "RegisterTab2")]
public partial class RegisterPage : MvxContentPage<RegisterViewModel>
{
public RegisterPage()
{
InitializeComponent();
}
}
Full sample in the Playground project
HIH
Related
I have a xaml page that contains two instances of the same content view. The content view have a datepicker which should update a value in the parent view model ( each content view should update a different variable in the view model). I tried to do the bindiable property but it's not working. I set the BindingMode to TwoWay but that's not working.
The issue is that the binding is not working from the contentview to the parent viewmodel through the bindiable property. Any input is much appreciated.
Below is my code:
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"
BackgroundColor="{DynamicResource PageBackgroundColor}"
xmlns:picker="clr-namespace:TestSync.View"
xmlns:viewmodel="clr-namespace:TestSync.ViewModel"
x:DataType="viewmodel:TimeTrackerViewModel"
x:Class="TestSync.MainPage">
<VerticalStackLayout>
<Label Text="{Binding SelectedDate}"/>
<Label Text="{Binding SelectedDate1}"/>
<picker:DateTimePickerContentView CardTitle="First DatePicker" CardDate="{Binding SelectedDate,Mode=TwoWay}" />
<picker:DateTimePickerContentView CardTitle="Second DatePicker" CardDate="{Binding SelectedDate1,Mode=TwoWay}" />
</VerticalStackLayout>
</ContentPage>
TimeTrackerViewModel.cs
namespace TestSync.ViewModel
{
public partial class TimeTrackerViewModel :ObservableObject
{
[ObservableProperty]
public DateTime selectedDate;
[ObservableProperty]
public DateTime selectedDate1;
}
}
DateTimePickerContentView.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"
xmlns:viewmodel="clr-namespace:TestSync.View"
x:DataType="viewmodel:DateTimePickerContentView"
x:Class="TestSync.View.DateTimePickerContentView"
>
<VerticalStackLayout>
<Label Text="{Binding CardTitle}"/>
<DatePicker x:Name="myDate" Date="{Binding CardDate}" />
</VerticalStackLayout>
</ContentView>
and DateTimePickerContetntView.xaml.cs
namespace TestSync.View;
public partial class DateTimePickerContentView : ContentView
{
public static readonly BindableProperty CardTitleProperty = BindableProperty.Create(nameof(CardTitle), typeof(string), typeof(DateTimePickerContentView), string.Empty);
public string CardTitle
{
get => (string)GetValue(DateTimePickerContentView.CardTitleProperty);
set => SetValue(DateTimePickerContentView.CardTitleProperty, value);
}
public static readonly BindableProperty CardDateProperty = BindableProperty.Create(nameof(CardDate), typeof(DateTime), typeof(DateTimePickerContentView), defaultValue:DateTime.Parse("12/15/1992"),defaultBindingMode:BindingMode.TwoWay,propertyChanged:test);
private static void test(BindableObject bindable, object oldValue, object newValue)
{
var mytest= bindable as DateTimePickerContentView;
mytest.myDate.Date = (DateTime)newValue;
}
public DateTime CardDate
{
get => (DateTime)GetValue(DateTimePickerContentView.CardDateProperty);
set => SetValue(DateTimePickerContentView.CardDateProperty, value);
}
public DateTimePickerContentView()
{
InitializeComponent();
BindingContext = this;
}
}
I give you a workaround here.
For DateTimePickerContentView.xaml, define the BindingContext
<ContentView xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
...
x:Name="this">
<VerticalStackLayout BindingContext="{x:Reference this}">
<Label Text="{Binding CardTitle}"/>
<DatePicker x:Name="myDate" Date="{Binding CardDate}" />
</VerticalStackLayout>
</ContentView>
So for DateTimePickerContentView.cs, just delete this line
...
public DateTimePickerContentView()
{
InitializeComponent();
//BindingContext = this;
}
For data binding in a ContentView, you could refer to this official doc: Define the UI.
And if you want to set a default value, you should set it in TimeTrackerViewModel, because TimeTrackerViewModel's constructor execute after custom control set the default value. Then it will be replaced such as 1/1/1900 .
public TimeTrackerViewModel()
{
SelectedDate = DateTime.Parse("12/15/1992");
SelectedDate1 = DateTime.Parse("12/15/1992");
}
Hope it works for you.
I am using Xamarin.Forms to develop an application for Android.
I am using the Xamarin.
I want to display the background as tiles.
I have prepared an image of 100x100 pixels.
\Android\MyApp\MyApp.Android\Resources\drawable\background.jpg
<?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:d="http://xamarin.com/schemas/2014/forms/design"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:viewmodels="clr-namespace:MyApp.ViewModels"
x:DataType="viewmodels:LoginViewModel"
mc:Ignorable="d"
x:Class="MyApp.Views.LoginPage"
Shell.NavBarIsVisible="False"
BackgroundImage="background.jpg"
>
This way, the background.jpg will be enlarged and displayed.
I want to display it repeatedly in tiles.
You can display it repeatedly in tiles through below steps.
1.Create a drawable xml file on your Android project:
<?xml version="1.0" encoding="utf-8" ?>
<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
android:tileMode="repeat"
android:src="#drawable/clock" >
</bitmap>
2.After that create below two effect classes on your shared project:
//base class
namespace AppTiles
{
public class BaseEffect : RoutingEffect
{
public const string EffectNamespace = "AppTiles";
public BaseEffect(String effectId) : base($"{EffectNamespace}.
{effectId}")
{
}
}
}
//effect class
namespace AppTiles
{
public class CoverEffect : BaseEffect
{
public CoverEffect() : base(nameof(CoverEffect))
{
}
}
}
3.Create a effect class in your Android project
[assembly: ResolutionGroupName("AppTiles") ]
[assembly: ExportEffect(typeof(AppTiles.Droid.CoverEffect),
nameof(CoverEffect))]
namespace AppTiles.Droid
{
public class CoverEffect : PlatformEffect
{
protected override void OnAttached()
{
UpdateBackground();
}
protected override void OnDetached()
{
}
private void UpdateBackground()
{
Android.Views.View mView = Container as Android.Views.View;
if (mView != null)
{
mView.Background = ContextCompat.GetDrawable(mView.Context, Resource.Drawable.XMLFile1);
}
}
}
}
4.Add below xmal in shared main page code behind:
<StackLayout Orientation="Vertical" Spacing="0">
<StackLayout.Effects>
<effects:CoverEffect/>
</StackLayout.Effects>
</StackLayout>
Below is the screenshot:
I have a simple Xamarin.Forms app with a main page as TabbedPage which looks like this:
<?xml version="1.0" encoding="utf-8" ?>
<TabbedPage
x:Class="PrismSample.Views.MainPage"
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:prism="http://prismlibrary.com"
xmlns:views="clr-namespace:PrismSample.Views"
Title="Main Page"
prism:ViewModelLocator.AutowireViewModel="True">
<TabbedPage.Children>
<NavigationPage Title="Page1">
<x:Arguments>
<views:OnePage Title="Page 1" />
</x:Arguments>
</NavigationPage>
<NavigationPage Title="Page2">
<x:Arguments>
<views:TwoPage Title="Page 2" />
</x:Arguments>
</NavigationPage>
</TabbedPage.Children>
</TabbedPage>
On the page "OnePage" I have a button. When I tap on that button I want to navigate to a page named "ThreePage".
OnPage:
<?xml version="1.0" encoding="UTF-8" ?>
<ContentPage
x:Class="PrismSample.Views.OnePage"
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:prism="http://prismlibrary.com"
prism:ViewModelLocator.AutowireViewModel="True">
<ContentPage.Content>
<StackLayout Margin="20" Spacing="20">
<Label HorizontalOptions="CenterAndExpand" Text="Page 1" />
<Button
x:Name="Button1"
Command="{Binding ClickMeCommand}"
HorizontalOptions="FillAndExpand"
Text="Open Page 3" />
</StackLayout>
</ContentPage.Content>
</ContentPage>
OnePageViewModel:
namespace PrismSample.ViewModels
{
public class OnePageViewModel : BindableBase
{
private readonly INavigationService navigationService;
public DelegateCommand ClickMeCommand { private set; get; }
public OnePageViewModel(INavigationService navigationService)
{
this.navigationService = navigationService;
ClickMeCommand = new DelegateCommand(async () => await ClickedAsync(), () => true);
Debug.WriteLine($"NavigationStack: {this.navigationService.GetNavigationUriPath()}", nameof(OnePageViewModel));
}
public Task ClickedAsync()
{
Debug.WriteLine($"You clicked me!", nameof(OnePageViewModel));
return navigationService.NavigateAsync("ThreePage");
}
}
}
App.xaml.cs
namespace PrismSample
{
public partial class App
{
public App() : this(null) { }
public App(IPlatformInitializer initializer) : base(initializer) { }
protected override async void OnInitialized()
{
InitializeComponent();
var result = await NavigationService.NavigateAsync("MainPage");
if(!result.Success)
{
System.Diagnostics.Debugger.Break();
}
}
protected override void RegisterTypes(IContainerRegistry containerRegistry)
{
containerRegistry.RegisterForNavigation<NavigationPage>();
containerRegistry.RegisterForNavigation<MainPage, MainPageViewModel>();
containerRegistry.RegisterForNavigation<OnePage, OnePageViewModel>();
containerRegistry.RegisterForNavigation<TwoPage, TwoPageViewModel>();
containerRegistry.RegisterForNavigation<ThreePage, ThreePageViewModel>();
}
}
}
I expected the following:
When tapping on the button a navigation to the page "ThreePage" is done within the first tab. I also expected to have a "Back" button in the navigation bar.
What happens:
Nothing in the first tab. But if I switch to the second tab it shows the page "ThreePage" but also without a "Back" button.
What is going wrong here???
I have attached my project here:
https://www.dropbox.com/s/fuu2wo5zqhp52gk/08-TabbedNavigation.zip?dl=0
Navigating via TabbedPages is broken in the latest Prism released version (8.0.0.1909).
[Bug] Navigation when in Tabbed Pages doesn't change currently selected tab #2218
(It seems they finally have fixed this issue but they haven't released a new version until now)
So downgrade Prism.DryIoc to 7.2.0.1422 or wait for the next Prism release...
I have created bindable property called Text in TargetClass.cs. That Text property is nested bindable property.
TargetClass.cs :
public class TargetClass : BindableObject
{
public static readonly BindableProperty TextProperty =
BindableProperty.Create("Text", typeof(string), typeof(TargetClass), "Default", BindingMode.TwoWay, null,
OnTextChanged);
private static void OnTextChanged(BindableObject bindable, object oldValue, object newValue)
{
}
public string Text
{
get
{
return (string)GetValue(TextProperty);
}
set
{
SetValue(TextProperty, value);
}
}
}
Then I have created MyView.cs
public class MyView : ContentView
{
private TargetClass target;
Label label;
public TargetClass Target
{
get
{
return target;
}
set
{
target = value;
label.Text = target.Text;
}
}
public MyView()
{
label = new Label();
label.FontSize = 50;
Content = label;
}
}
ViewModel.cs :
public class ViewModel : INotifyPropertyChanged
{
private string m_text = "New Value";
public string TextValue
{
get { return m_text; }
set
{
m_text = value;
OnPropertyChanged("TextValue");
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
MainPage.xaml :
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:d="http://xamarin.com/schemas/2014/forms/design"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
xmlns:local="clr-namespace:BindingDemo"
x:Class="BindingDemo.MainPage">
<ContentPage.BindingContext>
<local:ViewModel />
</ContentPage.BindingContext>
<ContentPage.Resources>
<ResourceDictionary>
<local:TargetClass x:Key="target" Text="{Binding TextValue}"/>
</ResourceDictionary>
</ContentPage.Resources>
<StackLayout>
<local:MyView Target="{StaticResource target}"/>
</StackLayout>
It is working when I give some string value to Text property like,
<ContentPage.BindingContext>
<local:ViewModel />
</ContentPage.BindingContext>
<ContentPage.Resources>
<ResourceDictionary>
<local:TargetClass x:Key="target" Text="Hello World"/>
</ResourceDictionary>
</ContentPage.Resources>
<StackLayout>
<local:MyView Target="{StaticResource target}"/>
</StackLayout>
in xaml.
But it doesnt work in MVVM binding When I bind the Text property like,
<ContentPage.BindingContext>
<local:ViewModel />
</ContentPage.BindingContext>
<ContentPage.Resources>
<ResourceDictionary>
<local:TargetClass x:Key="target" Text="{Binding TextValue}"/>
</ResourceDictionary>
</ContentPage.Resources>
<StackLayout>
<local:MyView Target="{StaticResource target}"/>
</StackLayout>
There is no problem with MVVM binding(ViewModel.cs) because it works well with another bindable property.
But it works if the Text property is added in MyView.cs and used like MyView.Text. It doesn't work only when it is added in TargetClass.cs and used like MyView.TargetClass.Text.
Is it possible to use the nested property in data binding in Xamarin Forms?
I don't think objects in the resource dictionary get a BindingContext assigned to them.
So you should modify it from code-behind to assign a context to any resource of type BindableObject
in your page.axml.cs:
protected override void OnBindingContextChanged()
{
base.OnBindingContextChanged();
if (this.Resources != null)
{
foreach (var resource in this.Resources.Values.OfType<BindableObject>())
{
resource.BindingContext = this.BindingContext;
}
}
}
I have a FlowListView control that is not displaying the items from a list. For test purposes I just wanted to display file path of the photo as a label.
Here is the xaml code:
<?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:controls="clr-namespace:XLabs.Forms.Controls;assembly=XLabs.Forms.Controls"
xmlns:flv="clr-namespace:DLToolkit.Forms.Controls;assembly=DLToolkit.Forms.Controls.FlowListView"
x:Class="TamarianApp.ImagePage">
<ContentPage.Content>
<flv:FlowListView FlowColumnCount="3" SeparatorVisibility="None" HasUnevenRows="true" x:Name="image_gallary" ItemsSource="{Binding photos}" HeightRequest="100" VerticalOptions="FillAndExpand" HorizontalOptions="FillAndExpand">
<flv:FlowListView.FlowColumnTemplate>
<DataTemplate>
<Label Text="{Binding filepath}" TextColor="Black" Margin="20" VerticalOptions="Fill" HorizontalOptions="Fill" XAlign="Center" YAlign="Center"></Label>
</DataTemplate>
</flv:FlowListView.FlowColumnTemplate>
</flv:FlowListView>
</ContentPage.Content>
</ContentPage>
Here is the backend:
public partial class ImagePage : ContentPage
{
public List<Photo> photos = App.rug.photos
public ImagePage()
{
InitializeComponent();
Title = "Photos";
}
}
'App.rug.photos' is a List of the Photo class which contains the string field 'filepath'. Debugging shows field 'photos' is not empty and contains the data from 'App.rug.photos.'
Please Help.
It may be to do with how you are assigning the list, you need to make sure that the list is a Property in order for the binding to work correctly.
Try..
public partial class ImagePage : ContentPage
{
public List<Photo> photos {get; set;}
public ImagePage()
{
InitializeComponent();
Title = "Photos";
photos = App.rug.photos
}
}
You may also need to set the binding context
public partial class ImagePage : ContentPage
{
public List<Photo> photos {get; set;};
public ImagePage()
{
InitializeComponent();
this.BindingContext = this;
Title = "Photos";
photos = App.rug.photos
}
}
Try this code.
public ObservableCollection<object>() photos;
...
var oc = new ObservableCollection<Photo>();
foreach (var item in App.rug.photos)
oc.Add(item);
photos = oc as ObservableCollection<object>;
I was having trouble with ObservableCollection<MyObject>, but solved it by using ObservableCollection<object>.