【问题标题】:WPF Double Listbox menu with tracking and selection problem带有跟踪和选择问题的 WPF 双列表框菜单
【发布时间】:2019-10-29 09:33:29
【问题描述】:

我正在尝试在 WPF 中实现带有轨道的双层菜单。 我创建了我的 NavigationBar,我使用 Binding 来填充 NavigationBar。 我有两个问题:

  1. 我无法从使用的列表框中删除选择和鼠标悬停。

我尝试在 NavigationBar 中设置样式,基于找到的其他链接: How to disable highlighting on listbox but keep selection? WPF: Remove highlight effect from ListViewItem https://www.codeproject.com/Questions/838199/WPF-ListBox-not-showing-selection-highlight 但是好像不行

  1. 选择和跟踪无法正常工作

添加: 对于跟踪,我的意思是选择是粗体,并且元素“SelectedMenuItemLine”位于所选元素下方。对于第二个 NavigationBar,“SubNavigationBar”不能正常工作,当我选择第一个 NavigationBar 的一个元素时,第二个会更新所有元素,我需要选择新选择的第一个元素。

这是我的代码

我的 ViewModel(ViewModelBase 是一个实现 INotifyPropertyChanged 的​​实用程序类)

    public class MainMenuViewModel : ViewModelBase
    {
        public ObservableCollection<PluginItem> MainMenuTabs = new ObservableCollection<PluginItem>();
        private PluginItem _selectedMainPluginItem;
        private PluginItem _selectedSubPluginItem;
        private ObservableCollection<PluginItem> _subMenuTabs;

        public ObservableCollection<PluginItem> SubMenuTabs
        {
            get => _subMenuTabs;
            set
            {
                _subMenuTabs = value;
                OnPropertyChanged(nameof(SubMenuTabs));
            }
        }

        public PluginItem SelectedMainPluginItem
        {
            get => _selectedMainPluginItem;
            set
            {
                _selectedMainPluginItem = value;
                OnPropertyChanged(nameof(SelectedMainPluginItem));
                SubMenuTabs = value.PluginItems;
            }
        }

        public PluginItem SelectedSubPluginItem
        {
            get => _selectedSubPluginItem;
            set
            {
                _selectedSubPluginItem = value;
                OnPropertyChanged(nameof(SelectedSubPluginItem));
            }
        }
    }

    public class PluginItem : ViewModelBase
    {
        private ObservableCollection<PluginItem> m_PluginItems = new ObservableCollection<PluginItem>();
        public string Name { get; set; }
        public string Description { get; set; }
        public PluginItem Parent { get; set; }
        public ObservableCollection<PluginItem> PluginItems
        {
            get => m_PluginItems;
            set
            {
                m_PluginItems = value;
                foreach (var pluginItem in m_PluginItems)
                    pluginItem.Parent = this;

                OnPropertyChanged(nameof(PluginItems));
            }
        }
        public bool HasChildren => PluginItems.Count > 0;
    }

导航栏.xaml

<UserControl x:Class="DoubleMenuTest.NavigationBar"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:local="clr-namespace:DoubleMenuTest"
             mc:Ignorable="d" 
             Height="32"
             BorderThickness="0">

    <UserControl.Resources>
        <SolidColorBrush x:Key="ListBox.Static.Background" Color="#FFFFFFFF"/>
        <SolidColorBrush x:Key="ListBox.Static.Border" Color="#FFABADB3"/>
        <SolidColorBrush x:Key="ListBox.Disabled.Background" Color="#FFFFFFFF"/>
        <SolidColorBrush x:Key="ListBox.Disabled.Border" Color="#FFD9D9D9"/>
    </UserControl.Resources>

    <Grid Height="32">
        <ListBox
            Name="MenuListBox"
            BorderThickness="0"
            Background="Transparent"
            ItemsSource="{Binding RelativeSource={RelativeSource FindAncestor, 
                                    AncestorType=local:NavigationBar, 
                                    AncestorLevel=1}, 
                                    Path=ItemsSource}"
            SelectionChanged="MenuListBox_OnSelectionChanged">
            <ListBox.Style>
                <Style>
                    <Setter Property="ListBox.Background" Value="{StaticResource ListBox.Static.Background}"/>
                    <Setter Property="ListBox.BorderBrush" Value="{StaticResource ListBox.Static.Border}"/>
                    <Setter Property="ListBox.BorderThickness" Value="1"/>
                    <Setter Property="ListBox.Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}"/>
                    <Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Disabled"/>
                    <Setter Property="ScrollViewer.VerticalScrollBarVisibility" Value="Disabled"/>
                    <Setter Property="ScrollViewer.CanContentScroll" Value="true"/>
                    <Setter Property="ScrollViewer.PanningMode" Value="Both"/>
                    <Setter Property="Stylus.IsFlicksEnabled" Value="False"/>
                    <Setter Property="ListBox.VerticalContentAlignment" Value="Center"/>
                    <Setter Property="ListBox.Template">
                        <Setter.Value>
                            <ControlTemplate TargetType="{x:Type ListBox}">
                                <Grid>
                                    <Grid.ColumnDefinitions>
                                        <ColumnDefinition Width="Auto"/>
                                        <ColumnDefinition Width="*"/>
                                        <ColumnDefinition Width="Auto"/>
                                    </Grid.ColumnDefinitions>

                                    <!--<RepeatButton x:Name="LeftButton" Tag="{Binding ElementName=sv}" Width="20" Click="Left_Click">
                                        <Path x:Name="ArrowLeft" Data="M 3.18,7 C3.18,7 5,7 5,7 5,7 1.81,3.5 1.81,3.5 1.81,3.5 5,0 5,0 5,0 3.18,0 3.18,0 3.18,0 0,3.5 0,3.5 0,3.5 3.18,7 3.18,7 z" Fill="Black" Margin="3" Stretch="Uniform"/>
                                    </RepeatButton>-->

                                    <Border x:Name="Bd" Grid.Column="1" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" Padding="1" SnapsToDevicePixels="true">
                                        <ScrollViewer x:Name="sv" Focusable="false" Padding="{TemplateBinding Padding}" HorizontalScrollBarVisibility="Hidden">
                                            <ItemsPresenter SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
                                        </ScrollViewer>
                                    </Border>

                                    <!--<RepeatButton x:Name="RightButton" Grid.Column="2" Tag="{Binding ElementName=sv}" Width="20" Click="Right_Click">
                                        <Path x:Name="ArrowRight" Data="M 1.81,7 C1.81,7 0,7 0,7 0,7 3.18,3.5 3.18,3.5 3.18,3.5 0,0 0,0 0,0 1.81,0 1.81,0 1.81,0 5,3.5 5,3.5 5,3.5 1.81,7 1.81,7 z" Fill="Black" Margin="3" Stretch="Uniform"/>
                                    </RepeatButton>-->
                                </Grid>

                                <ControlTemplate.Triggers>
                                    <Trigger Property="IsEnabled" Value="false">
                                        <Setter Property="Background" TargetName="Bd" Value="{StaticResource ListBox.Disabled.Background}"/>
                                        <Setter Property="BorderBrush" TargetName="Bd" Value="{StaticResource ListBox.Disabled.Border}"/>
                                    </Trigger>

                                    <MultiTrigger>
                                        <MultiTrigger.Conditions>
                                            <Condition Property="IsGrouping" Value="true"/>
                                            <Condition Property="VirtualizingPanel.IsVirtualizingWhenGrouping" Value="false"/>
                                        </MultiTrigger.Conditions>
                                        <Setter Property="ScrollViewer.CanContentScroll" Value="false"/>
                                    </MultiTrigger>
                                </ControlTemplate.Triggers>
                            </ControlTemplate>
                        </Setter.Value>
                    </Setter>
                </Style>
            </ListBox.Style>

            <ListBox.ItemsPanel>
                <ItemsPanelTemplate>
                    <StackPanel Orientation="Horizontal"/>
                </ItemsPanelTemplate>
            </ListBox.ItemsPanel>

            <ListBox.ItemContainerStyle>
                <Style TargetType="{x:Type ListBoxItem}">
                    <Style.Triggers>
                        <Trigger Property="IsSelected" Value="True">
                            <Setter Property="FontWeight" Value="Bold" />
                        </Trigger>
                    </Style.Triggers>
                </Style>
            </ListBox.ItemContainerStyle>

            <ListBox.ItemTemplate>
                <DataTemplate>
                    <Grid Width="156" Height="26">
                        <TextBlock x:Name="MenuItem" Width="100" Text="{Binding Name}" TextAlignment="Center" />
                    </Grid>
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>

        <Line Stroke="Gray" StrokeThickness="4"
              X1="0" Y1="{Binding ActualHeight, ElementName=MenuListBox}"
              X2="{Binding ActualWidth, ElementName=MenuListBox}" Y2="{Binding ActualHeight, ElementName=MenuListBox}"
        />

        <Line x:Name="SelectedMenuItemLine"
              X1="0" Y1="{Binding ActualHeight, ElementName=MenuListBox}"
              X2="156" Y2="{Binding ActualHeight, ElementName=MenuListBox}"
              Stroke="Black" StrokeThickness="8"
        >
            <!--<Line.Style>
                        <Style TargetType="Line">
                            <Style.Triggers>
                                <DataTrigger Binding="{Binding IsSelected, ElementName=_controlBoolField}" Value="True">
                                    <Setter Property="Opacity" Value="0"/>
                                </DataTrigger>
                            </Style.Triggers>
                        </Style>
                    </Line.Style>-->
        </Line>
    </Grid>
</UserControl>

NavigationBar.xaml.cs

    public partial class NavigationBar : UserControl
    {
        private bool m_FirstTime = true;
        private bool m_LeftButtonInitialized = false;
        private bool m_RightButtonInitialized = false;
        private const double s_AnimationDuration = .2;
        private double m_Offset;

        [Bindable(true)]
        public IEnumerable ItemsSource
        {
            get => (IEnumerable)GetValue(ItemsSourceProperty);
            set => SetValue(ItemsSourceProperty, value);
        }

        public static readonly DependencyProperty ItemsSourceProperty = ListBox.ItemsSourceProperty.AddOwner(typeof(NavigationBar));

        public NavigationBar()
        {
            InitializeComponent();
        }

        public int SelectedIndex
        {
            get => MenuListBox.SelectedIndex;
            set => MenuListBox.SelectedIndex = value;
        }

        public PluginItem SelectedItem
        {
            get => (PluginItem) MenuListBox.SelectedItem;
            set => MenuListBox.SelectedItem = value;
        }

        public event SelectionChangedEventHandler NavigationSelectionChanged
        {
            add => MenuListBox.SelectionChanged += value;
            remove => MenuListBox.SelectionChanged -= value;
        }

        private void MenuListBox_OnSelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            if (m_FirstTime)
            {
                m_FirstTime = false;
                return;
            }

            var listBoxItem = (ListBoxItem)MenuListBox.ItemContainerGenerator.ContainerFromItem(MenuListBox.SelectedItem);
            if (listBoxItem == null)
                return;

            var position = listBoxItem.TransformToAncestor(MenuListBox).Transform(new Point());

            var animation1 = new DoubleAnimation(position.X, new Duration(TimeSpan.FromSeconds(s_AnimationDuration)));
            var animation2 = new DoubleAnimation(position.X + listBoxItem.ActualWidth, new Duration(TimeSpan.FromSeconds(s_AnimationDuration)));
            SelectedMenuItemLine.BeginAnimation(Line.X1Property, animation1);
            SelectedMenuItemLine.BeginAnimation(Line.X2Property, animation2);
        }

        //private void Left_Click(object sender, RoutedEventArgs e)
        //{
        //  var btn = sender as System.Windows.Controls.Primitives.RepeatButton;
        //  var sv = btn.Tag as ScrollViewer;
        //  m_Offset -= 1;
        //  if (m_Offset < 0)
        //      m_Offset = 0;

        //  sv.ScrollToHorizontalOffset(m_Offset);

        //  if (m_LeftButtonInitialized)
        //      return;

        //  sv.ScrollChanged += (o, args) => MenuListBox_OnSelectionChanged(null, null);
        //  m_LeftButtonInitialized = true;
        //}

        //private void Right_Click(object sender, RoutedEventArgs e)
        //{
        //  var btn = sender as System.Windows.Controls.Primitives.RepeatButton;
        //  var sv = btn.Tag as ScrollViewer;
        //  m_Offset += 1;
        //  if (m_Offset > sv.ScrollableWidth)
        //      m_Offset = sv.ScrollableWidth;

        //  sv.ScrollToHorizontalOffset(m_Offset);

        //  if (m_RightButtonInitialized)
        //      return;

        //  sv.ScrollChanged += (o, args) => MenuListBox_OnSelectionChanged(null, null);
        //  m_RightButtonInitialized = true;
        //}
    }

MainWindow.xaml

<Window x:Class="DoubleMenuTest.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:DoubleMenuTest"
        mc:Ignorable="d"
        Title="MainWindow"
        x:Name="TestMainWindow"
        Height="768"
        Width="1024">

    <Window.Resources>
        <BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter"/>
        <!--<ControlTemplate x:Key="ListViewControlTemplate1" TargetType="{x:Type ListView}">
            <Grid Width="156" Height="26">
                <TextBlock x:Name="MenuItem" Width="100" Text="{Binding Name}" TextAlignment="Center" />
            </Grid>
        </ControlTemplate>-->
    </Window.Resources>

    <StackPanel>
        <local:NavigationBar
                x:Name="MainNavigationBar"
                NavigationSelectionChanged="NavigationBar_OnSelectionChanged"
                ItemsSource="{Binding MainMenuTabs}"
                />
        <!--DataContext="{Binding DataContext, ElementName=TestMainWindow}"-->

        <local:NavigationBar
                x:Name="SubNavigationBar"
                NavigationSelectionChanged="SubNavigationBar_OnSelectionChanged"
                DataContext="{Binding SelectedMainPluginItem}"
                ItemsSource="{Binding PluginItems}"
                DataContextChanged="SubNavigationBar_OnDataContextChanged"

                />
        <!--ItemsSource="{Binding Path=SelectedMainPluginItem.PluginItems}"-->
        <!--ItemsSource="{Binding SubMenuTabs}"-->
        <!--ItemsSource="{Binding ElementName=MainNavigationBar, Path=SelectedItem.PluginItems}"-->

        <ListView x:Name="PluginListView"
                      Width="1024"
                      Height="604"
                      Background="Transparent"
                      HorizontalAlignment="Left"
                      VerticalAlignment="Bottom"
                      SelectionMode="Single"
                      VerticalContentAlignment="Top"
                      BorderThickness="0"
                      ScrollViewer.HorizontalScrollBarVisibility="Disabled"
                      ScrollViewer.VerticalScrollBarVisibility="Auto"
                      DataContext="{Binding SelectedSubPluginItem}"
                      ItemsSource="{Binding PluginItems}"
                      >

            <ListView.ItemTemplate>
                <DataTemplate>
                    <Grid Margin="5">
                        <TextBlock Text="{Binding Name}" />
                        <!--<local:MenuPluginItem MouseLeftButtonUp="MenuItem_OnMouseLeftButtonUp"
                                                  MouseRightButtonUp="MenuItem_OnMouseRightButtonUp"/>-->
                    </Grid>
                </DataTemplate>
            </ListView.ItemTemplate>
        </ListView>

    </StackPanel>
</Window>

MainWindow.xaml.cs

    public partial class MainWindow : Window
    {
        private readonly MainMenuViewModel m_ViewModel;

        public MainWindow()
        {
            InitializeComponent();

            m_ViewModel = CreateViewModel();

            DataContext = m_ViewModel;

            MainNavigationBar.ItemsSource = m_ViewModel.MainMenuTabs;
            MainNavigationBar.SelectedIndex = 0;

            DependencyPropertyDescriptor.FromProperty(ItemsControl.ItemsSourceProperty, typeof(NavigationBar)).AddValueChanged(SubNavigationBar,
                (sender, args) =>
                {
                    SubNavigationBar.SelectedItem = m_ViewModel.SelectedMainPluginItem.PluginItems[0];
                    SubNavigationBar.SelectedIndex = 0;
                });
        }

        private static MainMenuViewModel CreateViewModel()
        {
            var viewModel = new MainMenuViewModel
            {
                MainMenuTabs = new ObservableCollection<PluginItem>
                {
                    CreateItem(1),
                    CreateItem(2),
                    CreateItem(3),
                    CreateItem(4)
                }
            };
            return viewModel;
        }

        private static PluginItem CreateItem(int id)
        {
            var pluginItem = new PluginItem { Name = $"Test {id}", Description = $"Desc {id}" };
            pluginItem.PluginItems = new ObservableCollection<PluginItem>
            {
                new PluginItem { Name = $"Test {id} 1", Description = $"Desc {id} 1", Parent = pluginItem, PluginItems = new ObservableCollection<PluginItem>
                {
                    new PluginItem { Name = $"Test {id} 1 1" },
                    new PluginItem { Name = $"Test {id} 1 2" },
                    new PluginItem { Name = $"Test {id} 1 3" },
                    new PluginItem { Name = $"Test {id} 1 4" }
                }},
                new PluginItem { Name = $"Test {id} 2", Description = $"Desc {id} 2", Parent = pluginItem, PluginItems = new ObservableCollection<PluginItem>
                {
                    new PluginItem { Name = $"Test {id} 2 1" },
                    new PluginItem { Name = $"Test {id} 2 2" },
                    new PluginItem { Name = $"Test {id} 2 3" },
                    new PluginItem { Name = $"Test {id} 2 4" }
                }},
                new PluginItem { Name = $"Test {id} 3", Description = $"Desc {id} 3", Parent = pluginItem, PluginItems = new ObservableCollection<PluginItem>
                {
                    new PluginItem { Name = $"Test {id} 3 1" },
                    new PluginItem { Name = $"Test {id} 3 2" },
                    new PluginItem { Name = $"Test {id} 3 3" },
                    new PluginItem { Name = $"Test {id} 3 4" }
                }},
                new PluginItem { Name = $"Test {id} 4", Description = $"Desc {id} 4", Parent = pluginItem, PluginItems = new ObservableCollection<PluginItem>
                {
                    new PluginItem { Name = $"Test {id} 4 1" },
                    new PluginItem { Name = $"Test {id} 4 2" },
                    new PluginItem { Name = $"Test {id} 4 3" },
                    new PluginItem { Name = $"Test {id} 4 4" }
                }}
            };
            return pluginItem;
        }

        private void MenuItem_OnMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
        {
        }

        private void MenuItem_OnMouseRightButtonUp(object sender, MouseButtonEventArgs e)
        {
        }

        private void NavigationBar_OnSelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            m_ViewModel.SelectedMainPluginItem = MainNavigationBar.SelectedItem;
        }

        private void SubNavigationBar_OnSelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            if (SubNavigationBar.SelectedItem != null)
                m_ViewModel.SelectedSubPluginItem = SubNavigationBar.SelectedItem;
        }

        private void SubNavigationBar_OnDataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
        {
            //SubNavigationBar.SelectedIndex = 0;
        }
    }

提前感谢所有阅读

添加: 在此链接,您可以下载示例项目 https://drive.google.com/file/d/1Z32br2WWnA8OJ8JgPTnkUEUCSJpT7jtf/view?usp=sharing

【问题讨论】:

    标签: wpf listbox


    【解决方案1】:

    我不确定跟踪不起作用是什么意思,关于你问题的第一部分,只需设置ListBoxItem的项目容器样式:

      <ListBox.ItemContainerStyle>
                <Style TargetType="{x:Type ListBoxItem}">
                    <Setter Property="Background" Value="Transparent" />
                    <Setter Property="Template">
                        <Setter.Value>
                            <ControlTemplate TargetType="{x:Type ListBoxItem}">
                                <ContentPresenter />
                            </ControlTemplate>
                        </Setter.Value>
                    </Setter>
                    <Style.Triggers>
                        <Trigger Property="IsSelected" Value="True">
                            <Setter Property="FontWeight" Value="Bold" />
                        </Trigger>
                    </Style.Triggers>
                </Style>
            </ListBox.ItemContainerStyle>
    

    您的第二个问题与 listBoxItem.ActualWidth 未在您的 MenuListBox_OnSelectionChanged 事件处理程序中按时计算有关,第一次执行有时使用 0,或者将其硬编码为 156,就像您对 @987654327 所做的那样@或有一个指定值的DP:

        private void MenuListBox_OnSelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            if (m_FirstTime)
            {
                m_FirstTime = false;
                return;
            }
    
            var listBoxItem = (ListBoxItem)MenuListBox.ItemContainerGenerator.ContainerFromItem(MenuListBox.SelectedItem);
            if (listBoxItem == null)
                return;
    
            var position = listBoxItem.TransformToAncestor(MenuListBox).Transform(new Point());
            var animation1 = new DoubleAnimation(position.X, new Duration(TimeSpan.FromSeconds(s_AnimationDuration)));
            var animation2 = new DoubleAnimation(position.X + 156, new Duration(TimeSpan.FromSeconds(s_AnimationDuration)));
            SelectedMenuItemLine.BeginAnimation(Line.X1Property, animation1);
            SelectedMenuItemLine.BeginAnimation(Line.X2Property, animation2);
        }
    

    【讨论】:

    • 好的,这项工作的亮点。对于跟踪,我的意思是选择是粗体,并且元素“SelectedMenuItemLine”位于所选元素下方。对于第二个 NavigationBar,“SubNavigationBar”无法正常工作,当我选择第一个 NavigationBar 的元素时,第二个会更新所有元素,我需要选择新选择的第一个元素
    • @AlanSerpini 所以默认情况下,您希望第二个 subNavBar 在第一个项目下有粗线?
    • 是的,当 DataContext 和 ItemSource 的绑定发生变化时,我希望第一个元素被选择为粗体并带有下面的黑线。这是在 DataContext 和 ItemSource 上绑定的第二个 NavigationBar,这似乎造成了问题。 &lt;local:NavigationBar x:Name="SubNavigationBar" NavigationSelectionChanged="SubNavigationBar_OnSelectionChanged" DataContext="{Binding SelectedMainPluginItem}" ItemsSource="{Binding PluginItems}" DataContextChanged="SubNavigationBar_OnDataContextChanged" /&gt;
    • @AlanSerpini 好吧,我不太确定这个问题的根源是什么,这个问题似乎只发生在第一次访问 ListBoxItem 时,我已经尝试了一些技巧,似乎没有去工作。我可能稍后再试。
    • 对不起,我的沟通能力不好,问题是行跟随位置,但所选项目不是粗体。我在这里上传了示例代码drive.google.com/file/d/1Z32br2WWnA8OJ8JgPTnkUEUCSJpT7jtf/…
    猜你喜欢
    • 2010-12-09
    • 1970-01-01
    • 2022-12-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-03-08
    • 2014-08-29
    • 2023-04-02
    相关资源
    最近更新 更多