Click here to Skip to main content
15,867,594 members
Articles / Desktop Programming / WPF

Enable MultiSelect in WPF ListView (2)

Rate me:
Please Sign up or sign in to vote.
4.47/5 (8 votes)
19 Jul 2010LGPL34 min read 80.5K   1.9K   32   15
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:

XML
<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:

C#
Rect GetChildRect(int itemIndex);

VirtualWrapPanel and VirtualStackPanel already have this implemented.

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

XML
<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:

XML
<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:

C#
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:

C#
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)


Written By
Founder
Hong Kong Hong Kong

Comments and Discussions

 
QuestionNew version. Pin
Leung Yat Chun8-Apr-14 7:29
Leung Yat Chun8-Apr-14 7:29 
QuestionThrowExceptionForHR Pin
Member 930529317-Sep-12 23:58
Member 930529317-Sep-12 23:58 
QuestionThrowing exception Pin
bkovacic28-Sep-11 11:52
bkovacic28-Sep-11 11:52 
AnswerRe: Throwing exception Pin
Leung Yat Chun30-Sep-11 9:05
Leung Yat Chun30-Sep-11 9:05 
GeneralHmm... Pin
SledgeHammer0131-Dec-10 9:52
SledgeHammer0131-Dec-10 9:52 
GeneralRe: Hmm... Pin
Leung Yat Chun31-Dec-10 21:39
Leung Yat Chun31-Dec-10 21:39 
To implement a single new feature, it requires someone to suggest it, then another one to implement it, then another to improve it. Newer implementation should perform better than previous, at least in some area, or it shouldn't be released at all, but lets not ignore the significance of the original version.

My MultiSelect component do not support Auto-Scroll, one have to use mousewheel. I would like to have my multi-select to support it, but I didn't advertise this feature. If I remember correctly, when Auto-Scroll is enabled, it will cause trouble when the ListView is using VirtualPanels.

I can feel your frustration for spending hours of time to invent something new, then later found a implementation is already available on the net for almost a Year, even worse, it's under LGPL license. Please keep in mind that this is just because I need this feature before you.

But nevertheless, if you found my idea useful, please reference my article.

Although I am satisfy with the current version, I too want a better MultiSelect, please drop me a message if you need any help to improve your implementation.

Thanks a lot.

GeneralRe: Hmm... Pin
SledgeHammer011-Jan-11 14:09
SledgeHammer011-Jan-11 14:09 
GeneralRe: Hmm... Pin
Leung Yat Chun1-Jan-11 19:13
Leung Yat Chun1-Jan-11 19:13 
GeneralRe: Hmm... Pin
SledgeHammer012-Jan-11 7:52
SledgeHammer012-Jan-11 7:52 
GeneralRe: Hmm... (and AutoScroll patch) Pin
Leung Yat Chun3-Jan-11 7:45
Leung Yat Chun3-Jan-11 7:45 
GeneralMy vote of 3 Pin
SledgeHammer0131-Dec-10 9:49
SledgeHammer0131-Dec-10 9:49 
GeneralGridView bug Pin
BlackGad26-Apr-10 0:19
BlackGad26-Apr-10 0:19 
GeneralPretty cool man Pin
Sacha Barber17-Mar-10 3:58
Sacha Barber17-Mar-10 3:58 
GeneralRe: Pretty cool man Pin
Leung Yat Chun17-Mar-10 4:37
Leung Yat Chun17-Mar-10 4:37 
GeneralRe: Pretty cool man Pin
Sacha Barber17-Mar-10 4:51
Sacha Barber17-Mar-10 4:51 

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

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