Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to Bind IsSelected Property in DataTemplate of ListViewItem

Tags:

xaml

uwp

Well, I want to make the ListViewItem in UWP ,that'll be changing his view on selecting. So I need to change the Visibility property of some elements of ListViewItem on selecting.

I found some way to do this with making custom Style of ListViewItem and Binding IsSelected property like this:

<Style x:Key="VehicleListViewItemStyle" TargetType="ListViewItem" >
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="ListViewItem">
                        <Grid Background="Gray" Margin="1">
                            <Border Margin="2" Padding="10" Background="Gray" >
                                <StackPanel>
                                    <ContentPresenter x:Name="Presenter1" />
                                    <StackPanel Orientation="Horizontal" Background="Transparent" Margin="-10,0,-9,-9" VerticalAlignment="Center" x:Name="infoPanel"
                                        Visibility="{Binding IsSelected, RelativeSource={RelativeSource Mode=TemplatedParent}, Converter={StaticResource BooleanToVisibilityConverter}}">
                                        <TextBlock Text="{Binding  DeviceID}/>                                                                            </StackPanel>
                                </StackPanel>
                            </Border>
                            <Border BorderThickness="1" BorderBrush="Orange" Visibility="{Binding IsSelected, RelativeSource={RelativeSource Mode=TemplatedParent}, Converter={StaticResource BooleanToVisibilityConverter}}"/>
                        </Grid>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>  

Its working good, but with this way I cant bind the DeviceID text.

Another one way is creating DataTemplate like this:

<DataTemplate x:Key="monitoringListViewItem" x:Name="item">
            <Grid Background="Gray" Margin="1" Width="300" >
                <StackPanel>
                    <ContentPresenter x:Name="Presenter"/>
                    <StackPanel Orientation="Horizontal">
                        <Image Source="/Assets/14th_crane_stop.png" Height="50" Width="50" Stretch="Uniform"/>
                        <StackPanel Orientation="Vertical" Margin="25,0,0,0 " 
                                    Visibility="{Binding IsSelected, RelativeSource={RelativeSource Mode=TemplatedParent}, Converter={StaticResource BooleanToVisibilityConverter}}"
                                    >
                            <TextBlock Text="{Binding DeviceID}" Style="{StaticResource VehicleTextStyle}"/>
                            <TextBlock Text="{Binding Mark}" Style="{StaticResource VehicleTextStyle}"/>
                        </StackPanel>
                    </StackPanel>
                </StackPanel >
            </Grid>
        </DataTemplate>

Now I can bind the text correctly, but cant bind the IsSelected property. I've tried to do this with different Modes, but it still doesn't work because I cant use the TemplatedParent key inside the DataTemplate.

So I need some answers:

-can I bind the text in first way and how can I do that? -how can I bind the IsSelected property in the second way?

like image 593
selezen88 Avatar asked Oct 30 '16 10:10

selezen88


2 Answers

I don't recommend changing the ListViewItem template since you lose all the bells and whistles it provides (selection appearance, ability to be checked, etc).

Using Mode=TemplatedParent in the second snippet won't work because the templated parent from that context is a ListViewItemPresenter, not the ListViewItem (which is the presenter's parent).

It looks like what you're trying to do is to show additional information in the list item when it is selected.

Single selection

C# classes

public class NotifyPropertyChangedBase : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    protected void OnPropertyChanged([CallerMemberName] string propertyName = "")
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }

    protected bool SetProperty<T>(ref T property, T value, [CallerMemberName] string propertyName = "")
    {
        if (EqualityComparer<T>.Default.Equals(property, value))
        {
            return false;
        }

        property = value;
        OnPropertyChanged(propertyName);
        return true;
    }
}

public class Item : NotifyPropertyChangedBase
{
    private string text;
    public string Text
    {
        get { return text; }
        set { SetProperty(ref text, value); }
    }

    private bool isSelected;
    public bool IsSelected
    {
        get { return isSelected; }
        set { SetProperty(ref isSelected, value); }
    }
}

public class MainPageViewModel : NotifyPropertyChangedBase
{
    public List<Item> Items { get; set; }

    private Item selectedItem;
    public Item SelectedItem
    {
        get { return selectedItem; }
        set
        {
            if (selectedItem != value)
            {
                if (selectedItem != null)
                {
                    selectedItem.IsSelected = false;
                }

                SetProperty(ref selectedItem, value);

                if (selectedItem != null)
                {
                    selectedItem.IsSelected = true;
                }
            }
        }
    }

    public MainPageViewModel()
    {
        Items = new List<Item>()
        {
            new Item() { Text = "Apple" },
            new Item() { Text = "Banana" },
        };
    }
}

MainPage.xaml

<ListView ItemsSource="{Binding Items}" SelectedItem="{Binding SelectedItem, Mode=TwoWay}">
    <ListView.ItemContainerStyle>
        <Style TargetType="ListViewItem">
            <Setter Property="HorizontalContentAlignment" Value="Stretch"/>
        </Style>
    </ListView.ItemContainerStyle>
    <ListView.ItemTemplate>
        <DataTemplate x:DataType="local:Item">
            <Grid>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition/>
                    <ColumnDefinition Width="Auto"/>
                </Grid.ColumnDefinitions>

                <TextBlock Text="{Binding Text}"/>

                <!-- x:Bind doesn't require visibility converter if min SDK is targeting Anniversary update -->
                <TextBlock Text="I'm selected!" Grid.Column="1" Visibility="{x:Bind IsSelected, Mode=OneWay}"/>
            </Grid>
        </DataTemplate>
    </ListView.ItemTemplate>
</ListView>

MainPage.xaml.cs

public sealed partial class MainPage : Page
{
    public MainPage()
    {
        this.InitializeComponent();
        DataContext = new MainPageViewModel();
    }
}

Screenshot

Multiple selection

Same as single selection but with the following changes:

MainPageViewModel

public class MainPageViewModel : NotifyPropertyChangedBase
{
    public List<Item> Items { get; set; }

    public MainPageViewModel()
    {
        Items = new List<Item>()
        {
            new Item() { Text = "Apple" },
            new Item() { Text = "Banana" },
        };
    }

    public void SelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        foreach (Item item in e.RemovedItems)
        {
            item.IsSelected = false;
        }

        foreach (Item item in e.AddedItems)
        {
            item.IsSelected = true;
        }
    }
}

MainPage.xaml

<ListView ItemsSource="{Binding Items}" SelectionChanged="{x:Bind ViewModel.SelectionChanged}" SelectionMode="Extended">
    <ListView.ItemContainerStyle>
        <Style TargetType="ListViewItem">
            <Setter Property="HorizontalContentAlignment" Value="Stretch"/>
        </Style>
    </ListView.ItemContainerStyle>
    <ListView.ItemTemplate>
        <DataTemplate x:DataType="local:Item">
            <Grid>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition/>
                    <ColumnDefinition Width="Auto"/>
                </Grid.ColumnDefinitions>

                <TextBlock Text="{Binding Text}"/>
                <TextBlock Text="I'm selected!" Grid.Column="1" Visibility="{x:Bind IsSelected, Mode=OneWay}"/>
            </Grid>
        </DataTemplate>
    </ListView.ItemTemplate>
</ListView>

MainPage.xaml.cs

public sealed partial class MainPage : Page
{
    public MainPage()
    {
        this.InitializeComponent();
        DataContext = new MainPageViewModel();
    }

    public MainPageViewModel ViewModel => (MainPageViewModel)DataContext;
}

Screenshot

like image 199
Decade Moon Avatar answered Nov 14 '22 09:11

Decade Moon


To answer the actual question in the topic: Yes you can bind ListViewItem.IsSelected quite easily. Make sure your DataTemplate content is wrapped in a ListViewItem.

<ListView.ItemTemplate>
    <DataTemplate x:DataType="local:MyItemViewModel">
        <ListViewItem IsSelected="{Binding IsSelected, Mode=TwoWay}">
            <Grid>
                // template content
            </Grid>
        </ListViewItem>
    </DataTemplate>
</ListView.ItemTemplate>

Note that there are numerous Q&As here for WPF saying this does not work. It does work with UWP (at least as of SDK 10.0.19041). The WPF answers suggest binding it in the ItemsPanelTemplate or in a ResourceDictionary. For some reason this does not work in UWP.

like image 1
Sean O'Neil Avatar answered Nov 14 '22 08:11

Sean O'Neil