Why LonglistSelector not display items though it display GroupHeaderItem? - xaml

My LonglistSelector only displays GroupHeaderTemplate Data (ImageSource,Title) but ItemTemplate DataTemplate (SubItemTitle, Location) not displayed. How can i solve it?
public class Data
{
public string Title { get; set; }
public string ImageSource { get; set; }
public List<SubItem> SubItems { get; set; }
public Data()
{
SubItems = new List<SubItem>();
}
}
public class SubItem
{
public string SubItemTitle { get; set; }
public string Location { get; set; }
}
<phone:LongListSelector ItemsSource="{Binding DataCollection}" Grid.Row="0" IsGroupingEnabled="True">
<phone:LongListSelector.GroupHeaderTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" Margin="10">
<Image Source="{Binding ImageSource}"/>
<TextBlock Text="{Binding Title}"/>
</StackPanel>
</DataTemplate>
</phone:LongListSelector.GroupHeaderTemplate>
<phone:LongListSelector.ItemTemplate>
<DataTemplate>
<StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding SubItemTitle}" Padding="5" FontSize="40"/>
<TextBlock Text="{Binding Location}" Padding="5" FontSize="40"/>
</StackPanel>
</StackPanel>
</DataTemplate>
</phone:LongListSelector.ItemTemplate>
</phone:LongListSelector>

You have to convert whatever the class you are using to group the items from inheriting. Try using the List than the IEnumerator.
This one discusses the same longlistselector issue.
Grouped LongListSelector: headers appear, items don't
Hope it helps!

This MSDN example helped me a lot when I had trouble understanding Grouping with LongListSelector
How to display data in a grouped list in LongListSelector for Windows Phone 8
It needs to be Grouped by a Key Value. Of all the of the examples I know of it's always something like this:
List<AlphaKeyGroup<your_data_type>> my_group_list; // or
ObservableCollection<AlphaKeyGroup<your_data_type>> my_group_list;
Not a List that has a property that is a SubList.
AlphaKeyGroup is just a List<T>/ObservableCollection<T> with an extra property for a Key
Think of it this way, in your code how does the LongListSelector know that your "Title" is the group key rather than the "ImageSource"?
If the code on the MSDN page is too complicated to understand, you can always take the easier route and use LINQ using the GroupBy.
Here a SO example: Group by in LINQ

Related

TreeViewItem template click/select/highlight issue

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}" />

UWP One to many binding

i'm building venue navigation app, so i have two views, one is a list of possible destinations and the second is an jpg image with markers on top, so problem is:
on my model:
public class NavigationItem
{
public string Name {get; set;}
public string Icon{get; set;}
public List<Vector2> Location {get; set;}
}
on my viewmodel:
{
public List<NavigationItem> NavigationItems {get; set;}
}
now the xaml part
<ScrollViewer>
<Grid>
<Image Source="map.jpg/>
<winui:ItemsRepeater ItemsSource="{Binding NavigationItems}">
<DataTemplate>
<!-- And that's where the question is....
I can't just do Image Source Binding, because location is List<Vector2>-->
</DataTemplate>
</winui:ItemsRepeater>
</ScrollViewer>
So, i thought to make another items repeater nested in first one, but i have no clue how to propagate Image Source to it like so:
<winui:ItemsRepeater ItemsSource="{Binding NavigationItems}">
<DataTemplate>
<winui:ItemsRepeater DataContext="{Binding}" ItemsSource="{Binding Location}" >
<DataTemplate x:DataType="models:NavigationItemModel">
<Image Source="{Binding Icon}"/>
</DataTemplate>
</winui:ItemsRepeater>
</DataTemplate>
</winui:ItemsRepeater>
All i get just bunch of binding errors, guess i'll just a user control and spawn all the crap in code behind...
If you want to bind the Image Source with the Icon property, you need to change the DataContext of Image to the DataContext of the second ItemsRepeater. So you can subscribe the ElementPrepared event from ItemsRepeater which means this event occurs each time an element is made ready for use. So when triggered this event, you can change the DataContext of Image in it.
.xaml:
<winui:ItemsRepeater ItemsSource="{Binding NavigationItems}">
<DataTemplate>
<winui:ItemsRepeater ItemsSource="{Binding Location}" ElementPrepared="ItemsRepeater_ElementPrepared">
<DataTemplate>
<Image Source="{Binding Icon}"/>
</DataTemplate>
</winui:ItemsRepeater>
</DataTemplate>
</winui:ItemsRepeater>
.cs:
private void ItemsRepeater_ElementPrepared(Microsoft.UI.Xaml.Controls.ItemsRepeater sender, Microsoft.UI.Xaml.Controls.ItemsRepeaterElementPreparedEventArgs args)
{
var repeaterDataContext = sender.DataContext;
Image MyImage = args.Element as Image;
MyImage.DataContext = repeaterDataContext;
}

UWP ListView DataTemplate Bind to Item instead of Property

How do you bind an item in a data template to the item itself, instead of a property of that item?
I have a user control that takes an item as a model. Given these models:
public class Car
{
public string Name { get; set; }
public Color color { get; set; }
public string Model { get; set; }
}
public MyUserControl : UserControl
{
public Car Model { get; set; }
}
public MyPage : Page
{
public ObservableCollection<Car> CareList { get; set; }
}
I want to do something like this in XAML:
<ListView ItemsSource="{x:Bind CarList}">
<ListView.ItemTemplate>
<DataTemplate x:DataType="models:Car">
<StackPanel>
<!-- Binding to properties of Car is simple... -->
<TextBlock Text="{x:Bind Name}">
<!-- But what if I want to bind to the car itself ??? -->
<userControls:MyUserControl Model="{x:Bind Car}">
</userControls:MyUserControl>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
The comment from #user2819245 is correct, if you do not specify the Path of the binding then the DataContext object will be bound directly instead.
In UWP, if your list source can change at runtime or is delay loaded, then you may also need to specify the Mode=OneWay, this is due to {x:Bind} defaulting to a OneTime binding mode.
This example includes how to set the Mode property in both use cases
<ListView ItemsSource="{x:Bind CarList, Mode=OneWay}">
<ListView.ItemTemplate>
<DataTemplate x:DataType="models:Car">
<StackPanel>
<!-- Binding to properties of Car is simple... -->
<TextBlock Text="{x:Bind Name, Mode=OneWay}">
<!-- Binding to the car itself (as the DataContext of this template) -->
<userControls:MyUserControl Model="{x:Bind Mode=OneWay}">
</userControls:MyUserControl>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>

ListPicker selected item not changing when selected in Full Mode

I have List of Categories that I want to display in a ListPicker. The Class is:
public class Category : INotifyPropertyChanged, INotifyPropertyChanging
{
private int _id;
public int Id
{
get { return _id; }
set
{
if (_id == value) return;
NotifyPropertyChanging();
_id = value;
NotifyPropertyChanged();
}
}
private string _description;
public string Description
{
get { return _description; }
set
{
if (_description == value) return;
NotifyPropertyChanging();
_description = value;
NotifyPropertyChanged();
}
}
#region INotifyPropertyChanging and INotifyPropertyChanged Members
public event PropertyChangingEventHandler PropertyChanging;
/// <summary>
/// Used to notify the data context that a property is about to change...
/// </summary>
private void NotifyPropertyChanging([CallerMemberName] string propertyName = null)
{
if (PropertyChanging != null) PropertyChanging(this, new PropertyChangingEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>
/// Used to notify the data context that a property has changed...
/// </summary>
private void NotifyPropertyChanged([CallerMemberName] string propertyName = null)
{
if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
I have a ListPicker Full Mode Item Template defined as a XAML Resource:
<phone:PhoneApplicationPage.Resources>
<DataTemplate x:Key="FullModeItemTemplate">
<TextBlock d:DataContext="{Binding}"
Text="{Binding Description}"
/>
</DataTemplate>
</phone:PhoneApplicationPage.Resources>
and a Listpicker for it defined in XAML on the Page:
<phone:PhoneApplicationPage
x:Class="QuestionPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:toolkit="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone.Controls.Toolkit"
FontFamily="{StaticResource PhoneFontFamilyNormal}"
FontSize="{StaticResource PhoneFontSizeMedium}"
Foreground="{StaticResource PhoneForegroundBrush}"
SupportedOrientations="Portrait" Orientation="Portrait"
mc:Ignorable="d"
shell:SystemTray.IsVisible="True">
<!--LayoutRoot is the root grid where all page content is placed.-->
<Grid x:Name="LayoutRoot" Background="Transparent">
<Border Style="{StaticResource ButtonBorderStyle}"
Background="Blue"
BorderBrush="White"
>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<!--TitlePanel contains the name of the app and page title.-->
<StackPanel x:Name="TitlePanel" Grid.Row="0" Margin="0,10,0,0">
<TextBlock x:Name="ApplicationTitle"
Text="pagetitle"
Style="{StaticResource PhoneTextLargeStyle}"/>
<TextBlock x:Name="PageTitle"
Text="subtitle"
Style="{StaticResource PhoneTextExtraLargeStyle}"/>
</StackPanel>
<StackPanel x:Name="ContentPanel" Grid.Row="1" Margin="0,0,0,15">
<TextBlock Text="question" Margin="20,5,0,0"/>
<TextBox x:Name="ItemDescription"/>
<TextBlock Text="category" Margin="20,0,0,-10"/>
<toolkit:ListPicker x:Name="CategoriesListPicker"
DataContext="{Binding}" ItemsSource="{Binding Categories}"
DisplayMemberPath="Description"
FullModeHeader="Categories:"
FullModeItemTemplate="{StaticResource FullModeItemTemplate}"
CacheMode="BitmapCache"
>
</toolkit:ListPicker>
</StackPanel>
</Grid>
</Border>
</Grid>
</phone:PhoneApplicationPage>
The ItemDescription and Category are to be saved to a Question Class Item in another list... but this is not relevant to the problem...
If the Category List has five or less items in it, the selection list is displayed in short mode and items from the list can be selected and this selection is reflected in the control field. This is fine.
If I increase the Categories List to over five items, the ListPicker switches to full mode, and although the display of these is fine, when an item from the list is selected, the item shown in the control field remains unchanged.
Is this a bug in the Full Mode selection list or am I missing something?
Any help you can give will be appreciated...
In my original question I stated:
"The ItemDescription and Category are to be saved to a Question Class Item in another list... but this is not relevant to the problem..."
Although this is correct, it made me realise I'd not taken into consideration how the data items for the page containing the ListPicker are populated before it is displayed. This data population also includes setting the SelectedItem parameter for the ListPicker to the Category for the Question.
I set this data population to happen during the OnNavigatedTo protected override for the page. I'd not realised that the OnNavigatedTo method also gets triggered on return from the Full Mode Listpicker which of course then reset the SelectedItem to the original value for the Question. To avoid this problem, I put in a check for null on the QuestionId so OnNavigatedTo only populates the data items on initial entry to the page... problem solved!

LongListMultiSelector: How to use it with MVVM

I am displaying a list of cities using LongListMultiSelector with Grouping. My ViewModel has DataList propery which is bind to LongListMultiSelector.
On a button click event, i want to remove an item from LongListMultiSelector and also want to update the UI at same time. I don't understand from where should i remove an item so that because of MVVM, UI gets updated automatically.
Below is my CS code.
public class City
{
public string Name { get; set; }
public string Country { get; set; }
public string Language { get; set; }
}
public class Group<T> : List<T>
{
public Group(string name, IEnumerable<T> items)
: base(items)
{
this.Title = name;
}
public string Title
{
get;
set;
}
}
public class myVM : INotifyPropertyChanged
{
static List<City> cityList;
public List<Group<City>> _datalist;
public List<Group<City>> DataList
{
get
{
_datalist = GetCityGroups();
return _datalist;
}
set
{
_datalist = value;
OnPropertyChanged("DataList");
}
}
private static IEnumerable<City> GetCityList()
{
cityList = new List<City>();
cityList.Add(new City() { Name = "Milan", Country = "IT", Language = "Italian" });
cityList.Add(new City() { Name = "Roma", Country = "IT", Language = "Italian" });
cityList.Add(new City() { Name = "Madrid", Country = "ES", Language = "Spanish" });
return cityList;
}
private List<Group<City>> GetCityGroups()
{
IEnumerable<City> cityList = GetCityList();
return GetItemGroups(cityList, c => c.Country);
}
private static List<Group<T>> GetItemGroups<T>(IEnumerable<T> itemList, Func<T, string> getKeyFunc)
{
IEnumerable<Group<T>> groupList = from item in itemList
group item by getKeyFunc(item) into g
orderby g.Key
select new Group<T>(g.Key, g);
return groupList.ToList();
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Below is my XAML code
<Button Content="bind" Width="150" Height="150" VerticalAlignment="Bottom" Click="Button_Click"></Button>
<toolkit:LongListMultiSelector x:Name="AddrBook"
ItemsSource="{Binding DataList}"
EnforceIsSelectionEnabled="True"
JumpListStyle="{StaticResource AddrBookJumpListStyle}"
IsSelectionEnabled="True"
Background="Transparent"
GroupHeaderTemplate="{StaticResource AddrBookGroupHeaderTemplate}"
ItemTemplate="{StaticResource AddrBookItemTemplate}"
LayoutMode="List"
IsGroupingEnabled="true"
HideEmptyGroups ="true"/>
In phone:PhoneApplicationPage.Resources i have below xaml
<phone:PhoneApplicationPage.Resources>
<DataTemplate x:Key="AddrBookItemTemplate">
<StackPanel VerticalAlignment="Top">
<TextBlock Text="{Binding Name, Mode=TwoWay}" />
<TextBlock Text="{Binding Language, Mode=TwoWay}" />
</StackPanel>
</DataTemplate>
<DataTemplate x:Key="AddrBookGroupHeaderTemplate">
<Border Background="Transparent" Margin="12,8,0,8">
<Border Background="{StaticResource PhoneAccentBrush}"
Padding="8,0,0,0" Width="62" Height="62"
HorizontalAlignment="Left">
<TextBlock Text="{Binding Title, Mode=TwoWay}" Foreground="{StaticResource PhoneForegroundBrush}" FontSize="48" Padding="6"
FontFamily="{StaticResource PhoneFontFamilySemiLight}" HorizontalAlignment="Left" VerticalAlignment="Center"/>
</Border>
</Border>
</DataTemplate>
<phone:JumpListItemBackgroundConverter x:Key="BackgroundConverter"/>
<phone:JumpListItemForegroundConverter x:Key="ForegroundConverter"/>
<Style x:Key="AddrBookJumpListStyle" TargetType="phone:LongListSelector">
<Setter Property="GridCellSize" Value="113,113"/>
<Setter Property="LayoutMode" Value="Grid" />
<Setter Property="ItemTemplate">
<Setter.Value>
<DataTemplate>
<Border Background="{Binding Converter={StaticResource BackgroundConverter}}" Width="Auto" Height="Auto" Margin="6" >
<TextBlock Text="{Binding Title, Mode=TwoWay}" FontFamily="{StaticResource PhoneFontFamilySemiBold}" FontSize="48" Padding="6"
Margin="8,0,0,0" Foreground="{Binding Converter={StaticResource ForegroundConverter}}" VerticalAlignment="Bottom"/>
</Border>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</phone:PhoneApplicationPage.Resources>
You simply remove the item from your DataList inside your VM.
The tricky part is to handle the SelectedItems of the MultiSelector since it isnt bindable.
The simplest solution for me was to hook up a command to the SelectionChanged event and pass the SelectedItems as a parameter with it (I used the Command class from the MvvmLight Toolkit for that). Inside the Command I check for any changes between the updated List and the old List in the VM.
Also you shouldn't use the Click Event on the button, in MVVM the Command Property is used along with the CommandParameter if needed.
For other Controls that dont have a build-in Command Property you can use something like the aforementioned class from the toolkit (or other MVVM frameworks).
Other things to notice:
You need to use something like an ObservableCollection instead of a List if you want the UI to automatically update after changes to the collection.
Also you cant actually remove anything from your DataList since your always re-reading your hardcoded items.