MenuItem.IsEnabled is bound to whether there is something selected in Listbox or not, but it doesn't update

StackOverflow https://stackoverflow.com/questions/21989313

Question

I have a MenuItem, which should be enabled only if there is something selected in ListBox. I wrote a converter from object to bool, which returns false, if that object == null, and true otherwise. I bound it to ListBox.SelectedItem with my converter, but it doesn't work. Placing a breakpoint in the converter shows, that it never runs. The MenuItem appears always enabled no matter what.

Here is xaml code of the ListBox and of MenuItem

<ListBox Name="TestsListBox" 
         HorizontalAlignment="Left" Height="93" VerticalAlignment="Top" Width="128"
         Margin="0,5,-1.723,0" ItemsSource="{Binding Path=Tests, Mode=OneWay}">
    <ListBox.ContextMenu>
        <ContextMenu>
            <MenuItem Header="Remove" Click="removeTest"
                      IsEnabled="{Binding ElementName=TestsListBox, Mode=OneWay,
                                          Path=SelectedItem, Converter={StaticResource ObjectToBool}}"/>
        </ContextMenu>
    </ListBox.ContextMenu>
</ListBox>

Here I show how converter is declared as window's resource

<Window x:Class="WpfApplication.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:System="clr-namespace:System;assembly=mscorlib"
        xmlns:ClassesLib="clr-namespace:Laba1;assembly=ClassesLib"
        xmlns:local="clr-namespace:WpfApplication"
        Title="MainWindow" Height="450" Width="525">
    <Window.Resources>
        <local:ObjectToBoolConverter x:Key="ObjectToBool"/>
    </Window.Resources>

And here is the converter class

namespace WpfApplication
{
    class ObjectToBoolConverter: IValueConverter
    {
        // Converts value to boolean. If value is null, returns false.
        // Otherwise returns true
        public object Convert(object value, Type targetType,
            object parameter, System.Globalization.CultureInfo culture)
        {
            if (null == value)
            {
                return false;
            }
            return true;
        }
        public object ConvertBack(object value, Type targetType,
            object parameter, System.Globalization.CultureInfo culture)
        {
            throw new NotSupportedException("This is oneway converter, so ConvertBack is not supported");
        }
    }
}
Was it helpful?

Solution

RelativeSource and Popup

From here you should be able to find out that the reason ElementName binding doesn't work is because the ContextMenu isn't part of the visual tree as other controls are, and therefore can not take part in such binding scenarios. AFAIK, PopUps have a PlacementTarget property that you can bind to and figure out how to use.

OTHER TIPS

This was how I solved it:

VIEW:

<Window x:Class="WpfApplication2.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         xmlns:local="clr-namespace:WpfApplication2"
          Title="MainWindow" Height="350" Width="525">
<Window.Resources>
    <local:ObjectToBoolConverter x:Key="ObjectToBool"/>
        <ContextMenu x:Key="contextMenu" DataContext="{Binding PlacementTarget.SelectedItem, RelativeSource={RelativeSource Self}}">
            <MenuItem Header="Remove" Click="removeTest"
                      IsEnabled="{Binding Path=., Converter={StaticResource ObjectToBool}}"/>

        </ContextMenu>
    </Window.Resources>

    <Grid>
        <ListBox Name="TestsListBox" 
         HorizontalAlignment="Left" Height="93" VerticalAlignment="Top" Width="128" 
         Margin="0,5,-1.723,0" ContextMenu="{StaticResource ResourceKey=contextMenu}">
        </ListBox>
    </Grid>
</Window>

CODE BEHIND

using System.Collections.Generic;
using System.Windows;
using System.Windows.Documents;

namespace WpfApplication2
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            this.DataContext = this;
            List<string> teste = new List<string>();
            teste.Add("test1");
            teste.Add("test3");
            teste.Add("test2");

            TestsListBox.ItemsSource = teste;

        }

        private void removeTest(object sender, RoutedEventArgs e)
        {

        }
    }
}

The converter stayed the same.

Regards,

Looks like ElementName property of Binding doesn't do what I thought it does. Also it sucks very much that XAML just ignores and does nothing about incorrect parameters of Binding: it should raise an error instead. I added DataContext to my ContextMenu, removed ElementName, and it is working now. This is how I changed the code:

<ContextMenu DataContext="{Binding PlacementTarget, RelativeSource={RelativeSource Self}}" >
    <MenuItem Header="Add" Click="addTest"/>
    <MenuItem Header="Remove" Click="removeTest"
              IsEnabled="{Binding Mode=OneWay,
                                  Path=SelectedItem, Converter={StaticResource ObjectToBool}}"/>
</ContextMenu>

Dtex's comment about a duplicate helped me with this, even though I thought I could use ElementName instead of DataContext.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top