how can I manipulate the View (XAML) from my ViewModel? (Xamarin Forms) - xaml

how can I manipulate the View (XAML) from my ViewModel?
For example, I have a detail view for an object. The view have a grid. Depending of the object, the Grid should have different count of rows and cols. The number of cols and rows is set in the object details. Is it possible to do that from the ViewModel or I have to do that in the View .cs?
I open the detail view from a ListView (OnItemSelect):
await Navigation.PushAsync(new AlgoDetailPage(new AlgoDetailViewModel(algo)));
AlgoDetailPage - Here I want to add the cols and rows, depending of the object.
<?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="NotsanHessen.Views.AlgoDetailPage"
Title="{Binding Algo.Title}">
<StackLayout>
<Grid>
</Grid>
</StackLayout>
</ContentPage>
The AlgoDetailPage.cs:
public partial class AlgoDetailPage : ContentPage
{
AlgoDetailViewModel viewModel;
public AlgoDetailPage(AlgoDetailViewModel viewModel)
{
InitializeComponent();
BindingContext = this.viewModel = viewModel;
}
public AlgoDetailPage()
{
InitializeComponent();
BindingContext = viewModel;
}
}
The ViewModel:
public class AlgoDetailViewModel : BaseViewModel
{
public Algo Algo { get; set; }
public AlgoDetailViewModel(Algo algo = null)
{
this.Algo = algo;
// Rows: algo.Rows
// Cols: algoCols
}
}

you don't manipulate the View from the VM. Instead, the View should use the VM's properties to determine it's layout. In this case, you would add the rows and cols in the View based on the data from the VM.

Technically, you don't have to do that from View itself. If you want to be exactly orthodox you can create the custom control that will handle the binding for you. Personally I wouldn't recommend this level of following some theory, but if you want it it is possible.
Other than that you may try to take a look at CollectionView that will appear in Xamarin 4.0 it may be close to what you have requested (you haven't specified exactly how column width is to be handled). Also there could be some 3rd party components that can handle this.

If it is a complex grid, have two different content views. In your xaml, based on the condition show or hide the content views. In this way, you can keep the code manageable any time.

And if you REALLY necessarily want to access the View from ViewModel, you can Bind View's Load Event to a Command and pass the View itself as a Command Parameter.

Related

Connecting Xamarin Form tabbed pages to XAML

I am fairly new to all of this and am currently attempting to get the height of the tabs in the Xamarin tabbed page form. The only solution I found to this is to write a custom renderer, and that is what I'm having a hard time with.
After a couple days of struggling I managed to get to this spot (hopefully on the right track), however I just cannot understand how to connect the XAML to my custom tabbed page. This is what I have so far.
GameTab.xaml
<?xml version="1.0" encoding="utf-8" ?>
<TabbedPage
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Diplomacy.Views.GameTab"
xmlns:pages="clr-namespace:Diplomacy.Views"
xmlns:custom="clr-namespace:Diplomacy.CustomRenderers">
<!--Pages can be added as references or inline-->
<TabbedPage.Children>
<pages:TabbedMap Title="Map" Icon="tank.png"/>
<pages:TabbedChat Title="Chat" Icon="chat.png"/>
</TabbedPage.Children>
</TabbedPage>
GameTab.xaml.cs
namespace Diplomacy.Views
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class GameTab : Xamarin.Forms.TabbedPage
{
SelectionGamesViewModel viewModel;
public GameTab(SelectionGamesViewModel viewModel)
{
InitializeComponent();
// Disables switching between tabs with the swipe gesture
On<Xamarin.Forms.PlatformConfiguration.Android>().DisableSwipePaging();
// Sets the tab at the bottom in android phones
On<Xamarin.Forms.PlatformConfiguration.Android>().SetToolbarPlacement(ToolbarPlacement.Bottom);
BindingContext = this.viewModel = viewModel;
}
}
MyCustomRenderer.cs
namespace Diplomacy.CustomRenderers
{
public class CustomTabbedPage : Xamarin.Forms.TabbedPage
{
}
}
At this point my next step is to use the CustomTabbedPage (correct me if I'm wrong from here on out).
With this line: xmlns:custom="clr-namespace:Diplomacy.CustomRenderers"
I should be able to wedge myself into the Xamarin Tabbed Page form with my custom render, which currently does nothing.
The way that I believe this is done is by changing TabbedPage to CustomTabbedPage like shown below
<?xml version="1.0" encoding="utf-8" ?>
<custom:CustomTabbedPage
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Diplomacy.Views.GameTab"
xmlns:pages="clr-namespace:Diplomacy.Views"
xmlns:custom="clr-namespace:Diplomacy.CustomRenderers">
<!--Pages can be added as references or inline-->
.
. // Same stuff goes here
.
</custom:CustomTabbedPage>
However when I do that, I get all sorts of errors in GameTab.xaml.cs and 1 error in the navigation page trying to push GameTab (the 2nd error)
I've been struggling probably for weeks now, I really need some help on how to set up this custom render. I get the theory of what it does and what is it's purpose, however I don't fully understand how the compiler handles it all, and how to link it all together. Please and thank you. Sorry for the long question, I just wanted to be thorough.
EDIT:
This is the Android custom renderer code that lives in Diplomacy.Android
[assembly: ExportRenderer(typeof(CustomTabbedPage), typeof(MyTabbedPage))]
namespace Diplomacy.Droid.CustomRenderer
{
public class MyTabbedPage : TabbedRenderer
{
public MyTabbedPage(Context context) : base(context)
{
}
}
}
All the errors that you get are for one and one reason alone that the other part of your partial class i.e. GameTab.xaml.cs files GameTab class is not inheriting from your CustomTabbedPage but your Xamarin.Forms.TabbedPage
All you have to do is something like this in your GameTab.xaml.cs
public partial class GameTab : Diplomacy.CustomRenderers.CustomTabbedPage

Xamarin TabbedPages MVVM Binding

How to bind tabbed page with different view models?
To make it clearer I have this tabbed page:
<TabbedPage.Children>
<tabPages:Page1/>
<tabPages:Page2/>
<tabPages:Page3/>
</TabbedPage.Children>
These 3 pages have different view models. However, the problem is the view models of each pages won't bind. Is there a specific way in order to do this?
To test if my view models for each pages works, I inlined the code in tabbed page:
e.g.
<TabbedPage.Children>
<ContentPage Title="Test">
<Label Text="{Binding TestBind}"/>
</ContentPage>
</TabbedPage.Children>
And for bind it to the view model of the tabbed page (parent) - this method works. However, if I do it separately, the view models wouldn't bind.
E.g.
public class Page1ViewModel : BaseViewModel
{
public Page1ViewModel()
{
TestBind = "Test";
}
private string _TestBind;
public string TestBind
{
get { return _TestBind; }
set { SetProperty(ref _TestBind, value); }
}
}
Using it this way, it wouldn't bind
I've made it worked, so this is what I did:
<TabbedPage.Children>
<views:ChildPage1>
<views:ChildPage1.BindingContext>
<viewModels:ChildPage1ViewModel/>
</views:ChildPage1.BindingContext>
</views:ChildPage1>
<views:ChildPage2>
<views:ChildPage2.BindingContext>
<viewModels:ChildPage2ViewModel/>
</views:ChildPage2.BindingContext>
</views:ChildPage2>
</TabbedPage.Children>
I used binding context property of the element to set it.
Why not just set the BindingContext of each TabPage to each ViewModel:
<TabbedPage.Children>
<tabPages:Page1 BindingContext="{Binding viewModel1}" />
<tabPages:Page2 BindingContext="{Binding viewModel2}" />
<tabPages:Page3 BindingContext="{Binding viewModel3}"/>
</TabbedPage.Children>
Those ViewModels would have to exist as properties in the parent VM.

Xamarin.Forms adding children to TabbedPage within OnBindingContextChanged

I'm trying to implement a TabbedPage using MvvmCross for my navigation. The problem is MvvmCross uses ViewModel first navigation and this doesn't seem to play well with the general approach one might take to add children to a TabbedPage; because I do not have access to a non-null ViewModel during page construction, but I do have access to it within OnBindingContextChanged.
Here's what I have so far...
DashboardPage.xaml:
<?xml version="1.0" encoding="UTF-8"?>
<TabbedPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="DashboardPage"
xmlns:local="clr-namespace:CoreUI;assembly=CoreUI"
SelectedItem="{Binding CurrentSection, Mode=TwoWay}">
</TabbedPage>
DashboardPage.xaml.cs:
public partial class DashboardPage : TabbedPage
{
public DashboardPage()
{
InitializeComponent();
}
protected override void OnBindingContextChanged()
{
var vm = (BindingContext as DashboardViewModel);
if (vm == null)
{
return;
}
ObservableCollection<MainMenuSection> sections = vm.MenuSections;
foreach (var section in sections)
{
MainMenuViewModel main_menu_vm = new MainMenuViewModel
{
Section = section
};
// Question 2:
// Going against the MvvmCross grain here by referring to other pages from within a page, as opposed to doing everything from a ViewModel. How do I get around this?
Children.Add(new MainMenuPage(main_menu_vm));
}
}
}
MainMenuPage.xaml (pay attention to the comments here):
<?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="MainMenuPage"
xmlns:local="clr-namespace:CoreUI;assembly=CoreUI"
Title="{Binding Title}" > <!-- The tabs that are displayed on Dashboard have the correct labels, so Binding appears to be working here. -->
<ContentPage.Content>
<StackLayout x:Name="Body" IsVisible="false">
<Label Text="{Binding Title}"/> <!-- Label doesn't get displayed, but does get displayed if Text is bound to something static, so Binding not quite working here. -->
</StackLayout>
</ContentPage.Content>
</ContentPage>
MainMenuPage.xaml.cs:
public partial class MainMenuPage : ContentPage
{
public MainMenuPage(MainMenuViewModel vm)
{
InitializeComponent();
BindingContext = vm;
}
protected override void OnBindingContextChanged()
{
Body.IsVisible = true;
}
}
The above MainMenuPage is a simplified version of what I have to illustrate my point, which is that I get a blank page for each tab within DashboardPage.
Question 1: Why are the tab pages blank?
Question 2: Refer to comment in DashboardPage.xaml.cs.
Why would you do this whole thing yourself? As you can see in the samples (https://github.com/MvvmCross/MvvmCross/tree/develop/TestProjects/Playground/Playground.Forms.UI/Pages) you can just decorate your view with a MvxTabbePagePresentation attribute and MvvmCross will handle the rest for you!
I would also advice to use the Mvx type pages to take advantage of lots of features.

Dynamically change DataTemplate for a ListView at Runtime

I have 2 DataTemplates for displaying the contents of ClassA or ClassB inside a single ListView; which template to select will be based on a RadioButton selection by the user.
Is it possible to change the ItemTemplate of a ListView (in XAML) based on user input dynamically at runtime?
An example snippet of code:
XAML Page:
<Page...>
<Page.Resources>
<DataTemplate x:Key="ClassAListViewItemTemplate" x:DataType="vm:ClassA" ... />
<DataTemplate x:Key="ClassBListViewItemTemplate" x:DataType="vm:ClassB" ... />
</Page.Resources>
<RelativePanel>
<RadioButton Content="ClassA" ... />
<RadioButton Content="ClassB" ... />
<ListView DataContext="{Binding Path=MainViewModel}"
ItemsSource="{Binding ListOfClassAOrB, Mode=TwoWay}"
ItemTemplate="{StaticResource ClassAListViewItemTemplate}"/>
</RelativePanel>
</Page>
I have stripped the code down somewhat to the essentials, but I would like to be able to change the following at runtime:
ItemTemplate="{StaticResource ClassAListViewItemTemplate}"
I have seen solutions for Classic WPF applications that use Style.Triggers, but these aren't applicable for UWP
Marco Minerva's blog on Adaptive Triggers, RelativePanel and DataTemplate in the Universal Windows Platform talks of using UserControls within DataTemplates to modify the visual state using Adaptive Triggers, but this doesn't take into account switching out of templates based on user input
The closest answer I have found to my problem is another blog he wrote "Dynamically choose DataTemplate in WinRT" where there is an element of code-behind involved - but it only appears to be an if statement - but its the cleanest solution I have come across thus far, and what I'd like to replicate in XAML
Thanks
you need to use overwrite SelectTemplateCore of Data template. Change your view model like this.
Below code will helps you.
public class SampleViewModel : DataTemplateSelector
{
public DataTemplate ClassAListViewItemTemplate{ get; set; }
public DataTemplate ClassBListViewItemTemplate{ get; set; }
protected override DataTemplate SelectTemplateCore(object item, DependencyObject container)
{
var itemsData = item as SampleClass; // add your Data class
if (itemsData.IsAddButton == false) // define any property to select the datatemplate
{
return ClassAListViewItemTemplate;
}
else
{
return ClassBListViewItemTemplate;
}
}
}
Add your two datatemplates to one key, and give the key to ItemTemplateSelector property in gridview.
<viewModels:SampleViewModel x:Key="FeedbackTempateSelector"
ClassAListViewItemTemplate="{StaticResource ClassAListViewItemTemplate}"
ClassBListViewItemTemplate="{StaticResource ClassBListViewItemTemplate}">
</viewModels:SampleViewModel>

Preview xaml layout with specific sample data

I'm developing a WP7 application, and I'm generating a listbox with a few items. I was wondering if there is a way to preview how the layout would look. So far, since the elements don't exist, I can't "preview" them.
Is there some way to feed some dummy data or other methods that would help in previewing xaml layouts ?
First - it helps if you use MVVM, or at least ItemsSource binding + ItemTemplate to display your items. Once you are there - Expression Blend has some great tools for sample data.
You go to Data tab, click Create Sample Data/New Sample Data. It will create a sample data as XAML and bind your page to it like that:
d:DataContext="{d:DesignData SampleData/MainViewModelSampleData.xaml}"
Then you can add new properties, model collections with different data types and it will automatically generate some data you can use in your XAML.
You should provide a designer data.
There are several ways to do it.
One of the simplest, is to provide a DataContext in your XAML declaration for designer to use when rendering your page display.
In Xaml page declaration:
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
d:DataContext="{d:DesignInstance local:DesignerSampleData, IsDesignTimeCreatable=True}"
The sample data class should have the data that your visual elements bind to:
public class DesignerSampleData: INotifyPropertyChanged
{
public DesignerSampleData()
{
_sampleData = "My test string that will display in VS designer for preview";
}
private String _sampleData;
public String SampleData
{
get { return _sampleData; }
set
{
if (value != _sampleData)
{
_sampleData = value;
NotifyPropertyChanged("SampleData");
}
}
}
In xaml bind to SampleData:
<TextBlock Text="{Binding SampleData}" />