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