As one of a few different methods for displaying ads on a page, I tried creating a ContentPage that gets instantiated as the parent class for another page.
Here is how the parent page is created called AdFrame.Xaml
<?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:services="clr-namespace:BoomStick.Services"
x:Class="BoomStick.Views.AdFrame">
<ContentPage.Content>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="0.1*" />
<RowDefinition Height="0.9*" />
<RowDefinition Height="0.1*" />
</Grid.RowDefinitions>
<Frame Grid.Row="0" BackgroundColor="Red" HeightRequest="100" WidthRequest="200" />
<ContentPresenter Grid.Row="1" />
<StackLayout Grid.Row="2" BackgroundColor="Blue">
<services:AdView />
</StackLayout>
</Grid>
</ContentPage.Content>
</ContentPage>
AdFrame.xaml.cs
public partial class AdFrame : ContentPage
{
public AdFrame()
{
InitializeComponent();
}
}
Then the page the is based on this parent.
Home.xaml
<d:AdFrame
xmlns:d="clr-namespace:BoomStick.Views;assembly=BoomStick"
x:Class="BoomStick.Views.Home"
xmlns="http://xamarin.com/schemas/2014/forms"
.......... />
<d:AdFrame.Resources>
<ResourceDictionary>
<converter:FormatShortDateConveter x:Key="FormatShortDateConveter" />
<converter:ConvertMonetaryFormat x:Key="ConvertMonetaryFormat" />
<converter:BoolFromValue x:Key="BoolFromValue" />
<converter:FormatNumberConverter x:Key="FormatNumberConverter" />
<converter:ConvertLengthFromText x:Key="ConvertLengthFromText" />
<DataTemplate x:Key="loadItemTemplate">
......
</DataTemplate>
</ResourceDictionary>
</d:AdFrame.Resources>
<d:AdFrame.ToolbarItems>
<ToolbarItem
Clicked="RefreshButtonClicked"
IconImageSource="{Binding Path=RefreshIcon}"
Text="Refresh" />
</d:AdFrame.ToolbarItems>
<d:AdFrame.Content>
<ScrollView
Margin="8"
Orientation="Vertical"
VerticalOptions="FillAndExpand">
<AbsoluteLayout HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand">
...............
</AbsoluteLayout>
</ScrollView>
</d:AdFrame.Content>
</d:AdFrame>
Home.xaml.cs
private HomeViewModel viewModel;
public Home()
{
try
{
InitializeComponent();
BindingContext = (viewModel = new HomeViewModel());
InitilizePage();
}
catch (Exception ex)
{
DebugTools.LogException(ex);
}
}
In the code behind for this page however, if I call InitializeComponent(), then the parent page is never created, and only shows the page that called it. If I comment out the InitializeComponent() then the parent page is created, and I see the red and blue bars, but the content isn't shown.
As an alternate approach I tried creating a ControlTemplate that was the layout I wanted for the page, but the problem I had there is that the page being displayed in the ContentPresenter never get's it's ViewModel associated with it, so the page displays the XAML, but won't receive any data, or content that the ViewModel provides it.
<ControlTemplate x:Key="AdEnabledPage">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="0.9*" />
<RowDefinition Height="0.1*" />
</Grid.RowDefinitions>
<ContentPresenter BindingContext="{Binding Source={RelativeSource Mode=FindAncestor, AncestorType={x:Type local:Home}}}" Grid.Row="0" />
<StackLayout Grid.Row="1" BackgroundColor="{StaticResource Key=cBackgroundColor}">
<services:AdView />
</StackLayout>
</Grid>
</ControlTemplate>
When I was trying this approach, I would put this code into the page that I wanted the ads to appear on.
protected override async void OnAppearing()
{
base.OnAppearing();
try
{
if (viewModel != null)
await ReloadPageData();
if (!CoreGlobals.isPurchased)
{
this.ControlTemplate = App.Current.Resources["AdEnabledPage"] as ControlTemplate;
}
}
catch (Exception ex)
{
DebugTools.LogException(ex);
}
}
How can I correct this, so that a page is shown inside of the parent page.
Okay, I found the problem. I had been basically trying to implement this using two different methods, taking half the solution from one and half from the other.
Once I figured that out,I was able to get things working.
I was able to make a fix by using code from a MS sample repository.
https://learn.microsoft.com/en-us/samples/xamarin/xamarin-forms-samples/templates-controltemplatedemos/
Cheers!
Related
I have a MvxContentPage with a CollectionView. The items in the CollectionView are to be presented with a MvxContentView. I've tried and tried but can't get the MvxContentView to load it's associated ViewModel. I've downloaded the MvvmCross code and looked at the samples. There is an example in the PlayGround project but it seems to be incomplete.
The page below shows the content of a Set. A Set contains n SetItems which should be shown in the CollectionView. Each SetItem is presented using the partial view.
Here's my code:
The MvxContentPage:
<?xml version="1.0" encoding="utf-8" ?>
<mvx:MvxContentPage
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:mvx="clr-namespace:MvvmCross.Forms.Views;assembly=MvvmCross.Forms"
xmlns:views="clr-namespace:eTabber.Views"
xmlns:model="clr-namespace:eTabber.Data.Model;assembly=eTabber.Data"
xmlns:viewmodels="clr-namespace:eTabber.Core.ViewModels;assembly=eTabber.Core"
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"
Padding="0"
x:Class="eTabber.Views.SetView" x:TypeArguments="viewmodels:SetViewModel"
Title="{Binding PageTitle}" >
<StackLayout
StyleClass="MainContainer"
Spacing="0"
Margin="0"
Padding="0">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="auto" />
<RowDefinition Height="auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Label Text="{Binding SetList.Name, Mode=OneWay}" StyleClass="Title" Grid.Row="0"></Label>
<StackLayout Orientation="Vertical" Grid.Row="1">
<Button Text="Add" Command="{Binding AddCommand}" />
<CollectionView ItemsSource="{Binding SetItems}"
HorizontalOptions="FillAndExpand"
VerticalOptions="FillAndExpand"
x:Name="SetlistsListView"
Grid.Row="2"
SelectionMode="Single"
ChildrenReordered="SetlistsListView_ChildrenReordered">
<CollectionView.ItemTemplate>
<DataTemplate x:DataType="model:SetItem" x:Name="DataTemplate">
<views:SetItemView BindingContext="{Binding Source={x:Reference DataTemplate}}" />
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</StackLayout>
</Grid>
</StackLayout>
</mvx:MvxContentPage>
And the partial view with MvxContentView:
<?xml version="1.0" encoding="UTF-8"?>
<mvx:MvxContentView xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:mvx="clr-namespace:MvvmCross.Forms.Views;assembly=MvvmCross.Forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:viewmodels="clr-namespace:eTabber.Core.ViewModels;assembly=eTabber.Core"
x:TypeArguments="viewmodels:SetItemViewModel"
x:Class="eTabber.Views.SetItemView">
<ContentView.Content>
<Grid x:Name="SetlistsInnerView" RowSpacing="0">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Label Text="{Binding Set.SetName, Mode=OneWay}"
IsVisible="{Binding TitleVisible, Mode=OneWay}"
StyleClass="Title"
Grid.Row="0">
</Label>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="2*"/>
</Grid.ColumnDefinitions>
<Label Text="{Binding Path=Song.Artist.Name, Mode=OneWay}" Grid.Column="0" Grid.Row="0" StyleClass="Label"/>
<Label Text="{Binding Path=Song.Title, Mode=OneWay}" Grid.Column="1" Grid.Row="0" StyleClass="Label"/>
</Grid>
</Grid>
</ContentView.Content>
</mvx:MvxContentView>
Code behind:
using eTabber.Core.ViewModels;
using MvvmCross.Forms.Views;
using Xamarin.Forms.Xaml;
namespace eTabber.Views
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class SetItemView : MvxContentView<SetItemViewModel>
{
public SetItemView()
{
InitializeComponent();
}
}
}
And the ViewModel:
using eTabber.Data.Model;
using MvvmCross.Navigation;
using Xamarin.Forms;
namespace eTabber.Core.ViewModels
{
public class SetItemViewModel : BaseViewModel<SetItem>
{
public SetItem SetItem { get => passedEntity; set => passedEntity = value; }
public SetItemViewModel(IMvxNavigationService navigationService) : base(navigationService)
{
var context = View.BindingContextProperty;
}
}
}
It does show the page with the information so some binding is taking place but I want it to load the ViewModel (in which case the bindings would have to be different of course). Here's what it displays:
The issue is in the first MvxContentPage code:
<views:SetItemView BindingContext="{Binding Source={x:Reference
DataTemplate}}" />
What do I specify in the Binding to get the SetItemViewModel to be loaded.
I'm trying to learn Xamarin.Form in Android and WPF platforms. I am using a very simple observable collection as data binding in a ViewModel file. Data binding works properly in Android platform, but it doesn't show anything in WPF platform! Other controls like buttons and labels work correctly in WPF platform. In debugging mode the button which fills the collection also works when I start WPF as default project.
ViewModel file codes
public sealed class MainPageViewModel : INotifyPropertyChanged
{
public ObservableCollection<string> Test { get; set; } = new ObservableCollection<string>();
public Command LoadCommand { get; }
public MainPageViewModel()
{
LoadCommand = new Command(() =>
{
for (int i = 0; i < 10; i++)
{
Test.Add(i.ToString());
}
});
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
MainPage.xaml file codes
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage.BindingContext>
<local:MainPageViewModel />
</ContentPage.BindingContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="*" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Label
Grid.Row="0"
Grid.Column="0"
Grid.ColumnSpan="2"
HorizontalOptions="Center"
Text="Select one of the items below."
VerticalOptions="CenterAndExpand" />
<Button
Grid.Row="1"
Grid.Column="1"
Command="{Binding LoadCommand}"
Text="test" />
<CollectionView
Grid.Row="2"
Grid.Column="0"
Grid.ColumnSpan="2"
ItemsSource="{Binding Test}">
<CollectionView.ItemTemplate>
<DataTemplate>
<StackLayout>
<Frame>
<Label Text="{Binding .}" />
</Frame>
</StackLayout>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</Grid>
Any suggestions?
I search some info about using Collectionview in WPF, but from this collectionview article, we can see that CollectionView is available on iOS and Android, but is only partially available on the Universal Windows Platform, so I think it doesn't works in WPF.
But If you can use ListView to replace CollectionView, it can works fine when you using Binding.
About adding a WPF project to a Xamarin.Forms solution, you can take a look:
https://learn.microsoft.com/en-us/xamarin/xamarin-forms/platform/other/wpf
This is sample :
https://github.com/techierathore/TrXamGuide
I Am trying to create a dummy app that has multiple "Branding Packages".
I have read that using a control template is the easiest way to apply a global styling to the app. most controls in the app will be added dynamically via the code behind.
However whenever I set the content view to use the control template none of the controls I have added via code will display
Here is the App.xaml
<?xml version="1.0" encoding="utf-8" ?>
<Application xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="TestWebApp.App">
<Application.Resources>
<ResourceDictionary>
<ControlTemplate x:Key="Template">
<Grid>
<Label Text="Control Template Demo App"
TextColor="White">
</Label>
<ContentPresenter/>
<BoxView Color="Teal" />
<Label Text="(c) WTF 2018"
TextColor="White"
VerticalOptions="Center"/>
</Grid>
</ControlTemplate>
</ResourceDictionary>
</Application.Resources>
</Application>
Here is the MainPage.xaml
<?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:TestWebApp"
x:Class="TestWebApp.MainPage">
<ContentView ControlTemplate="{StaticResource Template}">
<ScrollView>
<StackLayout Orientation="Vertical" x:Name="MenuLayout">
</StackLayout>
</ScrollView>
</ContentView>
</ContentPage>
And here is the MainPage.xaml.cs
using System;
using System.Collections.Generic;
using Xamarin.Forms;
namespace TestWebApp
{
public partial class MainPage : ContentPage
{
public MainPage()
{
InitializeComponent();
List<Object> testList = TestMethod();
foreach(Object testObject in testList)
{
MenuLayout.Children.Add((View)testObject);
}
}
public List<Object> TestMethod()
{
Button newButton = new Button {Text = "This is a Test Button", BackgroundColor = Color.White};
List<Object> returnList = new List<object>();
returnList.Add(newButton);
return returnList;
}
}
}
Is there a way to dynamically create a page from the code behind while still implementing the control template
Try to add some RowDefinitions to the Grid in the template:
<ControlTemplate x:Key="Template">
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Label Text="Control Template Demo App" TextColor="White" />
<ContentPresenter Grid.Row="1" />
<BoxView Color="Teal" Grid.Row="2" />
<Label Text="(c) WTF 2018" TextColor="White" VerticalOptions="Center" Grid.Row="3"/>
</Grid>
</ControlTemplate>
I was finally able to display the ListView using a <Grid> when clicking on a button. This is the xaml:
<?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:maps="clr-namespace:Xamarin.Forms.Maps;assembly=Xamarin.Forms.Maps"
xmlns:local="clr-namespace:GasStations"
x:Class="GasStations.MainPage">
<Grid RowSpacing="0">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="50" />
<RowDefinition Height="auto" />
</Grid.RowDefinitions>
<StackLayout Grid.Row="0" x:Name="MapGrid">
<maps:Map WidthRequest="960" HeightRequest="200"
x:Name="MyMap" IsShowingUser="true"/>
</StackLayout>
<StackLayout Grid.Row="1">
<Button Text="Show List" x:Name="Button_DisplayList"
VerticalOptions="CenterAndExpand"
HorizontalOptions="Center"
Clicked="OnButtonClicked" />
</StackLayout>
<StackLayout Grid.Row="2" x:Name="listSection" IsVisible="false" HeightRequest="200">
<ListView x:Name="ListView_Pets">
<ListView.ItemsSource>
<x:Array Type="{x:Type x:String}">
<x:String>dog</x:String>
<x:String>cat</x:String>
<x:String>bird</x:String>
</x:Array>
</ListView.ItemsSource>
</ListView>
</StackLayout>
</Grid>
</ContentPage>
And this is the codebehind:
void OnButtonClicked(object sender, EventArgs args)
{
listSection.IsVisible = true;
Button_DisplayList.IsVisible = false;
}
When I click the button, the ListView is displayed and the button is hidden. So far so good.
Once the ListView is open, how can I hide the ListView again when I tap on the map?
I tried using GestureRecognizers and <TapGestureRecognizer Tapped="OnTapGestureRecognizerTapped"/>, but it doesn't build.
Any help is appreciated.
I've included screenshots because I'm still learning the terminology.
Adding gesture recognizer on the parent stacklayout of the map
On xaml:
<StackLayout Grid.Row="0" x:Name="MapGrid">
<StackLayout.GestureRecognizer>
<TapGestureRecognizer Tapped="OnMapAreaTapped"/>
</StackLayout.GestureRecognizer>
<maps:Map WidthRequest="960" HeightRequest="200" x:Name="MyMap" IsShowingUser="true"/>
</StackLayout>
On code behind:
private void OnMapAreaTapped(object sen, EventArgs e)
{
listSection.IsVisible = false;
Button_DisplayList.IsVisible = true;
}
I have been trying all the "Qs that may have your answer" 3 days later, still no joy.
I have a XAML Page that holds a ListView of products and their details, using binding to a model called RackProducts as you can see:
<?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="PapillonWine.PapillonRackCatalogPage"
xmlns:local="clr-namespace:PapillonWine;assembly=PapillonWine"
xmlns:artina="clr-namespace:UXDivers.Artina.Shared;assembly=UXDivers.Artina.Shared"
xmlns:customContentView="clr-namespace:PapillonWine.NavBars"
Title="{ artina:Translate PageTitleProductsCatalog }"
BackgroundColor="{ DynamicResource MainWrapperBackgroundColor }"
x:Name="CatalogItemPage">
<ContentPage.Content>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="70" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<customContentView:CustomNavigationBar Grid.Row="0" x:Name="NavigationBarView" BackgroundColor="Transparent"/>
<ListView
x:Name="PapillonRackItems"
Grid.Row="1"
ItemsSource="{ Binding RackProducts }"
HasUnevenRows="True"
ItemSelected="OnItemSelected">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<local:PapillonCatalogItemTemplate />
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
</ContentPage.Content>
</ContentPage>
Each product is displayed using the PapillonCatalogItemTemplate which holds four buttons (to view a carousel of product images, to add to cart, to view dimensions and lastly to share the product). This PapillonCatalogItemTemplate is as follows:
<?xml version="1.0" encoding="utf-8"?>
<ContentView xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="PapillonWine.PapillonCatalogItemTemplate"
xmlns:local="clr-namespace:PapillonWine;assembly=PapillonWine"
xmlns:artina="clr-namespace:UXDivers.Artina.Shared;assembly=UXDivers.Artina.Shared"
xmlns:customContentView="clr-namespace:PapillonWine.NavBars"
xmlns:ffimageloading="clr-namespace:FFImageLoading.Forms;assembly=FFImageLoading.Forms"
xmlns:fftransformations="clr-namespace:FFImageLoading.Transformations;assembly=FFImageLoading.Transformations"
Padding="10" BackgroundColor="{ DynamicResource MainWrapperBackgroundColor }"
x:Name="CatalogItemTemplate">
<!-- FAVORITE ICON -->
<Grid>
<!-- COLUMN DEFS -->
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<!-- ROW DEFS -->
<Grid.RowDefinitions>
<RowDefinition Height="0.45*" />
<RowDefinition><RowDefinition.Height><OnIdiom x:TypeArguments="GridLength" Phone="200" Tablet="400" /></RowDefinition.Height></RowDefinition>
</Grid.RowDefinitions>
<StackLayout Grid.Column="0" Grid.Row="0" Spacing="5" HorizontalOptions="FillAndExpand" WidthRequest="1000" >
<!-- PRODUCT NAME -->
<Label Text="{ Binding Name }" />
<!-- DESCRIPTION -->
<Label Text="{Binding Description}" />
<!-- BUTTONS -->
<StackLayout x:Name="buttonstack" Orientation="Horizontal" >
<!-- SHOW IMAGES -->
<artina:Button
x:Name="ImageCarousel"
Text="{ x:Static local:FontAwesomeWeb511Font.CameraRetro }"
Style="{StaticResource FontIcon}"
BindingContext="{Binding Source={x:Reference CatalogItemTemplate}, Path=BindingContext}"
Command="{Binding OnClickViewImageCarousel}"
CommandParameter="{Binding Source={x:Reference buttonstack}, Path=BindingContext}" >
<!-- SHOW CART -->
<artina:Button
Text="{ x:Static local:FontAwesomeWeb511Font.cartplus }"
Style="{StaticResource FontIcon}"
Clicked="OnBuyItemSelected">
</artina:Button>
<!-- SHOW DIMENSIONS -->
<artina:Button
Text="{ x:Static local:FontAwesomeWeb511Font.RulerCombined }"
Style="{StaticResource FontIcon}"
Clicked="OnDimensionsSelected" >
<!-- SHOW IMAGES -->
<artina:Button
Text="{ x:Static local:FontAwesomeFont.Share }"
Style="{StaticResource FontIcon}"
Clicked="OnShareSelected" >
</StackLayout>
</StackLayout>
<ffimageloading:CachedImage
Grid.Column="0" Grid.Row="1"
Source="{ Binding Image }" />
</Grid>
</ContentView>
If I click the ffloading image at the bottom of the template item, the OnItemSelected button fires, and I get a nice new view page with a picture of the product that I selected and all its item details... bully! The code it fires is as follows:
public async void OnItemSelected(object sender, SelectedItemChangedEventArgs e)
{
var selectedItem = ((ListView)sender).SelectedItem;
var page = new PapillonRackItemViewPage(((RackProduct)selectedItem).Id);
await Navigation.PushModalAsync(page);
}
However... if I try to fire one of the buttons within the item template (for example the one directly beneath "SHOW IMAGES", nothing happens. I have left the Commmand, CommandParameter and Binding arguments in that first button but I'm not sure they're correct. I have tried ICommand route, TapGestureRecognizer route, and tried most similar posts, and I really need help on this one. How do I pass the same binding context as in the ListView and its "RackProducts" binding, through button items within a list view? Any chance of a much needed helping hand? Thanks!
in your XAML
Clicked="OnClickViewImageCarousel" CommandParameter="{Binding .}"
then in the code behind
protected void OnClickViewImageCarousel(object sender, EventArgs e)
{
var selectedItem = (RackProduct)((Button)sender).CommandParameter;
}