【问题标题】:Binding not working when modifying ObservableCollection修改 ObservableCollection 时绑定不起作用
【发布时间】:2022-10-15 17:53:59
【问题描述】:

我正在尝试将 RadioButton 绑定到 ObservableCollection<Face>。我已经做了几个绑定,它们都在工作。但是在那一个上,我无法理解出了什么问题。唯一不同的是,我尝试为此使用转换器。 首先,我的 ObservableCollection 是 ObservableCollection<int>,然后我将其更改为 ObservableCollection<Face>,因为我认为问题是我无法绑定到 ObservableCollection<int>,但没有任何改变。

这是我的转换器。我不想有一个 ConvertBack,但不能删除它? :

public class VisibleIfListIntContains : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        if (parameter == null)
        {
            return false;
        }
        ObservableCollection<Face> listInt = (ObservableCollection<Face>)value;
        int value2 = System.Convert.ToInt32(parameter.ToString());
        bool? retour = listInt.Any(x => x.FaceCode == value2);
        if (retour==true)
        {
            return Visibility.Visible; 
        }
        else
        {
            return 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>();
        }
    }
}

然后是我的 Face 课:

public class Face : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    public void NotifyPropertyChanged(string propName)
    {
        this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propName));
    }
    private int faceCode { get; set; } = 0;
    public int FaceCode { get { return this.faceCode; } set { this.faceCode = value; this.NotifyPropertyChanged("FaceCode"); } }
    public Face()
    {

    }
    public Face(int face)
    {
        this.FaceCode = face;
    }
}

最后是我绑定的类:

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

我这样修改我的 ObservableCollection :

this.ListFacesAllowedNew.Clear();
foreach(int face in this.listFacesAllowed)
{
    this.ListFacesAllowedNew.Add(new Face(face));
}

ObservableCollection 已修改,但转换器从未执行。

这是 XAML 代码:

<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="*"/>
                        <RowDefinition Height="*"/>
                        <RowDefinition Height="*"/>
                        <RowDefinition Height="*"/>
                        <RowDefinition Height="*"/>
                        <RowDefinition Height="*"/>
                        <RowDefinition Height="*"/>
                        <RowDefinition Height="*"/>
                    </Grid.RowDefinitions>
                    <CheckBox Grid.Row="0" IsChecked="{Binding DataContext.TraversingMacroSelected, RelativeSource={RelativeSource AncestorType=Window}}" 
                            Content="{x:Static p:Resources.Web}"
                            Margin="3"/>
                    <RadioButton Grid.Row="1" IsChecked="{Binding DataContext.SelectedMacro.Face, RelativeSource={RelativeSource AncestorType=Window},Mode=TwoWay,Converter={StaticResource isIntEqual},ConverterParameter=0}" 
                            Visibility="{Binding DataContext.FaceWebVisible, RelativeSource={RelativeSource AncestorType=Window},Mode=TwoWay, Converter={StaticResource BoolToVisConverter}}"
                            Content="Test0"
                            Margin="3"/>
                    <RadioButton Grid.Row="2" IsChecked="{Binding DataContext.SelectedMacro.Face, RelativeSource={RelativeSource AncestorType=Window},Mode=TwoWay,Converter={StaticResource isIntEqual},ConverterParameter=1}" 
                            Visibility="{Binding DataContext.SelectedMacro.ListFacesAllowedNew, RelativeSource={RelativeSource AncestorType=Window},Mode=TwoWay,Converter={StaticResource visibleIfListIntContains},ConverterParameter=1}"
                            Content="Test1"
                            Margin="3"/>
                    <RadioButton Grid.Row="3" IsChecked="{Binding DataContext.SelectedMacro.Face, RelativeSource={RelativeSource AncestorType=Window},Mode=TwoWay,Converter={StaticResource isIntEqual},ConverterParameter=2}" 
                            Visibility="{Binding DataContext.SelectedMacro.ListFacesAllowedNew, RelativeSource={RelativeSource AncestorType=Window},Mode=TwoWay,Converter={StaticResource visibleIfListIntContains},ConverterParameter=2}"
                            Content="Test2"
                            Margin="3"/>
                    <RadioButton Grid.Row="4" IsChecked="{Binding DataContext.SelectedMacro.Face, RelativeSource={RelativeSource AncestorType=Window},Mode=TwoWay,Converter={StaticResource isIntEqual},ConverterParameter=3}" 
                            Visibility="{Binding DataContext.SelectedMacro.ListFacesAllowedNew, RelativeSource={RelativeSource AncestorType=Window},Mode=TwoWay,Converter={StaticResource visibleIfListIntContains},ConverterParameter=3}"
                            Content="Test3"
                            Margin="3"/>
                    <CheckBox Grid.Row="5" IsChecked="{Binding DataContext.FaceWebSelected, RelativeSource={RelativeSource AncestorType=Window}}" 
                            Visibility="{Binding DataContext.FaceWebVisible, RelativeSource={RelativeSource AncestorType=Window}, Converter={StaticResource BoolToVisConverter}}"
                            Content="{x:Static p:Resources.Web}"
                            Margin="3"/>
                    <CheckBox Grid.Row="6" IsChecked="{Binding DataContext.FaceTopFlangeSelected, RelativeSource={RelativeSource AncestorType=Window}}" 
                            Visibility="{Binding DataContext.FaceTopFlangeVisible, RelativeSource={RelativeSource AncestorType=Window}, Converter={StaticResource BoolToVisConverter}}"
                            Content="{x:Static p:Resources.FlangeTop}"
                            Margin="3"/>
                    <CheckBox Grid.Row="7" IsChecked="{Binding DataContext.FaceBottomFlangeSelected, RelativeSource={RelativeSource AncestorType=Window}}" 
                            Visibility="{Binding DataContext.FaceBottomFlangeVisible, RelativeSource={RelativeSource AncestorType=Window}, Converter={StaticResource BoolToVisConverter}}"
                            Content="{x:Static p:Resources.FlangeBottom}"
                            Margin="3"/>
                    <CheckBox Grid.Row="8" IsChecked="{Binding DataContext.FaceBackWebSelected, RelativeSource={RelativeSource AncestorType=Window}}" 
                            Visibility="{Binding DataContext.FaceBackWebVisible, RelativeSource={RelativeSource AncestorType=Window}, Converter={StaticResource BoolToVisConverter}}"
                            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>

模板中的所有绑定都运行良好,DataContext.SelectedMacro.ListFacesAllowedNew 的绑定除外

【问题讨论】:

  • 我相信ICollection.Add() 不会在属性内调用您的setter 函数。您可以在foreach 循环中的.Add 之后显式尝试NotifyPropertyChanged。
  • @shrutisingh 实际上,添加 NotifyPropertyChanged 解决了这个问题。你能解释一下为什么在这种情况下我需要这样做吗?我在绑定ObservableCollection 时从不添加它。 (如果您可以将其解释为答案,以便我可以关闭问题,谢谢)
  • 您应该始终在任何视图模型上实现 inotifypropertychanged。除非你喜欢内存泄漏。
  • 在这种情况下出现差异的原因是您正在更改属性。 Observablecollection 通知集合更改。添加或删除集合中的项目。您还可以替换一个项目,它会通知项目更改。但这是一个完整的项目,而不是一个属性的变化。
  • 所有这些都是 wpf 的基础知识。你应该买一本书并阅读文档。

标签: c# wpf binding observablecollection converters


【解决方案1】:

据我了解,您需要检查 Id 集合中是否存在 UI 元素 Id。如果它在那里,然后安装它的可见性。

如果我理解正确,那么明显的问题是您误解了 Binding 的逻辑。
绑定将在第一次加载时起作用,对于后续更新,您需要通知已发生更改。
而且由于您绑定到整个集合并且需要全部检查,因此当您更改其任何元素时,您需要替换集合实例。
因为否则绑定将(有条件地)检查它是否是集合的另一个实例。如果相同,它不会更新,即使集合的元素已经不同。

简单的例子:

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>

使用了BaseInpc and RelayCommand classes。

【讨论】:

    猜你喜欢
    • 2021-08-25
    • 2019-02-12
    • 1970-01-01
    • 2023-03-13
    • 2012-12-01
    • 1970-01-01
    • 2014-03-18
    • 2013-01-14
    • 1970-01-01
    相关资源
    最近更新 更多