Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ListView isSelected

I have an listview in my xaml that i populate with items like this:

List<DataLayer.Models.Dictionary> dicts = DataLayer.Manager.getDictionaries();

if (dicts != null)
{
    foreach (DataLayer.Models.Dictionary dict in dicts)
    {
         this.itemListView.Items.Add(dict);
    }
}

My DataLayer.Models.Dictionary object have an isSelected property along with a Name and a SubName.

Name and SubName works fine in the template but how can i go about getting the item to be selected and also updated when user clicks an item?

Thanks!

Edit:

My xaml now looks like this, but the item is still not selected

    <ListView
        x:Name="itemListView"
        TabIndex="1"
        Grid.Row="1"
        Margin="0,60,0,0"
        Padding="0,0,0,0"
        IsSwipeEnabled="False"
        ScrollViewer.VerticalScrollBarVisibility="Hidden"
        SelectionChanged="itemListView_SelectionChanged_1"
        SelectionMode="Multiple"
        FontFamily="Global User Interface">
        <ListView.ItemContainerStyle>
            <Style TargetType="ListViewItem">
                <Setter Property="IsSelected" Value="{Binding Source=Selected,Mode=TwoWay}"/>
            </Style>
        </ListView.ItemContainerStyle>
        <ListView.ItemTemplate>
            <DataTemplate>
                <Grid Margin="6">
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="*"/>
                    </Grid.ColumnDefinitions>
                    <StackPanel Grid.Column="0" Margin="0,0,0,0">
                        <TextBlock Text="{Binding Name}"/>
                        <TextBlock Text="{Binding SubName}" Style="{StaticResource CaptionTextStyle}" TextWrapping="Wrap"/>
                    </StackPanel>
                </Grid>
            </DataTemplate>
        </ListView.ItemTemplate>
    </ListView>
like image 290
Rasmus Styrk Avatar asked Feb 05 '13 18:02

Rasmus Styrk


1 Answers

[Edit] Just noticed this question isn't actually tagged WPF; but I hopefully it still applies.

WPF is inherently MVVM. Directly manipulating controls in code behind is generally not a good idea. So it is advised to create a "view-friendly" model called a ViewModel; See Here. Also for bindings to be of any use, in a changing environment, you must inform of changes to properties and or collections, so that controls can be updated.

First and formost you have a collection of dictionaries, So you create this collection such that it can notify of changes; ObservableCollection can do that. As a general rule of thumb any collection used by WPF you should just use an ObservableCollection, and/or derive from it.

so here is compete working example:

Bare in mind I'm using FODY to inject my PropertyChanged raisers; See done manually

public class Dict : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    public string Name { get; set; }
    public string SubName { get; set; }
    public bool Selected { get; set; }
}

public class ViewModel : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    public ObservableCollection<Dict> Dictionaries { get; set; }

    public ViewModel()
    {
        Dictionaries = new ObservableCollection<Dict>()
        {
            new Dict()
                {
                    Name = "English",
                    SubName = "en",
                    Selected = false,
                },

                new Dict()
                {
                    Name = "English-British",
                    SubName = "en-uk",
                    Selected = true
                },

                new Dict()
                {
                    Name = "French",
                    SubName = "fr",
                    Selected = true
                }
        };

        Dictionaries.CollectionChanged += DictionariesCollectionChanged;
    }

    private void DictionariesCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
    {
        switch(e.Action)
        {
            case NotifyCollectionChangedAction.Add:
                foreach(var dict in e.NewItems.Cast<Dict>())
                    dict.PropertyChanged += DictionaryChanged;
                break;
            case NotifyCollectionChangedAction.Remove:
                foreach (var dict in e.OldItems.Cast<Dict>())
                    dict.PropertyChanged -= DictionaryChanged;
                break;
        }
    }

    private void DictionaryChanged(object sender, PropertyChangedEventArgs e)
    {
        Dict dictionary = (Dict)sender;

        //handle a change in Dictionary
    }
}

With this you can add or remove objects at any time, although here I am just initializing them in the constructor.

Then you would have this in your window or control. I've included the namespaces to make it more self-contained; but this would be for WPF namespaces.

<Window x:Class="WpfApplication1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
        xmlns:local="clr-namespace:WpfApplication1">

    <Window.Resources>
        <local:ViewModel x:Key="viewmodel"/>
    </Window.Resources>

    <ListView
        x:Name="itemListView"
        DataContext="{StaticResource ResourceKey=viewmodel}"
        ItemsSource="{Binding Path=Dictionaries}"
        SelectionMode="Multiple">

        <ListView.ItemContainerStyle>
            <Style TargetType="ListViewItem">
                <Setter Property="IsSelected" Value="{Binding Path=Selected, Mode=TwoWay}"/>
            </Style>
        </ListView.ItemContainerStyle>

        <ListView.ItemTemplate>
            <DataTemplate>
                <Grid Margin="6">
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="*"/>
                    </Grid.ColumnDefinitions>
                    <StackPanel Grid.Column="0" Margin="0,0,0,0">
                        <TextBlock Text="{Binding Name}"/>
                        <TextBlock Text="{Binding SubName}" TextWrapping="Wrap"/>
                    </StackPanel>
                </Grid>
            </DataTemplate>
        </ListView.ItemTemplate>
    </ListView>
</Window>

If you don't use ObservableCollection for your collection, then start adding elements to it after WPF has be loaded-up, it will never notify the binding manager that the ListView should be updated.

The above on start up:

On start, unfocused

you can easily see that the underlying Dictionaries collection is being changed (i.e.its not just the ListView), by overriding the returned value from selected:

public bool Selected { get { return true; } set {/* do nothing*/ }}

Which means everything is always selected, even if you try to deselect it in the view. It will always look like this:

Always seleted

Styling is a different issue, the list looks different if it doesn't have focus. See Here

Now reacting to selection changes could be done in the code-behind, but that would be mixing logic with view.

[Edit] updated to include ability to detect changes in any Dict (including when selected changes)

You could look into this to simplify it

Hope this helps.

like image 52
Meirion Hughes Avatar answered Nov 15 '22 03:11

Meirion Hughes