为什么验证错误在 ListBox 和 ListBoxItem 中无法按预期工作?

Why are validation errors not working as expected inside ListBox and ListBoxItem?

提问人:blazcowicz 提问时间:3/15/2023 最后编辑:blazcowicz 更新时间:3/16/2023 访问量:49

问:

我一直在尝试在 WPF 应用程序中实现一些错误验证,并且我已经为如何在 ListBox 中正确执行此操作而绞尽脑汁了两天。我正在使用 ObservableValidator 和 CommunityToolkit 中的属性,该部分似乎工作正常。

为了提供更多上下文,我有一个 ListBox,其中包含每个项目都可以使用多个 TextBox、ComboBox 等进行编辑。我正在验证诸如必填字段,IP地址之类的东西,相当标准的东西。我的问题是,当其中一个字段出现错误时,除了它有一个红色边框之外,另一个不相关的控件也会出现同样的情况。我已经在这个主题上搜索了很多,我不明白为什么同一网格中的另一个控件也在变化。

An example with the name being empty

至少 ListBox 的事情似乎是正常的,我用一个非常简单的示例重现了它,我终于明白,如果所选项的某个属性无效,则 ListBox 处于错误状态。可以通过在 ListBox 上放置一个空的错误模板来停用它,所以我对它很好。

另一方面,我完全不知道为什么项目网格内的其他一些控件也处于错误状态,并且我无法在我的简单示例中重现它。我也无法合理地共享使整个视图工作所需的所有代码,但我会尝试在下面提供最相关的部分。另请注意,我没有编写整个代码,并希望更改尽可能少的数量。

主视图内容:

<Grid>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="Auto"/>
        <ColumnDefinition Width="200"/>
    </Grid.ColumnDefinitions>
    <ListBox Grid.Column="0" Background="Transparent" Margin="10" 
             ItemsSource="{Binding Sources}" 
             SelectedItem="{Binding SelectedSource}"
             ItemContainerStyle="{DynamicResource SourceListBoxItem}">
        <ListBox.ItemsPanel>
            <ItemsPanelTemplate>
                <StackPanel/>
            </ItemsPanelTemplate>
        </ListBox.ItemsPanel>
    </ListBox>
<Grid>

对应的视图模型:


public partial class SourcesViewModel : ObservableObject, IMenuItemViewModel
{
    private readonly IDialogService dialogService;

    [ObservableProperty]
    private SourceItem selectedSource = new();

    [ObservableProperty]
    private ObservableCollection<SourceItem> sources;

    public SourcesViewModel(IDialogService dialogServiceParam)
    {
        dialogService = dialogServiceParam;
    }

    [RelayCommand]
    private void ShowColorIconDialog()
    {
        ColorIcon colorIcon = new(SelectedSource.Icon, SelectedSource.Color);

        dialogService.ShowDialog<ColorIconSelectionViewModel>(colorIcon);
    }
}

SourceItem 定义:


public partial class SourceItem : ObservableValidator, ICloneable
{
    public SourceItem() { }

    [ObservableProperty]
    private int id;

    [ObservableProperty]
    [NotifyDataErrorInfo]
    [Required(ErrorMessage = "Name is required")]
    private string name;

    [ObservableProperty]
    private string color;

    [ObservableProperty]
    [NotifyDataErrorInfo]
    [Required(ErrorMessage = "Icon is required")]
    private string icon;
}

ListBox 项的样式:

<Style x:Key="SourceListBoxItem" TargetType="ListBoxItem">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type ListBoxItem}">
                <customControls:CustomExpander x:Name="Expander"
                    Width="650"
                    Template="{DynamicResource ExpanderTemplate}"
                    IsExpanded="{Binding IsSelected, RelativeSource={RelativeSource TemplatedParent}}"
                    OverridesDefaultStyle="True"
                    TitlePart1="{lex:Loc Label_Sources_SourceHeader}"
                    TitlePart2="{Binding Id, StringFormat={}{0} :}"
                    TitlePart3="{Binding Name, ValidatesOnNotifyDataErrors=False}"
                    HorizontalAlignment="Left"
                    VerticalAlignment="Top"
                    Margin="10,2">
                </customControls:CustomExpander>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

扩展器内容的数据模板:

 <DataTemplate x:Key="ExpanderContentTemplate" xmlns:enum="clr-namespace:Medinbox.RoomManager.Models.Enums">
        <Grid Background="{DynamicResource WhiteBrush}">
            <StackPanel Orientation="Vertical" Margin="10,5">
               <TextBlock Text="{lex:Loc Label_Sources_SourceName}"/>
                   <TextBox Text="{Binding DataContext.Name, RelativeSource={RelativeSource AncestorType=ListBoxItem}}">
                </TextBox>
            </StackPanel>
            <StackPanel>
                 <userControls:SourceIconColorButton BackgroundColor="{Binding DataContext.Color, RelativeSource={RelativeSource AncestorType=ListBoxItem }}"
                                                            IconName="{Binding DataContext.Icon, RelativeSource={RelativeSource AncestorType=ListBoxItem}}"
                                                            DataContext="{Binding DataContext, RelativeSource={RelativeSource AncestorType= ListBoxItem}}"
                                                            Command ="{Binding DataContext.ShowColorIconDialogCommand, RelativeSource={RelativeSource AncestorType=UserControl}}"/>
            </StackPanel>
        </Grid>
</DataTemplate>

我通过删除面板和控件来简化它,以解决问题的核心:当名称 TextBox 无效时,ColorIcon 自定义控件也有一个红色边框,如上所示。如果所有控件都是这种情况,我可能会理解,但这只是这个。

在这一点上,我非常欢迎任何想法,以及关于这一切应该如何工作的解释。

编辑:我已经添加了更多代码(应该立即共享视图模型)并试图在某些部分上更清晰,我希望它有所帮助。

WPF 验证 XAML 列表框

评论

0赞 blazcowicz 3/16/2023
谁能告诉我我的问题有什么问题?我想这不是缺乏研究,所以如果你认为它不清楚,没有用,它缺乏代码,我很乐意提供更多信息,因为我真的被困在这里。

答:

0赞 blazcowicz 3/16/2023 #1

所以最后,由于 ListBox 如何处理验证,它非常令人困惑,我应该更早地看到真正的问题。

正是在那部分:

<userControls:SourceIconColorButton BackgroundColor="{Binding DataContext.Color, RelativeSource={RelativeSource AncestorType=ListBoxItem }}"
                                                            IconName="{Binding DataContext.Icon, RelativeSource={RelativeSource AncestorType=ListBoxItem}}"
                                                            DataContext="{Binding DataContext, RelativeSource={RelativeSource AncestorType= ListBoxItem}}"
                                                            Command ="{Binding DataContext.ShowColorIconDialogCommand, RelativeSource={RelativeSource AncestorType=UserControl}}"/>

该控件不应绑定到整个 SourceItem 对象的颜色图标顶部。当 SourceItem 的名称或 IP 地址无效时,对象本身为 true,因此自定义控件也具有红色边框。我删除了可能是剩余的绑定,它有效。HasErrors

另一个控件有一个 SourceItem 作为 a,我无法轻易更改它,所以我使用了这个绑定,因为在这种情况下它似乎是正确的解决方案。CommandParameterValidatesOnNotifyDataErrors=False