I have a ListView and I'm using the standard "native" template and groups.
How do I set the font for the text in the group header and the item?
Here is an example for setting dynamic LayoutOptions in ListHeader, like wise you can pre-set these attributes for individual label and customize your header. now as what you want, you can set your own font family or colour or font size, or may be customize your header cell, It is just an ItemTemplate
<ListView x:Name="ListView" IsGroupingEnabled="true">
<ListView.GroupHeaderTemplate>
<DataTemplate>
<ViewCell>
<Label
HorizontalOptions="{Binding Optn}"
Text="{Binding Heading}"
VerticalOptions="{Binding Optn}" />
</ViewCell>
</DataTemplate>
</ListView.GroupHeaderTemplate>
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Label Text="{Binding DisplayName}" />
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Happy Coding!
I've fixed the text and detail labels using this...
public class MyTextCellRenderer : TextCellRenderer
{
public override UITableViewCell GetCell(Cell item, UITableViewCell reusableCell, UITableView tv)
{
var cell = base.GetCell(item, reusableCell, tv);
if (cell.TextLabel.Font.FamilyName != CommonStyles.RegularFontName)
{
cell.TextLabel.Font = UIFont.FromName(
CommonStyles.RegularFontName, cell.TextLabel.Font.PointSize);
cell.DetailTextLabel.Font = UIFont.FromName(
CommonStyles.RegularFontName, cell.DetailTextLabel.Font.PointSize);
}
return cell;
}
}
For the section headers, this works initially...
UILabel.AppearanceWhenContainedIn(typeof(UITableView)).Font = ...
When the section header has been scrolled off screen and back on again, it reverts to the standard though.
But this works...
public class CustomTableViewRenderer : TableViewRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<TableView> e)
{
base.OnElementChanged(e);
if (e.NewElement != null)
{
Control.Source = new CustomTableViewModelRenderer(e.NewElement);
}
}
}
public class CustomTableViewModelRenderer : UnEvenTableViewModelRenderer
{
public CustomTableViewModelRenderer(TableView model) : base(model)
{
}
public override void WillDisplayHeaderView(UITableView tableView, UIView headerView, nint section)
{
if (headerView is UITableViewHeaderFooterView view)
{
view.TextLabel.Font = UIFont.FromName(CommonStyles.RegularFontName, 18);
}
}
}
That's a lot of code to change some fonts.
Related
I've created a customer specific task management app with tasks placed on specific dates (and sometime hours), but here the date is important.
I'm using a listView and have a DatePicker setting for selected other dates than today. So far so good.
I would like to implement a week quick-filter option so that e.g., the dates of the current week is displayed at the top of the list view and a click on a certain date would filter the listView accordingly. Kind of a standard outlook-like week view.
How would I do this in the best way?
CustomControl that I put above the listView?
ViewPager control?
Any ideas or suggestions much appreciated.
P.S. I need to be able to target both Android and iOS.
Set two Properties in the ViewModel one for containing all the Items EntireCollection and another to store the Filtered Items FilteredCollection. On button click derive the Filtered item from entire list using Where.
ViewModel
public class ViewModel : INotifyPropertyChanged
{
private ObservableCollection<ListItem> filteredCollection;
public ObservableCollection<ListItem> FilteredCollection
{
get
{
return filteredCollection;
}
set
{
filteredCollection = value;
OnPropertyChanged();
}
}
private ObservableCollection<ListItem> entireCollection;
public ObservableCollection<ListItem> EntireCollection
{
get
{
return entireCollection;
}
set
{
entireCollection = value;
OnPropertyChanged();
}
}
public ViewModel()
{ ...
this.FilterCollection = this.EntireCollection;
...
}
}
Button clicked
void Button_Clicked(System.Object sender, System.EventArgs e)
{
DateTime selectedDate = ((DateTime)((sender as VisualElement).BindingContext)).Date;
viewModel.FilteredCollection = new ObservableCollection<ListItem>(viewModel.EntireCollection.Where(x =>
{
if (DateTime.Equals(x.DateAdded, selectedDate))
{
var asd = x.DateAdded.Day;
return true;
}
return false;
}));
}
XAML
<StackLayout>
<ScrollView
x:Name="calender"
Orientation="Horizontal">
<StackLayout
BackgroundColor="Blue"
BindableLayout.ItemsSource="{Binding Dates}"
Orientation="Horizontal">
<BindableLayout.ItemTemplate>
<DataTemplate>
<Button
TextColor="White"
BackgroundColor="Blue"
Clicked="Button_Clicked"
Text="{Binding Day}"/>
</DataTemplate>
</BindableLayout.ItemTemplate>
</StackLayout>
</ScrollView>
<ListView
ItemsSource="{Binding FilteredCollection}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout>
<Label Text="{Binding Name}"/>
<Label Text="{Binding DateAdded}"/>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
Hope it helps!!
I'm using Xamarin.Forms MVVM to develop my app, and don't found what I'm doing wrong, I have an ObservableCollection with the values from web API, and when I set a break point all the values are good even in the view when I see the values of the binding source everything have the value, but the values are not showing up in my ListView.
Here is the ViewModel
class DatosMedicosViewModel : BaseViewModel
{
private ApiService apiService;
private ObservableCollection<Land> land;
private bool isRefreshing;
public ObservableCollection<Land> Lands
{
get { return this.land; }
set { SetValue(ref this.land, value); }
}
public bool IsRefreshing
{
get { return this.isRefreshing; }
set { SetValue(ref this.isRefreshing, value); }
}
public DatosMedicosViewModel()
{
this.apiService = new ApiService();
this.LoadLand();
}
private async void LoadLand()
{
this.IsRefreshing = true;
var connection = await this.apiService.CheckConnection();
if (!connection.IsSuccess)
{
this.IsRefreshing = false;
await Application.Current.MainPage.DisplayAlert(
"Error",
connection.Message,
"Accept");
await Application.Current.MainPage.Navigation.PopAsync();
return;
}
var response = await this.apiService.GetList<Land>(
"url Base",
"prefix",
"Controller");
if (!response.IsSuccess)
{
this.IsRefreshing = false;
await Application.Current.MainPage.DisplayAlert(
"Error",
response.Message,
"Accept"
);
return;
}
var list = (List<Land>)response.Result;
this.Lands = new ObservableCollection<Land>(list);
this.IsRefreshing = false;
}
public ICommand RefreshCommand
{
get
{
return new RelayCommand(LoadLand);
}
}
}
Here is the View
<?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="ARLAPP.Views.ConsultaPage"
BackgroundColor="White"
BindingContext="{Binding Main, Source={StaticResource Locator}}"
Title="Lands">
<ContentPage.Content>
<StackLayout
BindingContext="{Binding Lands}"
Padding="5">
<StackLayout>
<Image
VerticalOptions="Center"
WidthRequest="300"
Source="UserIcon"
BackgroundColor="Transparent"/>
<Label Text="Mark"
VerticalOptions="Center"
HorizontalOptions="CenterAndExpand"
FontAttributes="Bold"
FontSize="Medium"/>
</StackLayout>
<StackLayout>
<ListView
SeparatorVisibility="Default"
FlowDirection="LeftToRight"
BackgroundColor="White"
ItemsSource="{Binding Lands}"
HasUnevenRows="True">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Label
Grid.Column="2"
VerticalOptions="Center"
TextColor="Black"
Text="{Binding Currency}"/>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
</StackLayout>
</ContentPage.Content>
</ContentPage>
Here how I call the view
if (this.PageName == "Lands")
{
MainViewModel.GetInstance().Lands= new LandViewModel();
Application.Current.MainPage = new LandMasterPage();
}
Check your BindingContext. I think you are setting it wrong in your view.
In your top-level StackLayout you set the the BindingContext to your property: BindingContext="{Binding Lands}". And in your ListView you set the ItemsSource also to this property: ItemsSource="{Binding Lands}". That won't work because the ListView is trying to bind to a property Lands inside your BindingContext, which is also set to Lands.
Remove the BindingContext from your top-level StackLayout, because you don't need it.
Ensure the BindingContext of your page ConsultaPage is set to your view-model DatosMedicosViewModel.
Sample of setting the bindingcontext (abstract code):
var mypage = new ConsultaPage();
mypage.BindingContext = new DatosMedicosViewModel();
await Navigation.PushAsync(mypage);
// Load your data in OnAppearing() of the page-event
This should solve your binding-problem.
Side-Note: As Abdul Gani said in the comments: Ensure you implement the INotifyPropertyChanged interface, but I assume you do this already in your BaseViewModel and call the NotifyChanged-Event in your SetValue-Method.
Please consider the following issue.
In my Xamarin.Forms app I have a custom render for UWP that allows for a button to have two lines, and be centralised.
The buttons in questions are items in a Listview that are bound to objects. When they are initially generated, they display correctly with both lines of text in the center of the button, however if I update the text, it updates, but seems to bypass the custom renders "be in the center" code.
Please see the below code snippets and images to explain the situation further.
Custom Render
[assembly: ExportRenderer(typeof(TwoLinedButton), typeof(TwoLinedButtonUWP))]
namespace aphiresawesomeproject.UWP
{
public class TwoLinedButtonUWP : ButtonRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<Button> e)
{
base.OnElementChanged(e);
if (Control != null && e.NewElement.Text != null)
{
var textBlock = new Windows.UI.Xaml.Controls.TextBlock
{
Text = e.NewElement.Text,
TextAlignment = Windows.UI.Xaml.TextAlignment.Center,
TextWrapping = TextWrapping.WrapWholeWords
};
Control.Content = textBlock;
}
}
}
}
XAML
<ListView x:Name="AphiresListView" CachingStrategy="RecycleElement" ItemsSource="{Binding ListViewItems}" Margin="0,20,0,0" RowHeight="130" SeparatorVisibility="None" VerticalOptions="FillAndExpand" Grid.Column="1" Grid.Row ="3" >
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<local:TwoLinedButton Command="{Binding ClickedCommand}" Margin="5,10,5,10" HorizontalOptions ="FillAndExpand" BackgroundColor="{Binding color_hex}" Grid.Column="1" TextColor="{StaticResource LightTextColor}" FontSize="Medium" Text="{Binding problem_title}"></local:TwoLinedButton>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Update in Viewmodel
foreach (AphiresObject ViewItem in ListViewItems)
{
ViewItem.problem_title = ViewItem.problem_title.Replace("Line 2", "Updated Line 2");
}
Before
After
I think all you need to do is override OnElementPropertyChanged in your renderer and set the textBlock properties again when your text property changes.
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
if (e.PropertyName == TwoLinedButton.TextProperty.PropertyName)
{
//Set text block properties
}
}
You may also need to tell the view to re-render itself.
iOS: this.SetNeedsDisplay();
Android: this.Invalidate();
Dears,
I have an image in my listview. For that image, I set Aspect="AspectFill".
On tap of that image, I need to view the image in full screen and the background will be a blur.
My requirement is like the initial tap of WhatsApp dp. In WhatsApp, On initial tap of dp full-screen image showing on top of WhatsApp contacts.
Thanks in advance.
Completed this feature using Rg.Plugins.Popup
https://github.com/rotorgames/Rg.Plugins.Popup
Put the Listview in a grid
Put an image in the same grid and set IsVisible to false. Make it full width and height.
On tap of the listview item set the image source to the tapped list view item image and set IsVisible to true.
You could nest the image in a stacklayout with a white opaque background for the blur effect.
Add a tap handler to the image to hide it again.
Here is you answer
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<ListView x:Name="lstView" BackgroundColor="White" VerticalOptions="FillAndExpand" ItemsSource="{Binding ImageList}" ItemSelected="Handle_ItemSelected" >
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout VerticalOptions="FillAndExpand" Orientation="Horizontal" Padding="10">
<Image Aspect="AspectFill"
HeightRequest ="75" WidthRequest="75"
Source="{Binding ImageName}" />
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<Image Grid.RowSpan="1" Source="{Binding}" x:Name="grdImage" IsVisible="false"/>
</Grid>
public POCPage()
{
InitializeComponent();
ImageList = new ObservableCollection<Images>
{
new Images { ImageName = "first", ImageId = "1" },
new Images { ImageName = "Four", ImageId = "2" },
new Images { ImageName = "Second", ImageId = "3" },
new Images { ImageName = "Third", ImageId = "4" }
};
lstView.ItemsSource = ImageList;
}
void Handle_ItemSelected(object sender, Xamarin.Forms.SelectedItemChangedEventArgs e)
{
grdImage.IsVisible = true;
lstView.IsVisible = false;
var item = e.SelectedItem as Images;
grdImage.Source = ImageSource.FromFile(item.ImageName);
}
private ObservableCollection<Images> imageList;
public ObservableCollection<Images> ImageList
{
get
{
return imageList;
}
set
{
imageList = value;
}
}
}
public class Images
{
public string ImageName
{
get;
set;
}
public string ImageId
{
get;
set;
}
I am using a BoxView to accomplish underlining in my app. I have a couple of labels that are very short - Text such as Yes or No etc. Here is the XAML for one of the labels with the BoxView for underlining:
<StackLayout Orientation="Vertical" Grid.Row="5" Grid.Column="1" Margin="0,4,0,4" HorizontalOptions="Start" BackgroundColor="Purple" MinimumWidthRequest="1">
<Label x:Name="txtUseMetric" TextColor="Blue" FontSize="Small" Text="{Binding UseMetricText}" BackgroundColor="Yellow">
<Label.GestureRecognizers>
<TapGestureRecognizer Tapped="Value_Tapped" CommandParameter="usemetric" />
</Label.GestureRecognizers>
</Label>
<BoxView BackgroundColor="Green" HeightRequest="1" MinimumWidthRequest="1" />
</StackLayout>
My problem is that the width of the BoxView is always extending past my text I have tried overriding the MinWidthRequest in my App.Xaml file as seen below:
<Style TargetType="BoxView">
<Setter Property="MinimumWidthRequest" Value="3" />
</Style>
But this has not effect. I have included screen shots for you to see.
FYI - The yellow is the width of the Label. You don't see any purple (the background color of the StackLayout) because the StackLayout and Label are the same width. The second screen shot shows what the screen looks like if I remove the BoxView - i.e. the Label and StackLayout are sized correctly.
Any suggestions on how to fix this?
Screen shot with BoxView Too Long making label and StackLayout too long
Screen shot with BoxView removed and Label and Stack Layout sizing correctly
Please note the default HorizontalOptions and that Label derives from View:
Default value is LayoutOptions.Fill unless otherwise documented.
Add HorizontalOptions="Start" on the "Use Metric" Label:
<Label x:Name="txtUseMetric" TextColor="Blue" FontSize="Small"
Text="{Binding UseMetricText}" BackgroundColor="Yellow"
HorizontalOptions="Start">
<BoxView BackgroundColor="Green" HeightRequest="1"
WidthRequest="{Binding Path=Width, Source={x:Reference txtUseMetric}"
HorizontalOptions="Start"/>
One option is to replace the label/box underline with a custom renderer that adds an underline capability to the label.
Here is how to do it:
User Control
public class CustomLabel : Label
{
public static readonly BindableProperty IsUnderlinedProperty =
BindableProperty.Create(nameof(IsUnderlined), typeof(bool), typeof(CustomLabel), false);
public bool IsUnderlined
{
get { return (bool)GetValue(IsUnderlinedProperty); }
set
{
SetValue(IsUnderlinedProperty, value);
}
}
}
Android renderer
[assembly: ExportRenderer(typeof(CustomLabel), typeof(CustomLabelRenderer))]
namespace Incident.Droid.CustomRenderers
{
public class CustomLabelRenderer : LabelRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<Label> e)
{
base.OnElementChanged(e);
var view = (CustomLabel)Element;
var control = Control;
UpdateUi(view, control);
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
var view = (CustomLabel)Element;
if (e.PropertyName == CustomLabel.IsUnderlinedProperty.PropertyName)
{
Control.PaintFlags = view.IsUnderlined ? Control.PaintFlags | PaintFlags.UnderlineText : Control.PaintFlags &= ~PaintFlags.UnderlineText;
}
}
static void UpdateUi(CustomLabel view, TextView control)
{
if (view.FontSize > 0)
{
control.TextSize = (float)view.FontSize;
}
if (view.IsUnderlined)
{
control.PaintFlags = control.PaintFlags | PaintFlags.UnderlineText;
}
}
}
}
iOS Renderer
[assembly: ExportRenderer(typeof(CustomLabel), typeof(CustomLabelRenderer))]
namespace Incident.iOS.CustomRenderers
{
public class CustomLabelRenderer : LabelRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<Label> e)
{
base.OnElementChanged(e);
var view = (CustomLabel)Element;
UpdateUi(view, Control);
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
var view = (CustomLabel)Element;
if (e.PropertyName == CustomLabel.IsUnderlinedProperty.PropertyName)
{
UpdateUi(view, Control);
}
}
private static void UpdateUi(CustomLabel view, UILabel control)
{
var attrString = new NSMutableAttributedString(control.Text);
if (view != null && view.IsUnderlined)
{
attrString.AddAttribute(UIStringAttributeKey.UnderlineStyle,
NSNumber.FromInt32((int)NSUnderlineStyle.Single),
new NSRange(0, attrString.Length));
}
control.AttributedText = attrString;
}
}
}