Xamarin.Forms Open ContentView with parameters in xaml - xamarin.forms

In my app there is a certain listview that I use over and over in my app, only with different elements in it. Therefore, I put everything inside a contentview and inflated it in my xaml like so:
<ContentPage Title="Newbies" BackgroundColor="#fafafa">
<views:CV_AllAdsRes />
</ContentPage>
The class looks like this:
public partial class CV_AllAdsRes : ContentView
{
public CV_AllAdsRes(int id)
{
InitializeComponent();
SetAds();
}
}
Now, this doenst work, because I am not using a "default constructor". If I remove the "int id" from the constructor, it works no problem. But I need to be able to inflate this content view with different parameters inside the xaml.
Am I understanding this concept wrong?
How can I inflate my content view and give it parameters via xaml?
Thank you

I solved it by using a second constructor next to the default one and giving it arguments from xaml like so:
<views:CV_AllAdsRes >
<x:Arguments >
<x:Int32>5</x:Int32>
</x:Arguments>
</views:CV_AllAdsRes>
this will give ID=5.

Related

Xamarin.Forms inflate view in code and pass parameter

I set up multiple different views that I can inflate from xaml like so:
<Grid Grid.Row="2" x:Name="grid_someelsesprofile_currentproducts">
<!-- ID = 4 for profile-->
<views:CV_AllAdsRes>
<x:Arguments >
<x:Int32>4</x:Int32>
</x:Arguments>
</views:CV_AllAdsRes>
</Grid>
I even pass a parameter, in this case an int with the value of 4.
Now I need to be able to inflate this also but from code and also pass the parameter and add this view to the grid. So basically all I am doing from above only from code basis.
CV_AllAdsRes is a contentview file.
How would that translate?
Thank you :)
It is actually super easy:
load the class holding the view and logic:
var xy = new CV_AllAdsRes(4, userID);
Then add class to grid.children method:
grid_someelsesprofile_currentproducts.Children.Add(xy);

Xamarin Forms / ReactiveUI - Using ReactiveUI for masterdetail page shows that viewmodel is null

I have a xamarin forms application that is based on ReactiveUI. The viewmodels inherit from ReactiveObject and the codebehind the xaml of the pages , they inherit/are based on from ReactiveContentPage, in case of the masterdetailpage it inherits from the ReactiveMasterDetailPage. The contentpages/masterdetailpage it self are based on ReactiveContent.
What I want to achieve is the following: Get views/xamlpages by giving a viewmodel type. I have the following code for that. But it gives a null at _viewLocator.Resolve...
public async Task<TViewModel> PushViewModelAsync<TViewModel>(bool animated) where TViewModel : class
{
var viewModel = DependencyInjectionService.Get<TViewModel>();
var view = _viewLocator.ResolveView(viewModel);
if (view is Page page)
{
view.ViewModel = viewModel;
await Application.Current.MainPage.Navigation.PushAsync(page, animated);
return viewModel;
}
else
{
throw new ArgumentException($"resolved view for {typeof(TViewModel)} is not a page.");
}
}
The problem is that this works for normal contentpages but it doesnt work for my MasterDetailPage, how is that possible?
<?xml version="1.0" encoding="utf-8" ?>
<rxui:ReactiveMasterDetailPage 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:vm="clr-namespace:DriverApp.ViewModels"
x:TypeArguments="vm:MainViewModel"
xmlns:rxui="clr-namespace:ReactiveUI.XamForms;assembly=ReactiveUI.XamForms"
xmlns:local="clr-namespace:DriverApp.Views; assembly=MasterDetailPageNavigation"
x:Class="DriverApp.Views.MainPage"
Title="Personal Organiser">
<MasterDetailPage.Master>
<local:MasterPage x:Name="masterPage" />
</MasterDetailPage.Master>
<MasterDetailPage.Detail>
<NavigationPage>
<x:Arguments>
<local:PlanningPage/>
</x:Arguments>
</NavigationPage>
</MasterDetailPage.Detail>
The viewmodel:
public class MainViewModel : ReactiveObject, IActivatableViewModel
{
}
I inject it like this:
services.AddTransient<IViewFor<MainViewModel>, MainPage>();
var viewModel = DependencyInjectionService.Get<TViewModel>();
var view = _viewLocator.ResolveView(viewModel);
Based on the code provided I would expect the value you are passing into the _viewLocator.ResolveView to be null. You showed the code where you are registering the IViewFor but I don't see any code registering the ViewModel itself.
Registering IViewFor<Foo>, Foo doesn't register the view model. It tells the type system that a given ViewModel will resolve a specific page.
Also, it seems like you are using a different container than the one provided by ReactiveUI. Which is okay, but you have to make sure all your dependencies are registered correctly in the container you plan to resolve dependencies from.
Lastly. You say it gives a null, but you don't say if the ViewModel you are passing is null or the object you are using to resolve is null.
Either way, I think this is an issue of having the dependencies registered in the correct place, based on the information provided.

setting up a simple component with data binding

I am trying to set up a component with data binding. This is basically a seperate content view that would have a property Item of type Item and supports binding. The following is the definition for the binding:
public static readonly BindableProperty ItemProperty
= BindableProperty.Create(
nameof(Item), typeof(Item), typeof(ItemComponent), null,
defaultBindingMode: BindingMode.TwoWay,
propertyChanged: ItemPropertyChanged);
private readonly ItemComponentViewModel vm;
static void ItemPropertyChanged(BindableObject bindable, object oldValue, object newValue)
{
var view = (ItemComponent)bindable;
view.Item = (Item)newValue;
}
public Item Item
{
get => (Item)GetValue(ItemProperty);
set
{
SetValue(ItemProperty, value);
if (vm != null) vm.Data = value; // break point here
}
}
The item doesn't seem to get bound. The commented line had a breakpoint and doesn't break. The complete source code is here: https://github.com/neville-nazerane/xamarin-component-sample
The above code can be found in the ItemComponent class. This component is called in the MainPage class.
Update
Just to explain what I am trying to simulate and why:
Why do we use MVVM in pages? While we'll have better type safety and performance by using the behind code directly, when the page's logic gets bigger, it becomes cleaner to handle it with a view model and to have a view that is simply bound to it.
Why do we have components? So that we can reuse a UI we intend to use with some functionality. If this functionality becomes complex it might need a view model for the same reason explained above. Hence, if pages need view models, I don't see why components won't need them at some point too.
This being considered this does feel like a particle requirement without easy to find examples.
So after looking at your example it turns out it's a bit of a complicated problem. So if my explanation is not clear, please let me know.
Basically the problem lies in these 2 code pieces:
MainPage.xaml(line 14):
<local:ItemComponent Item="{Binding Demo}" />
ItemComponent.xaml.cs (line 43):
public ItemComponent()
{
InitializeComponent();
vm = new ItemComponentViewModel();
BindingContext = vm; //this breaks the functionality
}
The first part you tell it to bind to the Demo property, and as normal it looks for this property in it's BindingContext. However in the second part you override it's BindigContext and set it to a ItemComponentViewModel this ViewModel however does not have a property Demo so the {Binding Demo} does not work on this new BindingContext you've set.
Now a possible solution for your demo application would be to change MainPage.xaml to the following 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:local="clr-namespace:SampleApp"
x:Class="SampleApp.MainPage"
x:DataType="local:MainViewModel"
x:Name="MyDemoPage">
<StackLayout>
<Label Text="Manual:" />
<Label Text="{Binding Demo.Title}" />
<Label Text="Component: " />
<local:ItemComponent Item="{Binding Path=BindingContext.Demo, Source={x:Reference MyDemoPage}}" />
</StackLayout>
</ContentPage>
Basically we now place the Demo binding outside of the BindingContext of our ItemComponent control. However if you want to use it in a ListView (if I remember correctly from your original question, this solution might not work and it's possible you'll have to drop the ItemComponentViewModel and bind directly to the properties (ListView will already make sure that the BindingContext of your ItemComponent is set to the current Item, no need to pass it around through a bindable property.
Hope this helps!

AutoWirePartialView with prism does not work or badly used?

I'm trying to use prism 7.1 AutoWirePartialView to bind a PartialView to its viewModel. However, binding is not working, or at least, setting the viewModel to the PartialView does not seem to work, it still has the page's BindingContext as BindingContext.
There is my Page :
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Project.Core.Views.NotConnectedViews.ForecastDemoPage"
xmlns:carouselForecast="clr-namespace:Project.Core.Views.MainViews"
xmlns:prism="clr-namespace:Prism.Mvvm;assembly=Prism.Forms"
x:Name="ForecastDemo"
BackgroundColor="{StaticResource PrimaryColorOne}" ControlTemplate="{StaticResource MainAppTemplate}">
<ContentPage.ToolbarItems>
<ToolbarItem Name="SearchForecast" Command="{Binding ShowSearchForecastDemoCommand}" Order="Primary" Icon="ic_search_white_24dp.png" Priority="0" />
</ContentPage.ToolbarItems>
<ContentView x:Name="ContentViewForecast" ControlTemplate="{StaticResource ForecastTownControlTemplate}">
<carouselForecast:ForecastPartialViewCarousel prism:ViewModelLocator.AutowirePartialView="{x:Reference ForecastDemo}"></carouselForecast:ForecastPartialViewCarousel>
</ContentView>
</ContentPage>
Binding: 'DayWeatherForecasts' property not found on
'Project.Core.ViewModels.ForecastDemoPageViewModel', target property:
'Project.Core.Views.MainViews.ForecastPartialViewCarousel.ItemsSource'
As you can see, I'm using the partial view as a ContentPresenter for a ContentView that uses a ControlTemplate.
There is my PartialView :
<carousel:CarouselViewControl x:Name="carouselView"
Position="{Binding CarouselPosition}"
PositionSelectedCommand="{Binding PositionChanged}"
Orientation="Horizontal" AnimateTransition="True" IsSwipeEnabled="False"
ItemsSource="{Binding DayWeatherForecasts}" InterPageSpacing="10"
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:carousel="clr-namespace:CarouselView.FormsPlugin.Abstractions;assembly=CarouselView.FormsPlugin.Abstractions"
x:Class="Project.Core.Views.MainViews.ForecastPartialViewCarousel">
<!-- Item template is defined here, removed for readability -->
</carousel:CarouselViewControl>
And this is my PartialView ViewModel :
namespace Project.Core.ViewModels
{
public class ForecastPartialViewCarouselViewModel : ViewModelBase
{
public ForecastPartialViewCarouselViewModel(IForecastService forecastService,
INavigationService navigationService) : base(navigationService)
{
InitStubForecasts();
}
private ObservableCollection<DayWeatherForecast> _dayWeatherForecasts;
public ObservableCollection<DayWeatherForecast> DayWeatherForecasts
{
get => _dayWeatherForecasts;
set => SetProperty(ref _dayWeatherForecasts, value);
}
}
}
Of course DayWeatherForecasts is set with some stub values. I simplified the viewModel for readability purpose.
I'm not using prism AutoWiring viewModel, so in app.xaml.cs :
containerRegistry.RegisterForNavigation<ForecastDemoPage, ForecastDemoPageViewModel>();
Question : Could it be that my PartialViewModel is in the ViewModels folder and that the Partialview I want to be be bound to this ViewModel is under a subfolder MainViews ? Should I create a MainViewsViewModel folder and put my viewModel there ?
EDIT : I tried this solution, but as I expected it does nothing.
If not, then I don't know why it doesnt work ...
Thanks !
Ok so I finally found out that its not enough to put this to my PartialView
prism:ViewModelLocator.AutowirePartialView="{x:Reference ForecastDemo}
As I organized my views in subfolders, prism cannot register alone my ViewModel and my PartialView.
So what I needed is to register manually the ViewModel with the PartialView using ViewModelLocationProvider
protected override void RegisterTypes(IContainerRegistry containerRegistry)
{
ViewModelLocationProvider.Register<ForecastPartialViewCarousel,
ForecastPartialViewCarouselViewModel>();
}
It's not only a matter of name, but of namespace too. If I wanted the PartialView to have the correct ViewModel set w/o registering it manually, I should have put my PartialView in the Views root folder and the corresponding ViewModel in the ViewModels root folder (with naming convention)

ReactiveTabbedPage Data Binding

I have been using ReactiveUI for a while with Xamarin Forms, but I've hit a brick wall when trying to use a ReactiveTabbedPage. I can't figure out how the ViewModel will get bound to the ReactiveContentPage's that are the children of the ReactiveTabbedPage.
So, as an example, I might have the following XAML:
<ReactiveTabbedPage x:Name="TabbedPage">
<local:Page1View x:Name="Page1" />
<local:Page2View x:Name="Page2" />
</ReactiveTabbedPage>
Where Page1View and Page2View are both of type ReactiveContentPage and T is the associated ViewModel.
What I expected to happen was that when the ReactiveTabbedPage was navigated to, Page1View would be displayed, and the ViewModel would be loaded (in the same way it would if I navigated to the Page1View directly). However, the ViewModel never gets called (the constructor is never fired and no data binding occurs).
However, both Page1View and Page2View do render and I can see the initial data that is created in those views (e.g. default text for labels etc.).
I know that the ViewModel stuff is working correctly, because if I navigate to Page1View directly (e.g. not in the ReactiveTabbedPage) everything displays as I expect.
Have I missed something, or am I going about this the wrong way? Or is this just not supported in the current version of RxUI?
Any advice is greatly appreciated!
The responsibility for tying the VM to the child pages lies with the host page (i.e. the ReactiveTabbedPage). It alone knows which VM corresponds to which view.
Let's take this one step at a time. First of all, the MainViewModel:
public class MainViewModel : ReactiveObject
{
public ChildViewModel1 Child1 => new ChildViewModel1();
public ChildViewModel2 Child2 => new ChildViewModel2();
}
This code obviously isn't realistic because you wouldn't want to recreate the child VMs upon every property access. It's more the API that's pertinent here.
ChildViewModel1 looks like this:
public class ChildViewModel1 : ReactiveObject
{
public string Test => "Hello";
}
And ChildViewModel2 looks much the same.
Now we can go about setting the views up. Our MainView.xaml looks like this:
<?xml version="1.0" encoding="utf-8" ?>
<rxui:ReactiveTabbedPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:TypeArguments="vms:MainViewModel"
xmlns:local="clr-namespace:ReactiveTabbedPageTest"
xmlns:rxui="clr-namespace:ReactiveUI.XamForms;assembly=ReactiveUI.XamForms"
xmlns:vms="clr-namespace:ReactiveTabbedPageTest.VMs"
x:Class="ReactiveTabbedPageTest.MainView">
<local:Child1View x:Name="child1View" Title="Child 1"/>
<local:Child2View x:Name="child2View" Title="Child 2"/>
</rxui:ReactiveTabbedPage>
Notice it declares each of the child views. We need to hook up the VMs to those views, which we do in the code-behind for MainView:
public partial class MainView : ReactiveTabbedPage<VMs.MainViewModel>
{
public MainView()
{
InitializeComponent();
this.ViewModel = new VMs.MainViewModel();
this.WhenActivated(
disposables =>
{
this
.OneWayBind(this.ViewModel, x => x.Child1, x => x.child1View.ViewModel)
.DisposeWith(disposables);
this
.OneWayBind(this.ViewModel, x => x.Child2, x => x.child2View.ViewModel)
.DisposeWith(disposables);
});
}
}
I've done this the safest way by using WhenActivated and OneWayBind calls. In reality, it's unlikely your child VMs will change, so directly assigning them rather than binding is totally fine.
Now our child views can be thrown together. Here's ChildView1.xaml:
<?xml version="1.0" encoding="utf-8" ?>
<rxui:ReactiveContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="ReactiveTabbedPageTest.Child1View"
x:TypeArguments="vms:ChildViewModel1"
xmlns:rxui="clr-namespace:ReactiveUI.XamForms;assembly=ReactiveUI.XamForms"
xmlns:vms="clr-namespace:ReactiveTabbedPageTest.VMs">
<Label x:Name="label" VerticalTextAlignment="Center" HorizontalTextAlignment="Center"/>
</rxui:ReactiveContentPage>
And the code-behind:
public partial class Child1View : ReactiveContentPage<ChildViewModel1>
{
public Child1View()
{
InitializeComponent();
this.WhenActivated(
disposables =>
{
this
.OneWayBind(this.ViewModel, x => x.Test, x => x.label.Text)
.DisposeWith(disposables);
});
}
}
Once again we're doing the usual RxUI binding goodness to associate properties in the VM with controls in the UI. And once again you could optimize this for properties that don't mutate.
For the purposes of this example, ChildView2 is much the same as ChildView1, but obviously it could be totally different.
The end result is as you'd expect:
What's not evident from the screenshot but is very important is that each tab is deactivating when you switch away from it (as would its associated view model if it implemented ISupportsActivation). This means you can clean up any bindings and subscriptions for that tab when it's not in use, reducing memory pressure and improving performance.

Resources