Xamarin ListView with auto fit row height - xaml

I need to co create listView that set row height depends of its content. Another problem is when is set HasUnevenRows="true" then it makes a lot of empty space under my components in listView.
<ListView HasUnevenRows="true">
...
</ListView>

You need to use BindableLayout
<StackLayout
BindableLayout.ItemsSource="{Binding Item}" Padding="30,10">
<BindableLayout.ItemTemplate>
<DataTemplate>
<Label
Text="{Binding .}"
TextColor="Black" />
</DataTemplate>
</BindableLayout.ItemTemplate>
Code Behind
public ObservableCollection<string> Item { get; set; } = new ObservableCollection<string>();
public MainPage()
{
InitializeComponent();
BindingContext = this;
for (int i = 0; i < 5; i++)
{
Item.Add($"Abc{i}");
}
}

Related

Saving with SQL

Hi all I have a page that I'm trying to ADD to a list and save it with viewmodel and SQL. but my list is empty Can you tell me where am I wrong??
on my Xaml (Page1):
<Entry Margin="5" Text="{Binding Xname}" Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="2" FontSize="16" x:Name="entry1" Placeholder="Enter X-rays (6 MV)" PlaceholderColor="White" BackgroundColor="Gray"/>
<Button Grid.Column="0"
Grid.Row="2"
FontAttributes="Bold"
Command="{Binding AddXrayCommand}"
Text="Add" />
<ListView Grid.Row="3" Grid.ColumnSpan="2" HasUnevenRows="True" Margin="40"
ItemsSource="{Binding energyX}" SelectedItem="{Binding selecteditemX}"
HorizontalOptions="Start" WidthRequest="150" >
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="30"></RowDefinition>
<RowDefinition Height="5"></RowDefinition>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
</Grid.ColumnDefinitions>
<Label Grid.Row="0" BackgroundColor="White" Text="{Binding xray}" FontSize="Large" HorizontalTextAlignment="Center" TextColor="Black" WidthRequest="35"/>
<Frame Grid.Row="1" BackgroundColor="Gray"></Frame>
</Grid>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
my Model (Energypage):
public class EnergyX
{
[PrimaryKey, AutoIncrement]
public int Id { get; set; }
public string xray { get; set; }
}
And on my EnergyViewModel:
public class EnergyViewModel
{
public SQLiteConnection conn;
public ObservableCollection<EnergyX> energyX { get; set; } = new ObservableCollection<EnergyX>();
public string Xname { get; set; }
public ICommand AddXrayCommand => new Command(AddX);
public SQLiteConnection GetSQLiteConnection()
{
var fileName = "Energys.db";
var documentPath = System.Environment.GetFolderPath(System.Environment.SpecialFolder.LocalApplicationData);
var path = Path.Combine(documentPath, fileName);
var connection = new SQLiteConnection(path);
return connection;
}
public void AddX()
{
if (Xname != null && Xname.Length > 0)
{
EnergyX XX = new EnergyX();
XX.xray = Xname;
conn = GetSQLiteConnection();
conn.CreateTable<EnergyX>();
var dataX = conn.Table<EnergyX>();
var resultX = conn.Insert(XX);
energyX = new ObservableCollection<EnergyX>(conn.Table<EnergyX>().ToList());
}
}
}
}
I did bond my page1 to Energyviewmodel, and it works find when I didn't use SQL service, I think my SQL has problem...
I have debug the viewmodel and program run to the end of the Viewmodel but my xaml page list is empty.
this line creates a completely new instance of energyX when your ListView is bound to the old instance
energyX = new ObservableCollection(conn.Table().ToList());
there are two ways you could fix this
option 1, use INotifyPropertyChanged and raise a PropertyChanged event in the setter of energyX
option 2, don't create a new instance of energyX. Instead just add the new item to the existing instance
energyX.Add(XX);

Display elements in the center of the CollectionView

I have a CollectionView with 5 items per row. In all I have 12 elements and I would like to insert the last 2 elements of the third row in the center, and not in the left side.
<CollectionView
x:Name="CollectionIconDiary"
SelectionMode="None">
<CollectionView.ItemsLayout>
<GridItemsLayout Orientation="Vertical" Span="5" VerticalItemSpacing="3" HorizontalItemSpacing="3"/>
</CollectionView.ItemsLayout>
<CollectionView.ItemTemplate>
<DataTemplate>
//OTHER CODE
Example
In all I have 12 elements and I would like to insert the last 2 elements of the third row in the center, and not in the left side.
There is one simple workaround, you can use one CollectionView and one StackLayout to display all datas. Using the StackLayout to display the last items.
<StackLayout Spacing="0">
<CollectionView
x:Name="CollectionIconDiary"
HeightRequest="240"
ItemsSource="{Binding items}"
SelectionMode="None">
<CollectionView.ItemsLayout>
<GridItemsLayout
HorizontalItemSpacing="3"
Orientation="Vertical"
Span="5"
VerticalItemSpacing="3" />
</CollectionView.ItemsLayout>
<CollectionView.ItemTemplate>
<DataTemplate>
<StackLayout>
<Image Source="{Binding imagesource}" />
<Label Text="{Binding name}" />
</StackLayout>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
<StackLayout
x:Name="list"
BindableLayout.ItemsSource="{Binding lastitem}"
HorizontalOptions="CenterAndExpand"
Orientation="Horizontal">
<BindableLayout.ItemTemplate>
<DataTemplate>
<StackLayout>
<Image HeightRequest="80" Source="{Binding imagesource}" />
<Label Text="{Binding name}" />
</StackLayout>
</DataTemplate>
</BindableLayout.ItemTemplate>
</StackLayout>
</StackLayout>
public partial class Page6 : ContentPage
{
public itemviewmodel1 source { get; set; }
public Page6()
{
InitializeComponent();
source = new itemviewmodel1();
this.BindingContext = source;
}
}
public class itemviewmodel1
{
public ObservableCollection<itemmodel> items { get; set; }
public ObservableCollection<itemmodel> lastitem { get; set; }
public itemviewmodel1()
{
items = new ObservableCollection<itemmodel>();
lastitem = new ObservableCollection<itemmodel>();
for(int i=0;i<13;i++)
{
itemmodel model = new itemmodel();
model.name = "image "+i;
model.imagesource = "c1.png";
items.Add(model);
}
func();
}
private void func()
{
int value = (items.Count)% 5;
int count = items.Count;
if(value>0)
{
for(int i=count-1;i>=count-value;i--)
{
lastitem.Add(items[i]);
items.Remove(items[i]);
}
}
}
}
public class itemmodel
{
public string imagesource { get; set; }
public string name { get; set; }
}
You need to set HeightRequest for CollectionView, to prevent the large space between CollectionView and StackLayout, you can take a look the following thread to adjust HeightRequest according to items.
How to change CollectionView size according sizes of its items
You also need to set Image HeightRequest to make StackLayout item size.
The screenshot:

Refresh SemanticZoom ObservableCollection in ViewModel

When using a SemanticZoom control, is there a way to update the ObservableCollection in the ViewModel after a table change? After making changes to the table in SQLite, within the same page (categories.xaml.cs), the SemanticZoom control does not update. Reloading the page from menu navigation does reload the page with the correct data. If the control just took an ObservableCollection as it's items source, the ObservableCollection could just be refreshed. Using a ViewModel was the only code example I could find for the SemanticZoom control. Thanks in advance!
categories.xaml
<Page.DataContext>
<vm:CategoriesViewModel></vm:CategoriesViewModel>
</Page.DataContext>
<Page.Resources>
<CollectionViewSource x:Name="Collection" IsSourceGrouped="true" ItemsPath="Items" Source="{Binding CategoryGroups}" />
</Page.Resources>
<SemanticZoom Name="szCategories" ScrollViewer.ZoomMode="Enabled">
<SemanticZoom.ZoomedOutView>
<GridView ScrollViewer.IsHorizontalScrollChainingEnabled="False">
<GridView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Group.Name }" Foreground="Gray" Margin="5" FontSize="25" />
</DataTemplate>
</GridView.ItemTemplate>
</GridView>
</SemanticZoom.ZoomedOutView>
<SemanticZoom.ZoomedInView>
<ListView Name="lvCategories" ItemsSource="{Binding Source={StaticResource Collection}}" Tapped="lvCategories_Tapped">
<ListView.ItemTemplate>
<DataTemplate x:DataType="data:Category">
<StackPanel>
<TextBlock Text="{Binding Title}" Margin="5" />
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
<ListView.GroupStyle>
<GroupStyle>
<GroupStyle.HeaderTemplate>
<DataTemplate>
<StackPanel Orientation="Vertical">
<TextBlock Text='{Binding Name}' Foreground="Gray" FontSize="25" Margin="5,5,5,0" />
</StackPanel>
</DataTemplate>
</GroupStyle.HeaderTemplate>
</GroupStyle>
</ListView.GroupStyle>
</ListView>
</SemanticZoom.ZoomedInView>
</SemanticZoom>
categories.xaml.cs
public Categories()
{
this.InitializeComponent();
var collectionGroups = Collection.View.CollectionGroups;
((ListViewBase)this.szCategories.ZoomedOutView).ItemsSource = collectionGroups;
}
CategoriesViewModel.cs
internal class CategoriesViewModel : BindableBase
{
public CategoriesViewModel()
{
CategoryGroups = new ObservableCollection<CategoryDataGroup>(CategoryDataGenerator.GetGroupedData());
}
private ObservableCollection<CategoryDataGroup> _groups;
public ObservableCollection<CategoryDataGroup> CategoryGroups
{
get { return _groups; }
set { SetProperty(ref _groups, value); }
}
}
public abstract class BindableBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual bool SetProperty<T>(ref T storage, T value, [CallerMemberName] string propertyName = null)
{
if (object.Equals(storage, value)) return false;
storage = value;
this.OnPropertyChanged(propertyName);
return true;
}
protected void OnPropertyChanged(string propertyName)
{
var eventHandler = this.PropertyChanged;
if (eventHandler != null)
{
eventHandler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
SymanticZoom.cs
internal class CategoryDataGroup
{
public string Name { get; set; }
public List<CategoryData> Items { get; set; }
}
internal class CategoryData
{
public CategoryData(string grp, string title)
{
Grp = grp;
Title = title;
}
public string Grp { get; private set; }
public string Title { get; private set; }
}
internal class CategoryDataGenerator
{
private static List<CategoryData> _data;
public static List<CategoryDataGroup> GetGroupedData()
{
if (_data != null)
_data.Clear();
GenerateData();
return _data.GroupBy(d => d.Grp[0],
(key, items) => new CategoryDataGroup() { Name = key.ToString(), Items = items.ToList() }).ToList();
}
private static void GenerateData()
{
ObservableCollection<Category> ocCategories = new ObservableCollection<Category>();
SQLiteManager.Categories.Select(ocCategories);
_data = new List<CategoryData>();
foreach (var temp in ocCategories)
{
_data.Add(new CategoryData(temp.Name.Substring(0,1), temp.Name));
}
}
}
The zoomed-in view and zoomed-out view should be synchronized, so if a user selects a group in the zoomed-out view, the details of that same group are shown in the zoomed-in view. You can use a CollectionViewSource or add code to synchronize the views.
For more info, see Semantic zoom.
We can use CollectionViewSource control in our page, it provides a data source that adds grouping and current-item support to collection classes. Then we can bind the GridView.ItemSource and ListView.ItemSource to the CollectionViewSource. When we set new data to the CollectionViewSource, the GridView in SemanticZoom.ZoomedOutView and ListView in SemanticZoom.ZoomedInView will be updated.
xmlns:wuxdata="using:Windows.UI.Xaml.Data">
<Page.Resources>
<CollectionViewSource x:Name="ContactsCVS" IsSourceGrouped="True" />
<DataTemplate x:Key="ZoomedInTemplate" x:DataType="data:Contact">
<StackPanel Margin="20,0,0,0">
<TextBlock Text="{x:Bind Name}" />
<TextBlock Text="{x:Bind Position}" TextWrapping="Wrap" HorizontalAlignment="Left" Width="300" />
</StackPanel>
</DataTemplate>
<DataTemplate x:Key="ZoomedInGroupHeaderTemplate" x:DataType="data:GroupInfoList">
<TextBlock Text="{x:Bind Key}"/>
</DataTemplate>
<DataTemplate x:Key="ZoomedOutTemplate" x:DataType="wuxdata:ICollectionViewGroup">
<TextBlock Text="{x:Bind Group.(data:GroupInfoList.Key)}" TextWrapping="Wrap"/>
</DataTemplate>
</Page.Resources>
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<StackPanel>
<SemanticZoom x:Name="Control1" Height="500">
<SemanticZoom.ZoomedInView>
<GridView ItemsSource="{x:Bind ContactsCVS.View,Mode=OneWay}" ScrollViewer.IsHorizontalScrollChainingEnabled="False" SelectionMode="None"
ItemTemplate="{StaticResource ZoomedInTemplate}">
<GridView.GroupStyle>
<GroupStyle HeaderTemplate="{StaticResource ZoomedInGroupHeaderTemplate}" />
</GridView.GroupStyle>
</GridView>
</SemanticZoom.ZoomedInView>
<SemanticZoom.ZoomedOutView>
<ListView ItemsSource="{x:Bind ContactsCVS.View.CollectionGroups}" SelectionMode="None" ItemTemplate="{StaticResource ZoomedOutTemplate}" />
</SemanticZoom.ZoomedOutView>
</SemanticZoom>
</StackPanel>
</Grid>

How do I self-reference a DataTemplate in UWP XAML Resources?

I've seen something along these lines done with {DynamicResource xyz}on some other SO question, but it doesn't seem to work with UWP. Here's my XAML:
<DataTemplate x:Key="commentTemplate">
<StackPanel>
<Grid Margin="4" Background="#40606060" MinHeight="64">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="auto" />
</Grid.RowDefinitions>
<StackPanel>
<TextBlock Margin="4" FontSize="14" TextWrapping="WrapWholeWords" Text="{Binding Path=Message, Mode=OneWay}" />
<Image MaxHeight="96" Source="{Binding Path=Image}" HorizontalAlignment="Left" Margin="4" />
</StackPanel>
<StackPanel Background="#18808080" Orientation="Horizontal" Grid.Row="1">
<FontIcon Margin="2,0" Glyph="" />
<TextBlock Margin="2,0" Text="{Binding Path=Score, Mode=OneWay}" />
<Button Content="Reply" Margin="2,0" />
</StackPanel>
</Grid>
<ItemsControl ItemTemplate="{RelativeSource Mode=Self}" HorizontalAlignment="Stretch" HorizontalContentAlignment="Stretch" Margin="48" ItemsSource="{Binding Path=Comments}" />
</StackPanel>
</DataTemplate>
I'd like to self-reference the DataTemplate in the ItemsControl's ItemTemplate property. How would I go about replacing it?
Here is something that may work for you. I use this in our POS application - although this is my first attempt at nesting it. The xaml designer complains because of the selector StaticResource reference prior to its declaration, but it works at runtime.
Essentially, I use this to select a different data template based on the class that is bound to the item. In this case, there is only one type, so really we are just using the class type to decide what template to use.
MyModel:
public class MyModel
{
public int Id { get; set; }
public string Desc { get; set; }
public List<MyModel> Children { get; set; }
}
MyTemplateSelector:
public class MyTemplateSelector : DataTemplateSelector
{
public DataTemplate MyModelTemplate { get; set; }
protected override DataTemplate SelectTemplateCore(object item)
{
if (item is MyModel)
{
return MyModelTemplate;
}
else
{
return null;
}
}
protected override DataTemplate SelectTemplateCore(object item, DependencyObject container)
{
return SelectTemplateCore(item);
}
}
My MainPage:
<Page
x:Class="App7.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:App7"
xmlns:models="using:App7.Models"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Page.Resources>
<DataTemplate x:Key="myModelTemplate" x:DataType="models:MyModel">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition />
</Grid.RowDefinitions>
<TextBlock Text="{x:Bind Path=Desc, Mode=OneWay}" />
<ListView Grid.Row="1" ItemTemplateSelector="{StaticResource selector}" ItemsSource="{x:Bind Path=Children, Mode=OneWay}" />
</Grid>
</DataTemplate>
<local:MyTemplateSelector x:Key="selector" MyModelTemplate="{StaticResource myModelTemplate}" />
</Page.Resources>
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<ListView x:Name="lstView" ItemTemplateSelector="{StaticResource selector}" >
</ListView>
</Grid>
</Page>
My code behind:
public sealed partial class MainPage : Page
{
List<MyModel> lst { get; set; }
public MainPage()
{
this.InitializeComponent();
BuildList();
lstView.ItemsSource = lst;
}
private void BuildList()
{
lst = new List<MyModel>();
for (int i = 0; i < 10; i++)
{
MyModel mod = new MyModel();
mod.Id = i;
mod.Desc = "Desc" + i.ToString();
mod.Children = new List<MyModel>();
for (int j = 100; j < 102; j++)
{
MyModel mod2 = new MyModel();
mod2.Id = j;
mod2.Desc = "Desc" + j.ToString();
mod2.Children = new List<MyModel>();
for (int k = 1000; k < 1002; k++)
{
MyModel mod3 = new MyModel();
mod3.Id = k;
mod3.Desc = "Desc" + k.ToString();
mod3.Children = new List<MyModel>();
mod2.Children.Add(mod3);
}
mod.Children.Add(mod2);
}
lst.Add(mod);
}
}
}

How to associate view with viewmodel or multiple DataTemplates for ViewModel?

Given I have a GridView and I want to navigate to a different page by clicking each item.
How can navigate to a view associated to the viewmodel?
In WPF there is a way to set multiple Datatemplates for the viewmodel.
<TabControl Grid.Row="1" Margin="0" ItemsSource="{Binding Tabs}" SelectedIndex="0" SelectedItem="{Binding SelectedTab}">
<TabControl.Resources>
<DataTemplate DataType="{x:Type dashboard:DashboardViewModel}">
<dashboard:DashboardView/>
</DataTemplate>
<DataTemplate DataType="{x:Type controls:ExchangeViewModel}">
<controls:ExchangeView/>
</DataTemplate>
<DataTemplate DataType="{x:Type request:RequestViewModel}">
<request:RequestView/>
</DataTemplate>
<DataTemplate DataType="{x:Type addresses:AddressViewModel}">
<addresses:AddressView/>
</DataTemplate>
<DataTemplate DataType="{x:Type settings:ExchangeSettingsViewModel}">
<settings:ExchangeSettingsView/>
</DataTemplate>
</TabControl.Resources>
<TabControl.ItemTemplate>
<DataTemplate DataType="vm:ViewModelBase">
<TextBlock Text="{Binding Header}" FontSize="14"></TextBlock>
</DataTemplate>
</TabControl.ItemTemplate>
</TabControl>
This is what I tried in UWP in my particular case:
<Frame Grid.Row="1" DataContext="{x:Bind ViewModel.Value}">
<Frame.Resources>
<DataTemplate x:DataType="viewModels:ExampleViewModel1">
<views:ExampleView1></views:ExampleView1>
</DataTemplate>
<DataTemplate x:DataType="viewModels:ExampleViewModel2">
<views:ExampleView2></views:ExampleView2>
</DataTemplate>
</Frame.Resources>
</Frame>
The Frame is part of a page and I want to show the corresponding view based on the Value of the ViewModel.
Visual Studio tells me DataTemplate has to have a key attribute, but even then it doesn't work as it would in WPF, since it's not creating the view.
I know DataType was replaced with x:DataType and x:Type seems to be gone. Is there a way to achieve similar results?
In WPF, the DataType is a dependency property which can be retrieved in runtime.
In UWP, the x:DataType is compile-time property, you cannot get the value in runtime.
I created a simple demo about how to map the datatype and data template in UWP through DataTemplateSelector.
DataTemplateSelector:
namespace UWPApp
{
public class Template
{
public string DataType { get; set; }
public DataTemplate DataTemplate { get; set; }
}
public class TemplateCollection2 : System.Collections.ObjectModel.Collection<Template>
{
}
public class MyDataTemplateSelector : DataTemplateSelector
{
public TemplateCollection2 Templates { get; set; }
private IList<Template> _templateCache { get; set; }
public MyDataTemplateSelector()
{
}
private void InitTemplateCollection()
{
_templateCache = Templates.ToList();
}
protected override DataTemplate SelectTemplateCore(object item, DependencyObject container)
{
if (_templateCache == null)
{
InitTemplateCollection();
}
if(item != null)
{
var dataType = item.GetType().ToString();
var match = _templateCache.Where(m => m.DataType == dataType).FirstOrDefault();
if(match != null)
{
return match.DataTemplate;
}
}
return base.SelectTemplateCore(item, container);
}
}
}
ViewModel:
namespace UWPApp
{
public class ViewModel1
{
public string Text1 { get; set; }
}
public class ViewModel2
{
public string Text2 { get; set; }
}
}
XAML:
<Grid
x:Name="container"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid.Resources>
<local:TemplateCollection2 x:Key="templates">
<local:Template DataType="UWPApp.ViewModel1">
<local:Template.DataTemplate>
<DataTemplate x:DataType="local:ViewModel1">
<StackPanel>
<TextBlock Text="{Binding Text1}"></TextBlock>
<TextBlock Text="From template1"></TextBlock>
</StackPanel>
</DataTemplate>
</local:Template.DataTemplate>
</local:Template>
<local:Template DataType="UWPApp.ViewModel2">
<local:Template.DataTemplate>
<DataTemplate x:DataType="local:ViewModel2">
<StackPanel>
<TextBlock Text="{Binding Text2}"></TextBlock>
<TextBlock Text="From template2"></TextBlock>
</StackPanel>
</DataTemplate>
</local:Template.DataTemplate>
</local:Template>
</local:TemplateCollection2>
<local:MyDataTemplateSelector
x:Key="myDataTemplateSelector" Templates="{StaticResource templates}">
</local:MyDataTemplateSelector>
</Grid.Resources>
<StackPanel>
<Button x:Name="button" Click="button_Click">Click Me</Button>
<ContentControl x:Name="stage" ContentTemplateSelector="{StaticResource myDataTemplateSelector}">
</ContentControl>
</StackPanel>
</Grid>