0

When SelectedItem value changes it does not trigger SelectedItemConverter.

I tried to use SetCurrentValue and InvalidateProperty and fiddled with attributes like NotifyOnSourceUpdated, UpdateSourceTrigger and Mode.

Sadly they do not work.

Perhaps I should add something in parent xaml to nudge it? Currently its:

   <local:Menu
            SelectedItem="{Binding SelectedMenuItem}"
            OnSelect="{Binding OnSelectCommand}" Items="{Binding MenuItems}"/>

Thanks for help.

control xaml

<UserControl>
 <UserControl.Resources>
        <vm:MockMenuViewModel x:Key="DesignViewModel"/>
        <cv:SelectedItemConverter x:Key="SelectedItemConverter"/>
    </UserControl.Resources>
    <ItemsControl
        d:DataContext="{Binding Source={StaticResource DesignViewModel}}"
        ItemsSource="{Binding Path=MenuItems, Mode=OneWay}">
        <ItemsControl.ItemsPanel>
            <ItemsPanelTemplate>
                <StackPanel></StackPanel>
            </ItemsPanelTemplate>
        </ItemsControl.ItemsPanel>
        <ItemsControl.ItemTemplate>
            <DataTemplate DataType="{x:Type MenuItem}">
                <Button Content="{Binding Name}"
                        Command="{Binding OnSelect, RelativeSource={RelativeSource AncestorType=UserControl}}"
                        CommandParameter="{Binding}">
                    <Button.IsEnabled>
                        <MultiBinding Converter="{StaticResource SelectedItemConverter}"
                                      NotifyOnSourceUpdated="True"
                                      NotifyOnTargetUpdated="True" Mode="OneWay">
                            <Binding Path="SelectedItem" UpdateSourceTrigger="PropertyChanged" Mode="OneWay"/>
                            <Binding Path="." Mode="OneWay"/>
                        </MultiBinding>
                    </Button.IsEnabled>
                </Button>
            </DataTemplate>
        </ItemsControl.ItemTemplate>
    </ItemsControl>
</UserControl>

control cs

 public partial class Menu : UserControl, INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler? PropertyChanged;

        public Menu()
        {
            InitializeComponent();
        }

        public static readonly DependencyProperty SelectedItemProperty = DependencyProperty.Register(
            "SelectedItem",
            typeof(MenuItem),
            typeof(Menu),
            new PropertyMetadata(null, OnSelectedItemChanged));

        public static readonly DependencyProperty ItemsProperty = DependencyProperty.Register(
            "Items",
            typeof(IEnumerable<MenuItem>),
            typeof(Menu),
            new FrameworkPropertyMetadata(null));

        public static readonly DependencyProperty OnSelectProperty = DependencyProperty.Register(
            "OnSelect",
            typeof(ICommand),
            typeof(Menu),
            new PropertyMetadata(null, OnSelectChanged));


        public MenuItem SelectedItem
        {
            get => (MenuItem)GetValue(SelectedItemProperty);
            set
            {
                SetValue(SelectedItemProperty, value);
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Items)));
            }
        }

        public IEnumerable<MenuItem> Items
        {
            get => (IEnumerable<MenuItem>)GetValue(ItemsProperty);
            set => SetValue(ItemsProperty, value);
        }

        public ICommand OnSelect
        {
            get => (ICommand)GetValue(OnSelectProperty);
            set => SetValue(OnSelectProperty, value);
        }

        public static void OnSelectedItemChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs eventArgs)
        {
            var menu = dependencyObject as Menu;

            if (menu == null)
            {
                return;
            }

            menu.InvalidateProperty(SelectedItemProperty);
            menu.InvalidateProperty(ItemsProperty);
        }

        public static void OnSelectChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs eventArgs)
        {
            var menu = dependencyObject as Menu;

            if (menu == null)
            {
                return;
            }

            menu.SetCurrentValue(OnSelectProperty, eventArgs.NewValue);
        }
    }
Józef Podlecki
  • 10,453
  • 5
  • 24
  • 50
  • Doesn't `Path="SelectedItem"` points to `MenuItem.SelectedItem` and not `Menu.SelectedItem` ... remeber ... you are inside `DataTemplate` – Selvin Dec 01 '22 at 16:15
  • I thought it would try to find property on collection item and if theres nothing it would go up in hierarchy to user control. How to add RelativeSource here when Binding is a xml node, not attribute? – Józef Podlecki Dec 01 '22 at 16:22

1 Answers1

1

If you are trying to bind to the SelectedItem property of the parent Menu UserControl, you should specify a RelativeSource:

<Binding Path="SelectedItem"
    RelativeSource="{RelativeSource AncestorType=UserControl}"
    UpdateSourceTrigger="PropertyChanged" Mode="OneWay"/>
mm8
  • 163,881
  • 10
  • 57
  • 88
  • Thanks, whats really annoyed me here wpf does not tell you that "oh btw I could not find that property anywhere during binding". Is there something like strict mode? – Józef Podlecki Dec 01 '22 at 16:33
  • 1
    You can [trace binding errors](https://stackoverflow.com/questions/8850143/binding-errors-not-showing-on-output-window) but I am afraid there is no way to automatically validate them at compile time in WPF. – mm8 Dec 01 '22 at 16:34