0

I'm trying to implement a double level menu with track in WPF. I created my NavigationBar, I use Binding to fill the NavigationBar. I've two problem:

  1. I'm not able to remove the selection and mouse over from listbox used.

I tried to set the style in NavigationBar, base on some other link found: 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 But seems not working

  1. Seletion and track doesn't work fine

ADD: For tracking I mean Selection is Bold and the element "SelectedMenuItemLine" is under the selected element. For the second NavigationBar, "SubNavigationBar", does not work fine, when I select an element of the first NavigationBar the second updates all element and I need to select the first element of the new selection.

Here my code

My ViewModel (ViewModelBase is a utility class that implement 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;
    }

NavigationBar.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;
        }
    }

Thank in advance to all that read

ADD: At this link you can download the example projects https://drive.google.com/file/d/1Z32br2WWnA8OJ8JgPTnkUEUCSJpT7jtf/view?usp=sharing

1 Answers1

0

I am not sure what do you mean by the tracking that doesn't work, regarding the first part of your question, just set the item container style of the 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>

Your second problem is related to the listBoxItem.ActualWidth not being calculated on time in your MenuListBox_OnSelectionChanged event handler, the first execution is sometimes with a 0, either hardcode it to 156 like you are doing with the X2 or have a DP where the value is specified:

    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);
    }
SamTh3D3v
  • 9,854
  • 3
  • 31
  • 47
  • Ok, this work for highlight. For tracking I mean Selection is Bold and the element "SelectedMenuItemLine" is under the selected element. For the second NavigationBar, "SubNavigationBar", does not work fine, when I select an element of the first NavigationBar the second updates all element and I need to select the first element of the new selection – Alan Serpini Oct 29 '19 at 14:13
  • @AlanSerpini so by default, you want the second subNavBar to have the bold line under the first item? – SamTh3D3v Oct 29 '19 at 14:51
  • Yes, when the binding of DataContext and ItemSource change, I whant that first element is selected with Bold and with the black line under. This is the second NavigationBar with the Binding on DataContext and ItemSource, that seems create the problem. `` – Alan Serpini Oct 29 '19 at 15:30
  • @AlanSerpini well I am not quite sure what's the source of that issue, the problem seems to occur only on the first access to the ListBoxItem, i've tried a couple of hacks around that, nothing seems to work. I might try again later. – SamTh3D3v Oct 29 '19 at 16:05
  • sorry for my bad communication ability, the problem is that line follow the position, but selected item isn't Bold. I uploaded example here code https://drive.google.com/file/d/1Z32br2WWnA8OJ8JgPTnkUEUCSJpT7jtf/view?usp=sharing – Alan Serpini Oct 30 '19 at 14:33
  • Addendum: If you set submenu item in different number, there is more visible the problem. Example replace in CreateItem with `new PluginItem { Name = $"Test {id} 1", Description = $"Desc {id} 1", Parent = pluginItem, PluginItems = new ObservableCollection { new PluginItem { Name = $"Test {id} 1 1" }, }}, new PluginItem { Name = $"Test {id} 3", Description = $"Desc {id} 3", Parent = pluginItem, PluginItems = new ObservableCollection { new PluginItem { Name = $"Test {id} 3 1" }, new PluginItem { Name = $"Test {id} 3 2" }, }} ` – Alan Serpini Oct 30 '19 at 14:58