Sebastian
Sebastian

Reputation: 4811

Using VisualStateGroup inside Binded ListView

I have a binded listview in UWP and I want to show or hide some controls inside the ItemTemplate based on some properties

The XAML i am using is here for binding ObservableCollection

   <ListView x:Name="lvwMovieWatchlist"  ItemsSource="{x:Bind Books}" Margin="10">
            <ListView.ItemsPanel>
                <ItemsPanelTemplate>
                    <ItemsWrapGrid Orientation="Horizontal" />
                </ItemsPanelTemplate>
            </ListView.ItemsPanel>
            <ListView.ItemTemplate>              
                <DataTemplate x:DataType="data:BookModel">
                    <Grid>
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition Width="Auto"/>
                            <ColumnDefinition Width="*"/>
                        </Grid.ColumnDefinitions>
                        <Border Width="156" Height="200">
                            <Image Source="{Binding CoverImage}" Stretch="UniformToFill"/>
                        </Border>
                        <StackPanel Grid.Column="1" Orientation="Vertical">
                            <TextBlock Text="{Binding Title}" Style="{StaticResource TitleTextBlockStyle}" TextWrapping="NoWrap" Margin="9.6,0"/>
                            <TextBlock Text="{Binding Name}" Style="{StaticResource SubtitleTextBlockStyle}" TextWrapping="NoWrap" Margin="9.6,0"/>
                            <StackPanel Orientation="Horizontal" Margin="10">
                                <Button Name="Btn_Download" Tag="{Binding}" Click="Btn_Download_Click"> DOWNLOAD</Button>                                
                                <Button Name="Btn_Read" Tag="{Binding}" Click="Btn_Read_Click"> READ</Button>                                
                            </StackPanel>
                        </StackPanel>
                    </Grid>
                </DataTemplate>             
            </ListView.ItemTemplate>
        </ListView>

The 2 buttons Btn_Download and Btn_Read are in question here If Status property is "read" then i want to set the visibility as Visible for Btn_Read otherwise show Btn_Download button

In WPF i have Trigger for achieveing the same like

        <DataTemplate.Triggers>
            <DataTrigger Binding="{Binding Status}" Value="read">                   
                <Setter TargetName="Btn_Read" Property="Visibility" Value="Visible"/>
                <Setter TargetName="Btn_Download" Property="Visibility" Value="Collapsed"/>                 
            </DataTrigger>                                              
        </DataTemplate.Triggers>

But in UWP there is no Trigger only VisualStateManager.VisualStateGroups is avaiable. So how can i make it using VisualStateGroup inside the ItemTemplate

Upvotes: 1

Views: 1693

Answers (3)

Thomas LEBRUN
Thomas LEBRUN

Reputation: 436

An possible idea is to use a Converter instead of the VisualStateManager:

<StackPanel Orientation="Horizontal" Margin="10">
    <Button Name="Btn_Download"
        Tag="{Binding}"
        Click="Btn_Download_Click"
        Visibility={Binding Status, Converter={StaticResource StatusToDownloadVisibilityConverter}}>DOWNLOAD</Button>                                
    <Button Name="Btn_Read"
        Tag="{Binding}"
        Click="Btn_Read_Click"
        Visibility={Binding Status, Converter={StaticResource StatusToReadVisibilityConverter}}>READ</Button>                                
</StackPanel>

Just create 2 converters (StatusToDownloadVisibilityConverter and StatusToReadVisibilityConverter) that take a Status in parameter and returns a Visibility, according to the value of the Status property.

Maybe not 100% ideal solution but that should work!

Upvotes: 1

Patrick
Patrick

Reputation: 167

Wrap your template in a user control and add the visual state manager.

<ListView x:Name="lvwMovieWatchlist" ItemsSource="{x:Bind ViewModel.Books}">
    <ListView.ItemTemplate>
        <DataTemplate x:DataType="local:BookModel">
            <local:BookControl />
        </DataTemplate>
    </ListView.ItemTemplate>
</ListView>

<UserControl
    x:Class="App2.BookControl"
    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"
    d:DesignHeight="300"
    d:DesignWidth="400">
        <Grid>
            <VisualStateManager.VisualStateGroups>
                <VisualStateGroup x:Name="ButtonsVisibility">
                    <VisualState x:Name="ButtonsVisible">
                    </VisualState>
                    <VisualState x:Name="ButtonsHidden">
                        <VisualState.StateTriggers>
                            <StateTrigger IsActive="{Binding Status, Mode=OneWay}" />
                        </VisualState.StateTriggers>
                        <VisualState.Setters>
                            <Setter Target="Btn_Download.Visibility" Value="Collapsed" />
                            <Setter Target="Btn_Read.Visibility" Value="Collapsed" />
                        </VisualState.Setters>
                    </VisualState>
                </VisualStateGroup>
            </VisualStateManager.VisualStateGroups>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="*"/>
            </Grid.ColumnDefinitions>
            <StackPanel Orientation="Vertical">
                <TextBlock Text="{Binding Title}" />
                <TextBlock Text="{Binding Subtitle}" />
                <StackPanel Orientation="Horizontal">
                    <Button Name="Btn_Download" Tag="{Binding}" Click="Btn_Download_Click">DOWNLOAD</Button>
                    <Button Name="Btn_Read" Tag="{Binding}" Click="Btn_Read_Click">READ</Button>
                </StackPanel>
            </StackPanel>
        </Grid>
</UserControl>

This example uses the builtin StateTrigger on a boolean property. To compare a property to some value as you want to do use the EqualsStateTrigger from the awesome WindowsStateTriggers library (https://github.com/dotMorten/WindowsStateTriggers).

Upvotes: 0

Romasz
Romasz

Reputation: 29792

If you want to do it completely in XAML, you can do it with Interactivity behaviors extension. A sample using visual states and datatriggeraction can look like this - XAML:

<Grid xmlns:i="using:Microsoft.Xaml.Interactivity" xmlns:ic="using:Microsoft.Xaml.Interactions.Core" Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
    <VisualStateManager.VisualStateGroups>
        <VisualStateGroup x:Name="ReadOrNot">
            <VisualState x:Name="Normal"/>
            <VisualState x:Name="Read">
                <Storyboard BeginTime="0:0:0" Duration="0:0:1">
                    <FadeOutThemeAnimation TargetName="BtnDownload"/>
                </Storyboard>
            </VisualState>
        </VisualStateGroup>
    </VisualStateManager.VisualStateGroups>
    <i:Interaction.Behaviors>
        <i:BehaviorCollection>
            <ic:DataTriggerBehavior Binding="{Binding Status}" Value="read">
                <ic:GoToStateAction StateName="Read"/>
            </ic:DataTriggerBehavior>
        </i:BehaviorCollection>
    </i:Interaction.Behaviors>
    <Button Name="BtnDownload" HorizontalAlignment="Left" Content="Download" FontSize="20" Foreground="Red"/>
    <Button HorizontalAlignment="Center" Content="Change property" FontSize="20" Foreground="Orange" Click="Button_Click"/>
    <Button Name="BtnRead" HorizontalAlignment="Right" Content="Read" FontSize="20" Foreground="Green"/>
</Grid>

The code behind:

public sealed partial class MainPage : Page, INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;
    public void RaiseProperty(string name) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));

    private string status = "notRead";
    public string Status
    {
        get { return status; }
        set { status = value; RaiseProperty(nameof(Status)); }
    }

    public MainPage()
    {
        this.InitializeComponent();
        DataContext = this;
    }

    private void Button_Click(object sender, RoutedEventArgs e) => Status = "read";
}

Button click changes status to read and triggers visual state change via behavior. You can do something similar for your item template. As a side note, you may also use other actions, what may be little easier depending on what you want to achieve - for example ChangePropertyAction.

Of course to make it work you have to add reference to Behaviors in your project (Add reference -> Universal windows -> Extensions) or by NuGet, what like igrali has mentioned, is a better option (open source and targetting UWP).

Upvotes: 1

Related Questions