I have a problem displaying a large dataset (around 27k elements). The problem is that after a certain point items would not display anymore but would still be be clickable. In fact, the items would display when scrolling but would disappear as soon as the scrolling stops. After some testing I found out it was stopping rendering after the 2^21 pixel. Complexity of the objects being rendered do not seem to affect this limit.
I have made a sample project to illustrate this:
In a blank windows store app, replace MainPage.xaml default grid with this one
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition></ColumnDefinition>
<ColumnDefinition></ColumnDefinition>
<ColumnDefinition></ColumnDefinition>
<ColumnDefinition></ColumnDefinition>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="1*"></RowDefinition>
<RowDefinition Height="24"></RowDefinition>
<RowDefinition Height="24"></RowDefinition>
</Grid.RowDefinitions>
<TextBlock x:Name="textblock" Grid.Row="2"></TextBlock>
<Button Grid.Column="0" Grid.Row="1" Tapped="Button_Tapped"></Button>
<Button Grid.Column="1" Grid.Row="1" Tapped="Button_Tapped_1"></Button>
<Button Grid.Column="2" Grid.Row="1" Tapped="Button_Tapped_2"></Button>
<Button Grid.Column="3" Grid.Row="1" Tapped="Button_Tapped_3"></Button>
<ListView Grid.Column="0" Background="Red" x:Name="simpleList1" SelectionChanged="list_SelectionChanged">
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="HorizontalContentAlignment" Value="Left" />
<Setter Property="VerticalContentAlignment" Value="Center" />
<Setter Property="Margin" Value="0,0,0,-8" />
</Style>
</ListView.ItemContainerStyle>
<ListView.ItemTemplate>
<DataTemplate>
<Grid Height="100">
<TextBlock Text="{Binding Index}"></TextBlock>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<ListView Grid.Column="1" Background="DarkGoldenrod" x:Name="simpleList2" SelectionChanged="list_SelectionChanged">
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="HorizontalContentAlignment" Value="Left" />
<Setter Property="VerticalContentAlignment" Value="Center" />
<Setter Property="Margin" Value="0,0,0,-8" />
</Style>
</ListView.ItemContainerStyle>
<ListView.ItemTemplate>
<DataTemplate>
<Grid Height="250">
<TextBlock Text="{Binding Index}"></TextBlock>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<ListView Grid.Column="2" Background="Blue" x:Name="complexList1" SelectionChanged="list_SelectionChanged">
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="HorizontalContentAlignment" Value="Left" />
<Setter Property="VerticalContentAlignment" Value="Center" />
<Setter Property="Margin" Value="0,0,0,-8" />
</Style>
</ListView.ItemContainerStyle>
<ListView.ItemTemplate>
<DataTemplate>
<Grid Height="333">
<Grid.RowDefinitions>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Text="{Binding SimpleItem.Index}"></TextBlock>
<TextBlock Grid.Row="1" Text="{Binding ShortMsg}"></TextBlock>
<TextBlock Grid.Row="2" Text="{Binding RandomNumber}"></TextBlock>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<ListView Grid.Column="3" Background="Violet" x:Name="complexList2" SelectionChanged="list_SelectionChanged">
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="HorizontalContentAlignment" Value="Left" />
<Setter Property="VerticalContentAlignment" Value="Center" />
<Setter Property="Margin" Value="0,0,0,-8" />
</Style>
</ListView.ItemContainerStyle>
<ListView.ItemTemplate>
<DataTemplate>
<Grid Height="500">
<Grid.RowDefinitions>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Text="{Binding SimpleItem.Index}"></TextBlock>
<TextBlock Grid.Row="1" Text="{Binding ShortMsg}"></TextBlock>
<TextBlock Grid.Row="2" Text="{Binding RandomNumber}"></TextBlock>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
And in the MainPage.xaml.cs
/// <summary>
/// An empty page that can be used on its own or navigated to within a Frame.
/// </summary>
public sealed partial class MainPage : Page
{
public class SimpleItem
{
public int Index { get; set; }
public SimpleItem(int index)
{
Index = index;
}
public override string ToString()
{
return "I am a simple item with index " + Index;
}
}
public class ComplexItem
{
static Random rand = new Random();
public SimpleItem SimpleItem { get; set; }
public string ShortMsg { get; set; }
public double RandomNumber { get; set; }
public ComplexItem(int index)
{
SimpleItem = new SimpleItem(index);
ShortMsg = "My simple item has index " + SimpleItem.Index;
RandomNumber = rand.NextDouble() * (double)SimpleItem.Index;
}
public override string ToString()
{
return "I am a complex item with index " + SimpleItem.Index;
}
}
public ObservableCollection<SimpleItem> listSI = new ObservableCollection<SimpleItem>();
public ObservableCollection<ComplexItem> listCI = new ObservableCollection<ComplexItem>();
public MainPage()
{
this.InitializeComponent();
this.NavigationCacheMode = NavigationCacheMode.Required;
InitializeLists();
simpleList1.ItemsSource = listSI;
simpleList2.ItemsSource = listSI;
complexList1.ItemsSource = listCI;
complexList2.ItemsSource = listCI;
}
private void InitializeLists()
{
for (int i = 0; i < 2000000; ++i)
{
listSI.Add(new SimpleItem(i));
listCI.Add(new ComplexItem(i));
}
}
private void Button_Tapped(object sender, TappedRoutedEventArgs e)
{
int target = (int)Math.Pow(2, 21);
int heigthPerItem = 100;
SimpleItem item = listSI[target / heigthPerItem];
simpleList1.ScrollIntoView(item);
}
private void Button_Tapped_1(object sender, TappedRoutedEventArgs e)
{
int target = (int)Math.Pow(2, 21);
int heigthPerItem = 250;
SimpleItem item = listSI[target / heigthPerItem];
simpleList2.ScrollIntoView(item);
}
private void Button_Tapped_2(object sender, TappedRoutedEventArgs e)
{
int target = (int)Math.Pow(2, 21);
int heigthPerItem = 333;
ComplexItem item = listCI[target / heigthPerItem];
complexList1.ScrollIntoView(item);
}
private void Button_Tapped_3(object sender, TappedRoutedEventArgs e)
{
int target = (int)Math.Pow(2, 21);
int heigthPerItem = 500;
ComplexItem item = listCI[target / heigthPerItem];
complexList2.ScrollIntoView(item);
}
private void list_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (e.AddedItems.Count > 0)
{
string text = e.AddedItems[0].ToString();
textblock.Text = text;
}
}
}
This should be the result (with some info how to use it) : ListViewBugInterface
This person had the same problem as me and says it fixed it by using a VirtualizingStackPanel, but this shouldn't be needed since windows 8.1.
I doubt it is a UI virtualization problem since the ListViews scroll smoothly, the items start appearing when scrolling and can be tapped/clicked.
Anyone had this behavior? Am I missing the obvious? (e.g: to not have a dataset of 27k items) Anyone knows a solution to make the items render after the 2^21 pixel?
TLDR:
When displaying pixels after 2^21 pixels of combined ListViewItem they stop being rendered but can still be clicked. How do I fix this?
Related
I've got a ListBox in WPF where I'm adding items through an OpenFileDialog. Here the Code where I add the Items to my ListBox. (tbxFiles = my listbox name)
if (openFileDialog.ShowDialog() == true)
{
foreach (string filename in openFileDialog.FileNames)
{
path.Add(filename);
for (int i = 0; i < path.Count; i++)
{
tbxFiles.Items.Add(path[i]);
}
}
var _items = this.tbxFiles.Items.Cast<string>().Distinct().ToArray();
this.tbxFiles.Items.Clear();
foreach (var item in _items)
{
this.tbxFiles.Items.Add(item);
}
}
When I look at the breakpoint I can see that the content of what I added to my ListBox is correct. The problem starts showing as I added this DataTemplate to my ListBox:
<ListBox Grid.Column="1" BorderBrush="Black" Margin="15,20,31,15" MinHeight="25" Name="tbxFiles" VerticalAlignment="Stretch" ItemsSource="{Binding Items}" SelectionMode="Multiple">
<ListBox.Resources>
<Style TargetType="ListBoxItem">
<Setter Property="OverridesDefaultStyle" Value="true" />
<Setter Property="SnapsToDevicePixels" Value="true" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBoxItem">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="150"/>
<ColumnDefinition Width="50"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock x:Name="ListText" Grid.Column="0"/>
<RadioButton Grid.Column="1" Content="TF" />
<RadioButton Grid.Column="2" Content="AF" />
<ComboBox Grid.Column="3" Text="Periode" />
<Button Grid.Column="4" Click="RemoveMark_Click" Content="Delete" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ListBox.Resources>
</ListBox>
The moment I added this DataTemplate it stopped showing me the text, even when I remove the Items from the DataTemplate it isn't showing anything. Maybe I should start showing the text in the textbox I created, but I cannot adress it's name because it is in a DataTemplate. Have any of you an idea of how I can show the name I want to have in the ListBox?
Use binding for your ListBox,
change XAML source to:
<ListBox Grid.Column="1" BorderBrush="Black" Margin="15,20,31,15" MinHeight="25" Name="tbxFiles"
VerticalAlignment="Stretch"
ItemsSource="{Binding Items}"
SelectionMode="Multiple">
<ListBox.Resources>
<Style TargetType="ListBoxItem">
<Setter Property="OverridesDefaultStyle" Value="true" />
<Setter Property="SnapsToDevicePixels" Value="true" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBoxItem">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="150"/>
<ColumnDefinition Width="50"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock x:Name="ListText" Grid.Column="0" Text="{Binding FileName}"/>
<RadioButton Grid.Column="1" Content="TF" />
<RadioButton Grid.Column="2" Content="AF" />
<ComboBox Grid.Column="3" Text="Periode" />
<Button Grid.Column="4" Click="RemoveMark_Click" Content="Delete" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ListBox.Resources>
</ListBox>
and add two View model classes:
public class ViewModel
{
public List<FileItem> Items { get; set; }
}
public class FileItem
{
public string FileName { get; set; }
}
and you may bind loading file names to your ListBox like this:
var vm = new ViewModel();
vm.Items = new List<FileItem>();
if (openFileDialog.ShowDialog() == true)
{
foreach (string filename in openFileDialog.FileNames)
{
vm.Items.Add(new FileItem { FileName = filename });
}
tbxFiles.DataContext = vm;
}
In a proper MVVM approach you would however have a view model like this:
public class ViewModel
{
public ObservableCollection<FileItem> Items { get; }
= new ObservableCollection<FileItem>();
}
with a readonly ObservableCollection property, which is once assigned to the DataContext of the Window, e.g. like
private readonly ViewModel viewModel = new ViewModel();
public MainWindow()
{
InitializeComponent();
DataContext = viewModel;
}
and used like this:
if (openFileDialog.ShowDialog() == true)
{
viewModel.Items.Clear();
foreach (string filename in openFileDialog.FileNames)
{
viewModel.Items.Add(new FileItem { FileName = filename });
}
}
I created the Login Entities (Login.cs) and also I created Logins property of type DbSet of Login to save logins with (SubBVDbContext.cs) as public DbSet<Login> Logins { get; set;}, then I seed entries into the Login table in the (Configuration.cs) in Migration Folder. After that, I created the (LoginPageViewModel.cs) which includes LoginWrapper that wraps up all entities, as well as the (NavigationView.xaml) that allows navigation from model to another. I have already created (MainViewModel.cs), and the MainWindow.xaml that contain the views of the models and the navigations between them.
What I want is to display the LoginPage first then show the other views.
Login.cs
public class Login
{
public int Id { get; set; }
[Required]
[StringLength(50)]
[EmailAddress]
public string Email { get; set; }
[Required]
public string Password { get; set; }
}
LoginDetailView.xaml(UserControl)
<StackPanel Orientation="Horizontal">
<TextBox Text="{Binding Login.Email, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
<PasswordBox PasswordChar="{Binding Login.Password, UpdateSourceTrigger=PropertyChanged}" />
</StackPanel>
<Button Width="100" ToolTip="Login to Subcontractors" HorizontalAlignment="Center" Content="login"/>
NavigationViewModel.xaml (UserControl)
<UserControl.Resources>
<Style x:Key="NaviItemContainerStyle" TargetType="ContentPresenter">
<Setter Property="HorizontalAlignment" Value="Left"/>
<Setter Property="Margin" Value="2"/>
</Style>
<DataTemplate x:Key="NaviItemTemplate">
<Button Content="{Binding DisplayMember}"
Command="{Binding OpenDetailViewCommand}">
<Button.Template>
<ControlTemplate TargetType="Button">
<Grid x:Name="grid">
<ContentPresenter HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Cursor" Value="Hand"/>
<Setter Property="FontWeight" Value="Bold"/>
</Trigger>
<Trigger Property="IsPressed" Value="True">
<Setter Property="Foreground" Value="DarkRed"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Button.Template>
</Button>
</DataTemplate>
</UserControl.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="300"/>
<RowDefinition/>
</Grid.RowDefinitions>
<GroupBox Header="Subcontarctors">
<ScrollViewer VerticalScrollBarVisibility="Auto"
HorizontalScrollBarVisibility="Auto">
<ItemsControl ItemsSource="{Binding Subs}"
ItemContainerStyle="{StaticResource NaviItemContainerStyle}"
ItemTemplate="{StaticResource NaviItemTemplate}"/>
</ScrollViewer>
</GroupBox>
<GroupBox Header="POs" Grid.Row="1" >
<ScrollViewer VerticalScrollBarVisibility="Auto"
HorizontalScrollBarVisibility="Auto" >
<ItemsControl ItemsSource="{Binding SubPOs}"
ItemContainerStyle="{StaticResource NaviItemContainerStyle}"
ItemTemplate="{StaticResource NaviItemTemplate}"/>
</ScrollViewer>
</GroupBox>
</Grid>
LoginPageViewModel.cs
public class LoginPageViewModel:ViewModelBase
{
private LoginWrapper _login;
private IMessageDialogService _messageDialogService;
public LoginPageViewModel(IMessageDialogService messageDialogService)
{
_messageDialogService = messageDialogService;
}
public LoginWrapper Login
{
get { return _login; }
private set
{
_login = value;
OnPropertyChanged();
}
}
}
LoginWrapper.cs
public class LoginWrapper : ModelWrapper<Login>
{
public LoginWrapper(Login model) : base(model)
{
}
public int Id { get { return Model.Id; } }
public string Email
{
get { return GetValue<string>(); }
set { SetValue(value); }
}
public string Password
{
get { return GetValue<string>(); }
set { SetValue(value); }
}
}
App.xaml.cs
private void Application_Startup(object sender, StartupEventArgs e)
{
var bootstrapper = new Bootstrapper();
var container = bootstrapper.Bootstrap();
var mainWindow = container.Resolve<MainWindow>();
mainWindow.Show();
}
Note: in the Bootstrapper there is a container that contains the whole models and the views For Example:
public class Bootstrapper
{
public IContainer Bootstrap()
{
var builder = new ContainerBuilder();
builder.RegisterType<EventAggregator>().As<IEventAggregator>().SingleInstance();
builder.RegisterType<SubBVDbContext>().AsSelf();
builder.RegisterType<MainWindow>().AsSelf();
builder.RegisterType<SubPODetailViewModel>().As<ISubPODetailViewModel>();
builder.RegisterType<LoginPageViewModel>.AsSelf();
MainWindow.xaml
<Window.Resources>
<DataTemplate DataType="{x:Type viewModel:SubDetailViewModel}">
<view:SubDetailView/>
</DataTemplate>
<DataTemplate DataType="{x:Type viewModel:SubPODetailViewModel}">
<view:SubPODetailView/>
</DataTemplate>
<DataTemplate DataType="{x:Type viewModel:LoginPageViewModel}">
<view:LoginDetailView/>
</DataTemplate>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="200"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Menu Grid.ColumnSpan="2" FontSize="13">
<MenuItem Header="Create">
<MenuItem Header="New Subcontractor" Command="{Binding CreateNewDetailCommand}"
CommandParameter="{x:Type viewModel:SubDetailViewModel}"/>
<MenuItem Header="New P.O." Command="{Binding CreateNewDetailCommand}"
CommandParameter="{x:Type viewModel:SubPODetailViewModel}"/>
</MenuItem>
</Menu>
<view:NavigationView Grid.Row="1" DataContext="{Binding NavigationViewModel}"/>
<ContentControl Grid.Row="1" Grid.Column="1" Content="{Binding DetailViewModel}"/>
</Grid>
The Model view 1,The Model view 2
The SolutionExplorer Folders
The Link for My WPF App: http://www.filefactory.com/file/4of3jrkspmog/SubBvApp.zip
I would like to arrange my items in a ListView with a Text and an image like in this example:
The first column is automatic, so it expands to the widest element.
The second column should expand to fill the remaining space.
So, how can I make an Auto column... or simulate it?
Obviously, the difficult part of this layout is that different elements have their widths depending on other element's widths.
EDIT: Using #WPInfo's answer, I've tried with this:
XAML
<Page.DataContext>
<local:MainViewModel></local:MainViewModel>
</Page.DataContext>
<ListView ItemsSource="{Binding Items}">
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
</Style>
</ListView.ItemContainerStyle>
<ListView.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding}" />
<Border Background="CornflowerBlue" Grid.Column="1" />
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
CODE:
public class MainViewModel
{
public IList<string> Items { get; }
public MainViewModel()
{
Items = new List<string>()
{
"ABC",
"ABCDEF",
"ABCDEFGHI",
};
}
}
The expected result was:
However, the actual result is:
The goal is that the first column to be as wide as the widest element inside it.
In WPF, this is solved easily with the use of Grid.SharedSizeGroup. In UWP there is not such a thing.
The problem is that ListView children don't know anything about each other. There's no simple trick to have all the items know what the longest text item is, so a more complicated solution is required. The following works. It will determine a fixed TextBlock size based on the largest item from the string list, and it will update it should the font size change. Obviously a simpler solution closer resembling your example code can be had if you decide on a fixed column size for either the text or the image, but I assume you already know that.
XAML:
<Page.DataContext>
<local:MainViewModel></local:MainViewModel>
</Page.DataContext>
<ListView ItemsSource="{Binding Items}">
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
</Style>
</ListView.ItemContainerStyle>
<ListView.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding MyText, Mode=OneWay}" FontSize="{Binding ItemFontSize, Mode=OneWay}" Width="{Binding TextWidth, Mode=OneWay}" />
<Border Background="CornflowerBlue" Grid.Column="1" />
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
C# ViewModel
public class MainViewModel : INotifyPropertyChanged
{
public ObservableCollection<MyListViewItemsClass> Items { get; set; }
private List<string> ItemsFromModel;
private int _myFontSize;
public int MyFontSize
{
get { return _myFontSize; }
set
{
if (value != _myFontSize)
{
_myFontSize = value;
OnPropertyChanged("MyFontSize");
// Font size changed, need to refresh ListView items
LoadRefreshMyListViewItems();
}
}
}
public MainViewModel()
{
// set default font size
_myFontSize = 20;
// example data
ItemsFromModel = new List<string>()
{
"ABC",
"ABCDEF",
"ABCDEFGHI",
};
LoadRefreshMyListViewItems();
}
public void LoadRefreshMyListViewItems()
{
int itemMaxTextLength = 0;
foreach (var modelItem in ItemsFromModel)
{
if (modelItem.Length > itemMaxTextLength) { itemMaxTextLength = modelItem.Length; }
}
Items = new ObservableCollection<MyListViewItemsClass>();
// Convert points to pixels, multiply by max character length to determine fixed textblock width
// This assumes 96 DPI. Search for how to grab system DPI in C# there are answers on SO.
double width = MyFontSize * 0.75 * itemMaxTextLength;
foreach (var itemFromModel in ItemsFromModel)
{
var item = new MyListViewItemsClass();
item.MyText = itemFromModel;
item.ItemFontSize = MyFontSize;
item.TextWidth = width;
Items.Add(item);
}
OnPropertyChanged("Items");
}
public class MyListViewItemsClass
{
public string MyText { get; set; }
public int ItemFontSize { get; set; }
public double TextWidth { get; set; }
}
protected void OnPropertyChanged(PropertyChangedEventArgs e)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
handler(this, e);
}
public void OnPropertyChanged(string propertyName)
{
OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
}
Try setting ItemContainerStyle property for the ListView:
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
</Style>
</ListView.ItemContainerStyle>
Then your ListView may look like this:
<ListView x:Name="list">
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
</Style>
</ListView.ItemContainerStyle>
<ListView.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock ... />
<Image Grid.Column="1" HorizontalAlignment="Right" ... />
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
After that, you can set a proper value for HorizontalAlignment property of your ListView.
Universal Windows Platform, C#
How can I collapse/expand the child listview of item MainListView listitem from code behind? I have not found anything that works.
I'd like to do this on the SelectionChanged event.
XAML
<ListView x:Name="DestListView" SelectionMode="Single" Margin="0,60,0,0" SelectionChanged="listview_SelectionChanged" >
<ListView.ItemContainerStyle >
<Style TargetType="ListViewItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
<Setter Property="BorderThickness" Value="0,.5,0,0" />
<Setter Property="BorderBrush" Value="Gainsboro" />
</Style>
</ListView.ItemContainerStyle>
<ListView.ItemTemplate >
<DataTemplate>
<StackPanel>
<Grid>
<Grid HorizontalAlignment="Stretch">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="40"/>
<ColumnDefinition Width="100"/>
<ColumnDefinition Width="50"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="50"/>
</Grid.ColumnDefinitions>
<CheckBox Grid.Column="0" MinWidth="20" />
<TextBlock Grid.Column="1" Text="{Binding destination}" FontSize="20" />
<TextBlock Grid.Column="2" Text="{Binding total_quantity}" FontSize="20" Margin="10,0,0,0"/>
<TextBlock Grid.Column="3" Text="{Binding package_type}" FontSize="20" HorizontalAlignment="Center" Margin="10,0,0,0"/>
<TextBlock Grid.Column="4" Text="{Binding total_weight}" FontSize="20" Margin="10,0,0,0"/>
</Grid>
</Grid>
**<!--Collpase/Expand-->**
<ListView x:Name="DetailListView" ItemsSource="{Binding destination_data}" SelectionMode="Multiple" Margin="20,0,0,0" Visibility="Collapsed" >
<ListView.ItemTemplate >
<DataTemplate >
<StackPanel Orientation="Vertical">
<TextBlock Text="{Binding visual_number}" FontSize="14" Foreground="White" HorizontalAlignment="Stretch" />
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
CodeBehind
private void listview_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
//GET THE ITEM
var selectItem = DestListView.Items[DestListView.SelectedIndex];
//GET THE CHILD SOMEHOW
//ListView childListView = (ListView)...not sure what to do here
//if (childListView != null)
//{
// if (childListView.Visibility == Visibility.Collapsed)
// {
// //childListView.Visibility = Visibility.Collapsed;
// }
// else
// {
// //childListView.Visibility = new Visibility;
// }
//}
}
Here is how i would do it.
I would create two DataTemplates one to show when selected (Expanded), another when not expanded.
Below is my ViewModel.
public class MyViewModel
{
public MyViewModel()
{
myItems = new ObservableCollection<MyItems>();
for(int i=1;i<=10;i++)
{
MyItems item = new MyItems();
item.Name = "Main Item " + i.ToString();
ObservableCollection<MySubItems> subItems = new ObservableCollection<MySubItems>();
for (int j=1;j<=5;j++)
{
subItems.Add(new MySubItems() { Title = "Sub Item " + j.ToString() });
}
item.Data = subItems;
myItems.Add(item);
}
}
public ObservableCollection<MyItems> myItems { get; set; }
}
public class MyItems
{
public string Name { get; set; }
public ObservableCollection<MySubItems> Data { get; set; }
}
public class MySubItems
{
public string Title { get; set; }
}
Below is my MainPage.xaml as requested
<Page
x:Class="App2.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:App2"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Page.DataContext>
<local:MyViewModel/>
</Page.DataContext>
<Page.Resources>
<DataTemplate x:Key="NoSelectDataTemplate">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBlock Text="{Binding Name}" />
</Grid>
</DataTemplate>
<DataTemplate x:Key="SelectDataTemplate">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBlock Text="{Binding Name}" />
<ListView ItemsSource="{Binding Data}" Grid.Row="1">
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Title}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
</DataTemplate>
</Page.Resources>
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<ListView HorizontalAlignment="Stretch" VerticalAlignment="Stretch" ItemsSource="{Binding myItems}" SelectionChanged="ListView_SelectionChanged" ItemTemplate="{StaticResource NoSelectDataTemplate}">
<ListView.ItemContainerStyle >
<Style TargetType="ListViewItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
<Setter Property="BorderThickness" Value="0,.5,0,0" />
<Setter Property="BorderBrush" Value="Gainsboro" />
</Style>
</ListView.ItemContainerStyle>
</ListView>
</Grid>
</Page>
And Below is how my SelectionChanged Event Looks like.
private int PreviousIndex;
private void ListView_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
ListView lv = sender as ListView;
if (PreviousIndex >=0)
{
ListViewItem prevItem = (lv.ContainerFromIndex(PreviousIndex)) as ListViewItem;
prevItem.ContentTemplate = Resources["NoSelectDataTemplate"] as DataTemplate;
}
ListViewItem item = (lv.ContainerFromIndex(lv.SelectedIndex)) as ListViewItem;
item.ContentTemplate = Resources["SelectDataTemplate"] as DataTemplate;
PreviousIndex = lv.SelectedIndex;
}
Here is the Output
I have a Listview inside a Listview as you can see in my code. I am trying to collapse other categories when i open another. Is that possible? I have tried many things but I don't know how to access elements in other row...
<ListView x:Name="MainListView"
ItemsSource="{x:Bind menu}">
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="HorizontalContentAlignment"
Value="Stretch" />
</Style>
</ListView.ItemContainerStyle>
<ListView.ItemTemplate>
<DataTemplate x:DataType="data:MainCategories">
<Grid Background="blue">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBlock x:Name="test"
Text="{x:Bind CategoryName}"
Tapped="Category_TextBlock_Tapped"
FontSize="25" />
<Grid Grid.Row="1"
Name="tittleGrid"
Background="Gray">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*" />
<ColumnDefinition Width="1*" />
<ColumnDefinition Width="1*" />
</Grid.ColumnDefinitions>
<TextBlock Text="Name"
HorizontalAlignment="Center" />
<Border BorderBrush="Black"
BorderThickness="1,0,1,0"
Grid.Column="1">
<TextBlock HorizontalAlignment="Center"
Text="Price" />
</Border>
<TextBlock Text="QUantity"
Grid.Column="2"
HorizontalAlignment="Center" />
</Grid>
<ListView x:Name="SubListView"
Grid.Row="2"
Background="YellowGreen"
ItemsSource="{x:Bind SubMenuItems}">
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="HorizontalContentAlignment"
Value="Stretch" />
<Setter Property="Padding"
Value="0" />
<Setter Property="VerticalContentAlignment"
Value="Stretch" />
</Style>
</ListView.ItemContainerStyle>
<ListView.ItemTemplate>
<DataTemplate x:DataType="data:Dishes">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*" />
<ColumnDefinition Width="1*" />
<ColumnDefinition Width="1*" />
</Grid.ColumnDefinitions>
<TextBlock VerticalAlignment="Center"
Text="{x:Bind dishName}"
HorizontalAlignment="Center" />
<Border BorderBrush="Black"
BorderThickness="1,0,1,0"
Grid.Column="1">
<TextBlock VerticalAlignment="Center"
Grid.Column="1"
HorizontalAlignment="Center"
Text="{x:Bind dishPrice}" />
</Border>
<TextBlock Grid.Column="2"
VerticalAlignment="Center"
HorizontalAlignment="Center"
Text="{x:Bind dishPrice}" />
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
And here is the toggle-visibility method i have made and works fine for the one i am clicking but I want to collapse ALL the others when i expand the one i click... I might have many mistakes in my code but I'm kinda new to UWP
private void Category_TextBlock_Tapped(Object sender, TappedRoutedEventArgs e)
{
CloseAllOthers();
TextBlock categoryName = sender as TextBlock;
Grid grid = (categoryName.Parent as Grid);
ToggleVisibility(grid);
}
private void ToggleVisibility(Grid grid)
{
foreach (var gr in grid.Children)
{
if (gr.GetType() == grid.GetType() || gr.GetType() == MainListView.GetType())
{
if (gr.Visibility == Visibility.Visible)
{
gr.Visibility = Visibility.Collapsed;
}
else
gr.Visibility = Visibility.Visible;
}
}
}
My result so far
and the collapsed version
Here's a simplified example of what you want to do:
<ListView x:Name="menu" ItemsSource="{x:Bind MenuItems}" IsItemClickEnabled="True" ItemClick="onItemClick">
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<!-- Disable virtualization -->
<StackPanel/>
</ItemsPanelTemplate>
</ListView.ItemsPanel>
<ListView.Resources>
<Style TargetType="ListViewItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
<Setter Property="Padding" Value="0"/>
<Setter Property="MinHeight" Value="0"/>
</Style>
</ListView.Resources>
<ListView.ItemTemplate>
<DataTemplate x:DataType="local:MenuItem">
<StackPanel>
<TextBlock Text="{x:Bind Text}" FontWeight="Bold" Margin="10,10,0,10"/>
<ListView x:Name="subMenu" ItemsSource="{x:Bind SubItems}" Visibility="Collapsed">
<ListView.ItemTemplate>
<DataTemplate x:DataType="local:MenuItem">
<TextBlock Margin="20,5,0,5" Text="{x:Bind Text}"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
public class MenuItem
{
public string Text { get; set; }
public List<MenuItem> SubItems { get; set; }
}
public sealed partial class MainPage : Page
{
public List<MenuItem> MenuItems { get; }
public MainPage()
{
InitializeComponent();
MenuItems = Enumerable.Range(1, 3).Select(i => new MenuItem()
{
Text = $"Menu item {i}",
SubItems = Enumerable.Range(1, 3).Select(j => new MenuItem()
{
Text = $"Sub item {i}.{j}",
}).ToList(),
}).ToList();
}
private void onItemClick(object sender, ItemClickEventArgs e)
{
foreach (var item in menu.Items)
{
var container = (ListViewItem)menu.ContainerFromItem(item);
if (container != null)
{
var subMenu = (container.ContentTemplateRoot as FrameworkElement)?.FindName("subMenu") as FrameworkElement;
if (subMenu != null)
{
subMenu.Visibility = e.ClickedItem == item ? Visibility.Visible : Visibility.Collapsed;
}
}
}
}
}
I wouldn't necessarily recommend this approach; I prefer more of a data-driven approach with bindings to control the visibility of the sub menus when the parent list item is selected, rather than accessing the view directly, but this is fine for simple scenarios.