在WPF中,我试图创建一个自定义数据网格,当存在数据绑定数据列表(数据范围)时,将单元格显示为组合框;如果没有限制,则显示普通文本框。此外,我正在使用IDataErrorInfo和MVVM模式的随附验证。大部分效果很好,但细节在于魔鬼。我看到一个问题,我的组合框上的验证无法按我预期的那样工作。
我相信我的问题是使用数据触发器定义两个控件时,但是我不确定为什么它的行为方式如此。如果删除文本框上的验证,则组合框上的验证可以正常工作。当我将属性放回文本框中以指定验证时,组合框验证将不再起作用。
这是我的xaml代码,用于根据我的数据触发器指定要使用的控件:
<UserControl x:Class="GlobalVariableEditor.GlobalVariableEditorUserControl"
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:viewmodel="clr-namespace:GlobalVariableEditor.ViewModel"
xmlns:scm="clr-namespace:System.ComponentModel;assembly=WindowsBase"
xmlns:local="clr-namespace:GlobalVariableEditor"
xmlns:clr="clr-namespace:System;assembly=mscorlib"
mc:Ignorable="d"
d:DesignHeight="400" d:DesignWidth="500" Width="auto" Height="auto">
<UserControl.DataContext>
<viewmodel:GlobalVariableEditorViewModel x:Name="_GVEViewModel"/>
</UserControl.DataContext>
<UserControl.Resources>
<CollectionViewSource x:Key="VariablesViewSource" Source="{Binding VariablesData}">
</CollectionViewSource>
<Style x:Key="RangeStyleNormal" TargetType="ComboBox">
<Setter Property="HorizontalContentAlignment" Value="center"/>
<!--<EventSetter Event="SelectionChanged" Handler="ComboBox_SelectionChanged"></EventSetter>-->
<Setter Property="IsEditable" Value="False" />
<Setter Property="HorizontalAlignment" Value="Stretch"/>
<Setter Property="IsTextSearchCaseSensitive" Value="True"/>
</Style>
</UserControl.Resources>
<GroupBox x:Name="grpGVEditor" Header="Global variable editor" FontSize="12">
<Grid x:Name="MainGrid">
<Grid.RowDefinitions>
<!--This is for any declarations or headers we may need for initial information (where to load xml file from, etc)-->
<RowDefinition x:Name="TopRowDefinition" Height="*" MinHeight="30" MaxHeight="50" />
<RowDefinition x:Name="RevisionRowDefinition" Height="*" MinHeight="30" MaxHeight="50" />
<!--this is where the data from the xml file should be populated to-->
<RowDefinition x:Name="DataRowDefinition" Height="*" MinHeight="200"/>
<!--this is where the buttons should be placed (cancel, apply, etc)-->
<!--<RowDefinition x:Name="FilterRowDefinition" Height="*" MinHeight ="25" MaxHeight="30"/>-->
<RowDefinition x:Name="ButtonRowDefinition" Height="*" MinHeight ="30" MaxHeight="50" />
</Grid.RowDefinitions>
<StackPanel Grid.Row="0" Orientation="Horizontal">
<Label Height="30" Width="110" Content="XML file location: " BorderThickness="1" BorderBrush="Black" Margin="3,0,3,0"/>
<Label x:Name="lblCustomXMLFilePath" VerticalContentAlignment="Center" Height="30" Width="350" Content="{Binding FilePath}" BorderThickness="1" BorderBrush="Black" Margin="0,0,3,0" ToolTip="{Binding FilePath}" Background="Gray"/>
<!--<Button x:Name="btnLoad" Height="30" Width="50" Content="Load" Margin="0,5,5,5" Click="btnLoad_Click" Visibility="Hidden"/>-->
<Button Height="30" Width="50" Content="Revert" Margin="0,0,3,0" ToolTip="Revert to TP values" Visibility="Hidden"/>
</StackPanel>
<StackPanel Grid.Row="1" Orientation="Horizontal">
<Label Name="lblRevID" Height="30" Width="110" Content="File Revision ID: " BorderThickness="1" BorderBrush="Black" Margin="3,0,3,0"/>
<TextBox Name="txtRevID" Padding="3" Height="30" Width="100" VerticalContentAlignment="Center" TextAlignment="Left" BorderThickness="1" BorderBrush="Black" Margin="0,0,3,0" Text="{Binding FileVersion}" IsReadOnly="True" Background="Gray">
<TextBox.Style>
<Style>
<Style.Triggers>
<DataTrigger Binding="{Binding FileVersion}" Value="-1">
<Setter Property="TextBox.Background" Value="Salmon" />
<Setter Property="TextBox.ToolTip" Value="Invalid revision number. Settings file is in read-only mode." />
</DataTrigger>
<DataTrigger Binding="{Binding FileVersion}" Value="0">
<Setter Property="TextBox.Background" Value="Salmon" />
<Setter Property="TextBox.ToolTip" Value="Invalid revision number. Settings file is in read-only mode." />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
<TextBox x:Name="tbDummy" Visibility="Hidden"></TextBox>
</StackPanel>
<DockPanel Grid.Row="2" HorizontalAlignment="Stretch" MinHeight="200" Margin="0,5,0,0">
<DataGrid x:Name="PropertyGrid" SelectionMode="Single" SelectionUnit="Cell" RowHeaderWidth="0" IsTextSearchCaseSensitive="True" GridLinesVisibility="All"
AutoGenerateColumns="False" AlternationCount="2" ItemsSource="{Binding FilteredGridEntries}" Margin="3,0,0,0">
<DataGrid.RowStyle>
<Style TargetType="DataGridRow">
<Style.Resources>
<SolidColorBrush x:Key="{x:Static SystemColors.InactiveSelectionHighlightBrushKey}" Color="Transparent"/>
</Style.Resources>
<Setter Property="IsEnabled" Value="{Binding HasWriteAccess}" ></Setter>
<Style.Triggers>
<Trigger Property="AlternationIndex" Value="0">
<Setter Property="Background" Value="White" />
</Trigger>
<Trigger Property="AlternationIndex" Value="1">
<Setter Property="Background" Value="WhiteSmoke" />
</Trigger>
<MultiDataTrigger >
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding IsReadOnly}" Value="True"/>
<Condition Binding="{Binding IsChecked, ElementName=_cboShowAll}" Value="False"/>
</MultiDataTrigger.Conditions>
<MultiDataTrigger.Setters>
<Setter Property="Visibility" Value="Collapsed"/>
</MultiDataTrigger.Setters>
</MultiDataTrigger>
<DataTrigger Binding="{Binding IsReadOnly}" Value="True">
<Setter Property="Background" Value="Gray"/>
<Setter Property="IsEnabled" Value="False" />
</DataTrigger>
</Style.Triggers>
</Style>
</DataGrid.RowStyle>
<DataGrid.Resources>
<Style x:Name="CenterContent" TargetType="{x:Type TextBlock}">
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="HorizontalAlignment" Value="Center" />
</Style>
<Style TargetType="{x:Type DataGridColumnHeader}">
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="ToolTip" Value="{Binding Column.(ToolTipService.ToolTip), RelativeSource={RelativeSource Self}}"/>
</Trigger>
</Style.Triggers>
</Style>
<Image x:Name="rowHeaderImage"
x:Key="rowHeaderTemplate"
VerticalAlignment="Center"
HorizontalAlignment="Center"
Width="64"
Margin="1,0">
<Image.Style>
<Style TargetType="Image">
<Style.Triggers>
<DataTrigger Binding="{Binding IsReadOnly}" Value="true">
<Setter Property="Source" Value="/Resources/access.ico"/>
</DataTrigger >
<DataTrigger Binding="{Binding IsReadOnly}" Value="false">
<Setter Property="Source" Value="/Resources/no_access.jpg"/>
</DataTrigger >
</Style.Triggers>
</Style>
</Image.Style>
</Image>
</DataGrid.Resources>
<DataGrid.ItemBindingGroup>
<BindingGroup/>
</DataGrid.ItemBindingGroup>
<DataGrid.Columns>
<!--This is the Name column-->
<DataGridTemplateColumn Header="Name" ToolTipService.ToolTip="Global variable name/ID" MinWidth ="200" Width="2*">
<DataGridTemplateColumn.CellStyle>
<Style TargetType="DataGridCell">
<Setter Property="Background" Value="Gray"></Setter>
</Style>
</DataGridTemplateColumn.CellStyle>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBox Text="{Binding Name}" IsReadOnly="True" Background="Transparent" ToolTip="{Binding Name}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<!--This is the Site column-->
<!--<DataGridTemplateColumn Header="Site" ToolTipService.ToolTip="Site" Width="1*" MaxWidth ="30" IsReadOnly="False" Visibility="Hidden">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBox TextAlignment="Center" Text="{Binding Site, Mode=TwoWay, UpdateSourceTrigger=LostFocus}" IsReadOnly="{Binding IsReadOnly}" Background="Transparent"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>-->
<!--This is the Slot column-->
<!--<DataGridTemplateColumn Header="Slot" ToolTipService.ToolTip="Slot" Width="1*" MaxWidth ="30" IsReadOnly="False" Visibility="Hidden">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBox TextAlignment="Center" Text="{Binding Slot, Mode=TwoWay, UpdateSourceTrigger=LostFocus}" IsReadOnly="{Binding IsReadOnly}" Background="Transparent"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>-->
<!--This is the High Limit column-->
<!--<DataGridTemplateColumn Header="High Limit" ToolTipService.ToolTip="High limit" MaxWidth="125" Width="1*" Visibility="Hidden">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBox TextAlignment="Center" Text="{Binding HiLimit, Mode=TwoWay, UpdateSourceTrigger=LostFocus}" IsReadOnly="{Binding IsReadOnly}" Background="Transparent"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>-->
<!--This is the Low Limit column-->
<DataGridTemplateColumn Header="Value" MinWidth="200" ToolTipService.ToolTip="Global variable programmed value" Width="3*">
<DataGridTemplateColumn.HeaderStyle>
<Style TargetType="DataGridColumnHeader">
<Setter Property="HorizontalContentAlignment"
Value="Center" />
</Style>
</DataGridTemplateColumn.HeaderStyle>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<!--By default, allow the user to free edit the combobox.
This allows backwards compatibility with existing TP settings-->
<ContentControl Content="{Binding}">
<ContentControl.Style>
<Style TargetType="ContentControl">
<Style.Triggers>
<DataTrigger Binding="{Binding HasRange}" Value="true">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<ComboBox
ItemsSource="{Binding Range, Mode=OneWay}"
MinWidth="50"
Text="{Binding ProgrammedValue,
UpdateSourceTrigger=LostFocus,
ValidatesOnDataErrors=True,
NotifyOnValidationError=True}"
Style="{StaticResource RangeStyleNormal}"
Width="auto" >
</ComboBox>
</DataTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
<DataTrigger Binding="{Binding HasRange}" Value="false">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<TextBox
HorizontalAlignment="Stretch"
Background="Transparent"
BorderThickness="0"
MinWidth="50"
Text="{Binding ProgrammedValue, Mode=TwoWay,
UpdateSourceTrigger=LostFocus,
ValidatesOnDataErrors=True,
NotifyOnValidationError=True
}"
TextAlignment="Center"
Width="Auto">
</TextBox>
</DataTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<!--This is the Low Limit column-->
<!--<DataGridTemplateColumn Header="Low Limit" ToolTipService.ToolTip="Lower limit" Width="1*" MaxWidth="125" Visibility="Hidden">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBox TextAlignment="Center" Text="{Binding LowLimit, Mode=TwoWay, UpdateSourceTrigger=LostFocus}" IsReadOnly="{Binding IsReadOnly}" Background="Transparent"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>-->
<!--This is the Fleet column-->
<DataGridTemplateColumn Header="Fleet Variable" ToolTipService.ToolTip="Global variable scope" MinWidth ="75" Width="1*">
<DataGridTemplateColumn.CellStyle>
<Style TargetType="DataGridCell">
<Setter Property="Background" Value="Gray"></Setter>
</Style>
</DataGridTemplateColumn.CellStyle>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBox Text="{Binding IsFleet}" IsReadOnly="True" Background="Transparent" ToolTip="{Binding IsFleet}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<!--<DataGridCheckBoxColumn Header="Is fleet" Width="1*" MinWidth="50" ToolTipService.ToolTip="Variable of fleet scope" Binding="{Binding IsFleet}" IsReadOnly="True">
</DataGridCheckBoxColumn>-->
</DataGrid.Columns>
</DataGrid>
</DockPanel>
<!--These are the buttons at the bottom of the control-->
<Grid Grid.Row="3">
<StackPanel Height="40" Orientation="Horizontal" VerticalAlignment="Center" Margin="0,2" Width="auto">
<Label Height="25" HorizontalAlignment="Left" Width="40" Margin="5,2,3,0">Filter:</Label>
<TextBox x:Name="_tboFilter" HorizontalAlignment="Left" Height="25" Margin="0,3,0,0" Width="200" MaxLength="40" MaxLines="1" MinLines="1" AcceptsTab="True" TextAlignment="Left" Text="{Binding FilterString, UpdateSourceTrigger=PropertyChanged}" />
<CheckBox Name="_cboShowAll" HorizontalAlignment="Left" Height="25" IsChecked="False" Margin="15,7,0,0">
<TextBlock Text="Show all" />
</CheckBox>
</StackPanel>
<StackPanel Height="40" Width="auto" Orientation="Horizontal" HorizontalAlignment="Right" VerticalAlignment="Center" Margin="0,2">
<Button x:Name="btnTestError" HorizontalAlignment="Right" Height="25" Width="60" Content="TestError" ToolTip="Fire error events for logging" Margin="0,0,3,0" Command="{Binding TestErrorButtonCommand}" Visibility="{Binding EnableQADebuggingTools}" />
<!--<Button x:Name="btnApply" Height="25" Width="50" Content="Apply" ToolTip="Apply changes" Margin="0,3,3,0" Click="btnApply_Click" IsEnabled="{Binding HasDataChanged}" />-->
<Button x:Name="btnApply" HorizontalAlignment="Right" Height="25" Width="50" Content="Apply" ToolTip="Apply changes" Margin="0,0,3,0" Command="{Binding ApplyChangesButtonCommand}" >
<Button.Style>
<Style TargetType="Button">
<Setter Property="IsEnabled" Value="False"/>
<Style.Triggers>
<!-- Button enabled if the following conditions is met: HasDataChanged is true -->
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding HasDataChanged}" Value="True"/>
<Condition Binding="{Binding HasWriteAccess}" Value="True"/>
</MultiDataTrigger.Conditions>
<Setter Property="IsEnabled" Value="True"/>
</MultiDataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
<!--<Button x:Name="btnCancel" Height="25" Width="50" Content="Cancel" Margin="0,3,3,0" ToolTip="Revert to TP values" Click="btnCancel_Click" Command="{Binding CancelButtonCommand}"/>-->
<Button x:Name="btnCancel" HorizontalAlignment="Right" Height="25" Width="50" Content="Cancel" Margin="0,0,3,0" ToolTip="Revert to TP values" Click="btnCancel_Click"/>
</StackPanel>
</Grid>
</Grid>
</GroupBox>
</UserControl>
IDataErrorInfo这样实现:
public string this[string columnName]
{
get
{
string result = null;
try
{
if (columnName == "ProgrammedValue")
{
//Validation routine: data can not be empty field.
if (this.HasRange)
{
// This is a combobox
if (ProgrammedValue == null)
{
result = "Field must have selection";
}
else
{
if (Range != null && !Range.Contains(ProgrammedValue) )
{
result = "Field must have selection";
}
}
}
else
{
// This is a text box:
if (string.IsNullOrEmpty(ProgrammedValue))
{
result = "Field can not be empty.";
}
}
}
}
catch (Exception ex)
{
// Eat the exception.. really should never get here, but if there is an exception
result = null;
}
return result;
}
}
在我的模型中,定义了绑定属性:
public string ProgrammedValue
{
get
{
return _programmedValue;
}
set
{
_programmedValue = value;
RaisePropertyChanged("ProgrammedLimit");
}
}
public List<string> Range
{
get
{
return _range;
}
set
{
_range = value;
RaisePropertyChanged("Range");
}
}
public bool HasRange
{
get
{
return _hasRange;
}
set
{
_hasRange = value;
RaisePropertyChanged("HasRange");
}
}
RangeStyleNormal定义:
<UserControl.Resources>
<Style x:Key="RangeStyleNormal" TargetType="ComboBox">
<Setter Property="HorizontalContentAlignment" Value="center"/>
<EventSetter Event="SelectionChanged" Handler="ComboBox_SelectionChanged"></EventSetter>
<Setter Property="IsEditable" Value="False" />
<Setter Property="HorizontalAlignment" Value="Stretch"/>
<Setter Property="IsTextSearchCaseSensitive" Value="True"/>
</Style>
</UserControl.Resources>
如前所述,当我从文本框属性中删除ValidatesOnDataErrors和NotifyOnValidationError时,组合框的工作方式就像一个超级按钮。我知道我指定的验证例程可以正常工作。我希望这里的人可以看到我犯的一些明显的编码错误,并解释我做错了什么以及如何纠正它。预先感谢。