2010-09-06 10 views
36

Dado el siguiente código:¿Artículos de menú seleccionables mutuamente exclusivos?

<MenuItem x:Name="MenuItem_Root" Header="Root"> 
    <MenuItem x:Name="MenuItem_Item1" IsCheckable="True" Header="item1" /> 
    <MenuItem x:Name="MenuItem_Item2" IsCheckable="True" Header="item2"/> 
    <MenuItem x:Name="MenuItem_Item3" IsCheckable="True" Header="item3"/> 
</MenuItem> 

En XAML, ¿hay una manera de crear de menuitem checkable que son mutuamente excluyentes? ¿Dónde está el usuario revisa el elemento 2? Los elementos 1 y 3 se desactivan automáticamente.

puedo lograr esto en el código subyacente mediante el control de los eventos de clic en el menú, determinar qué elemento fue marcada, y desactivando los otros menuitems. Estoy pensando que hay una manera más fácil.

¿Alguna idea?

+1

En base a las respuestas proporcionadas, Diría que no hay una manera más fácil de hacerlo en el código subyacente con un bucle foreach que desmarca todos los elementos además del elemento nuevamente marcado. –

Respuesta

39

Esto puede no ser lo que estás buscando, pero podrías escribir una extensión para la clase MenuItem que te permite usar algo como la propiedad GroupName de la clase RadioButton. He modificado ligeramente this ejemplo práctica para la ampliación de manera similar ToggleButton controles y reelaborado un poco para su situación y se acercó con esto:

using System; 
using System.Collections.Generic; 
using System.Windows; 
using System.Windows.Controls; 
using System.Windows.Controls.Primitives; 

namespace WpfTest 
{ 
    public class MenuItemExtensions : DependencyObject 
    { 
      public static Dictionary<MenuItem, String> ElementToGroupNames = new Dictionary<MenuItem, String>(); 

      public static readonly DependencyProperty GroupNameProperty = 
       DependencyProperty.RegisterAttached("GroupName", 
              typeof(String), 
              typeof(MenuItemExtensions), 
              new PropertyMetadata(String.Empty, OnGroupNameChanged)); 

      public static void SetGroupName(MenuItem element, String value) 
      { 
       element.SetValue(GroupNameProperty, value); 
      } 

      public static String GetGroupName(MenuItem element) 
      { 
       return element.GetValue(GroupNameProperty).ToString(); 
      } 

      private static void OnGroupNameChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) 
      { 
       //Add an entry to the group name collection 
       var menuItem = d as MenuItem; 

       if (menuItem != null) 
       { 
        String newGroupName = e.NewValue.ToString(); 
        String oldGroupName = e.OldValue.ToString(); 
        if (String.IsNullOrEmpty(newGroupName)) 
        { 
          //Removing the toggle button from grouping 
          RemoveCheckboxFromGrouping(menuItem); 
        } 
        else 
        { 
          //Switching to a new group 
          if (newGroupName != oldGroupName) 
          { 
           if (!String.IsNullOrEmpty(oldGroupName)) 
           { 
            //Remove the old group mapping 
            RemoveCheckboxFromGrouping(menuItem); 
           } 
           ElementToGroupNames.Add(menuItem, e.NewValue.ToString()); 
           menuItem.Checked += MenuItemChecked; 
          } 
        } 
       } 
      } 

      private static void RemoveCheckboxFromGrouping(MenuItem checkBox) 
      { 
       ElementToGroupNames.Remove(checkBox); 
       checkBox.Checked -= MenuItemChecked; 
      } 


      static void MenuItemChecked(object sender, RoutedEventArgs e) 
      { 
       var menuItem = e.OriginalSource as MenuItem; 
       foreach (var item in ElementToGroupNames) 
       { 
        if (item.Key != menuItem && item.Value == GetGroupName(menuItem)) 
        { 
          item.Key.IsChecked = false; 
        } 
       } 
      } 
     } 
} 

Luego, en el XAML, se escribiría:

 <MenuItem x:Name="MenuItem_Root" Header="Root"> 
      <MenuItem x:Name="MenuItem_Item1" YourNamespace:MenuItemExtensions.GroupName="someGroup" IsCheckable="True" Header="item1" /> 
      <MenuItem x:Name="MenuItem_Item2" YourNamespace:MenuItemExtensions.GroupName="someGroup" IsCheckable="True" Header="item2"/> 
      <MenuItem x:Name="MenuItem_Item3" YourNamespace:MenuItemExtensions.GroupName="someGroup" IsCheckable="True" Header="item3"/> 
     </MenuItem> 

Es un poco molesto, pero ofrece la ventaja de no forzarte a escribir ningún código de procedimiento adicional (aparte de la clase de extensión, por supuesto) para implementarlo.

crédito va a Brad Cunningham que fue autor de la solución original ToggleButton.

+0

[Respuesta de MK10] (http://stackoverflow.com/a/18643222/2530736) muestra cómo puede deshabilitar la casilla de verificación – Vanlalhriata

4

no hay un mecanismo incorporado para realizar esta acción en XAML, tendrá que rodar su propia solución o conseguir una solución existente si está disponible.

0

Basta con crear una plantilla para Menultem que contendrá un RadioButton con un NombreGrupo fijar a algún valor. También puede cambiar la plantilla de RadioButtons para que se parezca al glifo de comprobación predeterminado de MenuItem (que se puede extraer fácilmente con Expression Blend).

Eso es todo!

+0

Estoy con usted en este caso. Mucha gente está haciendo demasiadas cosas en ingeniería ... Es exactamente tan simple como lo has descrito y lo he hecho muchas veces antes, incluso tan elaborado como un panel de menú de cajones. WPF permite un estilo insano, ¿por qué no aprovecharlo? –

+0

No es tan simple. Las respuestas de Rhyous y AnjumSKhan trabajan en los detalles de esto. – migle

0

Se podría hacer algo como esto:

 <Menu> 
      <MenuItem Header="File"> 
       <ListBox BorderThickness="0" Background="Transparent"> 
        <ListBox.ItemsPanel> 
         <ItemsPanelTemplate> 
          <StackPanel /> 
         </ItemsPanelTemplate> 
        </ListBox.ItemsPanel> 
        <ListBox.ItemContainerStyle> 
         <Style TargetType="{x:Type ListBoxItem}"> 
          <Setter Property="Template"> 
           <Setter.Value> 
            <ControlTemplate> 
             <MenuItem IsCheckable="True" IsChecked="{Binding IsSelected, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListBoxItem}}}" Header="{Binding Content, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListBoxItem}}}" /> 
            </ControlTemplate> 
           </Setter.Value> 
          </Setter> 
         </Style> 
        </ListBox.ItemContainerStyle> 
        <ListBox.Items> 
         <ListBoxItem Content="Test" /> 
         <ListBoxItem Content="Test2" /> 
        </ListBox.Items> 
       </ListBox> 
      </MenuItem> 
     </Menu> 

Tiene algún efecto secundario raro visualmente (verá cuando lo utiliza), pero funciona, no obstante

2

que logra esto utilizando una pareja de líneas de código:

Primera declarar una variable:

MenuItem LastBrightnessMenuItem =null; 

Cuando estamos considerando un grupo de elementos de menú, hay una probabilidad de usar un único controlador de eventos. En este caso podemos utilizar esta lógica:

private void BrightnessMenuClick(object sender, RoutedEventArgs e) 
       { 

        if (LastBrightnessMenuItem != null) 
        { 
         LastBrightnessMenuItem.IsChecked = false; 
        } 

        MenuItem m = sender as MenuItem; 
        LastBrightnessMenuItem = m; 

        //Handle the rest of the logic here 


       } 
4

simplemente pensé que iba a tirar mi solución, ya que ninguna de las respuestas se reunió mis necesidades. Mi solución completa está aquí ...

WPF MenuItem as a RadioButton

Sin embargo, la idea básica es utilizar ItemContainerStyle.

<MenuItem.ItemContainerStyle> 
    <Style TargetType="MenuItem"> 
     <Setter Property="Icon" Value="{DynamicResource RadioButtonResource}"/> 
     <EventSetter Event="Click" Handler="MenuItemWithRadioButtons_Click" /> 
    </Style> 
</MenuItem.ItemContainerStyle> 

Y el siguiente evento de clic debe añadirse para que el RadioButton se comprueba cuando se hace clic en el Menultem (de lo contrario hay que hacer clic exactamente en el RadioButton):

private void MenuItemWithRadioButtons_Click(object sender, System.Windows.RoutedEventArgs e) 
{ 
    MenuItem mi = sender as MenuItem; 
    if (mi != null) 
    { 
     RadioButton rb = mi.Icon as RadioButton; 
     if (rb != null) 
     { 
      rb.IsChecked = true; 
     } 
    } 
} 
+0

Me gusta esta solución, aunque echo de menos el tic cuando configuro MenuItem.IsCheck = True. – Gqqnbig

6

También se puede utilizar una Comportamiento. Como éste:

<MenuItem Header="menu"> 

    <MenuItem x:Name="item1" Header="item1" IsCheckable="true" ></MenuItem> 
    <MenuItem x:Name="item2" Header="item2" IsCheckable="true"></MenuItem> 
    <MenuItem x:Name="item3" Header="item3" IsCheckable="true" ></MenuItem> 

    <i:Interaction.Behaviors> 
    <local:MenuItemButtonGroupBehavior></local:MenuItemButtonGroupBehavior> 
    </i:Interaction.Behaviors> 

</MenuItem> 


public class MenuItemButtonGroupBehavior : Behavior<MenuItem> 
{ 
    protected override void OnAttached() 
    { 
     base.OnAttached(); 

     GetCheckableSubMenuItems(AssociatedObject) 
      .ToList() 
      .ForEach(item => item.Click += OnClick); 
    } 

    protected override void OnDetaching() 
    { 
     base.OnDetaching(); 

     GetCheckableSubMenuItems(AssociatedObject) 
      .ToList() 
      .ForEach(item => item.Click -= OnClick); 
    } 

    private static IEnumerable<MenuItem> GetCheckableSubMenuItems(ItemsControl menuItem) 
    { 
     var itemCollection = menuItem.Items; 
     return itemCollection.OfType<MenuItem>().Where(menuItemCandidate => menuItemCandidate.IsCheckable); 
    } 

    private void OnClick(object sender, RoutedEventArgs routedEventArgs) 
    { 
     var menuItem = (MenuItem)sender; 

     if (!menuItem.IsChecked) 
     { 
      menuItem.IsChecked = true; 
      return; 
     } 

     GetCheckableSubMenuItems(AssociatedObject) 
      .Where(item => item != menuItem) 
      .ToList() 
      .ForEach(item => item.IsChecked = false); 
    } 
} 
+0

Terminé usando esta solución en lugar de la respuesta aceptada, ya que era el ámbito de la aplicación. Terminé teniendo problemas con múltiples instancias del mismo control (el nombre registrado es compartido). – Sturm

0

Aquí hay otro enfoque que utiliza RoutedUICommands, una propiedad pública, enumeración y DataTriggers. Esta es una solución bastante detallada. Desafortunadamente, no veo ninguna forma de hacer que Style.Triggers sea más pequeño, porque no sé cómo decir que el valor de enlace es lo único diferente. (Por cierto, para MVVMers esto es un terrible ejemplo que puse todo en la clase MainWindow sólo para mantener las cosas simples..)

MainWindow.xaml:

<Window x:Class="MutuallyExclusiveMenuItems.MainWindow" 
     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
     xmlns:view="clr-namespace:MutuallyExclusiveMenuItems" 
     Title="MainWindow" Height="350" Width="525"> 

    <Window.CommandBindings> 
    <CommandBinding Command="{x:Static view:MainWindow.MenuItem1Cmd}" 
     CanExecute="CanExecute" 
     Executed="MenuItem1Execute" /> 
    <CommandBinding Command="{x:Static view:MainWindow.MenuItem2Cmd}" 
     CanExecute="CanExecute" 
     Executed="MenuItem2Execute" /> 
    <CommandBinding Command="{x:Static view:MainWindow.MenuItem3Cmd}" 
     CanExecute="CanExecute" 
     Executed="MenuItem3Execute" /> 
    </Window.CommandBindings> 

    <Window.InputBindings> 
    <KeyBinding Command="{x:Static view:MainWindow.MenuItem1Cmd}" Gesture="Ctrl+1"/> 
    <KeyBinding Command="{x:Static view:MainWindow.MenuItem2Cmd}" Gesture="Ctrl+2"/> 
    <KeyBinding Command="{x:Static view:MainWindow.MenuItem3Cmd}" Gesture="Ctrl+3"/> 
    </Window.InputBindings> 

    <DockPanel> 
    <DockPanel DockPanel.Dock="Top"> 
     <Menu> 
     <MenuItem Header="_Root"> 
      <MenuItem Command="{x:Static view:MainWindow.MenuItem1Cmd}" 
        InputGestureText="Ctrl+1"> 
      <MenuItem.Style> 
       <Style> 
       <Style.Triggers> 
        <DataTrigger Binding="{Binding CurrentMenuItem, Mode=OneWay}" 
           Value="{x:Static view:MainWindow+CurrentItemEnum.EnumItem1}"> 
        <Setter Property="MenuItem.IsChecked" Value="True"/> 
        </DataTrigger> 
       </Style.Triggers> 
       </Style> 
      </MenuItem.Style> 
      </MenuItem> 
      <MenuItem Command="{x:Static view:MainWindow.MenuItem2Cmd}" 
        InputGestureText="Ctrl+2"> 
      <MenuItem.Style> 
       <Style> 
       <Style.Triggers> 
        <DataTrigger Binding="{Binding CurrentMenuItem, Mode=OneWay}" 
           Value="{x:Static view:MainWindow+CurrentItemEnum.EnumItem2}"> 
        <Setter Property="MenuItem.IsChecked" Value="True"/> 
        </DataTrigger> 
       </Style.Triggers> 
       </Style> 
      </MenuItem.Style> 
      </MenuItem> 
      <MenuItem Command="{x:Static view:MainWindow.MenuItem3Cmd}" 
        InputGestureText="Ctrl+3"> 
      <MenuItem.Style> 
       <Style> 
       <Style.Triggers> 
        <DataTrigger Binding="{Binding CurrentMenuItem, Mode=OneWay}" 
           Value="{x:Static view:MainWindow+CurrentItemEnum.EnumItem3}"> 
        <Setter Property="MenuItem.IsChecked" Value="True"/> 
        </DataTrigger> 
       </Style.Triggers> 
       </Style> 
      </MenuItem.Style> 
      </MenuItem> 
     </MenuItem> 
     </Menu> 
    </DockPanel> 
    </DockPanel> 
</Window> 

MainWindow.xaml.cs:

using System.Windows; 
using System.Windows.Input; 
using System.ComponentModel; 

namespace MutuallyExclusiveMenuItems 
{ 
    public partial class MainWindow : Window, INotifyPropertyChanged 
    { 
    public MainWindow() 
    { 
     InitializeComponent(); 
     DataContext = this; 
    } 

    #region Enum Property 
    public enum CurrentItemEnum { EnumItem1, EnumItem2, EnumItem3 }; 

    private CurrentItemEnum _currentMenuItem; 
    public CurrentItemEnum CurrentMenuItem 
    { 
     get { return _currentMenuItem; } 
     set 
     { 
     _currentMenuItem = value; 
     OnPropertyChanged("CurrentMenuItem"); 
     } 
    } 

    public event PropertyChangedEventHandler PropertyChanged; 
    protected virtual void OnPropertyChanged(string propertyName) 
    { 
     PropertyChangedEventHandler handler = PropertyChanged; 
     if (handler != null) 
     handler(this, new PropertyChangedEventArgs(propertyName)); 
    } 
    #endregion Enum Property 

    #region Commands 
    public static RoutedUICommand MenuItem1Cmd = 
     new RoutedUICommand("Item_1", "Item1cmd", typeof(MainWindow)); 
    public void MenuItem1Execute(object sender, ExecutedRoutedEventArgs e) 
    { 
     CurrentMenuItem = CurrentItemEnum.EnumItem1; 
    } 
    public static RoutedUICommand MenuItem2Cmd = 
     new RoutedUICommand("Item_2", "Item2cmd", typeof(MainWindow)); 
    public void MenuItem2Execute(object sender, ExecutedRoutedEventArgs e) 
    { 
     CurrentMenuItem = CurrentItemEnum.EnumItem2; 
    } 
    public static RoutedUICommand MenuItem3Cmd = 
     new RoutedUICommand("Item_3", "Item3cmd", typeof(MainWindow)); 
    public void MenuItem3Execute(object sender, ExecutedRoutedEventArgs e) 
    { 
     CurrentMenuItem = CurrentItemEnum.EnumItem3; 
    } 
    public void CanExecute(object sender, CanExecuteRoutedEventArgs e) 
    { 
     e.CanExecute = true; 
    } 
    #endregion Commands 
    } 
} 
5

la adición de este en la parte inferior ya que no tienen la reputación ... todavía

tan útil como la respuesta de Patrick es, que no garantiza que los artículos no pueden estar sin marcar. Con el fin de hacer eso, el manejador de cuadros se debe cambiar a un controlador de clic, y cambió a la siguiente:

static void MenuItemClicked(object sender, RoutedEventArgs e) 
{ 
    var menuItem = e.OriginalSource as MenuItem; 
    if (menuItem.IsChecked) 
    { 
     foreach (var item in ElementToGroupNames) 
     { 
      if (item.Key != menuItem && item.Value == GetGroupName(menuItem)) 
      { 
       item.Key.IsChecked = false; 
      } 
     } 
    } 
    else // it's not possible for the user to deselect an item 
    { 
     menuItem.IsChecked = true; 
    } 
} 
+0

¿Cómo disparas este evento? –

0

Aquí es otra manera - no es fácil por cualquier tramo pero es MVVM compatibles, enlazable y altamente unidad comprobable. Si tiene la libertad de agregar un convertidor a su proyecto y no le importa un poco de basura en la forma de una nueva lista de elementos cada vez que se abre el menú contextual, esto funciona realmente bien. Cumple con la pregunta original de cómo proporcionar un conjunto mutuamente exclusivo de elementos marcados en un menú contextual.

Creo que si desea extraer todo esto en un control de usuario, podría convertirlo en un componente de biblioteca reutilizable para volver a utilizarlo en su aplicación. Los componentes utilizados son Type3.Xaml con una cuadrícula simple, un bloque de texto y el menú contextual. Haga clic derecho en cualquier lugar de la grilla para que aparezca el menú.

Un convertidor de valores llamado AllValuesEqualToBooleanConverter se usa para comparar el valor de cada elemento de menú con el valor actual del grupo y mostrar la marca de verificación junto al elemento de menú que está seleccionado actualmente.

Una clase simple que represente las opciones de su menú se utiliza como ilustración. El contenedor de muestra utiliza Tuple con las propiedades String y Entero que hacen que sea bastante fácil tener un fragmento de texto legible por humanos estrechamente emparejado con un valor amigable para la máquina. Puede usar cadenas solo o String y un Enum para realizar un seguimiento del valor para tomar decisiones sobre lo que es actual. Type3VM.cs es el ViewModel asignado a DataContext para Type3.Xaml. Sin embargo, puede asignar su contexto de datos en su marco de aplicación existente, use el mismo mecanismo aquí. El marco de aplicación en uso se basa en INotifyPropertyChanged para comunicar los valores modificados a WPF y su enlace goo. Si tiene propiedades de dependencia, puede necesitar modificar un poco el código.

El inconveniente de esta implementación, además del convertidor y su longitud, es que se crea una lista de elementos no utilizados cada vez que se abre el menú contextual. Para aplicaciones de usuario único, esto probablemente sea correcto, pero debe tenerlo en cuenta.

La aplicación utiliza una implementación de RelayCommand que está disponible en el sitio web de Haacked o en cualquier otra clase de ayuda compatible con ICommand disponible en cualquier marco que esté utilizando.

Type3VM clase pública: INotifyPropertyChanged { privada Lista menuData = nueva lista (new [] { nuevos MenuData ("Cero", 0), nueva MenuData ("One", 1), nueva MenuData ("Dos", 2), new MenuData ("Tres", 3), });

public IEnumerable<MenuData> MenuData { get { return menuData.ToList(); } } 

    private int selected; 
    public int Selected 
    { 
     get { return selected; } 
     set { selected = value; OnPropertyChanged(); } 
    } 

    private ICommand contextMenuClickedCommand; 
    public ICommand ContextMenuClickedCommand { get { return contextMenuClickedCommand; } } 

    private void ContextMenuClickedAction(object clicked) 
    { 
     var data = clicked as MenuData; 
     Selected = data.Item2; 
     OnPropertyChanged("MenuData"); 
    } 

    public Type3VM() 
    { 
     contextMenuClickedCommand = new RelayCommand(ContextMenuClickedAction); 
    } 

    private void OnPropertyChanged([CallerMemberName]string propertyName = null) 
    { 
     PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); 
    } 

    public event PropertyChangedEventHandler PropertyChanged; 
} 

public class MenuData : Tuple<String, int> 
{ 
    public MenuData(String DisplayValue, int value) : base(DisplayValue, value) { } 
} 

< UserControl x: Class = "SampleApp.Views.Type3" 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: Views =" clr-namespace: SampleApp.Views " xmlns: Converters =" clr-namespace: SampleApp.Converters " xmlns: ViewModels =" clr-namespace: SampleApp.ViewModels " mc: Ignorable = "d" d: DesignHeight = "300" d: DesignWidth = "300" d: DataContext = "{d: ViewModels DesignInstance: Type3VM}" > < UserControl.Resources> < Convertidores: AllValuesEqualToBooleanConverter x: Key = "IsCheckedVisibilityConverter" EqualValue = "true" NotEqualValue = "false" /> < /UserControl.Resources> < Cuadrícula> < Grid.ContextMenu> < ContextMenu ItemsSource = "{Binding MenuData, Modo = OneWay}"> < ContextMenu.ItemContainerStyle > < Estilo TargetType = "MenuItem"> < Setter propiedad = valor "de cabecera" = "{Binding Elemento1}" /> < Setter propiedad = "IsCheckable" Value = "true" /> < Setter propiedad = "IsChecked"> < Setter.Value> < MultiBinding convertidor = "{} StaticResource IsCheckedVisibilityConverter" Modo = "OneWay"> < ruta de enlace = "DataContext.Selected" RelativeSource = "{RelativeSource FindAncestor, AncestorType = {x: Tipo Vistas: Tipo 3}}" /> < ruta de enlace = "Item2" /> </MultiBinding> </SetVal.Valor> </Setter> < Setter propiedad = valor "Comando" = "{Binding Path = DataContext.ContextMenuClickedCommand, RelativeSource = {RelativeSource FindAncestor, AncestorType = {x: Tipo Vistas: Type3}}}" /> < Setter propiedad = "CommandParameter" Value = "{Binding.}" /> </Style> < /ContextMenu.ItemContainerStyle> </ContextMenu> < /Grid.ContextMenu> < Grid.RowDefinitions> < RowDefinition Altura = "" /> < /Grid.RowDefinitions> < Gri re.ColumnDefinitions> < ColumnDefinition width =" "/> < /Grid.ColumnDefinitions> < TextBlock Grid.Row =" 0" Grid.Column = "0" Tamaño de Letra = "30" Text = "click derecho Para los menús"/> </Cuadrícula> </UserControl>

public class AreAllValuesEqualConverter<T> : IMultiValueConverter 
{ 
    public T EqualValue { get; set; } 
    public T NotEqualValue { get; set; } 

    public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture) 
    { 
     T returnValue; 

     if (values.Length < 2) 
     { 
      returnValue = EqualValue; 
     } 

     // Need to use .Equals() instead of == so that string comparison works, but must check for null first. 
     else if (values[0] == null) 
     { 
      returnValue = (values.All(v => v == null)) ? EqualValue : NotEqualValue; 
     } 
     else 
     { 
      returnValue = (values.All(v => values[0].Equals(v))) ? EqualValue : NotEqualValue; 
     } 

     return returnValue; 
    } 

    public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture) 
    { 
     throw new NotImplementedException(); 
    } 
} 

[ValueConversion(typeof(object), typeof(Boolean))] 
public class AllValuesEqualToBooleanConverter : AreAllValuesEqualConverter<Boolean> 
{ } 
1

encuentro que consigo elementos de menú que se excluyen mutuamente cuando se une MenuItem.IsChecked a una variable.

Pero tiene una peculiaridad: si hace clic en el elemento del menú seleccionado, se vuelve inválido, se muestra en el rectángulo rojo habitual. Lo resolví agregando un controlador para MenuItem. Haga clic en el botón que impide deseleccionar simplemente ajustando IsChecked en verdadero.

El código ... Estoy vinculando a un tipo de enumeración, por lo que utilizo un convertidor enum que devuelve verdadero si la propiedad enlazada es igual al parámetro proporcionado. Aquí está el XAML:

<MenuItem Header="Black" 
       IsCheckable="True" 
       IsChecked="{Binding SelectedColor, Converter={StaticResource EnumConverter}, ConverterParameter=Black}" 
       Click="MenuItem_OnClickDisallowUnselect"/> 
    <MenuItem Header="Red" 
       IsCheckable="True" 
       IsChecked="{Binding SelectedColor, Converter={StaticResource EnumConverter}, ConverterParameter=Red}" 
       Click="MenuItem_OnClickDisallowUnselect"/> 

Y aquí está el código subyacente:

private void MenuItem_OnClickDisallowUnselect(object sender, RoutedEventArgs e) 
    { 
     var menuItem = e.OriginalSource as MenuItem; 
     if (menuItem == null) return; 

     if (! menuItem.IsChecked) 
     { 
      menuItem.IsChecked = true; 
     } 
    } 
4

Dado que no hay una respuesta samilar, he puesto mi solución aquí:

public class RadioMenuItem : MenuItem 
{ 
    public string GroupName { get; set; } 
    protected override void OnClick() 
    { 
     var ic = Parent as ItemsControl; 
     if (null != ic) 
     { 
      var rmi = ic.Items.OfType<RadioMenuItem>().FirstOrDefault(i => 
       i.GroupName == GroupName && i.IsChecked); 
      if (null != rmi) rmi.IsChecked = false; 

      IsChecked = true; 
     } 
     base.OnClick(); 
    } 
} 

En XAML solo Úselo como un elemento de menú habitual:

<MenuItem Header="OOO"> 
    <local:RadioMenuItem Header="111" GroupName="G1"/> 
    <local:RadioMenuItem Header="222" GroupName="G1"/> 
    <local:RadioMenuItem Header="333" GroupName="G1"/> 
    <local:RadioMenuItem Header="444" GroupName="G1"/> 
    <local:RadioMenuItem Header="555" GroupName="G1"/> 
    <local:RadioMenuItem Header="666" GroupName="G1"/> 
    <Separator/> 
    <local:RadioMenuItem Header="111" GroupName="G2"/> 
    <local:RadioMenuItem Header="222" GroupName="G2"/> 
    <local:RadioMenuItem Header="333" GroupName="G2"/> 
    <local:RadioMenuItem Header="444" GroupName="G2"/> 
    <local:RadioMenuItem Header="555" GroupName="G2"/> 
    <local:RadioMenuItem Header="666" GroupName="G2"/> 
</MenuItem> 

Bastante simple y limpio. Y, por supuesto, puede hacer que el GroupName sea una propiedad de dependencia mediante algunos códigos adicionales, eso es lo mismo que otros.

Por cierto, si no te gusta la marca de verificación, puede cambiarlo a lo que le gusta:

public override void OnApplyTemplate() 
{ 
    base.OnApplyTemplate(); 
    var p = GetTemplateChild("Glyph") as Path; 
    if (null == p) return; 
    var x = p.Width/2; 
    var y = p.Height/2; 
    var r = Math.Min(x, y) - 1; 
    var e = new EllipseGeometry(new Point(x,y), r, r); 
    // this is just a flattened dot, of course you can draw 
    // something else, e.g. a star? ;) 
    p.Data = e.GetFlattenedPathGeometry(); 
} 

Si ha utilizado un montón de este RadioMenuItem en su programa, hay otra versión más eficiente se muestra a continuación . Los datos literales se obtienen de e.GetFlattenedPathGeometry().ToString() en el fragmento de código anterior.

private static readonly Geometry RadioDot = Geometry.Parse("M9,5.5L8.7,7.1 7.8,8.3 6.6,9.2L5,9.5L3.4,9.2 2.2,8.3 1.3,7.1L1,5.5L1.3,3.9 2.2,2.7 3.4,1.8L5,1.5L6.6,1.8 7.8,2.7 8.7,3.9L9,5.5z"); 
public override void OnApplyTemplate() 
{ 
    base.OnApplyTemplate(); 
    var p = GetTemplateChild("Glyph") as Path; 
    if (null == p) return; 
    p.Data = RadioDot; 
} 

Y por último, si va a envolver para su uso en el proyecto, se debe ocultar IsCheckable propiedad de la clase base, ya que el machenism comprobación automática de MenuItem clase dirigirá el estado de comprobación de radio marca un mal comportamiento.

private new bool IsCheckable { get; } 

De este modo VS dará un error si un novato intenta compilar XAML como esto:

// en cuenta que este es un uso incorrecto!

<local:RadioMenuItem Header="111" GroupName="G1" IsCheckable="True"/>

// en cuenta que este es un uso incorrecto!

5

Sí, esto se puede hacer fácilmente haciendo que cada MenuItem sea un RadioButton. Esto puede hacerse editando la Plantilla de MenuItem.

  1. Haga clic con el Menultem en el panel Documento-Esquema izquierdo> EditTemplate> EditCopy. Esto agregará el código para editar en Window.Resources.

  2. Ahora, solo tiene que hacer dos cambios, que son muy simples.

    Mutually Exclusive MenuItems a. Agregue RadioButton con algunos recursos para ocultar su porción de círculo.

    b. Cambiar BorderThickness = 0 para la parte del borde de MenuItem.

    Estos cambios se muestran a continuación como comentarios, resto del estilo generados deben utilizarse como es:

    <Window.Resources> 
         <LinearGradientBrush x:Key="MenuItemSelectionFill" EndPoint="0,1" StartPoint="0,0"> 
          <GradientStop Color="#34C5EBFF" Offset="0"/> 
          <GradientStop Color="#3481D8FF" Offset="1"/> 
         </LinearGradientBrush> 
         <Geometry x:Key="Checkmark">M 0,5.1 L 1.7,5.2 L 3.4,7.1 L 8,0.4 L 9.2,0 L 3.3,10.8 Z</Geometry> 
         <ControlTemplate x:Key="{ComponentResourceKey ResourceId=SubmenuItemTemplateKey, TypeInTargetAssembly={x:Type MenuItem}}" TargetType="{x:Type MenuItem}"> 
          <Grid SnapsToDevicePixels="true"> 
           <Rectangle x:Name="Bg" Fill="{TemplateBinding Background}" RadiusY="2" RadiusX="2" Stroke="{TemplateBinding BorderBrush}" StrokeThickness="1"/> 
           <Rectangle x:Name="InnerBorder" Margin="1" RadiusY="2" RadiusX="2"/> 
        <!-- Add RadioButton around the Grid 
        --> 
           <RadioButton Background="Transparent" GroupName="MENUITEM_GRP" IsHitTestVisible="False" IsChecked="{Binding IsChecked, RelativeSource={RelativeSource AncestorType=MenuItem}}"> 
            <RadioButton.Resources> 
             <Style TargetType="Themes:BulletChrome"> 
              <Setter Property="Visibility" Value="Collapsed"/> 
             </Style> 
            </RadioButton.Resources> 
        <!-- Add RadioButton Top part ends here 
        --> 
            <Grid> 
             <Grid.ColumnDefinitions> 
              <ColumnDefinition MinWidth="24" SharedSizeGroup="MenuItemIconColumnGroup" Width="Auto"/> 
              <ColumnDefinition Width="4"/> 
              <ColumnDefinition Width="*"/> 
              <ColumnDefinition Width="37"/> 
              <ColumnDefinition SharedSizeGroup="MenuItemIGTColumnGroup" Width="Auto"/> 
              <ColumnDefinition Width="17"/> 
             </Grid.ColumnDefinitions> 
             <ContentPresenter x:Name="Icon" ContentSource="Icon" Margin="1" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" VerticalAlignment="Center"/> 
    
        <!-- Change border thickness to 0 
        -->  
             <Border x:Name="GlyphPanel" BorderBrush="#CDD3E6" BorderThickness="0" Background="#E6EFF4" CornerRadius="3" Height="22" Margin="1" Visibility="Hidden" Width="22"> 
              <Path x:Name="Glyph" Data="{StaticResource Checkmark}" Fill="#0C12A1" FlowDirection="LeftToRight" Height="11" Width="9"/> 
             </Border> 
             <ContentPresenter Grid.Column="2" ContentSource="Header" Margin="{TemplateBinding Padding}" RecognizesAccessKey="True" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/> 
             <TextBlock Grid.Column="4" Margin="{TemplateBinding Padding}" Text="{TemplateBinding InputGestureText}"/> 
            </Grid> 
           </RadioButton> 
        <!-- RadioButton closed , thats it ! 
        --> 
          </Grid> 
          ... 
        </Window.Resources> 
    
  3. aplicar el estilo,

    <MenuItem IsCheckable="True" Header="Open" Style="{DynamicResource MenuItemStyle1}" 
    
+0

¿Dónde se define MenuItemStyle1? – migle

Cuestiones relacionadas