Home > other >  Binding not working when modifying ObservableCollection
Binding not working when modifying ObservableCollection

Time:10-16

I am trying to bind RadioButton to an ObservableCollection<Face>. I already made several bindings, and they are all working. But on that one, I cannot understand what is going wrong.The only thing that differs that time, is I tried to use a converter for that. First, my ObservableCollection was an ObservableCollection<int>, then I change it to an ObservableCollection<Face>, because I believed the problem was I cannot bind on an ObservableCollection<int>, but nothing changed.

Here is my converter. I would like not to have a ConvertBack, but cannot remove it? :

public class VisibleIfListIntContains : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        if (parameter == null)
        {
            return false;
        }
        ObservableCollection<int> listInt = (ObservableCollection<int>)value;
        int value2 = System.Convert.ToInt32(parameter.ToString());
        //bool? retour = listInt.Contains(value2);
        return listInt.Contains(value2)
            ? Visibility.Visible
            : Visibility.Collapsed;
    }
    public object ConvertBack(object value, Type targetTypes, object parameter, System.Globalization.CultureInfo culture)
    {
        if ((bool)value == true)
        {
            return System.Convert.ToInt32(parameter);
        }
        else
        {
            return new List<int>();
        }
    }
}

Then the class on which I am binding :

public partial class Macro : INotifyPropertyChanged
{
    private ObservableCollection<int> listFacesAllowed = new ObservableCollection<int>();
    public ObservableCollection<int> ListFacesAllowed
    {
        get { return this.listFacesAllowed; }
        set
        {
            this.listFacesAllowed = value;
            this.NotifyPropertyChanged("ListFacesAllowed");
        }
    }
}

I modify my ObservableCollection this way :

private void setAvailableFaces()
{
    this.ListFacesAllowed.Clear();
    if (this.gpProfil == "I" || this.gpProfil == "H" || this.gpProfil == "U")
    {
        this.ListFacesAllowed.Add(0);
        this.ListFacesAllowed.Add(1);
        this.ListFacesAllowed.Add(2);
    }
    else if (this.gpProfil == "L")
    {
        this.ListFacesAllowed.Add(0);
        this.ListFacesAllowed.Add(2);
    }
    else if (this.gpProfil == "M")
    {
        this.ListFacesAllowed.Add(0);
        this.ListFacesAllowed.Add(1);
        this.ListFacesAllowed.Add(2);
        this.ListFacesAllowed.Add(3);
    }
    if (this.type==1)
    {
        if (this.isTraversingMacro)
        {
            if (this.gpProfil == "M")
            {
                this.ListFacesAllowed.Remove(1);
                this.ListFacesAllowed.Remove(3);
            }
            else if (gpProfil == "I" || gpProfil == "H" || gpProfil == "U")
            {
                this.ListFacesAllowed.Remove(1);
                if (this.name != "MV002" && this.name != "MV000")
                {
                    this.ListFacesAllowed.Remove(2);
                }
            }

        }
    }
    this.NotifyPropertyChanged("ListFacesAllowed");
    if (this.listFacesAllowed.Any() && !this.listFacesAllowed.Contains(this.face))
    {
        this.Face = this.listFacesAllowed[0];
    }
}

The ObservableCollection is modified, but the converter is never executed.

Here is XAML code :

<DataTemplate x:Key="TemplateType1">
        <Grid Background="{StaticResource WindowBackgroundColor}">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="auto"/>
                <ColumnDefinition Width="auto"/>
                <ColumnDefinition Width="200"/>
            </Grid.ColumnDefinitions>
            <Grid Margin="10,10,20,10">
                <Grid.RowDefinitions>
                    <RowDefinition Height="auto"/>
                    <RowDefinition Height="auto"/>
                    <RowDefinition Height="auto"/>
                </Grid.RowDefinitions>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="100"/>
                    <ColumnDefinition Width="*"/>
                </Grid.ColumnDefinitions>
                <Grid Grid.RowSpan="2">
                    <Grid.RowDefinitions>
                        <RowDefinition Height="*"/>
                        <RowDefinition Height="auto"/>
                    </Grid.RowDefinitions>
                    <ListView ItemsSource="{Binding}" SelectedItem="{Binding DataContext.SelectedMacro,   
                        RelativeSource={RelativeSource AncestorType=Window},Mode=TwoWay}"  Margin="3" Width="auto">
                        <ListView.View>
                            <GridView>
                                <GridViewColumn Header="{x:Static p:Resources.Name}" Width="auto" DisplayMemberBinding="{Binding Name}"/>
                            </GridView>
                        </ListView.View>
                    </ListView>
                    <Button Grid.Row="1" HorizontalAlignment="Left" Margin="3,10,3,3" Click="AddMacro_Click">
                        <Image Source="../img/image_add.png" Width="40"/>
                    </Button>
                </Grid>

                <Grid Grid.Column="1">
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="auto"/>
                        <ColumnDefinition Width="*"/>
                        <ColumnDefinition Width="auto"/>
                    </Grid.ColumnDefinitions>
                    <Grid Grid.Column="0">
                        <Grid.RowDefinitions>
                            <RowDefinition Height="*"/>
                            <RowDefinition Height="20"/>
                            <RowDefinition Height="*"/>
                            <RowDefinition Height="*"/>
                            <RowDefinition Height="*"/>
                            <RowDefinition Height="*"/>
                            <RowDefinition Height="*"/>
                            <RowDefinition Height="*"/>
                            <RowDefinition Height="*"/>
                        </Grid.RowDefinitions>
                        <CheckBox Grid.Row="0" IsChecked="{Binding DataContext.SelectedMacro.IsTraversingMacro, RelativeSource={RelativeSource AncestorType=Window},Mode=TwoWay}" 
                                    Content="{x:Static p:Resources.Traversing}"
                                    Margin="3"/>

                        <RadioButton Grid.Row="2" IsChecked="{Binding DataContext.SelectedMacro.Face, RelativeSource={RelativeSource AncestorType=Window},Converter={StaticResource isIntEqual},ConverterParameter=0}" 
                                    Visibility="{Binding DataContext.SelectedMacro.ListFacesAllowed, RelativeSource={RelativeSource AncestorType=Window},Converter={StaticResource visibleIfListIntContains},ConverterParameter=0}"
                                    Content="{x:Static p:Resources.Web}"
                                    Margin="3"/>
                        <RadioButton Grid.Row="3" IsChecked="{Binding DataContext.SelectedMacro.Face, RelativeSource={RelativeSource AncestorType=Window},Converter={StaticResource isIntEqual},ConverterParameter=1}" 
                                    Visibility="{Binding DataContext.SelectedMacro.ListFacesAllowed, RelativeSource={RelativeSource AncestorType=Window},Converter={StaticResource visibleIfListIntContains},ConverterParameter=1}"
                                    Content="{x:Static p:Resources.FlangeTop}"
                                    Margin="3"/>
                        <RadioButton Grid.Row="4" IsChecked="{Binding DataContext.SelectedMacro.Face, RelativeSource={RelativeSource AncestorType=Window},Converter={StaticResource isIntEqual},ConverterParameter=2}" 
                                    Visibility="{Binding DataContext.SelectedMacro.ListFacesAllowed, RelativeSource={RelativeSource AncestorType=Window},Converter={StaticResource visibleIfListIntContains},ConverterParameter=2}"
                                    Content="{x:Static p:Resources.FlangeBottom}"
                                    Margin="3"/>
                        <RadioButton Grid.Row="5" IsChecked="{Binding DataContext.SelectedMacro.Face, RelativeSource={RelativeSource AncestorType=Window},Converter={StaticResource isIntEqual},ConverterParameter=3}" 
                                    Visibility="{Binding DataContext.SelectedMacro.ListFacesAllowed, RelativeSource={RelativeSource AncestorType=Window},Converter={StaticResource visibleIfListIntContains},ConverterParameter=3}"
                                    Content="{x:Static p:Resources.WebBack}"
                                    Margin="3"/>
                    </Grid>
                    <Grid Grid.Column="2" HorizontalAlignment="Right">
                        <Grid.RowDefinitions>
                            <RowDefinition Height="*"/>
                            <RowDefinition Height="*"/>

                        </Grid.RowDefinitions>
                        <RadioButton Visibility="{Binding DataContext.SelectedMacro.BottomChoice,   
                        RelativeSource={RelativeSource AncestorType=Window}, Converter={StaticResource BoolToVisConverter}}"
                        Content="{x:Static p:Resources.Top}"
                        GroupName="radioGroup31" Margin="0,3,0,3"
                        IsChecked="{Binding DataContext.SelectedMacro.Bottom,   
                        RelativeSource={RelativeSource AncestorType=Window},
                                            Converter={StaticResource InverseBoolRadioConverter}}" />
                        <RadioButton Grid.Row="1" Visibility="{Binding DataContext.SelectedMacro.BottomChoice,   
                        RelativeSource={RelativeSource AncestorType=Window}, Converter={StaticResource BoolToVisConverter}}"
                        Content="{x:Static p:Resources.Bottom}"
                        GroupName="radioGroup32" Margin="0,3,0,3"
                        IsChecked="{Binding DataContext.SelectedMacro.Bottom,   
                        RelativeSource={RelativeSource AncestorType=Window}, Converter={StaticResource BoolRadioConverter}}" />
                    </Grid>

                </Grid>
                <Grid Grid.Column="1" Grid.Row="1">
                    <Grid.RowDefinitions>
                        <RowDefinition Height="auto"/>
                        <RowDefinition Height="auto"/>
                    </Grid.RowDefinitions>
                    <Label Content="{Binding DataContext.SelectedMacro.Name,   
                        RelativeSource={RelativeSource AncestorType=Window}}" HorizontalAlignment="Center" FontSize="30"/>
                    <Image Grid.Row="1" Width="250" Height="250" Source="{Binding DataContext.SelectedMacro.ImageName,   
                        RelativeSource={RelativeSource AncestorType=Window}}"/>
                </Grid>
                <Grid Grid.Row="2" Grid.Column="1">
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="80"/>
                        <ColumnDefinition Width="*"/>
                        <ColumnDefinition Width="80"/>
                    </Grid.ColumnDefinitions>
                    <Button Content="{x:Static p:Resources.Delete}" Click="DeleteMacro_Click" Margin="3" >
                        <Button.Style>
                            <Style TargetType="Button" BasedOn="{StaticResource {x:Type Button}}">
                                <Style.Triggers>
                                    <DataTrigger Binding="{Binding DataContext.SelectedMacro.Name,RelativeSource={RelativeSource AncestorType=Window}, Mode=OneWay}" Value="">
                                        <Setter Property="Button.IsEnabled" Value="False" />
                                    </DataTrigger>
                                </Style.Triggers>
                            </Style>
                        </Button.Style>
                    </Button>
                    <Button Content="{x:Static p:Resources.Change}" Grid.Column="2" Click="EditMacro_Click" Margin="3">
                        <Button.Style>
                            <Style TargetType="Button" BasedOn="{StaticResource {x:Type Button}}">
                                <Style.Triggers>
                                    <DataTrigger Binding="{Binding DataContext.SelectedMacro.Name,RelativeSource={RelativeSource AncestorType=Window}, Mode=OneWay}" Value="">
                                        <Setter Property="Button.Content" Value="{x:Static p:Resources.Add}" />
                                    </DataTrigger>
                                </Style.Triggers>
                            </Style>
                        </Button.Style>
                    </Button>
                </Grid>

            </Grid>
            <Grid Margin="20,10,10,10" Grid.Column="2">
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="auto"/>
                    <ColumnDefinition Width="auto"/>
                </Grid.ColumnDefinitions>
                <ItemsControl Grid.Column="0" ItemsSource="{Binding DataContext.SelectedMacro.ListParams,RelativeSource={RelativeSource AncestorType=Window}}">
                    <ItemsControl.ItemTemplate>
                        <DataTemplate>
                            <Grid Margin="0,0,0,5" Height="30">
                                <Grid.ColumnDefinitions>
                                    <ColumnDefinition Width="auto" />
                                </Grid.ColumnDefinitions>
                                <Label Content="{Binding Name}" VerticalAlignment="Center"/>
                            </Grid>
                        </DataTemplate>
                    </ItemsControl.ItemTemplate>
                </ItemsControl>
                <ItemsControl Grid.Column="1" ItemsSource="{Binding DataContext.SelectedMacro.ListParams,RelativeSource={RelativeSource AncestorType=Window}, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
                    <ItemsControl.ItemTemplate>
                        <DataTemplate>
                            <Grid Margin="0,0,0,5" Height="30">
                                <Grid.ColumnDefinitions>
                                    <ColumnDefinition Width="100" />
                                </Grid.ColumnDefinitions>
                                <TextBox Grid.Column="1" Text="{Binding Valeur}" PreviewTextInput="CheckIsPositiveDouble" VerticalAlignment="Center" />
                            </Grid>
                        </DataTemplate>
                    </ItemsControl.ItemTemplate>
                </ItemsControl>
            </Grid>
        </Grid>
    </DataTemplate>

All bindings in the template work good, except the ones for DataContext.SelectedMacro.ListFacesAllowed

CodePudding user response:

As far as I understand, you need to check for the presence of UI element Id in the Id collection. And if it is there, then install its Visibility.

If I understood this correctly, then the obvious problem is that you misunderstand the logic of Binding.
The binding will work on the first load, and for subsequent updates, you need to notify that there have been changes.
And since you bind to the ENTIRE collection and you need to check it all, then when you change any of its elements, you need to replace the collection instance.
Because otherwise the Binding will (conditionally) just check if it's another instance of the collection or not. And if the same, it will not update, even if the elements of the collection are already different.

Simple example:

using Simplified;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Core2022.SO.SiegfriedV
{
    public class IntsViewModel : BaseInpc
    {
        private HashSet<int> _numbers = new HashSet<int>(Enumerable.Range(0, 10));

        public HashSet<int> Numbers { get => _numbers; private set => Set(ref _numbers, value); }

        public IntsViewModel()
        {
            AddIntCommand = new RelayCommand<int>(num =>
            {
                HashSet<int> copy = new HashSet<int>(Numbers);
                copy.Add(num);
                Numbers = copy;
            });

            RemoveIntCommand = new RelayCommand<int>(num =>
            {
                HashSet<int> copy = new HashSet<int>(Numbers);
                copy.Remove(num);
                Numbers = copy;
            });
        }

        public RelayCommand<int> AddIntCommand { get; }
        public RelayCommand<int> RemoveIntCommand { get; }
    }
}
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.Windows;
using System.Windows.Data;

namespace Core2022.SO.SiegfriedV
{
    [ValueConversion(typeof(ISet<int>), typeof(Visibility))]
    public class VisibleIfListIntContains : IValueConverter
    {
        private static readonly Int32Converter converter = new Int32Converter();
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            if (value is not ISet<int> ints)
                return DependencyProperty.UnsetValue;

            if (parameter is not int num)
            {
                if (!converter.IsValid(parameter))
                    return DependencyProperty.UnsetValue;
                num = (int)(converter.ConvertFrom(parameter) ?? throw new Exception("God knows what happened."));
            }

            return ints.Contains(num)
                ? Visibility.Visible
                : Visibility.Collapsed;
        }

        // The reverse conversion in such logic, in principle, will not work,
        // since it is impossible to replace the collection itself with a new instance from the converter.
        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }

        private VisibleIfListIntContains() { }
        public static VisibleIfListIntContains Instance { get; } = new VisibleIfListIntContains();
    }
}
namespace Core2022.SO.SiegfriedV
{
    // Helper class for automatically converting a string (TextBox.Text) to an integer.
    public class DataProxy
    {
        public int Number { get; set; }
    }
}
<Window x:Class="Core2022.SO.SiegfriedV.IntsWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:Core2022.SO.SiegfriedV"
        mc:Ignorable="d"
        Title="IntsWindow" Height="450" Width="800"
        DataContext="{DynamicResource vm}">
    <Window.Resources>
        <local:IntsViewModel x:Key="vm"/>
        <local:DataProxy x:Key="proxy"/>
    </Window.Resources>
    <UniformGrid Columns="2">
        <UniformGrid HorizontalAlignment="Center" Columns="1">
            <TextBlock Text="0" Visibility="{Binding Numbers, Converter={x:Static local:VisibleIfListIntContains.Instance}, ConverterParameter=0}" VerticalAlignment="Center"/>
            <TextBlock Text="1" Visibility="{Binding Numbers, Converter={x:Static local:VisibleIfListIntContains.Instance}, ConverterParameter=1}" VerticalAlignment="Center"/>
            <TextBlock Text="2" Visibility="{Binding Numbers, Converter={x:Static local:VisibleIfListIntContains.Instance}, ConverterParameter=2}" VerticalAlignment="Center"/>
            <TextBlock Text="3" Visibility="{Binding Numbers, Converter={x:Static local:VisibleIfListIntContains.Instance}, ConverterParameter=3}" VerticalAlignment="Center"/>
            <TextBlock Text="4" Visibility="{Binding Numbers, Converter={x:Static local:VisibleIfListIntContains.Instance}, ConverterParameter=4}" VerticalAlignment="Center"/>
            <TextBlock Text="5" Visibility="{Binding Numbers, Converter={x:Static local:VisibleIfListIntContains.Instance}, ConverterParameter=5}" VerticalAlignment="Center"/>
            <TextBlock Text="6" Visibility="{Binding Numbers, Converter={x:Static local:VisibleIfListIntContains.Instance}, ConverterParameter=6}" VerticalAlignment="Center"/>
            <TextBlock Text="7" Visibility="{Binding Numbers, Converter={x:Static local:VisibleIfListIntContains.Instance}, ConverterParameter=7}" VerticalAlignment="Center"/>
            <TextBlock Text="8" Visibility="{Binding Numbers, Converter={x:Static local:VisibleIfListIntContains.Instance}, ConverterParameter=8}" VerticalAlignment="Center"/>
            <TextBlock Text="9" Visibility="{Binding Numbers, Converter={x:Static local:VisibleIfListIntContains.Instance}, ConverterParameter=9}" VerticalAlignment="Center"/>
        </UniformGrid>
        <UniformGrid Columns="1">
            <TextBox VerticalAlignment="Center" Width="40" HorizontalContentAlignment="Center"
                     Text="{Binding Number, Source={StaticResource proxy}}"/>
            <UniformGrid Columns="2" HorizontalAlignment="Center" VerticalAlignment="Top">
                <Button Content="Add" Padding="15 5" Margin="10"
                        Command="{Binding AddIntCommand}"
                        CommandParameter="{Binding Number, Source={StaticResource proxy}}"/>
                <Button Content="Remove" Padding="15 5" Margin="10"
                        Command="{Binding RemoveIntCommand}"
                        CommandParameter="{Binding Number, Source={StaticResource proxy}}"/>
            </UniformGrid>
        </UniformGrid>
    </UniformGrid>
</Window>

The BaseInpc and RelayCommand classes were used.

P.S. If I misunderstood your task, please explain in more detail its details that are important to you.

Addition to the answer in connection with the clarification of the question

As far as I understand your code, my explanations can be applied to it like this:

    public partial class Macro : BaseInpc
    {
        private IEnumerable<int> _listFacesAllowed = Enumerable.Empty<int>();
        public IEnumerable<int> ListFacesAllowed
        {
            get => _listFacesAllowed;
            set => Set(ref _listFacesAllowed, value);
        }
    private void setAvailableFaces()
    {
        if (this.gpProfil == "I" || this.gpProfil == "H" || this.gpProfil == "U")
        {
            ListFacesAllowed = ListFacesAllowed.Concat(new int[] { 0, 1, 3 }).Distinct().ToArray();
        }
        else if (this.gpProfil == "L")
        {
            ListFacesAllowed = ListFacesAllowed.Concat(new int[] { 0, 2 }).Distinct().ToArray();
        }
        else if (this.gpProfil == "M")
        {
            ListFacesAllowed = ListFacesAllowed.Concat(new int[] { 0, 1, 2, 3 }).Distinct().ToArray();
        }
        if (this.type == 1)
        {
            if (this.isTraversingMacro)
            {
                if (this.gpProfil == "M")
                {
                    ListFacesAllowed = ListFacesAllowed.Except(new int[] { 0, 3 }).Distinct().ToArray();
                }
                else if (gpProfil == "I" || gpProfil == "H" || gpProfil == "U")
                {
                    if (this.name != "MV002" && this.name != "MV000")
                    {
                        ListFacesAllowed = ListFacesAllowed.Except(new int[] { 1, 2 }).Distinct().ToArray();
                    }
                    else
                    {
                        ListFacesAllowed = ListFacesAllowed.Except(new int[] { 1 }).Distinct().ToArray();
                    }
                }

            }
        }
    }
    [ValueConversion(typeof(ISet<int>), typeof(Visibility))]
    public class VisibleIfListIntContains : IValueConverter
    {
        private static readonly Int32Converter converter = new Int32Converter();
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            if (value is not IEnumerable<int> ints)
                return DependencyProperty.UnsetValue;

            if (parameter is not int num)
            {
                if (!converter.IsValid(parameter))
                    return DependencyProperty.UnsetValue;
                num = (int)(converter.ConvertFrom(parameter) ?? throw new Exception("God knows what happened."));
            }

            return ints.Contains(num)
                ? Visibility.Visible
                : Visibility.Collapsed;
        }

        // The reverse conversion in such logic, in principle, will not work,
        // since it is impossible to replace the collection itself with a new instance from the converter.
        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }

        private VisibleIfListIntContains() { }
        public static VisibleIfListIntContains Instance { get; } = new VisibleIfListIntContains();
    }
  • Related