Question

I have this code sitting in a button's attached TargetedTriggerAction<DataGrid>

What I'm trying to do is find the items that I'd like selected and place them into a list. Once that is complete I'd like to set the DataGrid's SelectedItems with the list.

I would like to do it this way, as I have many thousands of items to iterate through, doing a majority on a background thread and setting SelectedItems at the end, avoiding the UI thread with singular calls to SelectedItems.Add() will be more efficient. That is my logic at this point anyway.

I realise that SelecteedItems is readonly so my question is can I actually do this? How can I set SelectedItems via a list? On another note if the SelectAll button can select everything in a short period of time then there must be a way achieve what I want also... shouldn't there?

IList<Object> tempItems = new List<Object>();
var itemsSource = this.Target.Items as IEnumerable;
Task.Factory.StartNew(() =>
{
    FullTextSearch<UserViewModel>.FullTextSearchInit();

    if (itemsSource != null)
    {
        foreach (var item in itemsSource)
        {
            if (FullTextSearch<UserViewModel>.Match((UserViewModel)item, sv))
            {
                tempItems.Add(item);
            }
        }
        if (tempItems.Count > 0)
        {
            Application.Current.Dispatcher.Invoke(new Action(() =>
                /**** How to set? ****/
                this.Target.SelectedItems = tempList
                ));
        }
    }
});

[EDIT] Tried doing the following, however, IsUpdatingSelectedItems, BeginUpdateSelectedItems and EndUpdateSelectedItems all come back with similar errors:

System.Windows.Controls.Primitives.MultiSelector.BeginUpdateSelectedItems()' is inaccessible due to its protection level

if (!this.Target.IsUpdatingSelectedItems)
{
    this.Target.BeginUpdateSelectedItems();
    foreach (object item in this.Target.Items)
    {
        if (FullTextSearch<UserViewModel>.Match((UserViewModel)item, sv))
        {
            this.Target.SelectedItems.Add(item);
        }
    }
    this.Target.EndUpdateSelectedItems();
}
Was it helpful?

Solution

In the comments as well as in the edited question, it has been asked how to invoke the protected BeginUpdateSelectedItems and EndUpdateSelectedItems methods of a DataGrid.

The simplest approach would be subclassing DataGrid and implement a method which can invoke those protected methods:

    public class MyDataGrid : DataGrid
    {
        public void SelectManyItems(IEnumerable itemsToBeSelected)
        {
            if (!IsUpdatingSelectedItems)
            {
                BeginUpdateSelectedItems();
                foreach (object item in itemsToBeSelected)
                    SelectedItems.Add(item);
                EndUpdateSelectedItems.Invoke();
            }
        }
    }

While this looks nice and easy, it has the disadvantage of requiring you to replace the DataGrid with MyDataGrid wherever you need this functionality. When dealing with an existing or 3rd-party code base, this approach quickly becomes unfeasible.

Another, more versatile approach is in leveraging reflection to call the protected methods from "outside" of the DataGrid object instances. This does not require sub-classing of DataGrid.

public static class MultiSelectorHelper
{
    private static readonly PropertyInfo _piIsUpdatingSelectedItems;
    private static readonly MethodInfo _miBeginUpdateSelectedItems;
    private static readonly MethodInfo _miEndUpdateSelectedItems;

    static MultiSelectorHelper()
    {
        _piIsUpdatingSelectedItems = typeof(MultiSelector).GetProperty("IsUpdatingSelectedItems", BindingFlags.NonPublic | BindingFlags.Instance);
        _miBeginUpdateSelectedItems = typeof(MultiSelector).GetMethod("BeginUpdateSelectedItems", BindingFlags.NonPublic | BindingFlags.Instance);
        _miEndUpdateSelectedItems = typeof(MultiSelector).GetMethod("EndUpdateSelectedItems", BindingFlags.NonPublic | BindingFlags.Instance);
    }


    public static void SelectManyItems(this MultiSelector control, IEnumerable itemsToBeSelected)
    {
        control.Dispatcher.Invoke(
            (Action) (() =>
            {
                if (!(bool) _piIsUpdatingSelectedItems.GetValue(control, null))
                {
                    _miBeginUpdateSelectedItems.Invoke(control, null);
                    try
                    {
                        foreach (object item in itemsToBeSelected)
                            control.SelectedItems.Add(item);
                    }
                    finally
                    {
                        _miEndUpdateSelectedItems.Invoke(control, null);
                    }
                }
            })
        );
    }
}

Note that the SelectManyItems is implemented as an extension method, working on any control which is derived from MultiSelector (which includes DataGrid).

Also note the try-finally block, which ensures calling of the EndUpdateSelectedItems method even when an exception occurs during adding of the selected items.

Usage of this extension method is simple:

IEnumerable collectionWithItemsToSelect = ...
dataGridInstance.SelectManyItems(collectionWithItemsToSelect);

OTHER TIPS

You can get Selecteditems in SelectionChanged()

XAML:

    <Window x:Class="ListViewSelectedItemsBinding.Window1"

xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

xmlns:local="clr-namespace:ListViewSelectedItemsBinding"

xmlns:w="clr-namespace:System.Windows.Workarounds"

x:Name="root" Width="500" Height="700">

<Window.Resources>

<local:MyConverter x:Key="myConverter"/>

</Window.Resources>

<StackPanel>

<ListView x:Name="listView" ItemsSource="{Binding}" SelectionMode="Extended"

SelectionChanged="listView_SelectionChanged"

w:ListView.HasBindableSelectedItems="True">

<ListView.View>

<GridView>

<GridViewColumn Header="Artist" DisplayMemberBinding="{Binding Path=Artist}" />

<GridViewColumn Header="Title" DisplayMemberBinding="{Binding Path=Title}" />

<GridViewColumn Header="Genre" DisplayMemberBinding="{Binding Path=Genre}" />

</GridView>

</ListView.View>

</ListView>

<StackPanel Background="LightGreen">

<DockPanel>

<Label DockPanel.Dock="Left" Content="SelectedItems.Count = " />

<TextBox Text="{Binding ElementName=listView, Path=SelectedItems.Count, Mode=OneWay}" IsReadOnly="True" />

</DockPanel>

<DockPanel>

<Label DockPanel.Dock="Left" Content="SelectedItems = " />

<TextBox Text="{Binding ElementName=listView, Path=SelectedItems, Mode=OneWay,

Converter={StaticResource myConverter}, ConverterParameter='Artist'}" IsReadOnly="True" />

</DockPanel>

</StackPanel>

<StackPanel Background="LightBlue">

<DockPanel>

<Label DockPanel.Dock="Left" Content="Selection.Count = " />

<TextBox Text="{Binding ElementName=root, Path=Selection.Count, Mode=OneWay}" IsReadOnly="True" />

</DockPanel>

<DockPanel>

<Label DockPanel.Dock="Left" Content="Selection = " />

<TextBox Text="{Binding ElementName=root, Path=Selection, Mode=OneWay,

Converter={StaticResource myConverter}, ConverterParameter='Artist'}" IsReadOnly="True" />

</DockPanel>

</StackPanel>

<local:PropertiesPanel x:Name="propertiesPanel1" Background="LightGreen"

Subjects="{Binding ElementName=listView, Path=SelectedItems, Mode=OneWay}" />

<local:PropertiesPanel x:Name="propertiesPanel2" Background="LightBlue"

Subjects="{Binding ElementName=root, Path=Selection, Mode=OneWay}" />

<local:PropertiesPanel x:Name="propertiesPanel3" Background="LightPink"

Subjects="{Binding ElementName=listView, Path=BindableSelectedItems}" />

</StackPanel>

</Window>

background:

    using System;

using System.Collections;

using System.Windows.Data;

using System.Windows.Controls;



namespace System.Windows.Workarounds

{

public static class ListView

{

    public static readonly DependencyProperty HasBindableSelectedItemsProperty;

    public static readonly DependencyProperty BindableSelectedItemsProperty;

    static DependencyProperty SelectionChangedHandlerProperty;



    static ListView()

    {

    BindableSelectedItemsProperty = DependencyProperty.Register("BindableSelectedItems", typeof(IList),typeof(System.Windows.Controls.ListView));

    HasBindableSelectedItemsProperty = DependencyProperty.RegisterAttached("HasBindableSelectedItems", typeof(bool),typeof(System.Windows.Controls.ListView), new PropertyMetadata(false));

    SelectionChangedHandlerProperty = DependencyProperty.RegisterAttached("SelectionChangedHandler", typeof(SelectionChangedHandler),typeof(System.Windows.Controls.ListView));

    }



    public static void SetHasBindableSelectedItems(System.Windows.Controls.ListView source, bool value)
    {
    SelectionChangedHandler Handler = (SelectionChangedHandler)source.GetValue(SelectionChangedHandlerProperty);

    if (value && Handler == null)
    {
        Handler = new SelectionChangedHandler(source);
        source.SetValue(SelectionChangedHandlerProperty, Handler);
    } 
    else if (!value && Handler != null)
    {
        source.ClearValue(SelectionChangedHandlerProperty);
    }
}

}



internal class SelectionChangedHandler
{

    Binding Binding;
    internal SelectionChangedHandler(System.Windows.Controls.ListView owner)
    {
        Binding = new Binding("SelectedItems");
        Binding.Source = owner;
        owner.SetBinding(ListView.BindableSelectedItemsProperty, Binding);
        owner.SelectionChanged +=new SelectionChangedEventHandler(Owner_SelectionChanged);
    }

    void Owner_SelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        System.Windows.Controls.ListView Owner =(System.Windows.Controls.ListView)sender;
        BindingOperations.ClearBinding(Owner, ListView.BindableSelectedItemsProperty);
        Owner.SetBinding(ListView.BindableSelectedItemsProperty, Binding);
    }
}
}

you can also make a dependencyproperty to replace 'selecteditems'

http://blog.functionalfun.net/2009/02/how-to-databind-to-selecteditems.html

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top