Tab icons in a Xamarin.Forms UWP TabbedPage? - xamarin.forms

When putting together a TabbedPage in Xamarin.Forms, how do I get UWP to use the page's Icon property?
It looks like UWP could support this just fine, if I configure my Forms attributes/files correctly.
Here's my TabbedPage XAML. The icons are all set up and working for iOS and Android, and even the on-page Image in UWP renders fine (meaning the files are likely in the project correctly).
<TabbedPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:tabbed"
x:Class="tabbed.MainPage">
<TabbedPage.Children>
<local:InitialPage Title="Tab1" Icon="star.png" />
<ContentPage Title="Tab2" Icon="gear.png">
<ContentPage.Content>
<StackLayout>
<Label Text="A nice label." />
<Image Source="star.png" /><!-- works here -->
</StackLayout>
</ContentPage.Content>
</ContentPage>
</TabbedPage.Children>
</TabbedPage>

I outlined how this is possible here http://depblog.weblogs.us/2017/07/12/xamarin-forms-tabbed-page-uwp-with-images/
In short, you need to change the default HeaderTemplate that is being used by UWP. But due to the way Xamarin forms is started, this is not straightforward.
So you need to inject a custom template into the resource dictionary.
Example project is up on Github here https://github.com/Depechie/XamarinFormsTabbedPageUWPWithIcons
Longer detail:
You need to supply your own TabbedPageStyle and switch out the one that Xamarin is using for their UWP rendering.
So the new style contains an Image where we data bind the Source to the Xamarin Icon property.
<Style x:Key="TabbedPageStyle2" TargetType="uwp:FormsPivot">
<Setter Property="HeaderTemplate">
<Setter.Value>
<DataTemplate>
<StackPanel Orientation="Vertical">
<Image Source="{Binding Icon, Converter={StaticResource IconConverter}}" Width="15" Height="15" />
<TextBlock Name="TabbedPageHeaderTextBlock" Text="{Binding Title}"
Style="{ThemeResource BodyTextBlockStyle}" />
</StackPanel>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
The actual style switching is done in the App.Xaml.cs like this
((Style)this.Resources["TabbedPageStyle"]).Setters[0] = ((Style)this.Resources["TabbedPageStyle2"]).Setters[0];
You'll also need a converter to be sure the Image control understands the Icon source giving by Xamarin
public class IconConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
if (value != null && value is Xamarin.Forms.FileImageSource)
return ((Xamarin.Forms.FileImageSource)value).File;
return null;
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}
}

As it is currently, the UWP TabbedPage renderer does not use the Icon property at all, so getting tab icons will require a custom renderer. Even the official UWP samples don't actually seem to have this baked-in, requiring a custom UserControl.
The Android TabbedPageRenderer and iOS TabbedRenderer, and even the macOS TabbedPageRenderer, use the Icon property to adjust the tab UI, but the UWP renderer would need updating to make this work.

Related

Changing BindingContext in listView template does not point to Viewmodel

When changing BindingContext for a button in listView by adding
BindingContext="{Binding Source={x:Reference parentContePage}}"
Command="{Binding ViewPdf}"
for some reason the app is trying to find the method in the view class instead and i am getting error in the output console
[0:] Binding: 'ViewPdf' property not found on 'WinterEventMobileForm.UI.Pages.ReservationView'
The parentContePage element
<views:MvxContentPage xmlns="http://xamarin.com/schemas/2014/forms"
x:Name="parentContePage"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:TypeArguments="viewModels:ReservationViewModel"
xmlns:views="clr-namespace:MvvmCross.Forms.Views;assembly=MvvmCross.Forms"
xmlns:viewModels="clr-namespace:WinterEventMobileForm.Core.ViewModels;assembly=WinterEventMobileForm.Core"
xmlns:navigationdrawer="clr-namespace:Syncfusion.SfNavigationDrawer.XForms;assembly=Syncfusion.SfNavigationDrawer.XForms"
xmlns:buttons="clr-namespace:Syncfusion.XForms.Buttons;assembly=Syncfusion.Buttons.XForms"
xmlns:view="clr-namespace:WinterEventMobileForm.UI.Pages"
xmlns:ListView="clr-namespace:Syncfusion.ListView.XForms;assembly=Syncfusion.SfListView.XForms"
xmlns:core="clr-namespace:Syncfusion.XForms.Core;assembly=Syncfusion.Core.XForms"
x:Class="WinterEventMobileForm.UI.Pages.ReservationView"
xmlns:controls="clr-namespace:WinterEventMobileForm.UI.Controls" xmlns:border="clr-namespace:Syncfusion.XForms.Border;assembly=Syncfusion.Core.XForms"
Title="test"
NavigationPage.HasNavigationBar="False"
xmlns:mvx="clr-namespace:MvvmCross.Forms.Bindings;assembly=MvvmCross.Forms"
>
Why is the x:Reference returning View instead of ViewModel?
Problem solved by modifing Command to:
Command="{Binding ViewModel.ViewPdf}"

xamarin System.InvalidOperationException: 'Cannot convert "VerticalGrid, 2" into Xamarin.Forms.IItemsLayout'

I'm getting the error System.InvalidOperationException: 'Cannot convert "VerticalGrid, 2" into Xamarin.Forms.IItemsLayout' during InitalizeComponent() of my ContentPage containing a CollectionsView.
The code works properly on UWP, and the error occurs when running on Android. Being new to xamarin.forms, I'm not really sure what to start looking for.
EDIT: it works if I choose "VerticalList".
Here's a bit of my xaml:
<ContentPage ... >
<StackLayout>
<CollectionView
x:Name="DetailGrid"
ItemsLayout="VerticalGrid, 2" >
<CollectionView.ItemTemplate>
<DataTemplate>
<StackLayout Padding="20" BackgroundColor="Crimson">
<Image Source= "{Binding Path}" WidthRequest="300"/>
<Label Text="{Binding FileName}" TextColor ="Bisque" LineBreakMode="WordWrap" />
</StackLayout>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</StackLayout>
</ContentPage>
And here's some code-behind:
namespace varlist
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class CollectionPage : ContentPage
{
public ObservableCollection<NodeData> nodes = new ObservableCollection<NodeData>();
public CollectionPage ()
{
InitializeComponent ();
nodes.Add (new ImageData { FileName = "image_chair_pk.jpg" });
...
DetailGrid.ItemsSource = nodes;
}
}
}
I got the same error with you when I use a Xamarin.forms version 4.5.0.725.
After I update my Xamarin.forms to the latest version 4.8.0.1687, everything works well.
Please update your Xamarin.forms version to fix this problem:
I found a workaround. This appears to be a framework bug.
According to Xamarin.Forms CollectionView Layout the syntax I used should work - and it does work for UWP.
They also show an alternate declaration to specify ItemsLayout:
<CollectionView.ItemsLayout>
<GridItemsLayout Orientation="Vertical" Span="2" />
</CollectionView.ItemsLayout>
And, this syntax works on Android.
I filed a bug on github:[Bug] CollectionView with ItemsLayout=VerticalGrid crashes on Android #12920
You can omit this property if you wish it vertical, the default is set to VerticalList. If you want your collectionView to be Horizontal, just use HorizontalList.
See https://learn.microsoft.com/en-us/xamarin/xamarin-forms/user-interface/collectionview/layout

Images not shown in CarouselView on first load

The scenario is, a content page is opened with a carousel displaying multiple images.
The images are bound as an observable collection of ImageSource and they're fine
When the page is displayed, I can see the carousels empty.
I have tried with two different CarouselView. The one that xamarin brings by default and the one from CardsView nuget library. With both of them the result is the same.
The page shows the carousel views without the image, but with the right amount of items (i.e: I can see that the observable collection has 3 items).
I suspect it must be some kind of racing condition because as soon as I force a hot reload of the view while debuggin (ie: I save the XAML and the view reloads in the device) I can see the carousels with the images displayed properly.
To reproduce it I have also added the images separately outside the carousel to prove that they're there.
I've also added both carousels to the view. This is the result on the first load:
and after I save the xaml (without doing anything on it at all) and the view refreshes, this is what I see, and it's correct.
Notice how in the first load, the 3 images are displayed correctly outside the carousel, but not inside any of the carousels, although there is no problem if what I add in the carousel is text.
Any idea where may be the issue? Is it a racing condition? Or is it a problem with both different carousels?
This is my 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:d="http://xamarin.com/schemas/2014/forms/design"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:viewModels="clr-namespace:Sasw.AforoPass.ViewModels;assembly=Sasw.AforoPass"
xmlns:panCardView="clr-namespace:PanCardView;assembly=PanCardView"
xmlns:controls="clr-namespace:PanCardView.Controls;assembly=PanCardView"
mc:Ignorable="d"
x:Class="Sasw.AforoPass.Views.MainPage"
x:DataType="viewModels:MainViewModel">
<ContentPage.Content>
<StackLayout>
<StackLayout.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="/Styles/Styles.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</StackLayout.Resources>
<CarouselView BackgroundColor="White"
x:Name="Carousel"
ItemsSource="{Binding Images}">
<CarouselView.ItemTemplate>
<DataTemplate>
<StackLayout
HorizontalOptions="CenterAndExpand"
VerticalOptions="CenterAndExpand">
<Image Source="{Binding .}"></Image>
<Label Text="image should be here"></Label>
</StackLayout>
</DataTemplate>
</CarouselView.ItemTemplate>
</CarouselView>
<panCardView:CarouselView
ItemsSource="{Binding Images}"
BackgroundColor="{StaticResource ColorPrimary}"
HorizontalOptions="FillAndExpand"
VerticalOptions="FillAndExpand">
<panCardView:CarouselView.ItemTemplate>
<DataTemplate>
<StackLayout
HorizontalOptions="CenterAndExpand"
VerticalOptions="CenterAndExpand">
<Image Source="{Binding .}"></Image>
<Label Text="image should be here"></Label>
</StackLayout>
</DataTemplate>
</panCardView:CarouselView.ItemTemplate>
<controls:LeftArrowControl />
<controls:RightArrowControl />
<controls:IndicatorsControl ToFadeDuration="1500"/>
</panCardView:CarouselView>
<Image WidthRequest="20" HeightRequest="20" Source ="{Binding Images[0]}"></Image>
<Image WidthRequest="20" HeightRequest="20" Source ="{Binding Images[1]}"></Image>
<Image WidthRequest="20" HeightRequest="20" Source ="{Binding Images[2]}"></Image>
<Button Text="Close"></Button>
</StackLayout>
</ContentPage.Content>
</ContentPage>
and the collection bound is:
public ObservableCollection<ImageSource> Images
{
get => _images;
set
{
_images = value;
OnPropertyChanged();
}
}
UPDATE 1:
I've just edited the whole question because at first I though the problem could be related with Rg.Plugins.Popup because it was happening within a popup. But I've tried the same with a normal content page and the problem remains, so it's definitely something to do with the carousel.
UPDATE 2
I keep investigating. So far, the problem seems to be with image streams in carousels, either the Xamarin carousel or the CardView's library carousel (which have different mechanisms).
I've tried adding options and height and width request to the image that accesses the stream without luck
<!--in DataTemplate within the carousel-->
<Image Source="{Binding .}" HeightRequest="100"
WidthRequest="100"
VerticalOptions="FillAndExpand"
HorizontalOptions="FillAndExpand"></Image>
<Label Text="image should be here"></Label>
UPDATE 3:
Thanks to Jack Hua for his comment and repo, I updated it to reflect the issue I have and managed to reproduce it at this specific commit here
Basically I have taken the same repo, added a QRCoder library to quickly generate images as byte[] at runtime and added the image sources from stream.
public Model() {
Images = new ObservableCollection<ImageSource>();
var imageOne = GetQrImageAsBytes();
var imageTwo = GetQrImageAsBytes();
var imageThree = GetQrImageAsBytes();
// This works
//Images.Add(ImageSource.FromFile("logo.jpg"));
//Images.Add(ImageSource.FromFile("sample.jpg"));
//Images.Add(ImageSource.FromFile("ttt.png"));
Images.Add(ImageSource.FromStream(() => new MemoryStream(imageOne)));
Images.Add(ImageSource.FromStream(() => new MemoryStream(imageTwo)));
Images.Add(ImageSource.FromStream(() => new MemoryStream(imageThree)));
}
private byte[] GetQrImageAsBytes()
{
var randomText = Guid.NewGuid().ToString();
var qrGenerator = new QRCodeGenerator();
var qrCodeData = qrGenerator.CreateQrCode(randomText, QRCodeGenerator.ECCLevel.L);
var qRCode = new PngByteQRCode(qrCodeData);
var qrCodeBytes = qRCode.GetGraphic(20);
return qrCodeBytes;
}
UPDATE 4:
I found the issue, I had to try a few times to make sure this really was the problem, but it certainly is.
Re-Sharper suggests me to make a change in ItemsSource="{Binding Images}" as it does not recognize Images. So I follow Re-Sharper recommendation and let it add a x:DataType="app265:Model" in xaml's ContentPage tag. That causes the problem. I don't really know why, but I'll sleep better tonight :)
<?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:app265="clr-namespace:App265;assembly=App265"
mc:Ignorable="d"
x:Class="App265.MainPage"
x:DataType="app265:Model"><!--this line that Re-Sharper adds causes the issue-->
Is this a Xamarin bug? or does it make sense to you? An explanation to why this x:DataType breaks things but only on the first load would be chosen as the right answer.
x:DataType="ImageSource"
Add this to your carousel view DataTemplate. It should solve your problem.
It seems there some issues with BindignContext detecting, so you should "help" to detect it yourself.
Same problem here with xamarin.forms 5.0.0.2012 but in my case my image sources are urls. It worked with x:DataType="x:String" in the carousel view DataTemplate
<CarouselView
ItemsSource="{Binding ImageSources}"
>
<CarouselView.ItemTemplate>
<DataTemplate
x:DataType="x:String">
<forms:CachedImage
Aspect="AspectFit"
ErrorPlaceholder="Placeholder"
LoadingPlaceholder="Placeholder"
DownsampleWidth="300"
DownsampleToViewSize="True"
Source="{Binding .}">

reusability user controls of Xaml in xamarin.forms

I am trying a new approach i.e. XAML to make application in xamarin.forms. At this time i am facing an issue to reuse my stack layout which is having a image and label. How i can reuse my layout in different pages using XAML.
You can actually define your custom component in a separate XAML file and then just link the component wherever you need it.
For example a label with image can be grouped together in a dedicated XAML file:
<?xml version="1.0" encoding="utf-8" ?>
<StackLayout xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="UserControls.ImageWithTitle"
VerticalOptions="Center" HorizontalOptions="Center" >
<Label HorizontalOptions="Center"
x:Name="TitleLabel" />
<Image Source="noimage.png" />
</StackLayout>
On the .cs file I've defined a binding for the TitleLabel
public string TitleName
{
get { return TitleLabel.Text; }
set { TitleLabel.Text = value; }
}
So when you include the component on another layout, you can assign the label value directly (or via a binding):
<usercontrols:ImageWithTitle TitleName="Home"/>

Silverlight4 Element Binding Weirdness

I'm a bit of a .net newbie and I've been working on my first big silverlight project. So pardon the lack of lingo and the length of the question. But my problem is as follows.
The project is being built according to the MVVM pattern ( in this case I'm using LightMVVM ). Most of the views contain ListBoxes. These listboxes need to handle multiple different types of data each of which has it's own visual look. After some poking around I decoded tp try this implementation for datatemplate selection:
http://silverscratch.blogspot.com/2010/04/changing-data-templates-at-run-time.html
Some of my items, however, have sub controls that need to talk to the viewmodel. From what I've been reading Commands with element bindings is the best ways to handle this.
So, for example:
<Grid x:Name="NavMainLayoutRoot" DataContext="{Binding Source={StaticResource NavMainViewModelDataSource}}" Margin="15,0,0,0">
....
<ListBox x:Name="MenuListBox" HorizontalAlignment="Left" Background="{x:Null}" BorderBrush="{x:Null}" Foreground="White" ItemsSource="{Binding Items}" ItemContainerStyle="{StaticResource MainNavigationButtonStyle}" Padding="0" VerticalAlignment="Top" >
<ListBox.RenderTransform>
<CompositeTransform/>
</ListBox.RenderTransform>
<ListBox.ItemTemplate>
<DataTemplate>
<xxxControls:SelectableContentControl TemplateName="{Binding Path=Type}" Content="{Binding Details}" IsTabStop="{Binding IsHitTestEnabled}">
<xxxControls:SelectableContentControl.Templates>
<DataTemplate>
<local:GenericItem />
</DataTemplate>
<DataTemplate x:Name="navbutton">
<local:MainNavItem />
</DataTemplate>
</xxxControls:SelectableContentControl.Templates>
</xxxControls:SelectableContentControl>
</DataTemplate>
</ListBox.ItemTemplate>
....
And MainNavItem, simplified is:
<Grid x:Name="NavItemRoot" VerticalAlignment="Top" Margin="0,0,0,0">
<Button Content="{Binding Label}" VerticalAlignment="Top" Style="{StaticResource MainNavItemButtonStyle}" HorizontalAlignment="Left" Margin="5,0" Command="{Binding DataContext.NavButtonClick, ElementName=NavMainLayoutRoot}"/>
</Grid>
The problem is that this didn't work. So for grins I went ahead and copy and pasted the code for the MainNavItem directly into the tag and like magic it started working.
Since I reuse a lot of these item templates all over the application, having them in nice contained external files is very nice and not something I want to give up.
(( Thinking about it, this example is not the best, suffice it to say that some of these data templates contain multiple controls and I can't just use selectedItem on the listbox to handle the selected events. ))
So any suggestions are welcome. What's the best practice here?
My first thought is that something in your MainNavItem user control is setting its DataContext to something else. If you don't set the DataContext it should automatically pick it up from the current item in your MenuListBox.
You can try creating a test value converter and putting a breakpoint in it to check what the data context is at runtime.
public class TestConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
Debug.WriteLine("TestConverter.Convert(value := {0}, targetType := {1}, parameter := {2}, culture := {3})",
value, targetType, parameter, culture);
return value; // put break point here to test data binding
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
Debug.WriteLine("TestConverter.ConvertBack(value := {0}, targetType := {1}, parameter := {2}, culture := {3})",
value, targetType, parameter, culture);
return value;
}
}
And modify your MainNavItem to look like this in order to break in the TestConverter at runtime.
<UserControl.Resources>
<ResourceDictionary>
<TestConverter x:Key="TestConverter" />
</ResourceDictionary>
</UserControl.Resources>
<Grid x:Name="NavItemRoot" DataContext="{Binding Converter={StaticResource TestConverter}}">
<Button Content="{Binding Path=Label, Converter={StaticResource TestConverter}}" />
</Grid>
This will help you determine the issue with data binding.
I suspect that the problem with your command is that you're using element-to-element data binding to attempt to bind to an element that is outside of the user control you're currently within. This won't work. Instead, try setting up your NavMainViewModelDataSource static resource in App.xaml, then you can bind directly to it from your user control.
<Button Content="{Binding Label}" Command="{Binding Path=NavButtonClick, Source={StaticResource NavMainViewModelDataSource}}" />

Resources