Im working with the ListView component and I would like to set categories which makes use of the Header within a ListView.
The content I have is a list of events that are happening today, tomorrow and the next day
How would I go about adding that to a list
My code so far is below
<ListView>
<ListView.ItemsSource>
<x:Array Type="{x:Type x:String}">
<x:String>Event 1 - Today</x:String>
<x:String>Event 2 - Today</x:String>
<x:String>Event 3 - Today</x:String>
<x:String>Event 4 - Today</x:String>
<x:String>Event 1 - Tomorrow</x:String>
<x:String>Event 2 - Tomorrow</x:String>
<x:String>Event 3 - Tomorrow</x:String>
</x:Array>
</ListView.ItemsSource>
<ListView.Header>
<Label Text="Today"/>
</ListView.Header>
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Label Text="{Binding}" Style="{StaticResource listViewRacecourse}" />
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
I just have no clue how to get two values within the array, and then split them into two groups using ListView headers.
This is just a POC so I don't have a DB with all the content, hence not using a API or connection to a DB.
About ListView Group, Jason have provided one article about detailed info. If you still have some problem, you can also take a look the following sample.
Firstly, create class to hold event info.
public class eventmodel
{
public string eventname { get; set; }
}
Then, a way to group the data, with a heading for each list.
public class eventlist:List<eventmodel>
{
public string heading { get; set; }
public List<eventmodel> events => this;
}
Finally, using Observablecollection to bind to listview. Represents a dynamic data collection that provides notifications when items get added, removed, or when the whole list is refreshed.
public class eventgroup
{
public ObservableCollection<eventlist> eventgroups { get; set; }
public eventgroup()
{
eventgroups = new ObservableCollection<eventlist>();
var list1 = new eventlist()
{
new eventmodel() { eventname= "Event 1 - Today" },
new eventmodel() { eventname= "Event 2 - Today" },
new eventmodel() { eventname= "Event 3 - Today" },
new eventmodel() { eventname= "Event 4 - Today" }
};
list1.heading = "Today";
var list2 = new eventlist()
{
new eventmodel() { eventname= "Event 1 - Tomorrow" },
new eventmodel() { eventname= "Event 2 - Tomorrow" },
new eventmodel() { eventname= "Event 3 - Tomorrow" },
new eventmodel() { eventname= "Event 4 - Tomorrow" }
};
list2.heading = "Tomorrow";
eventgroups.Add(list1);
eventgroups.Add(list2);
}
}
ListView displaying data.
<ListView IsGroupingEnabled="true" ItemsSource="{Binding eventgroups}">
<ListView.GroupHeaderTemplate>
<DataTemplate>
<ViewCell>
<Label BackgroundColor="Red" Text="{Binding heading}" />
</ViewCell>
</DataTemplate>
</ListView.GroupHeaderTemplate>
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Label Text="{Binding eventname}" />
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
public partial class Page6 : ContentPage
{
public Page6()
{
InitializeComponent();
this.BindingContext = new eventgroup();
}
}
The screenshot:
Related
I am new to WinUI 3 and I am currently building a TreeView (CommunityToolkit) where I can drag/drop TreeViewItems on top of each other. The TreeViewItem that I have consist of 3 parts, a group name, a display name and children items. The drag/drop part of the code works fine, however there is an issue whereby clicking on an item doesn’t always select/highlight it and I cannot seem to find the root issue as to why. See image below.
In the image above, the first item is "selected" as I would like it to be with the blue highlight to the left. But when I click on either of the other 2 items (Level 1 or Level 2), I have observed the following behaviours.
A click on "U" or "Level 1" does not select the item. There is some "pressed" style showing, but once the mouse button is released nothing happens. There is no highlight or selected style present
A click just above or below the red line selects the item as I expect it to.
See the XAML below
<Grid>
<Border BorderThickness="2" BorderBrush="DimGray">
<TreeView AllowDrop = "True"
CanDragItems="True"
CanReorderItems = "False"
ItemsSource="{x:Bind Items}"
SelectedItem="{x:Bind SelectedDemoItem, Mode=TwoWay}">
<TreeView.ItemTemplate>
<DataTemplate x:DataType="local:DemoItem">
<TreeViewItem AllowDrop="True"
CanDrag="True"
CollapsedGlyph=""
ExpandedGlyph=""
IsExpanded="True"
ItemsSource="{x:Bind Children}"
Padding="-10,0,0,0">
<TreeViewItem.Content>
<StackPanel AllowDrop="True"
BorderBrush="Red"
BorderThickness="1"
CanDrag="True"
Orientation="Horizontal">
<TextBlock FontSize="14"
FontWeight="ExtraBold"
IsColorFontEnabled="True"
Margin="0,0,10,0"
MinWidth="30"
TextAlignment="Center"
Text="{x:Bind Group}" />
<TextBlock Text="{x:Bind DisplayName}" Margin="0,0,5,0"/>
</StackPanel>
</TreeViewItem.Content>
</TreeViewItem>
</DataTemplate>
</TreeView.ItemTemplate>
</TreeView>
</Border>
</Grid>
And the code-behind
public sealed partial class TestUserControl : UserControl
{
public TestUserControl()
{
InitializeComponent();
FillData();
}
private void FillData()
{
var level0 = new DemoItem { DisplayName = "Level 0", Group = Groups.M };
var level1 = new DemoItem { DisplayName = "Level 1", Group = Groups.U };
var level2 = new DemoItem { DisplayName = "Level 2", Group = Groups.C };
level1.Children.Add(level2);
level0.Children.Add(level1);
Items.Add(level0);
Items.Add(level0);
}
public ObservableCollection<DemoItem> Items { get; } = new();
public DemoItem SelectedDemoItem { get; set; }
}
public enum Groups
{
S, M, U, C
}
public class DemoItem
{
public string DisplayName { get; set; }
public ObservableCollection<DemoItem> Children { get; } = new();
public Groups Group { get; set; }
}
For the purpose of this test, I have removed all drag/drop code as they have no affect on the problem above. However, it may help to mention that I have seen this problem occur only when CanDrag is set to True within my item template.
Any help to fix this will be greatly appreciated.
Click events won't reach the TreeViewItem because of the TextBlocks. The easiest way is to disable IsHistTestVisible on both TextBlocks.
<TextBlock
MinWidth="30"
Margin="0,0,10,0"
FontSize="14"
FontWeight="ExtraBold"
IsColorFontEnabled="True"
IsHitTestVisible="False"
Text="{x:Bind Group}"
TextAlignment="Center" />
<TextBlock
Margin="0,0,5,0"
IsHitTestVisible="False"
Text="{x:Bind DisplayName}" />
Nested list view is not working, I have a list which contains another list in it. To show it in View I am using Nested listview; But the code is not working,and i am not able to identify on where it went wrong... Below is my code
Main Page
<ContentPage.Content>
<StackLayout>
<ListView x:Name="outerListview" ItemsSource="{Binding lst}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell x:Name="outerListviewCell">
<ViewCell.View>
<ContentView>
<Label Text="{Binding ItemName}"/>
<StackLayout>
<ListView ItemsSource="{Binding ItemList}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell x:Name="InnerListviewCell">
<Grid>
<Label Text="{Binding stockQty}"/>
</Grid>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
</ContentView>
</ViewCell.View>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
</ContentPage.Content>
ViewModel
public MainPageViewModel()
{
lst = new ObservableCollection<A>()
{
new A()
{
ItemName="Item1", ItemList=new ObservableCollection<ItemDetails>()
{
new ItemDetails() { stockQty="2"},
new ItemDetails(){ stockQty="3"}
}
},
new A()
{
ItemName="Item2", ItemList=new ObservableCollection<ItemDetails>()
{
new ItemDetails() { stockQty="3"},
new ItemDetails(){ stockQty="4"}
}
}
};
}
Model ( Class A and Class Itemdetails)
class A:INotifyPropertyChanged
{
public A()
{
ItemName = string.Empty;
ItemList = new ObservableCollection<ItemDetails>();
}
private string _ItemName;
public string ItemName
{
get { return _ItemName; }
set { _ItemName = value; OnPropertyChanged(); }
}
private ObservableCollection<ItemDetails> _itemlist;
public ObservableCollection<ItemDetails> ItemList
{
get { return _itemlist; }
set { _itemlist = value; OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
if (PropertyChanged != null)
PropertyChanged(this,
new PropertyChangedEventArgs(propertyName));
}
}
class ItemDetails:INotifyPropertyChanged
{
private string _stockQty;
public string stockQty
{
get { return _stockQty; }
set { _stockQty = value; OnPropertyChanged(); }
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
if (PropertyChanged != null)
PropertyChanged(this,
new PropertyChangedEventArgs(propertyName));
}
}
When I Run the above code I am getting below output in screen
2
3
What is expected actually is
Item1
2
3
Item2
3
4
What is wrong in above code? could anyone can help me?
Nesting Listview inside another Listview is not a good idea and it is not a supported on Xamarin.Forms.
ListView is very "sensitive" and it can easialy cause problems with scrolling and of course there are problems with poor performance of your app.
So I strongly recommend you to rethink about your layout and take a look at Grouping with ListView more about it here, maybe you can achieve what you want with Grouping.
After checking your code , I found something need to modify in your code.
In order to show the ItemName , you should wrap Label inside StackLayout .
In order to get Uneven Row, you should set listview.HasUnevenRows = true.
Modify your code as below:
<ContentPage.Content>
<ListView x:Name="outerListview" HasUnevenRows="True" ItemsSource="{Binding lst}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell x:Name="outerListviewCell">
<ViewCell.View>
<ContentView>
<StackLayout>
<Label Text="{Binding ItemName}"/>
<ListView ItemsSource="{Binding ItemList}" RowHeight="20">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell x:Name="InnerListviewCell">
<Grid>
<Label Text="{Binding stockQty}"/>
</Grid>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
</ContentView>
</ViewCell.View>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</ContentPage.Content>
That is absolutely wrong. You must use one ListView with IsGroupingEnabled set to True.
Follow instuctions here to make it work correct: https://xamarinhelp.com/xamarin-forms-listview-grouping/
I would remove an item with button inside listview item and change color of ellipse with another button in listview item.
The class product code:
class Product
{
public string Name { get; set; }
public double Price { get; set; }
}
The xaml mainpage code:
<Page
x:Class="ListViewTest.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:ListViewTest"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" Loaded="Page_Loaded">
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<ListView x:Name="ListViewProducts"
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
ScrollViewer.VerticalScrollBarVisibility="Auto"
FontSize="18"
BorderThickness="0"
Width="600"
Height="800"
HorizontalAlignment="Center"
VerticalAlignment="Center"
ItemsSource="{Binding LineItems}">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Margin="10">
<Grid HorizontalAlignment="Left" VerticalAlignment="Center" Margin="5,0,0,0">
<Ellipse x:Name="EllipseColor" HorizontalAlignment="Left" Height="20" Stroke="Black" VerticalAlignment="Top" Width="20" StrokeThickness="1"/>
</Grid>
<TextBlock Text="{Binding Name}" Margin="5,0,0,0"/>
<TextBlock Text="{Binding Price}" Margin="5,0,0,0"/>
<Button x:Name="btnRemove" Click="btnRemove_Click" Height="20" Width="60" Margin="5"/>
<Button x:Name="btnChangeColor" Click="btnChangeColor_Click" Height="20" Width="60" Margin="5"/>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
The code behind of mainpage:
public sealed partial class MainPage : Page
{
public MainPage()
{
this.InitializeComponent();
}
private void Page_Loaded(object sender, RoutedEventArgs e)
{
ObservableCollection<Product> _listProduct = new ObservableCollection<Product>();
_listProduct = new ObservableCollection<Product>
{
new Product
{
Name = "Phone",
Price = 100
},
new Product
{
Name = "TV",
Price = 120
},
new Product
{
Name = "Computer",
Price = 80
},
new Product
{
Name = "Laptop",
Price = 250
},
new Product
{
Name = "Tablet",
Price = 150
},
new Product
{
Name = "Monitor",
Price = 200
},
};
ListViewProducts.ItemsSource = _listProduct;
}
private void btnRemove_Click(object sender, RoutedEventArgs e)
{
// Code to remove item
}
private void btnChangeColor_Click(object sender, RoutedEventArgs e)
{
// Code to color EllipseColor
}
}
With btnRemove i would delete listview item and with btnChangeColor i would color red the fill of EllipseColor, in btnChangeColor_Click i would the index of item.
Thanks in advance.
It looks to me like you've got several issues. First off is that you're setting your ListView source via binding to an apparently non-existent collection, as well as setting it in C#. You should move it to using a proper binding. For example, in MainPage.xaml.cs:
private ObservableCollection<Product> _products = new ObservableCollection<Product>();
public ObservableCollection<Product> Products { get => _products; set => _products = value; }
And then bind to it:
<ListView ItemsSource={x:Bind Products, Mode=OneWay} />
Then, in btnRemove_Click, you can just remove the item from the collection:
var product = (sender as Button).DataContext as Product;
Products.Remove(product);
As for coloring the Ellipse, you shouldn't really do that in C#. Instead, you should have a Status property on your Product class, and then change that property.
First off, you'll need to make sure your property changes fire notifications.
public class Product : INotifyPropertyChanged
{
private string _status;
public string Status
{
get => _status;
set
{
_status = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Status)));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
Then change the property.
var product = (sender as Button).DataContext as Product;
product.Status = "invalid";
Then in your XAML, use a binding converter to change the Ellipse's Fill property based on the status. E.g.
using System;
using Windows.UI;
using Windows.UI.Xaml.Data;
using Windows.UI.Xaml.Media;
public class StatusConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language) =>
new SolidColorBrush(value.ToString() == "invalid" ? Colors.Red : Colors.Gray);
public object ConvertBack(object value, Type targetType, object parameter, string language) =>
throw new NotImplementedException();
}
You'll then need to add the converter to your resources.
<Page...>
<Page.Resources>
<locationofyourconverter:StatusConverter x:Key="StatusConverter" />
</Page.Resources>
...
<Ellipse Fill={Binding Status, Mode=OneWay, Converter={StaticResource StatusConverter}} />
I have a condition where I want to add multiple PivotItems(which is dynamic).
ie; I have a List<CustomModel> whose size is dynamic, And for every item in the List I want to create a PivotItem with header as CustomModel.Title.
Is it possible to achieve this with xaml alone by creating a DataTemplate and binding it to a Pivot?
It is surely possible. See below sample solution
<Pivot x:Name="TestPivot">
<Pivot.HeaderTemplate>
<DataTemplate x:DataType="local:TestClass">
<TextBlock Text="{Binding HeaderTitle, Mode=OneWay}"/>
</DataTemplate>
</Pivot.HeaderTemplate>
<Pivot.ItemTemplate>
<DataTemplate x:DataType="local:TestClass">
<Grid>
<TextBlock Text="{Binding Content, Mode=OneWay}"/>
</Grid>
</DataTemplate>
</Pivot.ItemTemplate>
</Pivot>
Code behind for simulating the binding
public sealed partial class BlankPage6 : Page
{
ObservableCollection<TestClass> SampleSource = new ObservableCollection<TestClass>();
public BlankPage6()
{
this.InitializeComponent();
SampleSource.Add(new TestClass { HeaderTitle = "Test Header1", Content = "Test content 1" });
SampleSource.Add(new TestClass { HeaderTitle = "Test Header2", Content = "Test content 2" });
SampleSource.Add(new TestClass { HeaderTitle = "Test Header3", Content = "Test content 3" });
TestPivot.ItemsSource = SampleSource;
}
}
public class TestClass
{
public string HeaderTitle { get; set; }
public string Content { get; set; }
}
Output:
This is the xaml code what i am using
<GridView
Grid.Row="0"
x:Name="RootGrid"
SelectionMode="None"
IsItemClickEnabled="True"
ItemsSource="{Binding RootListSource}">
<GridView.ItemTemplate>
<DataTemplate>
<UserControl:TreeInfoControl/>
</DataTemplate>
</GridView.ItemTemplate>
</GridView>
In this my user control, it contain another GridView that holds a different IEnumerable collection. What i am trying to achieve is i need to pass this collection through code. I tried this by adding a dependency property to the treecontrol but it is not working. So i am looking for a solution that enable passing the collection through xaml (somehow through the user control). I know it is possible to add that collection to my existing collection and bind that one. But for now i can't use that method.
Here's how you do it.
Start with your App.xaml so we can reuse the demo template
<Application.Resources>
<DataTemplate x:Key="MyContentControl">
<Grid Height="100" Width="100" Background="Maroon">
<TextBlock Text="{Binding FallbackValue=0}" Foreground="White" FontSize="40" VerticalAlignment="Center" HorizontalAlignment="Center" />
</Grid>
</DataTemplate>
</Application.Resources>
Then we can define your user control
<d:UserControl.DataContext>
<local:MyControlViewModel Number="-1" Letter="~K" />
</d:UserControl.DataContext>
<StackPanel Orientation="Horizontal" VerticalAlignment="Top" HorizontalAlignment="Left">
<ContentControl Content="{Binding Number}"
ContentTemplate="{StaticResource MyContentControl}" />
<ListView ItemsSource="{Binding Letters}" IsHitTestVisible="False"
ItemTemplate="{StaticResource MyContentControl}"
SelectedItem="{Binding Letter, Mode=TwoWay}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<ItemsStackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ListView>
</StackPanel>
And then we can define your MainPage.xaml
<Page.DataContext>
<local:MainPageViewModel Letter="C" />
</Page.DataContext>
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="140" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<ListView x:Name="MyList" ItemsSource="{Binding Letters}"
ItemTemplate="{StaticResource MyContentControl}"
SelectedItem="{Binding Letter, Mode=TwoWay}" />
<ListView Grid.Column="1" ItemsSource="{Binding Numbers}">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<StackPanel.Resources>
<local:MyControlViewModel
x:Key="MyDataContext" Number="{Binding}"
Letters="{Binding ItemsSource, ElementName=MyList}"
Letter="{Binding SelectedItem, ElementName=MyList}" />
</StackPanel.Resources>
<local:MyControl DataContext="{StaticResource MyDataContext}" />
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
Nothing special yet, right? Well, not so fast. We're creating the viewmodel for the user control , setting the properties of the view model from the surrounding scope, then passing it in to the DataContext of the user control explicitly. Cool, huh? Simple enough, if you think about it. Want to set those properties inside the tag? Sure you do. But you can't. The order of operation would be all wrong. You'll just have to trust me.
Now, there's ZERO code behind for your user control. But the view model looks like this:
public class MyControlViewModel : BindableBase
{
public int Number
{
get { return (int)GetValue(NumberProperty); }
set
{
SetValue(NumberProperty, value);
base.RaisePropertyChanged();
}
}
public static readonly DependencyProperty NumberProperty =
DependencyProperty.Register("Number", typeof(int), typeof(MyControlViewModel),
new PropertyMetadata(0, (s, e) => { }));
public string Letter
{
get { return (string)GetValue(LetterProperty); }
set
{
SetValue(LetterProperty, value);
base.RaisePropertyChanged();
}
}
public static readonly DependencyProperty LetterProperty =
DependencyProperty.Register("Letter", typeof(string), typeof(MyControlViewModel),
new PropertyMetadata("Z", (s, e) => { }));
public ObservableCollection<string> Letters
{
get { return (ObservableCollection<string>)GetValue(LettersProperty); }
set
{
SetValue(LettersProperty, value);
base.RaisePropertyChanged();
}
}
public static readonly DependencyProperty LettersProperty =
DependencyProperty.Register("Letters", typeof(ObservableCollection<string>),
typeof(MyControlViewModel),
new PropertyMetadata(new ObservableCollection<string>(new[] { "~W", "~X", "~Y", "~Z" }), (s, e) => { }));
}
All the properties are dependency properties. I hope you noticed. I didn't just do that because I like to type. Though I do like to type. Fact is, I did that because in order to have internal binding you must use a dependency property - and a dependency property that raises property changed! That last part isn't trivial. But does it have to be in a view model? No. But I like it that way.
You might reference this: http://blog.jerrynixon.com/2013/07/solved-two-way-binding-inside-user.html
There's also no code behind for your MainPage. But the view model looks like this:
public class MainPageViewModel : BindableBase
{
public MainPageViewModel()
{
this._Letters = new ObservableCollection<string>(new[] { "A", "B", "C", "D" });
this._Numbers = new ObservableCollection<int>(new[] { 1, 2, 3, 4 });
}
public string Letter
{
get { return (string)GetValue(LetterProperty); }
set
{
SetValue(LetterProperty, value);
base.RaisePropertyChanged();
}
}
public static readonly DependencyProperty LetterProperty =
DependencyProperty.Register("Letter", typeof(string), typeof(MyControlViewModel),
new PropertyMetadata("Z", (s, e) => { }));
ObservableCollection<string> _Letters = new ObservableCollection<string>();
public ObservableCollection<string> Letters { get { return _Letters; } }
ObservableCollection<int> _Numbers = new ObservableCollection<int>();
public ObservableCollection<int> Numbers { get { return _Numbers; } }
}
The bindable base is standard, here's the code for it:
public abstract class BindableBase : DependencyObject, System.ComponentModel.INotifyPropertyChanged
{
public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged;
protected void SetProperty<T>(ref T storage, T value, [System.Runtime.CompilerServices.CallerMemberName] String propertyName = null)
{
if (!object.Equals(storage, value))
{
storage = value;
if (PropertyChanged != null)
PropertyChanged(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName));
}
}
protected void RaisePropertyChanged([System.Runtime.CompilerServices.CallerMemberName] String propertyName = null)
{
if (PropertyChanged != null)
PropertyChanged(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName));
}
}
When it's all done, you should get exactly what you want. Something like this:
Not to over-simplify things. But, it's that easy.
Look, getting your head wrapped around XAML is not always easy when you start to nest contexts. I don't blame you for not getting it on first run. But I hope this helps you get started. Keep pushing
Best of luck!