I'm trying to create a ListView with grouping where the elements in each group are shown horizontally (as a scrollable content). No matter what I tried with the GroupStyle.Panel of the ListView it doesn't seem to have any effect on the list.
Here is how my XAML looks:
<ListView x:Name="itemListView"
Padding="10"
SelectionMode="None"
IsSwipeEnabled="False"
IsItemClickEnabled="True"
ItemTemplate="{StaticResource listItemTemplate}">
<ListView.GroupStyle>
<GroupStyle>
<GroupStyle.Panel>
<ItemsPanelTemplate>
<ItemsWrapGrid ItemWidth="144" Orientation="Horizontal" />
</ItemsPanelTemplate>
</GroupStyle.Panel>
<GroupStyle.HeaderTemplate>
<DataTemplate>
<Grid>
<TextBlock Text="{Binding DisplayTitle}"
Margin="0,10,0,5"
Foreground="Black"
Style="{StaticResource SubheaderTextBlockStyle}"
TextWrapping="NoWrap" />
</Grid>
</DataTemplate>
</GroupStyle.HeaderTemplate>
</GroupStyle>
</ListView.GroupStyle>
</ListView>
Where
<Page.Resources>
<DataTemplate x:Key="listItemTemplate">
<Grid Width="144" Margin="5">
<!-- details -->
</Grid>
</DataTemplate>
</Page.Resources>
The following image shows on the left the actual result I get, and on the right what I want to have.
I tried using a ItemsWrapGrid with different properties, I tried a StackPanel and even an VariableSizedWrapGrid, but nothing changed in the way the list items are displayed.
How can this be done?
#kubakista was right about
It looks like if ListView.ItemsPanel contains ItemsStackPanel then
GroupStyle.Panel is ignored...
However, changing this won't solve your problem as -
The scrolling becomes a bit laggy.
There is no horizontal scrolling.
The ListView loses virtualization.
The nice group header rolling up animation is gone.
Here is an alternative without changing the structure of the ListView itself but a little bit modification in your data structure.
The idea is, treat each horizontal list of rectangles under a group as one collection item on the UI.
This means, each group in the ListView will only have one child, which is actually a collection of rectangles that will be presented in an horizontal scrollable ItemsControl.
So, assume you have some collection of type ObservableCollection<Item> as the CollectionViewSource, the Item will now become type of <ObservableCollection<Item>> in order to hold the collection of rectangles. Therefore, the type of the collection will need to be updated to something like ObservableCollection<ObservableCollection<Item>>.
Inside the ListView's ItemTemplate, you will need to create a horizontally scrolling ScrollViewer and put an ItemsControl inside. Make sure you have set the latter's ItemsSource to {Binding}.
To enable horizontal swiping, you will need to disable the tilt effect by editing the default style of ListViewItem and commenting out the following code.
<!--
<VisualStateGroup.Transitions>
<VisualTransition From="Pressed" To="Normal">
<Storyboard>
<PointerUpThemeAnimation Storyboard.TargetName="TiltContainer"/>
</Storyboard>
</VisualTransition>
</VisualStateGroup.Transitions>
<VisualState x:Name="Normal"/>
<VisualState x:Name="Pressed">
<Storyboard>
<PointerDownThemeAnimation Storyboard.TargetName="TiltContainer"/>
</Storyboard>
</VisualState>
-->
I have attached a working sample project here as well as a screenshot shown below.
Hope this helps!
Related
I have a problem concerning a CollectionView with a GridItemsLayout. In my application, I have a StackLayout with a horizontal CollectionView(GridItemsLayout), which contains a Button bound to a certain category.
Whenever a button is clicked/tapped, it filters the ListView (of type Surgery) below based on the category. All of that is working fine, however, I would like to highlight the Category/Button by changing it BackgroundColor to see which category is currently used for filtering.
<CollectionView HeightRequest="70"
x:Name="categoryCollection"
ItemsSource="{Binding Categories}"
Margin="20,0,20,0"
SelectionMode="Single">
<CollectionView.ItemsLayout>
<GridItemsLayout Orientation="Horizontal" Span="1" HorizontalItemSpacing="5"/>
</CollectionView.ItemsLayout>
<CollectionView.ItemTemplate>
<DataTemplate>
<Grid Padding="0,5,0,5">
<Button x:Name="categoryButton" Text="{Binding Name}"
Command="{Binding Path=BindingContext.FilterCommand, Source={x:Reference Name=SurgeryListView}}"
CommandParameter="{Binding .}"
CornerRadius="15"
FontFamily="OpenSans" Margin="0,5,10,5"
Opacity="0.6" FontSize="16" TextTransform="None">
</Button>
</Grid>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
<ListView x:Name="listView"
ItemsSource="{Binding Surgeries}"
RefreshCommand="{Binding LoadSurgeriesCommand}"
IsRefreshing="{Binding IsRefreshing}"
IsPullToRefreshEnabled="True"
RowHeight="70"
BackgroundColor="{StaticResource darkThemeBackground}"
ItemTemplate="{StaticResource surgeryDataTemplateSelector}">
<ListView.Behaviors>
<behaviors:EventToCommandBehavior
EventName="ItemTapped"
Command="{Binding SurgerySelectedCommand}"
EventArgsConverter="{StaticResource ItemTappedConverter}">
</behaviors:EventToCommandBehavior>
</ListView.Behaviors>
</ListView>
I tried to use VisualStates but that was not working, because tapping the button does not actually change the SelectedItem (one would need to click the surrounding/parent grid element for that). Moreover, the altered VisualState was only applied to the Grid's BackgroundColor, not to that of the actual button.
Question: How can I highlight the current Category/Button by changing its Background Color?
Since the selection is working good, remains only the ui/xaml part, if you look at the documentation about VisualStateManager, you can add the following style:
<CollectionView.Resources>
<Style TargetType="Grid">
<Setter Property="VisualStateManager.VisualStateGroups">
<VisualStateGroupList>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal" />
<VisualState x:Name="Selected">
<VisualState.Setters>
<Setter Property="BackgroundColor"
Value="LightSkyBlue" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateGroupList>
</Setter>
</Style>
</CollectionView.Resources>
In a UWP app, I have a property that returns 3 values. I want to show a different grid based on this value using a converter. What is the best way to a achieve this? The direction I think I am going to head towards is to create 3 different grid templates, and then set the style to one of these 3 templates based on what the converter returns. Does anyone know if this will work? My grid doesn have to be a grid, it can be a contentcontrol or something like that. I basically want to show a different section of UI based on a property
Thanks
I would use the WindowsStateTriggers NuGet package.
Once installed you can reference at the top of your xaml
xmlns:triggers="using:WindowsStateTriggers"
and say you had a property in your Backend class called IsBusy
public bool IsBusy { get; set; } = false;
and for example you had 2 simple Grids in your xaml.
<StackPanel x:Name="root">
<Grid x:Name="RedGrid" Background="Red" Width="200" Height="100" />
<Grid x:Name="GreenGrid" Background="Green" Width="200" Height="100" Visibility="Collapsed"/>
</StackPanel>
You could setup a Visual State Group to show the Grids based on the IsBusy property. We want the Green Grid to be visible and the Red Grid to be collapsed when IsBusy = true
<StackPanel x:Name="root">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup>
<VisualState x:Name="IsBusy">
<VisualState.StateTriggers>
<triggers:EqualsStateTrigger EqualTo="True" Value="{Binding IsBusy, ElementName=root}" />
</VisualState.StateTriggers>
<VisualState.Setters>
<Setter Target="RedGrid.Visibilty" Value="Collapsed"/>
<Setter Target="GreenGrid.Visibility" Value="Visible" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Grid x:Name="RedGrid" Background="Red" Width="200" Height="100" />
<Grid x:Name="GreenGrid" Background="Green" Width="200" Height="100" />
</StackPanel>
NB The {Binding IsBusy, ElementName=root} depends on your DataContext and location of IsBusy property. Here its just in the code behind for the page.
Hope that gives you an idea.
So, I've just started working again with Windows apps and there are a few things that I can't get working as I want (probably because I coulnd't find any sample and Channel9 videos didn't cover my case).
Starting from this article, I decided that the "reposition" technique is the one that fits my app when moving from a big screen to a smaller one.
What I did is using a StackPanel and changing its orientation using two AdaptiveTriggers (one for 0 width and another one for 720, based on the table here).
This kinda works but there are some issues that I'll illustrate with some ugly paint-edited screenshots.
This is what happens when I'm in the BigScreen situation, where there's enough space to have both A and B on the same row. The problem here is that B should take the full remaining width, covering all the blue part.
The second issue is related to resizing. When there's not enough space, the green part gets cut instead of being resized (you can see that the right border disappeared). This didn't happen before using the StackPanel to make the layout responsive.
Finally, when we are in the SmallScreen situation, orientation changes to be vertical and we have the same problem as the first one: green part's height doesn't fill the screen.
Here's the XAML used for the page:
<Page
x:Class="Page"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:WifiAnalyzerFinal.Views"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:mvvm="using:Mvvm"
mc:Ignorable="d">
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="SmallScreen">
<VisualState>
<VisualState.StateTriggers>
<AdaptiveTrigger MinWindowWidth="0"/>
</VisualState.StateTriggers>
<VisualState.Setters>
<Setter Target="StackPanel.Orientation"
Value="Vertical"/>
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
<VisualStateGroup x:Name="BigScreen">
<VisualState>
<VisualState.StateTriggers>
<AdaptiveTrigger MinWindowWidth="720"/>
</VisualState.StateTriggers>
<VisualState.Setters>
<Setter Target="StackPanel.Orientation"
Value="Horizontal"/>
<Setter Target="Rect.Width"
Value="200"/>
<Setter Target="Rect.Height"
Value="Auto"/>
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<StackPanel Orientation="Vertical"
Background="Blue"
x:Name="StackPanel">
<Rectangle Fill="Red"
Height="50"
x:Name="Rect"
Width="Auto"/>
<ListView ItemsSource="{Binding Stuff}"
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Stretch"
VerticalAlignment="Stretch"
Background="Green"
Width="Auto"
BorderBrush="DarkGreen"
BorderThickness="5"
Padding="5">
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
<Setter Property="Margin" Value="0,0,0,5"/>
</Style>
</ListView.ItemContainerStyle>
</ListView>
</StackPanel>
</Grid>
</Page>
Please note that without the StackPanel the green part fits the page as it should, covering all the available area. Unfortunately I couldn't came up with a better solution because there's no sample telling us how this technique should be implemented. I also tried using the new RelativePanel but it seems that the AdaptiveTrigger's Setter doesn't work with attached properties like RelativePanel.RightOf.
Is there someone who's been successful is applying this technique without having to use code-behind?
EDIT:
I got this working using a Grid with 2 rows and 2 columns, with the AdaptiveTrigger moving all the content from row to column and viceversa.
It is possible to change RelativePanel attached property values through setters. The syntax goes like this:
<Setter Target="SomeXAMLObject.(RelativePanel.RightOf)" Value="SomeOtherXAMLObject" />
Why don't you try to use grid (instead of StackPanel) and define rows using proportional dimensions like:
`<Grid>
<Grid.RowDefinitions>
<RowDefinition width="2*"/>
<RowDefinition width="3*"/>
<RowDefinition width="1*"/>
</Grid.RowDefinitions>
</Grid>`
I have a HubSection that is dynamically shown/hidden using Visibility Collapsed/Visible during runtime. I would like to animate the HubSection by having it expand and shrink instead of just instantly showing or hiding it.
Animations in WinRT tend to be a science, and I haven't found a good method yet.
If someone could help with a code snippet I would appreciate it. I would prefer to keep it to XAML if possible, rather than putting the animation in a code-behind. Thanks!
Assuming that the visibiity of the hubsection is controlled by a boolean property of a viewmodel, bound through a converter then the solution is simpler than I thought. Even if this is not the case I'm sure you can modify the solution below easily.
You will need to reference the Behaviors SDK for this to work. You can do that either by going to References>Add Reference>Windows 8.1>Extensions>Behaviors SDK or by opening your project in blend and dragging a DataTriggerBehavior on yor hubsection.
Necessary xmlns declarations
xmlns:Interactivity="using:Microsoft.Xaml.Interactivity"
xmlns:Core="using:Microsoft.Xaml.Interactions.Core"
xmlns:Media="using:Microsoft.Xaml.Interactions.Media"
<Page.Resources>
<Converters:BoolToVisConverter x:Key="BoolToVisConverter"/>
<Storyboard x:Name="Storyboard1">
<DoubleAnimationUsingKeyFrames EnableDependentAnimation="True" Storyboard.TargetProperty="(FrameworkElement.Width)" Storyboard.TargetName="hubSection">
<EasingDoubleKeyFrame KeyTime="0" Value="0"/>
<EasingDoubleKeyFrame KeyTime="0:0:1" Value="400"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</Page.Resources>
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Hub Header="Hub">
<Interactivity:Interaction.Behaviors>
<Core:DataTriggerBehavior/>
</Interactivity:Interaction.Behaviors>
<HubSection Header="HubSection 0">
<DataTemplate>
<Grid/>
</DataTemplate>
</HubSection>
<HubSection x:Name="hubSection" Header="HubSection 1" Visibility="{Binding MyProperty, Converter={StaticResource BoolToVisConverter}}" Width="400" Background="#FFB02626">
<Interactivity:Interaction.Behaviors>
<Core:DataTriggerBehavior Binding="{Binding MyProperty}" Value="True">
<Media:ControlStoryboardAction Storyboard="{StaticResource Storyboard1}"/>
</Core:DataTriggerBehavior>
</Interactivity:Interaction.Behaviors>
<DataTemplate>
<Grid/>
</DataTemplate>
</HubSection>
</Hub>
<ToggleSwitch Header="ToggleSwitch" HorizontalAlignment="Left" Margin="824,249,0,0" VerticalAlignment="Top" IsOn="{Binding MyProperty, Mode=TwoWay}"/>
The xaml defines a hub with 2 hubsections a toggleswitch to manipulate the boolean property in the viewmodel and a storyboard to animate the second hubsection.
The trick is to use a DataTriggerBehavior instead of an EventTriggerBehavior.
You can even bind directly to the visibility property of the hubsection for the DataTriggerBevavior if you want.
I am trying to get a listview to display a list of items made up of textblocks...
when the listview item is clicked i would like to show instead a list made up of textboxes...
Below is what i have come up with, it does not work.
I have two grids within the templates and was hoping to simply show and hide the grids depending on if the listview item is selected. Where have i gone wrong?
I ripped these visual states from the listview's template itself but i must admit im not sure how they work, or how they are meant to be triggered. Should there be some code behind to do this?
<ListView Grid.Row="2" ItemsSource="{Binding Lines}" HorizontalAlignment="Stretch">
<ListView.ItemTemplate>
<DataTemplate>
<Grid>
<Grid Name="Readonly">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="3*"/>
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding One}" Grid.Column="0"/>
<TextBlock Text="{Binding Two}" Grid.Column="1"/>
</Grid>
<Grid Name="Editing" Visibility="Collapsed">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="3*"/>
</Grid.ColumnDefinitions>
<TextBox Text="{Binding One}" Grid.Column="0"/>
<TextBox Text="{Binding Two}" Grid.Column="1"/>
</Grid>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="SelectionStates">
<VisualState x:Name="Selected">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="Editing" Storyboard.TargetProperty="Visibility">
<DiscreteObjectKeyFrame KeyTime="0" Value="Visible"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="Readonly" Storyboard.TargetProperty="Visibility">
<DiscreteObjectKeyFrame KeyTime="0" Value="Collapsed"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Many thanks,
Kohan
You are setting the Storyboard Animation up outside the Items that are being rendered. The targets you are specifying are not only out of the scope of the outer page, but they potentially do not exist yet. As a result, the Storyboard cannot be setup when the page is rendered.
Here's what you want to do.
Create a user control that will represent the layout you want in your ListView item. When you define your ListView, be sure to include your UserControl in your DataTemplate, like this:
<ListView>
<ListView.ItemTemplate>
<DataTemplate>
<local:MyUserControl />
</DataTemplate>
</ListView.ItemTemplate>
</ListView.ItemsPanel>
</ListView>
Now, for the VisualStates. You need to set the states up inside the UserControl. That means a state for Edit and a state for View. A state needs to be localized like this. Think of the Button control. The states in a button are defined in each Button not some shared location.
When you are ready to change the state of one of the items, you need to wire it to your code behind. In your code behind, you need to loop through the items in your ListView and call a method you create, something like MakeStateEdit() and MakeStateView(). It will be your implementation of those methods that sets the states of the user control. The outside code just trusts it to happen.
This means you need to call VisualStateManager.GoToState(this, "Edit", true); (or whatever state you create) inside your UserControl, in the code-behind. Conversely you might set the "View" state when the MakeStateView() is called.
To iterate a ListView Items property, you need to use a technique like this (http://blog.jerrynixon.com/2012/09/how-to-access-named-control-inside-xaml.html). You'll find that once you start down this path, it really isn't very complicated. You might be disappointed that you can't do all of this in XAML. You can't. But it can be done!
Best of luck!
I don't know if visual state changes propagate, so maybe your solution should somehow work, but I would edit the visual states in the ListViewItem template instead (through ItemContainerStyle).