wpf 为什么验证错误在ListBox和ListBoxItem中不能按预期工作?

8fsztsew  于 2023-03-19  发布在  其他
关注(0)|答案(1)|浏览(128)

我一直在尝试在WPF应用程序中实现一些错误验证,两天来我一直在思考如何在ListBox中正确地实现它。我使用了CommunityToolkit中的ObservableValidator和property属性,这部分看起来工作得很好。
为了给予更多的上下文,我有一个列表框,其中包含可以用几个文本框、组合框等编辑的项。我正在验证必填字段、IP地址等相当标准的内容。我的问题是,当其中一个字段有错误时,它的顶部有一个红色边框,对于另一个不相关的控件也是如此。我在这个主题上搜索了很多,我不明白为什么同一个网格中的另一个控件也在改变。

列表框的事情看起来是正常的,至少,我已经用一个非常简单的例子重现了它,我终于明白了,如果所选项目的属性之一无效,列表框就处于错误状态。可以通过在列表框上放置一个空的错误模板来停用它,所以我对它很满意。
另一方面,我完全不知道为什么项目网格中的其他控件也处于错误状态,我无法在我的简单示例中重现它。我也无法合理地共享使整个视图工作所需的所有代码,但我将尝试在下面给予最相关的部分。还请注意,我没有编写整个代码,并希望尽可能少地更改。
主视图的内容:

<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);
    }
}

源项定义:

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;
}

列表框项的样式:

<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>

我已经简化了很多,通过删除面板和控件,以获得问题的核心:当名称文本框无效时,ColorIcon自定义控件也有一个红色边框,如上所示。如果所有控件都是这样,我可能会理解,但只有这个控件是这样。
在这一点上,我真的欢迎任何想法,以及关于这一切应该如何工作的解释。
编辑:我已经添加了更多的代码(应该马上共享视图模型),并试图在一些部分更清晰,我希望这会有所帮助。

omjgkv6w

omjgkv6w1#

所以最后由于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地址无效时,对象本身的HasErrors为true,因此自定义控件也有红色边框。我删除了可能是遗留的绑定,它可以工作。
另一个控件有一个作为CommandParameter的SourceItem,我不能那么容易地更改它,所以我在这个绑定上使用ValidatesOnNotifyDataErrors=False,因为它似乎是本例中正确的解决方案。

相关问题