平,
我正在使用MVVM创建简单的应用程序,偶然发现了一个我很难解决的问题。在我的应用程序上,我有datagrid和几个控件来编辑datagrid中当前选定的项目。在我的ViewModel中,我有CurrentSequence
属性,其中包含ColorSettingsSequencesSequence
对象(这些对象的集合用作datagrid的DataContext)。
这是xaml:
<DataGrid AutoGenerateColumns="False" ItemsSource="{Binding Path=ColorSettingsSequences}"
SelectedItem="{Binding Path=CurrentSequence, Mode=TwoWay}">
.... more things here ...
</DataGrid>
<StackPanel Grid.Column="0" Grid.Row="0">
<Grid>
<Label Content="Start temperature (°C)" Height="28" HorizontalAlignment="Left" x:Name="lblSeqStartTemp" VerticalAlignment="Top" />
<TextBox Height="23" Margin="0,28,10,0" x:Name="tbSeqStartTemp" VerticalAlignment="Top" Text="{Binding Path=CurrentSequence.StartTemp}" />
</Grid>
<Grid>
<Label Content="Start color" Height="28" HorizontalAlignment="Left" x:Name="lblSeqHue" VerticalAlignment="Top" />
<xctk:ColorPicker Margin="0,28,10,0" x:Name="clrpSeqHue" SelectedColor="{Binding Path=CurrentSequence.StartHue, Converter={StaticResource hueToColor}, ConverterParameter=False}" ShowStandardColors="False" />
</Grid>
</StackPanel>
<StackPanel Grid.Column="1" Grid.Row="0">
<Grid>
<Label Content="End temperature (°C)" Height="28" HorizontalAlignment="Left" x:Name="lblSeqEndTemp" VerticalAlignment="Top" />
<TextBox Height="23" Margin="0,28,10,0" x:Name="tbSeqEndTemp" VerticalAlignment="Top" Text="{Binding Path=CurrentSequence.EndTemp}" />
</Grid>
<Grid>
<Label Content="End color" Height="28" HorizontalAlignment="Left" x:Name="lblSeqEndHue" VerticalAlignment="Top" />
<xctk:ColorPicker Margin="0,28,10,0" x:Name="clrpSeqEndHue" SelectedColor="{Binding Path=CurrentSequence.EndHue, Converter={StaticResource hueToColor}, ConverterParameter=False}" ShowStandardColors="False" />
</Grid>
</StackPanel>
代码:
private ColorSettingsSequencesSequence _currentSequence;
public ColorSettingsSequencesSequence CurrentSequence
{
get
{
return this._currentSequence;
}
set
{
this._currentSequence = value;
OnPropertyChanged("CurrentSequence");
}
}
这很好用,但问题出现在我想添加验证时。我想分别验证StartTemp
和EndTemp
并提供不同的错误。如果我编辑一个在数据网格中更新的值,我将如何分解ColorSettingsSequencesSequence
对象以便绑定也仍然可用?
这是我尝试过的,我创建了2个新属性并将我的验证添加到那些:
private String _currentSequenceStartTemp;
public String CurrentSequenceStartTemp
{
get
{
return _currentSequenceStartTemp;
}
set
{
this._currentSequenceStartTemp = value;
CurrentSequence.StartTemp = value;
RaisePropertyChanged("CurrentSequenceStartTemp");
Validator.Validate(() => CurrentSequenceStartTemp);
ValidateCommand.Execute(null);
}
}
private String _currentSequenceEndTemp;
public String CurrentSequenceEndTemp
{
get
{
return _currentSequenceEndTemp;
}
set
{
this._currentSequenceEndTemp = value;
CurrentSequence.EndTemp = value;
RaisePropertyChanged("CurrentSequenceEndTemp");
Validator.Validate(() => CurrentSequenceEndTemp);
ValidateCommand.Execute(null);
}
}
我只是将TextBox绑定到这些值,而不是将它们直接绑定到CurrentSequence
。我还添加了在setter中设置CurrentSequence值,并希望我的更改将一直推回到原始集合,并将在datagrid中更改。那没发生..
当CurrentSequence改变时,我也改变了这些属性的值:
private ColorSettingsSequencesSequence _currentSequence;
public ColorSettingsSequencesSequence CurrentSequence
{
get
{
return this._currentSequence;
}
set
{
this._currentSequence = value;
RaisePropertyChanged("CurrentSequence");
if (value != null)
{
CurrentSequenceStartTemp = value.StartTemp;
CurrentSequenceEndTemp = value.EndTemp;
}
else
{
CurrentSequenceStartTemp = String.Empty;
CurrentSequenceEndTemp = String.Empty;
}
}
}
答案 0 :(得分:2)
如果我已正确理解,您的问题是即使验证失败也要提交属性值。如果我在这个假设中错了,那么解决方案就更容易了,基本上他的评论中暗示了这一点,你只需要在INotifyPropertyChanged
类中实现ColorSettingsSequencesSequence
。
我无法从你的帖子中推断出你使用了什么样的验证,但这是我如何做到的。即使文本框中的验证失败,更新数据网格的关键还是ValidationStep="UpdatedValue"
的{{1}}部分(以及规则的实施)。
查看:
ValidationRule
视图模型:
<UserControl x:Class="WpfApplication1.DemoValidation"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:WpfApplication1"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<UserControl.DataContext>
<local:DemoValidationViewModel />
</UserControl.DataContext>
<UserControl.Resources>
<Style TargetType="{x:Type TextBox}">
<Setter Property="Validation.ErrorTemplate">
<Setter.Value>
<ControlTemplate>
<StackPanel>
<Border BorderBrush="Red" BorderThickness="1">
<AdornedElementPlaceholder Name="ph" />
</Border>
<Border BorderBrush="LightGray" BorderThickness="1" Background="Beige">
<TextBlock Foreground="Red" FontSize="12" Margin="5"
Text="{Binding ElementName=ph, Path=AdornedElement.(Validation.Errors)[0].ErrorContent}">
</TextBlock>
</Border>
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</UserControl.Resources>
<Grid Margin="10">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="10" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0" Grid.Row="0">
<Label Content="Start temperature (°C)" Height="28" HorizontalAlignment="Left" x:Name="lblSeqStartTemp" VerticalAlignment="Top" />
<TextBox Height="23" x:Name="tbSeqStartTemp" VerticalAlignment="Top" >
<TextBox.Text>
<Binding Path="CurrentSequence.StartTemp" UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<local:TempValidationRule MaximumTemp="400" MinimumTemp="-100" ValidationStep="UpdatedValue" />
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
</StackPanel>
<StackPanel Grid.Column="2" Grid.Row="0">
<Label Content="End temperature (°C)" Height="28" HorizontalAlignment="Left" x:Name="lblSeqEndTemp" VerticalAlignment="Top" />
<TextBox Height="23" x:Name="tbSeqEndTemp" VerticalAlignment="Top" >
<TextBox.Text>
<Binding Path="CurrentSequence.EndTemp" UpdateSourceTrigger="PropertyChanged" >
<Binding.ValidationRules>
<local:TempValidationRule MaximumTemp="500" MinimumTemp="100" ValidationStep="UpdatedValue" />
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
</StackPanel>
<DataGrid Grid.Row="2" Grid.ColumnSpan="3" Margin="0,10,0,0"
ItemsSource="{Binding Path=ColorSettingsSequences}"
SelectedItem="{Binding Path=CurrentSequence, Mode=TwoWay}" />
</Grid>
</UserControl>
ColorSettingsSequencesSequence:
public class DemoValidationViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
private ColorSettingsSequencesSequence _currentSequence;
public ColorSettingsSequencesSequence CurrentSequence
{
get { return this._currentSequence; }
set
{
this._currentSequence = value;
OnPropertyChanged("CurrentSequence");
}
}
public List<ColorSettingsSequencesSequence> ColorSettingsSequences { get; private set; }
public DemoValidationViewModel()
{
// dummy data
this.ColorSettingsSequences = new List<ColorSettingsSequencesSequence>()
{
new ColorSettingsSequencesSequence() { StartTemp = "10", EndTemp = "20" },
new ColorSettingsSequencesSequence() { StartTemp = "20", EndTemp = "30" },
new ColorSettingsSequencesSequence() { StartTemp = "30", EndTemp = "40" }
};
}
}
ValidationRule(另见this thread):
public class ColorSettingsSequencesSequence : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
private string _startTemp;
public string StartTemp { get { return _startTemp; } set { _startTemp = value; OnPropertyChanged("StartTemp");}}
private string _endTemp;
public string EndTemp { get { return _endTemp; } set { _endTemp = value; OnPropertyChanged("EndTemp"); } }
}
答案 1 :(得分:2)
我已经复制了你的问题。但我couldn't find
任何问题。一切正常。
StartTemp
和EndTemp
。所以我在项目中解决了上述两个问题。
结果
将开始温度更改为40后,数据网格值也已更改。
让我们在开始温度文本框中创建一个错误。
现在另一个
您现在可以看到两个属性都是单独验证的。
这是我创建的项目。
项目结构
ViewModelBase类
public class ViewModelBase : INotifyPropertyChanged
{
#region Implementation of INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
}
protected virtual void OnPropertyChanged(PropertyChangedEventArgs args)
{
var handler = PropertyChanged;
if (handler != null)
handler(this, args);
}
#endregion
}
ColorSettingsSequencesSequence类
public class ColorSettingsSequencesSequence : ViewModelBase, IDataErrorInfo
{
#region Declarations
private string startColor;
private string startTemperature;
private string endTemperature;
#endregion
#region Properties
/// <summary>
/// Gets or sets the start color.
/// </summary>
/// <value>
/// The start color.
/// </value>
public string StartColor
{
get
{
return this.startColor;
}
set
{
this.startColor = value;
OnPropertyChanged("StartColor");
}
}
/// <summary>
/// Gets or sets the start temperature.
/// </summary>
/// <value>
/// The start temperature.
/// </value>
public string StartTemperature
{
get
{
return this.startTemperature;
}
set
{
this.startTemperature = value;
OnPropertyChanged("StartTemperature");
}
}
/// <summary>
/// Gets or sets the end temperature.
/// </summary>
/// <value>
/// The end temperature.
/// </value>
public string EndTemperature
{
get
{
return this.endTemperature;
}
set
{
this.endTemperature = value;
OnPropertyChanged("EndTemperature");
}
}
#endregion
/// <summary>
/// Gets an error message indicating what is wrong with this object.
/// </summary>
/// <returns>An error message indicating what is wrong with this object. The default is an empty string ("").</returns>
public string Error
{
get
{
return "";
}
}
/// <summary>
/// Gets the error message for the property with the given name.
/// </summary>
/// <param name="columnName">Name of the column.</param>
/// <returns></returns>
public string this[string columnName]
{
get
{
if (columnName.Equals("StartTemperature"))
{
if (string.IsNullOrEmpty(this.StartTemperature))
{
return "Please enter a start temperature";
}
}
if (columnName.Equals("EndTemperature"))
{
if (string.IsNullOrEmpty(this.EndTemperature))
{
return "Please enter a end temperature";
}
}
return "";
}
}
}
MainViewModel
public class MainViewModel : ViewModelBase
{
#region Declarations
private ColorSettingsSequencesSequence currentSequence;
private ObservableCollection<ColorSettingsSequencesSequence> colorSettingsSequences;
#endregion
#region Properties
/// <summary>
/// Gets or sets the current sequence.
/// </summary>
/// <value>
/// The current sequence.
/// </value>
public ColorSettingsSequencesSequence CurrentSequence
{
get
{
return this.currentSequence;
}
set
{
this.currentSequence = value;
OnPropertyChanged("CurrentSequence");
}
}
/// <summary>
/// Gets or sets the color settings sequences.
/// </summary>
/// <value>
/// The color settings sequences.
/// </value>
public ObservableCollection<ColorSettingsSequencesSequence> ColorSettingsSequences
{
get
{
return this.colorSettingsSequences;
}
set
{
this.colorSettingsSequences = value;
OnPropertyChanged("ColorSettingsSequences");
}
}
#endregion
#region Commands
#endregion
#region Constructors
/// <summary>
/// Initializes a new instance of the <see cref="MainViewModel" /> class.
/// </summary>
public MainViewModel()
{
this.ColorSettingsSequences = new ObservableCollection<ColorSettingsSequencesSequence>();
ColorSettingsSequencesSequence sequence1 = new ColorSettingsSequencesSequence();
sequence1.StartColor = "Blue";
sequence1.StartTemperature = "10";
sequence1.EndTemperature = "50";
ColorSettingsSequences.Add(sequence1);
ColorSettingsSequencesSequence sequence2 = new ColorSettingsSequencesSequence();
sequence2.StartColor = "Red";
sequence2.StartTemperature = "20";
sequence2.EndTemperature = "60";
ColorSettingsSequences.Add(sequence2);
ColorSettingsSequencesSequence sequence3 = new ColorSettingsSequencesSequence();
sequence3.StartColor = "Yellow";
sequence3.StartTemperature = "30";
sequence3.EndTemperature = "70";
ColorSettingsSequences.Add(sequence3);
this.CurrentSequence = sequence1;
}
#endregion
#region Private Methods
#endregion
}
MainWindow.xaml(XAML)
<Window x:Class="DataGridValidation.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow"
Height="350"
Width="525">
<Window.Resources>
<Style TargetType="TextBox">
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="true">
<Setter Property="ToolTip"
Value="{Binding RelativeSource={x:Static RelativeSource.Self},
Path=(Validation.Errors)[0].ErrorContent}"/>
</Trigger>
</Style.Triggers>
</Style>
</Window.Resources>
<Grid Name="mainGrid">
<Grid.RowDefinitions>
<RowDefinition Height="149" />
<RowDefinition Height="73" />
<RowDefinition Height="123" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="249*" />
</Grid.ColumnDefinitions>
<DataGrid AutoGenerateColumns="False" ItemsSource="{Binding ColorSettingsSequences}"
SelectedItem="{Binding CurrentSequence}"
IsReadOnly="True">
<DataGrid.Columns>
<DataGridTextColumn Header="Start Color" Binding="{Binding StartColor}" />
<DataGridTextColumn Header="End Color" Binding="{Binding StartTemperature}" />
<DataGridTextColumn Header="End Color" Binding="{Binding EndTemperature}" />
</DataGrid.Columns>
</DataGrid>
<StackPanel Grid.Column="0" Grid.Row="1">
<Grid>
<Label Content="Start temperature (°C)"
Height="28"
HorizontalAlignment="Left"
x:Name="lblSeqStartTemp"
VerticalAlignment="Top" />
<TextBox Height="23"
Margin="10,28,10,0"
x:Name="tbSeqStartTemp"
VerticalAlignment="Top"
Text="{Binding Path=CurrentSequence.StartTemperature, ValidatesOnDataErrors=True, NotifyOnValidationError=True}"/>
</Grid>
</StackPanel>
<StackPanel Grid.Row="2" Margin="0,0,0,43">
<Grid>
<Label Content="End temperature (°C)"
HorizontalAlignment="Left"
VerticalAlignment="Top" />
<TextBox Height="23"
Margin="10,28,10,0"
x:Name="tbSeqEndTemp"
VerticalAlignment="Top"
Text="{Binding Path=CurrentSequence.EndTemperature, ValidatesOnDataErrors=True, NotifyOnValidationError=True}"/>
</Grid>
</StackPanel>
</Grid>
</Window>
MainWindow.xaml.cs(代码隐藏文件)
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
mainGrid.DataContext = new MainViewModel();
}
}