How to customize the TabbedPage's Tab-Items in Xamarin.Forms? - c#

I'm using a TabbedPage as my MainPage of my Xamarin.Forms app (Xamarin.Forms Version: 2.3.5.239-pre3). My MainActivity inherits from FormsAppCompatActivity.
There are four pages of type ContentPage added to the TabbedPage like:
<TabbedPage ... >
<pages:FirstPage Title="Testpage1" Icon="icon.png" />
<pages:SecondPage Title="Testpage2" Icon="icon.png" />
<pages:ThirdPage Title="Testpage3" Icon="icon.png" />
<pages:FourthPage Title="Testpage3" Icon="icon.png" />
</TabbedPage>
However, the tabs are displayed like:
Now I need to change the font size of the title property, so the whole title will be displayed. Whats the best way to do this? I tried a CustomRenderer but I couldn't figure out how to access the tab-items.
I tried:
[assembly: ExportRenderer(typeof(TabbedPage), typeof(CustomTab))]
namespace AdvancedForms.Droid.CustomRenderer
{
public class CustomTab : TabbedRenderer
{
protected override void DispatchDraw(Canvas canvas)
{
base.DispatchDraw(canvas);
ActionBar actionBar = activity.ActionBar;
// Do Stuff here
}
}
}
But activity.ActionBar is always null.

You should be looking for a TabLayout, not an ActionBar. Last I checked the TabLayout is the second child in the renderer's view hierarchy so you should be able to get at it like so:
var tabLayout = (TabLayout)GetChildAt(1);
Once you have that you need to loop through the individual tabs and apply your desired font size to each tab's textview.
Helpful hint, the view hierarchy looks like this:
MsiTabbedRenderer
FormsViewPager
TabLayout
SlidingTabStrip
TabView
AppCompatImageView
AppCompatTextView
TabView
AppCompatImageView
AppCompatTextView
TabView
AppCompatImageView
AppCompatTextView
...
The method I use to generate this information is included below for your enjoyment:
public static void DebugLayout(this View self, int indent = 0)
{
// write info about me first
var indents = new string('\t', indent);
System.Diagnostics.Debug.WriteLine(indents + self.GetType().Name);
// check if I'm a view group
var vg = self as ViewGroup;
if (vg == null) return;
// enumerate my children
var children = Enumerable.Range(0, vg.ChildCount).Select(x => vg.GetChildAt(x));
// recurse on each child
foreach (var child in children)
DebugLayout(child, indent+1);
}

For Custom tab view/page i am using the xam.Tabview (Xamarin.Forms) Component and it's working as expected.
It has the support for custom header and content view.
Install Nuget: TabView Nuget
Sample: Tabview Sample

Related

How Can I Create Multiple Views (i.e., layout .xml files) using Xamarin.Android and Display Them Depending on What Navigation Button is Clicked?

I am developing a Xamarin.Android app. I will have multiple tabs to select from. I want to implement functionality so that when I click on a navigation tab (from the Navigation Drawer to the left), a view specific to that tab will be displayed. I want to be able to call xml files from the layout folder when needed.
I tried using Fragments, but I encountered a message in Visual Studio 2019 saying that Fragments are obsolete. I also checked this question on button click switch xml layout page but it doesn't make sense to me. I also tried using SetContentView from the main program and adding my xml file as a parameter, however it was causing a build error.
My SetContentView code was this: SetContentView(Resources.Layout.rego)with rego being the xml file, but, as I mentioned, is causing a build error.
This is one of the if statements for calling the xml file, however it does not work (causing a build error):
public bool OnNavigationItemSelected(IMenuItem item)
{
int id = item.ItemId;
if (id == Resource.Id.nav_rego)
{
SetContentView(Resource.Layout.rego);
}
nav_rego is the navigation tab in the Navigation Drawer.
I am expecting to have an app with navigation functionality so that once a tab is clicked on, a view specific to that tab is displayed.
The Fragment class is deprecated in Android.App indeed(using Android.App;), but the Support Library Fragments are here to stay and are part of the new Android Jetpack components.
Because fragments play a big part in Android development. Just make sure to update your project by using Support Library Fragments, using the following namespace in your fragment class:
using Android.Support.V4.App;
instead of
using Android.App;
For example:
public bool OnNavigationItemSelected(IMenuItem item)
{
int id = item.ItemId;
if (id == Resource.Id.nav_rego)
{
var fragment = SpinnerFragment.NewInstance();
//var fragmentManager = FragmentManager;
var fragmentManager = SupportFragmentManager;
var ft = fragmentManager.BeginTransaction();
ft.Replace(Resource.Id.LLTarget, fragment);
ft.Commit();
}
}
Note:
SpinnerFragment is a Fragment class extends Fragment
public class SpinnerFragment : Fragment{
public static Fragment NewInstance()
{
Fragment fragment = new SpinnerFragment();
return fragment;
}
// other code
}
I figured it out. I was on the right path. I created menu items for the navigation bar and checked the id of the selected navigation button against the id of the navigation buttons. I added layouts under Resources > layout and checked if the selected id (navigation button) was a match for the id of the navigation link/button I specified. For example:
public bool OnNavigationItemSelected(IMenuItem item)
{
int id = item.ItemId;
if (id == Resource.Id.rego_page_nav)
{
SetContentView(Resource.Layout.rego_page_layout);
//code here
}
}

Xamarin clickable substring in custom label

I am trying to make it so users can click a certain substring in a label and it would run a method, for example clicking #hashtag would run OpenHashtag(string hashtagand clicking a #taggedUser would run ViewProfile(taggedUser)
I found this tutorial, except I don't want phone numbers or URLs to be clickable, only hashtags and tagged users.
These are the renders its using
Android
[assembly: ExportRenderer(typeof(BodyLabel), typeof(BodyLabelAndroid))]
namespace SocialNetwork.Droid.Renderers
{
public class BodyLabelAndroid : LabelRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<Label> e)
{
base.OnElementChanged(e);
var view = (BodyLabel)Element;
if (view == null) return;
TextView textView = new TextView(Forms.Context);
textView.LayoutParameters = new LayoutParams(LayoutParams.WrapContent, LayoutParams.WrapContent);
textView.SetTextColor(view.TextColor.ToAndroid());
// Setting the auto link mask to capture all types of link-able data
textView.AutoLinkMask = MatchOptions.All;
// Make sure to set text after setting the mask
textView.Text = view.Text;
textView.SetTextSize(ComplexUnitType.Dip, (float)view.FontSize);
// overriding Xamarin Forms Label and replace with our native control
SetNativeControl(textView);
}
}
}
IOS
[assembly: ExportRenderer(typeof(BodyLabel), typeof(BodyLabeliOS))]
namespace SocialNetwork.iOS.Renderers
{
public class BodyLabeliOS : ViewRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<View> e)
{
base.OnElementChanged(e);
var view = (AwesomeHyperLinkLabel)Element;
if (view == null) return;
UITextView uilabelleftside = new UITextView(new CGRect(0, 0, view.Width, view.Height));
uilabelleftside.Text = view.Text;
uilabelleftside.Font = UIFont.SystemFontOfSize((float)view.FontSize);
uilabelleftside.Editable = false;
uilabelleftside.DataDetectorTypes = UIDataDetectorType.All;
uilabelleftside.BackgroundColor = UIColor.Clear;
SetNativeControl(uilabelleftside);
}
}
}
Android:
Instead of using textView.AutoLinkMask = MatchOptions.All
you can use
Linkify.AddLinks method. Define your regular expression (for example, any word which starts with # or #) and it will work.
But on iOS, it is more complicated I think.
There I see two options:
Use WebView. Parse your string and add "<a href" where needed.
Break your text to pieces and add separate labels for each clickable part. If you want to click only hashtags and tagged users you can add the appropriate labels just below the text. Afterwards you can add tap gesture recognizers to handle the clicks.

Xamarin Forms Android: TabbedPage Font Style

how can I change the style of the tab title? The most important thing is, that the titles not cutted off like at the screenshot. So I have to change the size of the titles.
I started creating a Custom Renderer for tabbedpage, but I don't know how to go on.
Xaml:
<custom:CustomTabbedPage...
Forms:
public class CustomTabbedPage : TabbedPage...
Forms.Droid
public class CustomTabbedPageRenderer : TabbedPageRenderer
The TabbedPage has NavigationPages with ContentPages.
If you need further information, let me know. Thank you.
<TabbedPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:TabbedPageWithNavigationPage;assembly=TabbedPageWithNavigationPage"
x:Class="TabbedPageWithNavigationPage.MainPage">
<NavigationPage Title="Start" Icon="start.png">
<x:Arguments>
<local:StartPage />
</x:Arguments>
</NavigationPage>
<NavigationPage Title="Symptom-Tagebuch" Icon="tagebuch.png">
<x:Arguments>
<local:TagebuchPage />
</x:Arguments>
</NavigationPage>
...
It seems that the default title style of NavigationPage behaviors different on different size of device. By my side on 7" KitKat emulator it looks so:
and on 5" KitKat emulator it looks so:
Or it could be the version problem which cause the behavior of this NavigationPage by my side so different from yours, I can't reproduce yout issue. Anyway, if you want to customize the layout of your NavigationPage, you can create a custom render for your android platform. For more information, you can refer to the official document Customizing Controls on Each Platform, and if you're looking for a demo, there are discussions on the official forum about customize the title font of NavigationPage you may also take a look: Discussion 1 and Discussion 2.
Another possible solution to your problem is that I think you can change the NavigationPage to ContentPage, and change your sub pages to content view, and by doing this, you can refer to Xamarin.Forms: Can I embed one ContentPage or ContentView into another ContentPage.
According to your description, maybe the first solution by creating your own custom render is more suitable for your scenario.
You can find the textview of title through TabLayout view children in CustomRendered and change font style
[assembly: ExportRenderer(typeof(MainPage), typeof(BottomTabbedPageRenderer))]
namespace Daddy.Droid
{
public class BottomTabbedPageRenderer : TabbedPageRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<TabbedPage> e)
{
base.OnElementChanged(e);
if (e.NewElement == null || e.OldElement != null)
return;
TabLayout tablayout = (TabLayout)ViewGroup.GetChildAt(1);
Android.Views.ViewGroup vgroup = (Android.Views.ViewGroup)tablayout.GetChildAt(0);
for (int i = 0; i < vgroup.ChildCount; i++)
{
Android.Views.ViewGroup vvgroup = (Android.Views.ViewGroup)vgroup.GetChildAt(i);
Typeface fontFace = Typeface.CreateFromAsset(this.Context.Assets, "IranSans.ttf");
for (int j = 0; j < vvgroup.ChildCount; j++)
{
Android.Views.View vView = (Android.Views.View)vvgroup.GetChildAt(j);
if (vView.GetType() == typeof(Android.Support.V7.Widget.AppCompatTextView) || vView.GetType() == typeof(Android.Widget.TextView))
{
//here change textview style
TextView txtView = (TextView)vView;
txtView.TextSize = 14f;
txtView.SetTypeface(fontFace, TypefaceStyle.Normal);
}
}
}
}
}
}

Add Children to element of type Page created in code behind Windows Phone 8.1

I can create a new element of type Page in code behind, but I want to add children elements of type UIElement as I would to a Grid like: myGrid.Children.Add(myUIElement);
I don't have the Children property and setting the Content property does not work.How can I achieve this?
UPDATE:
This is what I have so far, but does not work:
Page myNewPage = new Page();
Grid myGrid = new Grid();
TextBlock myText = new TextBlock();
myText.Text = "I am a TextBlock!";
myGrid.Children.Add(myText);
myNewPage.Content = myGrid;
Frame.Navigate(myNewPage.GetType());
A Page can host a single UIElement in its Content property. To add several children, you have to do it like you would do it in XAML: add a panel that can contain and layout several children and add your elements to that panel.
In your question you talk about a grid called myGrid. You could add and layout your items in myGrid and then set myGrid as yourPage.Content.
Your code correctly builds the page. The problem with your code is that your Frame is navigating to a new instance of Page that is not the one that you created. Frame creates a new instance of the type that you pass as a parameter.
If you want to create a page fully in code, you can simply create class that extends Page and build the page in its constructor:
public class MyPage : Page
{
public MyPage()
{
this.BuildPage();
}
private void BuildPage()
{
var panel = new StackPanel();
panel.Children.Add(new TextBlock { Text = "Hello" });
panel.Children.Add(new TextBlock { Text = "World" });
this.Content = panel;
}
}
That's, after all, what the InitializeComponent method does in pages created in XAML.

Retrieve the DataContext of the Content set by using ContentSource property

I need to get the DataContext of the View set by using ContentSource property of the ModernWindow, Could you please help.I am using MVVM framework with Modern UI. The ViewModel code from where I need to show another window is as follows,
public void ShowPrompt()
{
this.PromptWindow = ObjectFactory.GetInstance<IPromptWindowViewModel>().Window as ModernWindow;
this.PromptWindow.Owner = Application.Current.MainWindow;
this.PWPMainViewModel.PromptWindowsCollection.Add(this.PromptWindow);
// Here I need to get the DataContext of PromptWindow's Content
this.PromptWindow.Show();
}
I did some debugging and found that by inherting IContent interface from ModernUI in the 'OnNavigatedTo' event
public void OnNavigatedTo(FirstFloor.ModernUI.Windows.Navigation.NavigationEventArgs e)
{
IPWPMainViewModel pwpMainViewModel = ObjectFactory.GetInstance<IPWPMainViewModel>();
pwpMainViewModel.PromptMainsCollection.Add(new ContentControl { Content = e.Content });
IPromptMainViewModel promptMainViewModel = ((UserControl)e.Content).DataContext as IPromptMainViewModel;
}
Here I am able to get the DataContext of the ModernWindow's Content i.e. of type 'IPromptMainViewModel' but here its very difficult to map/load the views into this ModernWindow as there are multiple instances of views, but I would like to do it in the ViewModel where 'ShowPrompt()' is present as there the Model will be associated with the View correctly so I can map there the views easily.
Thank you.
To get this done, I set the Content of the ModernWindow by myself (as shown in below code in a method in a ViewModel) without using the ContentSource DependencyProperty, If we use the ContentSource property it will be set for a ModernFrame type by the ModernWindow itself creating its Content instance after Navigation to that View completes in some method in ModernFrame class from ModernUI for WPF by using ModernFrame's Source DependencyProperty.
public void ShowPrompt()
{
this.PromptWindow = ObjectFactory.GetInstance<IPromptWindowViewModel>().Window as ModernWindow;
this.PromptWindow.Title = string.Concat("Control ", this.PromptOriginsEntity.PromptOriginsIdentity);
this.PromptWindow.Tag = this.PromptOriginsEntity.PromptOriginsIdentity;
this.PromptWindow.Owner = Application.Current.MainWindow;
// Store Window object in PromptWindowsCollection
this.PWPMainViewModel.PromptWindowsCollection.Add(this.PromptWindow);
this.PromptWindow.Show(); // inorder to retrieve the ModernFrame the ModernWindow is to be shown first
ModernFrame frameContent = (ModernFrame)this.PromptWindow.Template.FindName("ContentFrame", this.PromptWindow);
UserControl userControl = new UserControl { Content = GetView<IPromptViewModel>(), Tag = this.PromptOriginsEntity.PromptOriginsIdentity };
frameContent.Content = userControl;
this.PWPMainViewModel.PromptsCollection.Add(userControl);
IPromptViewModel promptViewModel = (IPromptViewModel)((IView)userControl.Content).DataContext;
promptViewModel.PromptEntity.Identity = this.PromptOriginsEntity.PromptOriginsIdentity;
}
I've uploaded a prototype app at https://wpfmvvmsamples.codeplex.com/SourceControl/latest
Thanks.

Categories