Xamarin.Forms Listview with 2 line label? - xamarin

I'm converting a MonoTouch.Dialog app to Xamarin.Forms.
I have a Cell in a ListView and that has a "Detail" line that should be 2 lines long, and should truncate the tail after that.
var lblDetail = new Label
{
LineBreakMode = LineBreakMode.TailTruncation,
Text = "a really long string that should show 2 lines then ..."
};
How do I set something like, "Lines = 2"

I solved this with a custom renderer
This is my xaml (in my pcl project)
<ListView ItemsSource="{Binding Cards}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Grid Padding="10">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="60"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Image Source="{Binding Icon}"/>
<customRenderers:MultiLineLabel Text="{Binding Summary}"
Grid.Column="1"/>
</Grid>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
This is my MultiLineLabel class
public class MultiLineLabel : Label
{
}
This is the renderer for iOS:
[assembly: ExportRenderer(typeof(MultiLineLabel), typeof(MultiLineLabelRenderer))]
namespace NameSpace.iOS.Renderers
{
public class MultiLineLabelRenderer : LabelRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<Label> e)
{
base.OnElementChanged(e);
if (Control != null)
{
Control.LineBreakMode = UILineBreakMode.TailTruncation;
Control.Lines = 3;
}
}
}
}

the following code working fine for me :)
UserNotesListView = new ListView() {
HorizontalOptions = LayoutOptions.FillAndExpand,
VerticalOptions = LayoutOptions.FillAndExpand,
HasUnevenRows=true,
ItemsSource=//upon your bussiess
};
DataTemplate dt=new DataTemplate(()=>
{
var LabelText = new Label();
LabelText.SetBinding(Label.TextProperty, new Binding("note"));
return new ViewCell { View = LabelText };
});
UserNotesListView.ItemTemplate = dt;
Content = new StackLayout
{
HorizontalOptions=LayoutOptions.FillAndExpand,
Children=
{
UserNotesListView
}
};

You might want to customize ListView:
<ListView x:Name="ListMenu" ItemsSource="{Binding _YourItems_}" >
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<ViewCell.View>
<StackLayout Orientation="Horizontal">
<Label Text="{Binding _YourText_}" TextColor="Black" />
</StackLayout>
</ViewCell.View>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
even if text not fit one line, it will wrap automatically.

Related

FindByName control inside another control on ContentPage in Xamarin

I want to set focus on SearchBar when he appears. The problem is that SearchBar is placed inside of popup view and I need to access him from ViewModel.
In standard way I would use
Xamarin.Forms.SearchBar tmp_SearchBar = this.Page.FindByName("fro_SearchBar_NewItem") as Xamarin.Forms.SearchBar;
but that's not working anymore.
Here is XAML
<sfPopup:SfPopupLayout x:Name="fro_Popup_NewItem" Opened="fro_Popup_NewItem_Opened" Grid.Row="1" HorizontalOptions="Center" VerticalOptions="Center" BackgroundColor="Black">
<sfPopup:SfPopupLayout.PopupView>
<sfPopup:PopupView BackgroundColor="Black" WidthRequest ="400" HeightRequest ="100" ShowFooter="False" ShowHeader="False">
<sfPopup:PopupView.ContentTemplate>
<DataTemplate>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<!--Search bar-->
<Grid Grid.Row="0" HorizontalOptions="Center" VerticalOptions="Center">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<SearchBar x:Name="fro_SearchBar_NewItem"
Grid.Column="0"
Text="{Binding SearchText_Popup, Mode=TwoWay}"
SearchCommand="{Binding SearchCommand}"
Placeholder="Find"
CancelButtonColor="White"
TextColor="White"
PlaceholderColor="Gray"/>
</Grid>
</Grid>
</DataTemplate>
</sfPopup:PopupView.ContentTemplate>
</sfPopup:PopupView>
</sfPopup:SfPopupLayout.PopupView>
</sfPopup:SfPopupLayout>
Nested FindByName doesn't work either.
Syncfusion.XForms.PopupLayout.SfPopupLayout tmp_Popup = _Page.FindByName("fro_Popup_NewItem") as Syncfusion.XForms.PopupLayout.SfPopupLayout;
Xamarin.Forms.SearchBar tmp_SearchBar = tmp_Popup.FindByName("fro_SearchBar_NewItem") as Xamarin.Forms.SearchBar;
Thanks
You can use Task to open a new thread to complete this operation in code-behind.
Xaml:
<SearchBar x:Name="fro_SearchBar_NewItem" Placeholder="...." BackgroundColor="White"/>
Code behind:
public partial class PagePop : Popup
{
public PagePop()
{
InitializeComponent();
Task.Run(() => myTask());//Create and start the thread
}
private void myTask()
{
Thread.Sleep(300);
fro_SearchBar_NewItem.Focus();
}
}
It seems that this wasn't so far from right direction, neither of the comments actually.
Syncfusion.XForms.PopupLayout.SfPopupLayout tmp_Popup = _Page.FindByName("fro_Popup_NewItem") as Syncfusion.XForms.PopupLayout.SfPopupLayout;
Xamarin.Forms.SearchBar tmp_SearchBar = tmp_Popup.FindByName("fro_SearchBar_NewItem") as Xamarin.Forms.SearchBar;
but I found the one which works and I don't have to create separate XAML for Popup.
private void fro_Popup_NewItem_Opened(object sender, EventArgs e)
{
try
{
var nativeObject = (object)fro_Popup_NewItem.GetType().GetRuntimeProperties().FirstOrDefault(x => x.Name.Equals("NativeObject")).GetValue(fro_Popup_NewItem);
var formsPopupviewContentTemplate = nativeObject.GetType().GetRuntimeFields().FirstOrDefault(x => x.Name.Equals("formsPopupViewContentTemplate")).GetValue(nativeObject);
var SearchBar = (formsPopupviewContentTemplate as Grid).FindByName<SearchBar>("fro_SearchBar_NewItem");
SearchBar?.Focus();
}
catch(Exception ex)
{
Console.WriteLine(ex.Message);
}
}
Thanks for help, everyone.

How do we bind data to a `footer` in Xamarin Forms

How do we bind data to a footer inside ListView in Xamarin Forms, here I would need to pass the count_in value to footer.
<ListView x:Name="listView">
<ListView.Footer>
<StackLayout>
<Label Text="{Binding Count}" BackgroundColor="Gray"></Label>
</StackLayout>
</ListView.Footer>
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="2*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="2*"/>
</Grid.ColumnDefinitions>
<Image Source="{Binding image}" WidthRequest="50" HeightRequest="50" Grid.Column="0" VerticalOptions="Center"/>
<StackLayout Grid.Column="1">
<Label Text="{Binding FullName}" TextColor="#f35e20" HorizontalTextAlignment="Center"/>
</StackLayout>
<StackLayout Grid.Column="2">
<Label Text="{Binding SoccerStatus}" HorizontalTextAlignment="Center" TextColor="#503026"/>
</StackLayout>
<StackLayout Grid.Column="3">
<Label Text="{Binding CurrentDate}" HorizontalTextAlignment="Center" TextColor="#503026"/>
</StackLayout>
</Grid>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Below is the DisplayCount() gets the count from database;
public void DisplayCount()
{
var datetoday = DateTime.Now.ToString("ddMMyyyy");
var count_in = (from x in conn.Table<SoccerAvailability>().Where(x => string.Equals(x.SoccerStatus, "IN", StringComparison.OrdinalIgnoreCase) && x.CurrentDate == datetoday) select x).Count();
}
you are binding to Count
<Label Text="{Binding Count}" BackgroundColor="Gray"></Label>
so Count needs to be a public property on your ViewModel
public int Count { get; set; }
Now getting database exception SQLite exception no such function
equals
This is because SQLLite linq doesn't recognize the string.Equals method. You could convert it to list using ToListAsync for one condition. Then filter the c# list object using equals:
var datetoday = DateTime.Now.ToString("ddMMyyyy");
var items = await conn.Table<SoccerAvailability>().Where(x => x.CurrentDate == datetoday).ToListAsync();
var finalsItems = items.Where(x => string.Equals(x.SoccerStatus, "IN", StringComparison.OrdinalIgnoreCase)).ToList();
Count = finalsItems.Count();
At last, binding your Lable's Text to this Count property.
Edit about binding:
Have you set your content page to your ViewModel? Moreover, implement the INotifyPropertyChanged interface in your view model:
// Set your page's binding context
BindingContext = new PageViewModel();
public class PageViewModel : INotifyPropertyChanged
{
int count;
public int Count
{
set
{
if (count != value)
{
count = value;
onPropertyChanged();
}
}
get
{
return count;
}
}
public event PropertyChangedEventHandler PropertyChanged;
void onPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Edit2:
If you didn't use view model, set a name to your footer label:
<ListView.Footer>
<StackLayout>
<Label x:Name="FooterLabel" BackgroundColor="Gray"></Label>
</StackLayout>
</ListView.Footer>
Then set its value directly:
//...
FooterLabel.Text = finalsItems.Count().ToString();
You need to specify the binding context for the footer.
By default for footers, headers, and templates in a list view, its binding context is going to be the item currently being displayed. So in this case Count would need to be a public property on the item from ItemsSource. Point the Count binding at your view model instead.
<Label Text="{Binding BindingContext.Count, Source={x:Reference xNameSetForCurrentPage}" BackgroundColor="Gray"></Label>
xNameSetForCurrentPage is the x:Name="name" set at the top most element(where all the xmlns stuff is) of the page.

How to update the rowheight of a clicked row in a listview in Xamarin forms?

This is my current listview XAML that works and i successfully fill my listview with data. I try to adjust the rowheight of a unique cell by both making an item in a stacklayout visible/not visible, but also by adjusting the heightrequest of both the stacklayout and grid.
<ContentPage.Resources>
<ResourceDictionary>
<DataTemplate
x:Key="ClassTemplate">
<ViewCell> <!-- I've tried to bind viewchells height (Height = "{Binding RowHeight}") but its not allowed to bind it -->
<StackLayout HeightRequest = "{Binding RowHeight}" HorizontalOptions = "FillAndExpand" VerticalOptions = "FillAndExpand">
<Button HeightRequest = "10" />
<Button IsVisible = "{Binding ExpandedRowVisibility}" HeightRequest = "300" />
<Grid VerticalOptions="Fill" HeightRequest = "{Binding RowHeight}">
<Grid.GestureRecognizers>
<TapGestureRecognizer
Command="{Binding OpenRowDetails}"
CommandParameter="{Binding .}"
NumberOfTapsRequired="1" />
</Grid.GestureRecognizers>
</Grid>
</StackLayout>
</ViewCell>
</DataTemplate>
</ResourceDictionary>
</ContentPage.Resources>
<ListView
ItemsSource="{Binding List}"
HasUnevenRows="true"
Header="{Binding ShowHeaderObject}"
ItemTemplate="{StaticResource ClassTemplate}">
<ListView.HeaderTemplate>
<DataTemplate>
<StackLayout>
<Label
Text="{Binding .}"
HorizontalTextAlignment="Center"
TextColor="White" />
</StackLayout>
</DataTemplate>
</ListView.HeaderTemplate>
</ListView>
When I click a row in the list I update my RowHeight value that is binded to the Stacklayout, grid and button (i've tried all three, alone and together) but the row i click on does not update.
private string Height = "140";
public string RowHeight
{
get { return Height; }
set
{
private bool ExpandRow;
public bool ExpandedRowVisibility
{
get { return ExpandRow; }
set
{
this.ExpandRow = value;
OnPropertyChanged("ExpandedRowVisibility");
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged([CallerMemberName]string propertyName = null)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public ICommand OpenRowDetails
{
get
{
return new Command(e =>
{
var Item = (ClassItemViewModel)e;
Item.RowHeight = "280";
Item.ExpandedRowVisibility = true;
});
}
}
How can i update my code so that the row that was clicked will adjust its rowheight but the others stays the same?
Try this
<ListView x:Name="listView" HasUnevenRows="true">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout HorizontalOptions = "FillAndExpand" VerticalOptions = "FillAndExpand">
<StackLayout.GestureRecognizers>
<TapGestureRecognizer Tapped="OnItemTapped" NumberOfTapsRequired="1">
</StackLayout.GestureRecognizers>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
And in your tap event method change the height of the layout and update cell
void OnItemTapped(object sender, EventArgs args)
{
var layout = sender as StackLayout;
var viewCell = layout.Parent as ViewCell;
layout.HeightRequest = layout.Height + 100;
viewCell.ForceUpdateSize ();
}

Group Swtich Xamarin forms

I have a Listview that is creating dynamically data and a switch in every row, what i want is when i toggle a switch the others untoggle.
Is this possible to do?
Example:
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<ViewCell.View>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition Height="Auto"></RowDefinition>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="6*"></ColumnDefinition>
<ColumnDefinition Width="Auto"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Label Text="{Binding article_description}"
FontAttributes="Bold" FontSize="13" Margin="10,5,0,-6" Grid.Row="0" LineBreakMode="NoWrap"/>
<Label Text="{Binding dish_name}"
FontSize="13" Margin="10,0,0,2" Grid.Row="1" Grid.Column="0"/>
<Label Grid.Row="0" Grid.Column="0" x:Name="LabelReserved" Text="{Binding reserved}" IsVisible="false" LineBreakMode="NoWrap"/>
<Switch Grid.Row="0" Grid.RowSpan="2" Grid.Column="1" HorizontalOptions="Start" VerticalOptions="Center" IsEnabled="False" Toggled="SwitchMenu_OnToggled" >
<Switch.Triggers>
<DataTrigger TargetType="Switch" Binding="{Binding Source={x:Reference LabelReserved},
Path=Text.Length}" Value="7">
<Setter Property="IsToggled" Value="true" />
</DataTrigger>
</Switch.Triggers>
</Switch>
</Grid>
</ViewCell.View>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
Note: This solution was a bit of an experiment on my part - so I would recommend that, if you decide to implement this, use it with caution.
The intent here is to extend Switch to be able to act as a grouped radio button.
First step would be to create a IsToggled or IsChecked or similar property in the item that acts as BindingContext to each list-item. You can implement an interface like:
public interface IToggableItem
{
string GroupName { get; } //not mandatory, only added to support grouped lists
bool IsChecked { get; set; }
}
Second step would be extend Switch to be aware of items-list. We can do that by adding a GroupContext bindable property - which basically represents the parent list-view's ItemsSource.
When a switch is toggled, it iterates through the items-list to set the property as false on other items.
For example:
public class GroupedSwitch : CustomSwitch
{
public static readonly BindableProperty IsGroupingEnabledProperty =
BindableProperty.Create(
"IsGroupingEnabled", typeof(bool), typeof(GroupedSwitch),
defaultValue: default(bool));
public bool IsGroupingEnabled
{
get { return (bool)GetValue(IsGroupingEnabledProperty); }
set { SetValue(IsGroupingEnabledProperty, value); }
}
public static readonly BindableProperty GroupContextProperty =
BindableProperty.Create(
"GroupContext", typeof(IEnumerable), typeof(GroupedSwitch),
defaultValue: default(IEnumerable));
public IEnumerable GroupContext
{
get { return (IEnumerable)GetValue(GroupContextProperty); }
set { SetValue(GroupContextProperty, value); }
}
protected override void OnPropertyChanged(string propertyName = null)
{
base.OnPropertyChanged(propertyName);
if (propertyName != nameof(IsToggled))
return;
if (IsToggled != true || GroupContext == null)
return;
var currentItem = BindingContext as IToggableItem;
if (currentItem == null)
return;
if (IsGroupingEnabled)
{
var groupList = GroupContext as IEnumerable<IGrouping<string, IToggableItem>>;
var currentGroup = groupList.FirstOrDefault(x => x.Key == currentItem.GroupName);
if (currentGroup != null)
foreach (var item in currentGroup)
{
if (item != currentItem)
item.IsChecked = false;
}
}
else
{
var simpleList = GroupContext as IEnumerable<IToggableItem>;
if (simpleList != null)
foreach (var item in simpleList)
{
if (item != currentItem)
item.IsChecked = false;
}
}
}
}
Third step would be bind GroupContext property to parent ListView's items source. For example:
<ListView x:Name="ParentListView" >
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout Orientation="Horizontal" VerticalOptions="Center">
<Label Text="{Binding Name}" />
<local:GroupedSwitch
IsToggled="{Binding IsChecked}"
GroupContext="{Binding ItemsSource, Source={x:Reference ParentListView}}" />
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Or,
<ListView x:Name="_parentList" IsGroupingEnabled="true" >
<ListView.GroupHeaderTemplate>
<DataTemplate>
<TextCell Text="{Binding Key}" />
</DataTemplate>
</ListView.GroupHeaderTemplate>
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout Orientation="Horizontal">
<Label Text="{Binding Name}" />
<local:GroupedSwitch
ToggledStateFromCode="{Binding IsSwitchOn}"
IsGroupingEnabled="true"
GroupContext="{Binding ItemsSource, Source={x:Reference _parentList}}" />
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
EDIT 1: Updated code to add support for grouped list.
You should use triggers on each switch. Check this out:
https://www.tutorialspoint.com/xaml/xaml_triggers.htm

Xamarin Forms XAML Label Rotation

I've got a problem with Xamarin.Forms and Label.
I'm trying to set a label on a grid column.
The first image here shows the expected result, which is written in AXML on Android.
The second image here is written in XAML in Xamarin.Forms.
The code in the XAML file is as follows:
<Grid
VerticalOptions="FillAndExpand"
HorizontalOptions="FillAndExpand">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="400*"/>
<ColumnDefinition Width="75*"/>
</Grid.ColumnDefinitions>
<WebView Source="{Binding ContentSource}" />
<!--<ProgressBar IsVisible="{Binding IsLoading}"
Progress="{Binding Progress}"/>-->
<Grid Grid.Column="1"
BackgroundColor="#EE7F00"
VerticalOptions="FillAndExpand"
HorizontalOptions="FillAndExpand">
<Label
Text="{Binding DocumentIndex}"
LineBreakMode="NoWrap"
HorizontalOptions="Center"
Rotation="-90"
VerticalOptions="Center" />
</Grid>
</Grid>
How can I expand the height or width of the label to equal to the text length?
Thank you so far
Remove the Grid container for label and place a Box view instead, and set the Grid Row and Column same for both the box view and label. like this
<Grid
VerticalOptions="FillAndExpand"
HorizontalOptions="FillAndExpand">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="80*"/>
<ColumnDefinition Width="20*"/>
</Grid.ColumnDefinitions>
<WebView Grid.Row="0" Grid.Column="0" Source="{Binding ContentSource}" />
<!--<ProgressBar IsVisible="{Binding IsLoading}"
Progress="{Binding Progress}"/>-->
<BoxView Grid.Row="0" Grid.Column="1" BackgroundColor="#EE7F00" VerticalOptions="FillAndExpand" HorizontalOptions="FillAndExpand"/>
<Label Grid.Row="0" Grid.Column="1" Text="{Binding DocumentIndex}"
LineBreakMode="NoWrap"
HorizontalOptions="Center"
Rotation="-90"
VerticalOptions="Center" />
</Grid>
I hope this will solve your label length problem after rotation.
I solved this with a custom renderer. In your Forms project:
public class RotatedText : View
{
public static BindableProperty TitleValueProperty = BindableProperty.Create(nameof(TitleValue), typeof(string), typeof(string), null, BindingMode.TwoWay, null,
(bindable, oldValue, newValue) =>
{
});
public string TitleValue
{
get => (string)GetValue(TitleValueProperty);
set => SetValue(TitleValueProperty, value);
}
}
And in your Android project:
[assembly: ExportRenderer(typeof(RotatedText), typeof(RotatedTextRenderer))]
namespace MyNamespace
{
public class RotatedTextRenderer : ViewRenderer
{
private Context _context;
public RotatedTextRenderer(Context c) : base(c)
{
_context = c;
}
protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.View> e)
{
base.OnElementChanged(e);
if (e.NewElement is RotatedText)
{
string title = ((RotatedText)e.NewElement).TitleValue;
SetNativeControl(new RotatedTextView(_context, title));
}
}
}
public class RotatedTextView : Android.Views.View
{
private int DEFAULT_TEXT_SIZE = 30;
private string _text;
private TextPaint _textPaint;
public RotatedTextView(Context c, string title) : base(c)
{
_text = title;
initLabelView();
}
private void initLabelView()
{
this._textPaint = new TextPaint();
this._textPaint.AntiAlias = true;
this._textPaint.TextAlign = Paint.Align.Center;
this._textPaint.TextSize = DEFAULT_TEXT_SIZE;
this._textPaint.Color = new Android.Graphics.Color(0, 0, 0);
}
public override void Draw(Canvas canvas)
{
base.Draw(canvas);
if (!string.IsNullOrEmpty(this._text))
{
float x = (Width / 2) - DEFAULT_TEXT_SIZE/3;
float y = (Height / 2);
canvas.Rotate(90);
canvas.DrawText(this._text, y, -x, this._textPaint);
}
}
}
}
Then just set TitleValue where ever you use RotatedText. It's a little ugly but I couldn't find a better way.

Resources