Prism View is being assigned parent page ViewModel inside XCT tabview - c#

I have historically been using the syncfusion tab control as part of my layout on my apps, however, with the recent introduction of the Xamarin Community Toolkit (xct) and with the LazyView being in the latest pre-release nuget I want to use this and see what the experience is like.
I perform the registration as normal in the app.xaml.cs file as I always do:
ViewModelLocationProvider.Register<HomeView, HomeViewViewModel>();
When I am on my content page I add the view to a tab item:
<xct:TabViewItem Icon="{AppThemeBinding Light={local:ImageResource App.Assets.Icons.Black.home_100px.png}, Dark={local:ImageResource App.Assets.Icons.White.home_100px.png}}" Text="Home">
<xct:TabViewItem.Content>
<xct:LazyView x:TypeArguments="dressmakerViews:HomeView" />
</xct:TabViewItem.Content>
</xct:TabViewItem>
At this point the view is added as expected but is assigned the viewmodel of the contentpage instead of the viewmodel I registered for the view.
Obvious troubleshooting here was to use LazyView outside of the tabview and that assigned the expected viewmodel.
My question here is to help me understand why it does this and how to resolve the problem.
As a workaround to get the app working I've had to move out the code into the view model for the content page but everything here feels wrong and I want to be in a position to resolve.

For some reason, TabView replaces any previous BindingContext values assigned to TabItems with its own BindingContext when it changes..
As a work-around you can add a listener to BindingContextChanged event for each TabViewItem.Content and reassign to its expected ViewModel.
UPDATE:
You can also wrap TabView's Content with a ContentView
<xct:TabViewItem Icon="{AppThemeBinding Light={local:ImageResource App.Assets.Icons.Black.home_100px.png}, Dark={local:ImageResource App.Assets.Icons.White.home_100px.png}}" Text="Home">
<xct:TabViewItem.Content>
<ContentView>
<xct:LazyView x:TypeArguments="dressmakerViews:HomeView" />
</ContentView>
</xct:TabViewItem.Content>
</xct:TabViewItem>
That way, LazyView's BindingContext won't be overwritten.

Related

Xamarin Forms - LoadFromXaml and referencing containing page

I have a ListView that's on a ContentPage, and I want the ItemTemplate of the ListView to be user-definable - i.e. parsing the XAML for the template at runtime. However, I need to give the user the option of having a button that invokes a command on the page. See below XAML;
<SwipeView>
<SwipeView.RightItems>
<SwipeItems>
<SwipeItem
Text = ""Delete""
BackgroundColor=""Red""
Command=""{Binding Path=ItemDeletedCommand, Source={x:Reference multiEntryPage}}""
CommandParameter=""{Binding .}"" />
</SwipeItems>
</SwipeView.RightItems>
<SwipeView.Content>
...
</SwipeView.Content>
And my content page has the x:Name="multiEntryPage" attribute in its ContentPage element. The code I use to parse the XAML is shown here:
listView.ItemTemplate = new DataTemplate(() => new ViewCell().LoadFromXaml(xaml));
However, this gives the below error:
{Xamarin.Forms.Xaml.XamlParseException: Position 11:33. Can not find
the object referenced by multiEntryPage at
Xamarin.Forms.Xaml.ApplyPropertiesVisitor.ProvideValue (System.Object&
value, Xamarin.Forms.Xaml.ElementNode node, System.Object source,
Xamarin.Forms.Xaml.XmlName propertyName)...
So I think it's fairly clear that the error is happening because the SwipeView XAML doesn't know about its parent at this stage, but I'm at a loss of how to resolve this. Any ideas?
Thanks!
You can create DataTemplate in following ways
Creating an Inline DataTemplate
Creating a DataTemplate with a Type
Creating a DataTemplate as a Resource
Refer https://learn.microsoft.com/en-us/xamarin/xamarin-forms/app-fundamentals/templates/data-templates/creating.

Hiding a tab in Xamarin.Forms using Prism framework

I am working on a Xamarin Forms project that uses an MVVM pattern and Prism.
My issue is, I need to be able to hide a tab on a tab page based off of a bool. There is a bindable property in the XAML called "IsVisible", which I assumed would hide the tab from the user, but instead the tab still shows but displays a blank page when selected.
Currently my XAML looks like this
<?xml version="1.0" encoding="utf-8"?>
<TabbedPage 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"
xmlns:local="clr-namespace:com.XXXXXXX"
prism:ViewModelLocator.AutowireViewModel="True"
BackgroundColor="White"
x:Class="com.XXXXXXXXXX.EditChargePage"
Title="{Binding Title}">
<local:EditChargeDetailsPage Title="Details" />
<local:EditChargeTrackingPage Title="Tracking" IsVisible="{Binding TabContext.HasTracking}" IsEnabled="{Binding TabContext.HasTracking}"/>
<local:EditChargeNotesPage Title="Notes" />
</TabbedPage>
Originally to solve the problem I just used "IsVisible", when that didn't work as expected I added "IsEnabled" which didn't seem to do anything.
Is there a way to hide the Tab from the UI using a boolean value in Xamarin (or Prism for Xamarin) without breaking MVVM?
You might want to check out the Prism Samples. The TabbedNavigation sample demonstrates how to initialize your tabbed children with INavigatingAware or the IEventAggregator, as well as dynamically adding the tabs at run time. If you need even finer control, such as a value in a ViewModel of one of your Child Pages that determines whether some other page should be shown, then you could use this to publish an event with the IEventAggregator and subscribe to that event in your TabbedPage to handle adding or removing the page.
Unfortunately IsVisible and IsEnabled do not actually work within a TabbedPage the way you were hoping. So the only way you can accomplish this is to actually manipulate the Children of the TabbedPage directly pushing or popping them from the collection.

MVVM Displayer OberservableCollection<ViewModel> with unknown UserControl

im a little bit stuck with my current project and hope someone can help me out of this.
My application works with plugins so that the user is able to add additional functionality to the application. Now i would like to have the configuration window in the same style (Maybe a plugin need some kind of configurations).
The configuration window loads all plugins and get the configuration ViewModel from the plugins. All the ViewModels are stored in an ObservableCollection. These ViewModels should be displayed in a TabControl (one tab per ViewModel)
But i dont know the type of UserControl the plugin is using, because the plugin come up with its own UserControl for configuration purposes.
Otherwise i would create a TabControl, bind its ItemsSource to the ObservableCollection and specify the UserControl in the Resources (DataTemplates).
But how to do it in case the UserControls are unknown to compile time?
I thought about using a ObservableCollection instead of ViewModels, but im not realy happy with that and even dont know if this will work.
Do you have some idea how to deal with that?
Kind regards,
SyLuS
You could use a ContentControl to achieve this.
It's used to show views depending on their viewmodel.
In your xaml you can specifiy which view should be shown. Based on the viewmodel which is the current DataContext.
<ContentControl>
<ContentControl.Resources>
<DataTemplate DataType="{x:Type vm:MyViewModel}">
<v:MyView/>
</DataTemplate>
</<ContentControl.Resources>
</ContentControl>
But when you say you are using a plugin system, maybe something like PRISM, you have to setup the datatemplates automatically. Never done this before. But maybe I gave you a point where you can start.

WPF and MVVM: bind UserControl to XAML dynamically

seems like a trivial task: i am building a wpf application, using MVVM pattern. what i want is dynamically change part of a view, using different UserControls, dependent on user input.
let's say, i have got 2 UserControls, one with a button, and another with a label.
in main view i have a container for that. following XAML "works":
<GroupBox Header="container" >
<local:UserControlButton />
</GroupBox>
and a UserControl element with buttons pops up. if i change it to another one, it works too.
question is how to feed that groupbox dynamically. if i put something like that in my model view:
private UserControl _myControl;
public UserControl MyControl
{
get
{
return _myControl;
}
set
{
_myControl= value;
InvokePropertyChanged("MyControl");
}
}
and change my view XAML to something like:
<GroupBox Header="container" >
<ItemsControl ItemsSource="{Binding MyControl}" />
</GroupBox>
and feed it from command with usercontrol for button or for label: nothing happens, although "MyControl" variable is set and is "invoke property changed"..
Obviously there are many ways to skin this particular cat - but to answer the question of why it doesn't work you need to look into the ItemsSource property of ItemsControl on MSDN.
The items control is designed to show multiple items, provided through an IEnumerable passed to the ItemsSource property. You are passing a UserControl, so the binding will fail.
For your example, I would change the ItemsControl to a ContentControl and bind the content to your MyControl property. This should then work.
<GroupBox Header="container" >
<ContentControl Content="{Binding MyControl}" />
</GroupBox>
However, I would strongly recommend looking into other ways of doing this - having a control in your VM breaks MVVM to my mind. Depending on what you are doing look at data templates - #Sheridan's link in the comments provides an great description of a way to do it.
Couldn't post this as a comment so adding as answer..
Have a look at this:
Implementing an own "Factory" for reusing Views in WPF
It uses DataTemplates but doesn't require the DataTemplate section for each view. If you potentially have a lot of user controls/views you wish to display or you are reusing through multiple views or you are intending to actually dynamically generate a view (versus just loading an existing user control) then this might suite your needs.

Changing content of Window (WPF)

I've created a simple WPF application which has two Windows. The user fills in some information on the first Window and then clicks Ok which will take them to the second Window. This is working fine but I'm trying to incorporate both Windows into a single Window so just the content changes.
I managed to find this Resource management when changing window content which seems like it is what I'm after. However, I've search for ContentPresenter but couldn't find much help for how I need to use it. For example, if I use a ContentPresenter, where do I put the existing XAML elements that are in the two Windows? I'm guessing the first Window will go into the ContentPresenter but the second one will need to be put somewhere for when it needs to be switched in.
Any help would be great. A simple working example would be even better.
TIA
A ContentPresenter is normally used when restyling existing controls. It is the place where the Content of a control is placed. Instead you should use a ContentControl, which is simply a control that has a content element. Alternatively, you could directly set the Content of your window.
You extract the contents of your two existing windows into two UserControls. Then you create a new Window which will host the contents. Depending on your business logic, you set the content of that window (or that window's ContentControl if you want additional "master" content) to either of those two UserControls.
EDIT:
As a starting point. This is not complete working code, just to get you started. Note that this is bad architecture; you should probably use a MVVM or similar approach once you get this running!
<Window>
<ContentControl Name="ContentHolder" />
</Window>
<UserControl x:Class="MyFirstUserControl" /> <!-- Originally the first window -->
<UserControl x:Class="MySecondUserControl" /> <!-- Originally the second window -->
In code behind of Window:
// Somewhere, ex. in constructor
this.ContentHolder.Content = new MyFirstUserControl;
// Somewhere else, ex. in reaction to user interaction
this.ContentHolder.Content = new MySecondUserControl;
I use ContentPresenter for snapping in content. In the window, I put something like this:
<ContentPresenter Content="{Binding MainContent}" />
In the view model, I have a property called MainContent of type object:
public object MainContent { get { return (object)GetValue(MainContentProperty); } set { SetValue(MainContentProperty, value); } }
public static readonly DependencyProperty MainContentProperty = DependencyProperty.Register("MainContent", typeof(object), typeof(SomeViewModel), new FrameworkPropertyMetadata(null));
Whatever you set MainContent to will show up in the window.
To keep the separation between view and view model, I typically set the MainContent property to another view model and use a data template to map that view model to a view:
<DataTemplate DataType="{x:Type viewmodels:PlanViewModel}">
<views:PlanView />
</DataTemplate>
I put that data template in some central resource dictionary along with a bunch of other view-model-to-view mappers.

Categories