Click here to Skip to main content
11,486,023 members (76,284 online)
Click here to Skip to main content

Enable MultiSelect in WPF ListView (2)

, 19 Jul 2010 LGPL3 37K 1.3K 28
Rate this:
Please Sign up or sign in to vote.
Make your ListView support item selection by dragging

Introduction

As I am trying to code a WPF File Explorer, Multi-Select List View is very important to me. Two years ago, I developed a MultiSelection Helper which uses HitTest to do the tricks, but as Mickey Mousoff points out, it's an overcomplicated and non-effective approach, I agreed, but that's all I could offer at that moment.

The new approach is even more complicated, I rewrite everything except the ListView, but it is worth it as it can provide better performance.

Index

  • SelectionHelper class
    • How to use?
    • How it works?
      • Scrolling issues
      • Panels unable to report position
      • Unable to draw the selection properly
    • Incomplete items
    • References
    • Version history

SelectionHelper Class

How to Use?

You can enable multiselect by using SelectionHelper.EnableSelection attached property:

<ListView x:Name="listView" uc:SelectionHelper.EnableSelection="True" />

If you are not using GridView, you have to use IChildInfo interface supported Panels. It contains only one method:

Rect GetChildRect(int itemIndex);

VirtualWrapPanel and VirtualStackPanel already have this implemented.

You can define a view using something similar to the following:

 <uc:VirutalWrapPanelView x:Key="ListView"  ItemHeight="20" ItemWidth="100" 
 HorizontalContentAlignment="Left"  Orientation="Vertical"  >
 <uc:VirutalWrapPanelView.ItemTemplate>
            <DataTemplate>
                <StackPanel Orientation="Horizontal">
                    <Image x:Name="img" Source="Generic_Document.png" Width="16"/>
                    <TextBlock Text="{Binding}" Margin="5,0"  />
                </StackPanel>
            </DataTemplate>
        </uc:VirutalWrapPanelView.ItemTemplate>
    </uc:VirutalWrapPanelView>

You can change the ListViewItem ControlTemplate to trigger when SelectionHelper.IsDragging (demo not included):

Taken from VirtualWrapPanelView.xaml:

<ControlTemplate TargetType="{x:Type ListBoxItem}">
  <Grid>
    <Border Background="{TemplateBinding Background}" />
    <Border Background="#BEFFFFFF" Margin="1,1">
      <Grid>
        <Grid.RowDefinitions>
          <RowDefinition />
          <RowDefinition />
        </Grid.RowDefinitions>
        <Border Margin="2,1,2,0" Grid.Row="0" Background="#57FFFFFF" />
      </Grid>
    </Border>
    <ContentPresenter Margin="5,0" />
    </Grid>
    <ControlTemplate.Triggers>
      <MultiTrigger>
        <MultiTrigger.Conditions>
          <Condition Property="IsMouseOver" Value="True" />
          <Condition Property="IsSelected" Value="False"/>
        </MultiTrigger.Conditions>
        <Setter Property="Background" Value="{DynamicResource fileListHotTrackBrush}" />
      </MultiTrigger>
    <Trigger Property="IsSelected" Value="True">
      <Setter Property="Background" Value="{DynamicResource fileListSelectionBrush}" />
    </Trigger>
    <Trigger Property="uc:SelectionHelper.IsDragging" Value="True">
      <Setter Property="Background" Value="Black" />
    </Trigger>
  </ControlTemplate.Triggers>
</ControlTemplate> 

How It Works?

The structure of a ListView is shown above, which embedded a ScrollViewer, and a ScrollContentPresenter then ItemPresenter. The ItemPresenter contains the Panel specified in the View, in this case, VirtualWrapPanel, it's the Panel that hosts, measures and arranges the items.

Because the ScrollContentPresenter is the topmost control without the scrollbars, I attached most events here, as well as the adorner in the AdornerLayer shown above.

The SelectionHelper works this way:

  • PreviewMouseDown
    • SetStartPosition
    • SetStartScrollbarPosition
    • SetIsDragging
    • Capture mouse, so it works if user moves outside the listview
  • MouseMove - If IsDragging...
    • SelectionAdorner shown?
      • (No) if (move distance > threshold) Show SelectionAdorner
      • (Yes)
        • GetMousePosition
        • GetScrollbarPostion
        • UpdateAdornerPosition
        • UpdateSelection (preview)
          • HighlightItems
  • MouseUp - If IsDragging...
    • UpdateSelection(final)
      • Update SelectedItems
    • Hide SelectionAdorner
    • SetIsDragging to False
    • Release mouse

It looks very simple, but there are a number of issues that are required to be solved.

Scrolling Issues

Because most events are attached to ScrollContentPresenter, it's not aware of the scrolling position. So when drag started, I have to obtain the scrollbar position and record it. The position can be obtained by:

 ScrollViewer scrollViewer = UITools.FindAncestor<ScrollViewer>(p);
 return new Point(p.ActualWidth / scrollViewer.ViewportWidth * 
	scrollViewer.HorizontalOffset,
         p.ActualHeight / scrollViewer.ViewportHeight * scrollViewer.VerticalOffset);

The reason that some calculation is required (instead of returning the offset directly) is that GridView storeViewportHeight and VerticalOffset differently store ViewportHeight as total number of items and VerticalOffset as the item scrolled to.

When user moves the mouse when dragging, both mouse position and scrollbar position is used to calculate the selected:

 UpdateSelection(p, new Rect(
     new Point(startPosition.X + startScrollbarPosition.X, 
	startPosition.Y + startScrollbarPosition.Y),
     new Point(curPosition.X + curScrollbarPosition.X, 
	curPosition.Y + curScrollbarPosition.Y)));

Panels Unable to Report Position

For GridView, it's easy to deal with because I can just return the items between first and last selected item.
For other views, VirtualWrapPanel and VirtualStackPanel are designed for this purpose, as most listviews use these two panels (all file lists except gridview can be represented by VirtualWrapView), both panels are designed based on Dan Crevier's VirtualizingTilePanel. Because the panels are virtual, the listview items are generated when needed, and thus you must specify the item size. Both panels expose a method named GetChildRect() allowing SelectionHelper to obtain the position of individual ListViewItem.

VirutalWrapPanelView is a ViewBase, it allows the coder to set a number of properties of ListView at a time, so to change the list method all it takes is to assign the ListView.View to a new one instead of assigning a dozen properties. VirtualWrapPanelView exposes the ItemWidth, ItemHeight and Orientation properties of its VirtualWrapPanel.

Because VirtualPanel is used, not all ListViewItems are generated, thus I cannot signal ListView.SelectedEvent and ListView.UnselectedEvent. Listen to ListView.SelectionChangeEvent instead.

Unable to Draw the Selection Properly

SelectionAdorner is designed for this purpose, it is an adorner attached to ScrollContentPresenter (a control inside the ListView which holds the child items). It can display the drag area based on its three properties, IsSelecting (whether the adorner is visible), StartPosition and EndPosition.

References

Version History

  • 11-03-10 - version 0.1
    • Initial version
  • 12-03-10 - version 0.2
    • Handles shift / control button properly
    • Handle drag outside the scroll control properly
    • (Most events attached to the listview now)
  • 17-03-10 - version 0.3
    • SelectedItems is now only changed when drag is completed
      SelectionHelper.GetIsDragging(aListViewItem) is true when the ListViewItem is inside the user-selected region (therefore you can theme the selection)
    • SelectedItems is now changed by adding / removing items, instead of clearing it and re-polling the list
  • 19-07-10 - version 0.4
    • Fixed click on GridView Header recognize as drag start. For GridView, only support selection if drag occur inside the first column Fixed VirtualListView selection problem by adding IVirtualListView interface.

License

This article, along with any associated source code and files, is licensed under The GNU Lesser General Public License (LGPLv3)

Share

About the Author


Comments and Discussions

 
QuestionNew version. Pin
Leung Yat Chun8-Apr-14 8:29
memberLeung Yat Chun8-Apr-14 8:29 
QuestionThrowExceptionForHR Pin
Member 930529318-Sep-12 0:58
memberMember 930529318-Sep-12 0:58 
QuestionThrowing exception Pin
bkovacic28-Sep-11 12:52
memberbkovacic28-Sep-11 12:52 
AnswerRe: Throwing exception Pin
Leung Yat Chun30-Sep-11 10:05
memberLeung Yat Chun30-Sep-11 10:05 
GeneralHmm... Pin
SledgeHammer0131-Dec-10 10:52
memberSledgeHammer0131-Dec-10 10:52 
GeneralRe: Hmm... Pin
Leung Yat Chun31-Dec-10 22:39
memberLeung Yat Chun31-Dec-10 22:39 
GeneralRe: Hmm... Pin
SledgeHammer011-Jan-11 15:09
memberSledgeHammer011-Jan-11 15:09 
GeneralRe: Hmm... Pin
Leung Yat Chun1-Jan-11 20:13
memberLeung Yat Chun1-Jan-11 20:13 
GeneralRe: Hmm... Pin
SledgeHammer012-Jan-11 8:52
memberSledgeHammer012-Jan-11 8:52 
GeneralRe: Hmm... (and AutoScroll patch) Pin
Leung Yat Chun3-Jan-11 8:45
memberLeung Yat Chun3-Jan-11 8:45 
GeneralMy vote of 3 Pin
SledgeHammer0131-Dec-10 10:49
memberSledgeHammer0131-Dec-10 10:49 
GeneralGridView bug Pin
BlackGad26-Apr-10 1:19
memberBlackGad26-Apr-10 1:19 
GeneralPretty cool man Pin
Sacha Barber17-Mar-10 4:58
mvpSacha Barber17-Mar-10 4:58 
GeneralRe: Pretty cool man Pin
Leung Yat Chun17-Mar-10 5:37
memberLeung Yat Chun17-Mar-10 5:37 
GeneralRe: Pretty cool man Pin
Sacha Barber17-Mar-10 5:51
mvpSacha Barber17-Mar-10 5:51 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web01 | 2.8.150520.1 | Last Updated 19 Jul 2010
Article Copyright 2010 by Leung Yat Chun
Everything else Copyright © CodeProject, 1999-2015
Layout: fixed | fluid