1

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");
        }
    }
}
CrabMan
  • 1,578
  • 18
  • 37
  • is your datacontext set? Also have you tried binding the menu-item to a command and implementing a canExecute method? – Spook Kruger Feb 24 '14 at 13:39
  • My DataContext is set in Window's constructor for the whole window to my ViewModel from which the ListBox takes its items, but it has nothing to do with whether to enable the item or not. I have not tried doing what you said, it's the first time I hear about that method. I am not sure how 'Click="removeTest"' is different from binding it to a command. – CrabMan Feb 24 '14 at 13:45
  • When using an mvvm pattern, the best-practise is to keep domain (business logic) from the code-behind files. The code behind should have as little logic as possible to increase the flexibility of reuse. The commands allow for various benefits, one could for example implement an undo pattern more easily. Another benefit of commands is that it gives you the ability to pre-check conditions which would automatically enable and disable the button. This helps to separate conditional logic from business logic (in other words, i can only execute the method if all conditions are met) – Spook Kruger Feb 24 '14 at 14:10
  • I think enabling/disabling menu item has nothing to do with business logic. – CrabMan Feb 24 '14 at 14:21
  • if you have "oneWay", when the source it's updated it won't update the view. – sexta13 Feb 24 '14 at 15:04
  • Why? This is what MSDN says: "OneWay binding causes changes to the source property to automatically update the target property, but changes to the target property are not propagated back to the source property." ListBox.SelectedItem is the source, MenuItem.IsEnabled is the target, so it should be updated. – CrabMan Feb 24 '14 at 15:21
  • Also I tried changing OneWay to OneWayToSource, and nothing changed at all. – CrabMan Feb 24 '14 at 15:30
  • did you try twoway? I'll do a test and tell you something right away – sexta13 Feb 24 '14 at 15:37
  • Even though TwoWay makes no sense in this situation, I have tried it. Nothing changed. ObjectToBoolConverter methods never get called (I checked that by debugging and inserting MessageBox call in them). – CrabMan Feb 24 '14 at 15:43
  • Only a comment. But I would try SelectedIndex. -1 is nothing selected. – paparazzo Feb 24 '14 at 15:52
  • possible duplicate of [WPF ContextMenu woes: How do I set the DataContext of the ContextMenu?](http://stackoverflow.com/questions/15033522/wpf-contextmenu-woes-how-do-i-set-the-datacontext-of-the-contextmenu) – Dtex Feb 24 '14 at 15:53

3 Answers3

4

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.

Community
  • 1
  • 1
2

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,

sexta13
  • 1,558
  • 1
  • 11
  • 19
1

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.

CrabMan
  • 1,578
  • 18
  • 37