binding style of menu item from mvvm

essamce 621 Reputation points
2023-09-02T14:28:25.01+00:00

is it possible to bind menuItems to a property in viewmodel?

here is my code:

   <Menu x:Name="mainMenus"
       VerticalAlignment="Center" 
       Background="Transparent" 
       ItemsSource="{Binding Path=MenuItems}" 
         >
       <Menu.Resources>
           <Style TargetType="{x:Type MenuItem}" BasedOn="{StaticResource this.MenuItem.Title}">
               <Setter Property="Header" Value="{Binding Path=Name}"/>
               <Setter Property="Command" Value="{Binding Path=MenuItemCmd}"/>
               <!--<Setter Property="Style" 
                   Value="{Binding Path=StyleName, 
                   Converter={StaticResource styleNameParser}}" />-->
               <Setter Property="CommandParameter" Value="{Binding}"/>
           </Style>
           <HierarchicalDataTemplate 
              DataType="{x:Type models:MenuNode}"
              ItemsSource="{Binding Path=Children}" 
              >
           </HierarchicalDataTemplate>
       </Menu.Resources>
</Menu>
    <Style x:Key="this.MenuItem.Title"  TargetType="{x:Type MenuItem}" 
           
           BasedOn="{StaticResource CurrentTheme.Control}">
        <Setter Property = "Height" Value= "25"/>

        <!--<Style.Triggers>
            <Trigger Property="MenuItem.IsMouseOver" Value="true">
                <Setter Property="Foreground" Value="Black" />
            </Trigger>
        </Style.Triggers>-->
    </Style>
    <Style x:Key="this.MenuItem.Intermediate" TargetType="{x:Type MenuItem}" 
           BasedOn="{StaticResource this.MenuItem.Title}">
        <!--<Setter Property="Foreground" Value="Blue" />-->
        <Style.Triggers>
            <Trigger Property="MenuItem.IsMouseOver" Value="true">
                <Setter Property="Foreground" Value="Black" />
            </Trigger>
        </Style.Triggers>
    </Style>
    <Style x:Key="this.MenuItem.Leaf" TargetType="{x:Type MenuItem}" 
           BasedOn="{StaticResource this.MenuItem.Intermediate}">
        <!--<Setter Property="Foreground" Value="Yellow" />-->
        <Setter Property="Command" Value="{Binding SegCmd}" />
    </Style>
</ResourceDictionary>

view model


        public ObservableCollection<UI.Models.MenuNode> MenuItems { get; set; }

 public class MenuNode : Utils.ViewModelBase
 {
     #region ctor

     public MenuNode()
     {
         Children = new ObservableCollection<MenuNode>();
         MenuItemCmd = new Utils.RelayCommand<object>(OnMenuAction);
     }

     #endregion

     public bool IsLeaf() => Children.Count == 0;

     public MenuNode FindOrNull(Predicate<MenuNode> filter)
     {
         if (filter(this))
         {
             return this;
         }
         else if (! IsLeaf())
         {
             foreach (var child in Children)
             {
                 if (filter(child))
                 {
                     return child;
                 }
                 else
                 {
                     return child.FindOrNull(filter);
                 }
             }
         }

         return null;
     }

     public ObservableCollection<MenuNode> Children { get; set; }

     private void OnMenuAction(object param)
     {
         var node = param as MenuNode;
         string name = string.Empty;
         if (node != null) 
             name = node.Name;

         log.Instance.log($" {name} <{this}.{nameof(OnMenuAction)}>");
     }


     #region props

     public Utils.RelayCommand<object> MenuItemCmd { get; protected set; }

     private bool _isEnabled;
     public bool IsEnabled
     {
         get => _isEnabled;
         set
         {
             _isEnabled = value;
             OnPropertyChanged();
         }
     }
     private string _name;
     public string Name
     {
         get => _name;
         set
         {
             _name = value;
             OnPropertyChanged();
         }
     }
     private string _style;
     public string StyleName
     {
         get => _style;
         set
         {
             _style = value;
             OnPropertyChanged();
         }
     }

     #endregion


 }

every things works fine, but i'm wondering if it's possible to bind each menu item to the property StyleName and using converter like this:


    public class StyleNameParser : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            if (targetType != typeof(Style))
            {
                throw new InvalidOperationException("The target must be a Style");
            }

            string styleValue = value?.ToString();
            if (styleValue == null)
            {
                return null;
            }

            Style newStyle = (Style)Application.Current.TryFindResource(styleValue);
            return newStyle;
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    

example in viewmodel would be like:

 var item = new UI.Models.MenuNode()
 {
     Name = node.Value.ToString(),
     StyleName = "this.MenuItem.Title",
     IsEnabled = true,
 };
 // add menu item
 MenuItems.Add(item);

i'm using VS 2022 c# class lib project .net framework 4.8

Visual Studio
Visual Studio
A family of Microsoft suites of integrated development tools for building applications for Windows, the web and mobile devices.
4,763 questions
C#
C#
An object-oriented and type-safe programming language that has its roots in the C family of languages and includes support for component-oriented programming.
10,481 questions
XAML
XAML
A language based on Extensible Markup Language (XML) that enables developers to specify a hierarchy of objects with a set of properties and logic.
778 questions
{count} votes

Accepted answer
  1. Hui Liu-MSFT 46,961 Reputation points Microsoft Vendor
    2023-09-04T09:56:30.1433333+00:00

    Hi,@essamce. I can't reproduce your problem. You could try modifying your code by referring to the example below. Please let me know if there are questions.

    Create a custom IValueConverter that converts a string to a Style. In the converter, you can define the mapping between the string values and the corresponding Style resources.

    In your XAML, bind the Style property of the MenuItem to the StyleName property using the converter.

    Create a custom IValueConverter class to convert the StyleName string to a Style:

    
    public class StyleNameConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            if (value is string styleName && Application.Current.TryFindResource(styleName) is Style style)
            {
                return style;
            }
    
            return DependencyProperty.UnsetValue; // Return an unset value if the style is not found.
        }
    
        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }
    
    

    Add the StyleNameConverter to XAML resources:

    
    <Window.Resources>
        <local:StyleNameConverter x:Key="styleNameConverter" />
    </Window.Resources>
    
    

    Bind the MenuItem.Style property to the StyleName property using the converter:

    
    <MenuItem Header="{Binding Name}"
              Command="{Binding MenuItemCmd}"
              CommandParameter="{Binding}"
              Style="{Binding StyleName, Converter={StaticResource styleNameConverter}}">
    </MenuItem>
    
    

    In this example, the StyleNameConverter is used to convert the StyleName property (a string) to a Style. It does this by attempting to find a Style resource in the application's resources with the name specified in StyleName. If it finds a matching Style, it returns that Style; otherwise, it returns an unset value.

    Make sure you have defined your Style resources in your XAML resources or in your application's resource dictionary, and the StyleName property in your data source matches the names of these Style resources.

    Update:

      <Window.DataContext>
            <local:ViewModel/>
        </Window.DataContext>
        <Window.Resources>
            <Style x:Key="DefaultMenuItemStyle" TargetType="{x:Type MenuItem}">
                <Setter Property="Foreground" Value="Black" />
                <Setter Property="Background" Value="LightGreen" />
              
                <Setter Property="Command" Value="{Binding MenuItemCmd}"/>
               
            </Style>
            <Style x:Key="Style1" TargetType="{x:Type MenuItem}">
                <Setter Property="Foreground" Value="Black" />
                <Setter Property="Background" Value="pink" />
            
                <Setter Property="Command" Value="{Binding Command}"/>
               
            </Style>
    
            <Style x:Key="Style2" TargetType="{x:Type MenuItem}">
               
                <Setter Property="Foreground" Value="Black" />
                <Setter Property="Background" Value="LightBlue" />
              
                <Setter Property="Command" Value="{Binding Command}"/>
                
    
            </Style>
            <local:MenuItemStyleSelector x:Key="ItemStyleSelector" DefaultStyle="{StaticResource DefaultMenuItemStyle}"
                                         Style1="{StaticResource Style1}"
                                         Style2="{StaticResource Style2}"    />
        </Window.Resources>
        <Grid>
        
            <StackPanel>
            
    
                <Menu DockPanel.Dock="Top" ItemsSource="{Binding MenuItems}"   ItemContainerStyleSelector="{StaticResource ItemStyleSelector}">
              
                    <Menu.ItemTemplate>
                        <HierarchicalDataTemplate DataType="{x:Type local:MenuItemViewModel}" ItemsSource="{Binding Path=MenuItems}">
                            <TextBlock Text="{Binding Header}"/>
                        </HierarchicalDataTemplate>
                    </Menu.ItemTemplate>
                </Menu>
            </StackPanel>
    
    
        </Grid>
    

    Codebedhind:

    
        public class MenuItemStyleSelector : StyleSelector
        {
            public Style DefaultStyle { get; set; }
            public Style Style1 { get; set; }
            public Style Style2 { get; set; }
           
    
            public override Style SelectStyle(object item, DependencyObject container)
            {
                if (item is MenuItemViewModel menuItem)
                {
                    switch (menuItem.StyleName)
                    {
                        case "Style1":
                            return Style1;
                        case "Style2":
                            return Style2;
                       
                        default:
                            return DefaultStyle;
                    }
                }
    
                return base.SelectStyle(item, container);
            }
        }
       
        public class ViewModel : INotifyPropertyChanged
        {
            public ObservableCollection<MenuItemViewModel> MenuItems { get; set; }
    
         
            public ViewModel()
            {
                MenuItems = new ObservableCollection<MenuItemViewModel>
                {
                    new MenuItemViewModel { Header = "Alpha" ,Level=0,StyleName="Style2" },
                    new MenuItemViewModel { Header = "Beta",Level=1,StyleName="Style2",
                        MenuItems = new ObservableCollection<MenuItemViewModel>
                            {
                                new MenuItemViewModel { Header = "Beta1",Level=1 ,StyleName="Style1"},
                                new MenuItemViewModel { Header = "Beta2",Level=1, StyleName="Style1",
                                    MenuItems = new ObservableCollection<MenuItemViewModel>
                                    {
                                        new MenuItemViewModel { Header = "Beta1a" ,Level=2,StyleName="Style1",},
                                        new MenuItemViewModel { Header = "Beta1b" ,Level=2,}
                                    }
                                },
                                new MenuItemViewModel { Header = "Beta3",Level=0,StyleName="Style2", }
                            }
                    },
                    new MenuItemViewModel { Header = "Gamma",Level=1 }
                };
             
            }
            
         
            public event PropertyChangedEventHandler PropertyChanged;
            protected void OnPropertyChanged([CallerMemberName] string name = null)
            {
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
            }
        }
    
    
    
        public class MenuItemViewModel : INotifyPropertyChanged
        {
            public string Header { get; set; }
            public ObservableCollection<MenuItemViewModel> MenuItems { get; set; }
            private string _style;
            public string StyleName
            {
                get => _style;
                set
                {
                    _style = value;
                    OnPropertyChanged();
                }
            }
            public event PropertyChangedEventHandler PropertyChanged;
            protected void OnPropertyChanged([CallerMemberName] string name = null)
            {
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
            }
            public int Level { get; set; }
            private readonly ICommand _command;
    
            public MenuItemViewModel()
            {
                _command = new MyCommand(Execute);
            }
            public ICommand Command
            {
                get
                {
                    return _command;
                }
            }
            private void Execute()
            {
                MessageBox.Show("Clicked at " + Header);
            }
        }
        public class MyCommand : ICommand
        {
            private readonly Action _action;
    
            public MyCommand(Action action)
            {
                _action = action;
            }
    
            public void Execute(object o)
            {
                _action();
            }
    
            public bool CanExecute(object o)
            {
                return true;
            }
    
            public event EventHandler CanExecuteChanged
            {
                add { }
                remove { }
            }
        }
    
    

    The result:


    If the response is helpful, please click "Accept Answer" and upvote it.

    Note: Please follow the steps in our documentation to enable e-mail notifications if you want to receive the related email notification for this thread.


0 additional answers

Sort by: Most helpful