locked
Hit test ListView/GridView RRS feed

  • Question

  • I have a ListView set up as a GridView with a context menu. I want to be able to identify the data row under the cursor (if any at all) when the context menu is invoked so that I can appropriately enable/disable menu items, and I need it to work for anywhere in the row. The current selection (SelectedItem) doesn't do the job because it retains the selection even when the menu is invoked over a void area in the list (the case when I want the menu to present as if nothing has been selected). Here's the XAML of my ListView:

                            <ListView Grid.Row="3" HorizontalAlignment="Stretch" Name="lvSummary" VerticalAlignment="Stretch" SelectionMode="Single" SelectionChanged="lvSummary_SelectionChanged" ContextMenuOpening="lvSummary_ContextMenuOpening">
                                <ListView.Resources>
                                    <DataTemplate x:Key="TemplLeft">
                                        <TextBlock HorizontalAlignment="Left" Text="{Binding}"/>
                                    </DataTemplate>
                                    <Style x:Key="HeaderStyleLeft" TargetType="GridViewColumnHeader">
                                        <Setter Property="HorizontalContentAlignment" Value="Left"/>
                                    </Style>
                                    <DataTemplate x:Key="TemplRight">
                                        <TextBlock HorizontalAlignment="Right" Text="{Binding}"/>
                                    </DataTemplate>
                                    <Style x:Key="HeaderStyleRight" TargetType="GridViewColumnHeader">
                                        <Setter Property="HorizontalContentAlignment" Value="Right"/>
                                    </Style>
                                </ListView.Resources>
                                <ListView.ItemContainerStyle>
                                    <Style TargetType="ListViewItem">
                                        <Setter Property="HorizontalContentAlignment" Value="Stretch" />
                                    </Style>
                                </ListView.ItemContainerStyle>
                                <ListView.View>
                                    <GridView>
                                        <GridViewColumn Width="50" Header="Year" DisplayMemberBinding="{Binding Year}" HeaderTemplate="{StaticResource TemplLeft}" HeaderContainerStyle="{StaticResource HeaderStyleLeft}"/>
                                        <GridViewColumn Width="80" Header="Period" >
                                            <GridViewColumn.CellTemplate>
                                                <DataTemplate>
                                                    <TextBlock HorizontalAlignment="Left" Text="{Binding Period}" />
                                                </DataTemplate>
                                            </GridViewColumn.CellTemplate>
                                        </GridViewColumn>
                                        <GridViewColumn Width="70" Header="Total"  HeaderTemplate="{StaticResource TemplRight}" HeaderContainerStyle="{StaticResource HeaderStyleRight}">
                                            <GridViewColumn.CellTemplate>
                                                <DataTemplate>
                                                    <TextBlock HorizontalAlignment="Right" Text="{Binding Total, StringFormat='#,0.00'}" />
                                                </DataTemplate>
                                            </GridViewColumn.CellTemplate>
                                        </GridViewColumn>
                                    </GridView>
                                </ListView.View>
                                <ListView.ContextMenu>
                                    <ContextMenu >
                                        <MenuItem Name="ShowPeriod" Header="Show Period" Click="ShowPeriod_Click" />
                                        <MenuItem Name="ShowYear" Header="Show Year" Click="ShowYear_Click" />
                                        <MenuItem Name="DrillDown" Header="Drill Down" Click="DrillDown_Click" />
                                    </ContextMenu>
                                </ListView.ContextMenu>
                            </ListView>

    Can anyone offer any pointers on how to do this?

    Thanks!

    • Edited by Zenilogix Sunday, August 26, 2012 8:58 PM
    Sunday, August 26, 2012 8:49 PM

Answers

  • In answer to your question, yes. A value converter can be specified in a data trigger.

    Here is a complete example. Extrapolate as required :)

    using System;
    using System.Windows;
    using System.Windows.Data;
    using WpfApplication79.ViewModel;
    
    namespace WpfApplication79
    {
        public partial class MainWindow : Window
        {
            public MainWindow()
            {
                InitializeComponent();
            }
        }
    
        public class MyValueConverter : IValueConverter
        {
            public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
            {
                return (bool)value ? "YES" : "NO";
            }
    
            public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
            {
                throw new NotImplementedException();
            }
        }
    }
    

    <Window x:Class="WpfApplication79.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            Title="MainWindow" Height="350" Width="525"
            xmlns:local="clr-namespace:WpfApplication79">
        <Window.Resources>
            <local:MyValueConverter x:Key="MyValueConverter"/>
        </Window.Resources>
        <StackPanel>
            <CheckBox x:Name="MyCheckBox"/>
            <TextBlock Text="Test">
                <TextBlock.Style>
                    <Style TargetType="TextBlock">
                        <Style.Triggers>
                            <DataTrigger Binding="{Binding IsChecked, ElementName=MyCheckBox, Converter={StaticResource MyValueConverter}}" Value="YES">
                                <Setter Property="Foreground" Value="Red"/>
                            </DataTrigger>
                        </Style.Triggers>
                    </Style>
                </TextBlock.Style>
            </TextBlock>
        </StackPanel>
    </Window>
    
    This is a rather pointless example (you would normally just bind to property=IsChecked and value=True) but to show a simple example, I am converting to a string "YES"/"NO"

     

    Regards,
    Pete


    #PEJL

    • Marked as answer by Zenilogix Tuesday, September 4, 2012 10:57 PM
    Monday, September 3, 2012 8:51 PM

All replies

  • Hi rObjects2,

    Do you mean you want the contextmenu show only in SelectedItem of ListView? You can add contextmenu to each ListViewItem in ItemContainerStyle.

    Please refer to beblow code for your reference:

    <ListView>
      <ListView.Resources>
        <ContextMenu x:Key="ItemContextMenu">
          ...
        </ContextMenu>
      </ListView.Resources>
      <ListView.ItemContainerStyle>
        <Style TargetType="{x:Type ListViewItem}">
          <EventSetter Event="PreviewMouseLeftButtonDown" Handler="OnListViewItem_PreviewMouseLeftButtonDown" />
          <Setter Property="ContextMenu" Value="{StaticResource ItemContextMenu}"/>
        </Style>
      </ListView.ItemContainerStyle>
    </ListView>

    If I misunderstood you, please feel free to let me know.

    Have a nice day.


    Annabella Luo[MSFT]
    MSDN Community Support | Feedback to us

    • Proposed as answer by Pete LakerMVP Tuesday, August 28, 2012 10:41 PM
    Tuesday, August 28, 2012 11:29 AM
  • I think that could be part of a solution. Basically, I want one context menu presentation if the user right-clicks over an item, and a different presentation if the user right-clicks in the list view control, but not over an item. I had thought of doing it with only one menu attached to the list view control, enabling/disabling items according to whether the user right-clicked over an item or not, but your suggestion is leading me to a slightly different approach which would achieve my intent - one context menu attached to the item(s) as you've shown, and a different context menu attached to the list view control as I had shown. Will that work?

    Tuesday, August 28, 2012 4:07 PM
  • I think I have my solution, but for one detail...

    I want to enable/disable menu items based on content of the row.

    I see that I can use an event setter for something like ContextMenuOpening, I have my choice of at least two approaches to access the associated data row, and I can access the context menu via the sender parameter, and I can always access the specific menu item via integer subscript...

    But... if I wanted to access a menu item by name instead of ordinal position, how would I do it? The only answer I can come up with is to iterate over the context menu's ItemCollection and test each menu item for a matching name. Is there any other way?

    Tuesday, August 28, 2012 9:00 PM
  • Hi rObjects

    I would advise that you wire it all up in the bindings and/or triggers.

    Annabella has answered your original question, what you ask is another. This further question is one that I hope I can best answer for you by pointing you to a complete solution I just uploaded to MSDN Samples, as I thought it sounded like a good sample:

    http://code.msdn.microsoft.com/Row-Bound-ContextMenu-be82a755

    (please give it some stars if you like it ;)

    Best regards,
    Pete


    #PEJL


    • Edited by Pete LakerMVP Tuesday, August 28, 2012 10:42 PM added beg! XD
    Tuesday, August 28, 2012 10:41 PM
  • As you've probably guessed, I'm still very new to WPF (coming at it from .NET Forms). Bit of a struggle to wrap my head around everything in the sample, but it looks exactly like the solution I was seeking, thanks!

    I don't think the following applies in my current case, but... if the logic required to enable/disable an item was not implemented in the data class and was beyond the limits of expression in a DataTrigger, one could still implement it in code-behind using a value converter in the Data Trigger binding?


    • Edited by Zenilogix Thursday, August 30, 2012 5:26 PM
    Thursday, August 30, 2012 5:25 PM
  • Hi rObjects2,

    XAML Guy's sample code is just what you need to set ContextMenuItem Isenble base on binding Value. You can check out it.

    And for the Value Converter is with no help on this demand I think. The key is DataTrigger binding.

    Have a nice day.


    Annabella Luo[MSFT]
    MSDN Community Support | Feedback to us

    Monday, September 3, 2012 9:19 AM
  • What I was wondering is whether a value converter can be specified in a data trigger i.e.

    <Style.Triggers>
      <DataTrigger Binding="{Binding myVal Converter={StaticResource MyValueConverter}}" Value="{x:Null}">
      <!-- etc. -->
      </DataTrigger>
    </Style.Triggers> 

    Monday, September 3, 2012 7:50 PM
  • In answer to your question, yes. A value converter can be specified in a data trigger.

    Here is a complete example. Extrapolate as required :)

    using System;
    using System.Windows;
    using System.Windows.Data;
    using WpfApplication79.ViewModel;
    
    namespace WpfApplication79
    {
        public partial class MainWindow : Window
        {
            public MainWindow()
            {
                InitializeComponent();
            }
        }
    
        public class MyValueConverter : IValueConverter
        {
            public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
            {
                return (bool)value ? "YES" : "NO";
            }
    
            public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
            {
                throw new NotImplementedException();
            }
        }
    }
    

    <Window x:Class="WpfApplication79.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            Title="MainWindow" Height="350" Width="525"
            xmlns:local="clr-namespace:WpfApplication79">
        <Window.Resources>
            <local:MyValueConverter x:Key="MyValueConverter"/>
        </Window.Resources>
        <StackPanel>
            <CheckBox x:Name="MyCheckBox"/>
            <TextBlock Text="Test">
                <TextBlock.Style>
                    <Style TargetType="TextBlock">
                        <Style.Triggers>
                            <DataTrigger Binding="{Binding IsChecked, ElementName=MyCheckBox, Converter={StaticResource MyValueConverter}}" Value="YES">
                                <Setter Property="Foreground" Value="Red"/>
                            </DataTrigger>
                        </Style.Triggers>
                    </Style>
                </TextBlock.Style>
            </TextBlock>
        </StackPanel>
    </Window>
    
    This is a rather pointless example (you would normally just bind to property=IsChecked and value=True) but to show a simple example, I am converting to a string "YES"/"NO"

     

    Regards,
    Pete


    #PEJL

    • Marked as answer by Zenilogix Tuesday, September 4, 2012 10:57 PM
    Monday, September 3, 2012 8:51 PM
  • Pointless example perhaps, but communicates the point nicely. Thanks!
    Tuesday, September 4, 2012 10:57 PM