3

嘿,

我正在使用 MVVM 创建简单的应用程序,并偶然发现了一个我发现难以解决的问题。在我的应用程序中,我有数据网格和几个控件来编辑数据网格中当前选定的项目。在我的 ViewModel 中,我有CurrentSequence持有ColorSettingsSequencesSequence对象的属性(这些对象的集合用作数据网格的 DataContext)。

这是xml:

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

这很好用,但是当我想添加验证时问题就来了。我想分别验证StartTempEndTemp给出不同的错误。如果我编辑一个值,它也会在数据网格中更新,我将如何分解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);
    }
}

我只是将 TextBoxes 绑定到这些值,而不是将它们直接绑定到CurrentSequence. 我还添加了在设置器中设置 CurrentSequence 值,并希望这样我的更改将一直推回原始集合并在数据网格中进行更改。那没有发生..当 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;
        }
    }
}
4

2 回答 2

2

如果我理解正确,您的问题是即使验证失败,您也想提交您的属性值。如果我在这个假设中错了,解决方案会更简单,基本上是他的评论中暗示的,你只需要INotifyPropertyChanged在你的ColorSettingsSequencesSequence班级中实现。

我无法从你的帖子中推断出你采用了什么样的验证,但我会这样做。即使文本框中的验证失败,更新数据网格的关键是(以及规则的实现)的ValidationStep="UpdatedValue"一部分。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>

视图模型:

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

}

ColorSettingsSequencesSequence:

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

ValidationRule(另见此线程):

public class TempValidationRule : ValidationRule
{
    // default values
    private int _minimumTemp = -273;
    private int _maximumTemp = 2500;

    public int MinimumTemp
    {
        get { return _minimumTemp; }
        set { _minimumTemp = value; }
    }

    public int MaximumTemp
    {
        get { return _maximumTemp; }
        set { _maximumTemp = value; }
    }

    public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo)
    {
        string error = null;
        string s = GetBoundValue(value) as string;

        if (!string.IsNullOrEmpty(s))
        {
            int temp;
            if (!int.TryParse(s, out temp))
                error = "No valid integer";
            else if (temp > this.MaximumTemp)
                error = string.Format("Temperature too high. Maximum is {0}.", this.MaximumTemp);
            else if (temp < this.MinimumTemp)
                error = string.Format("Temperature too low. Minimum is {0}.", this.MinimumTemp);
        }

        return new ValidationResult(string.IsNullOrEmpty(error), error);

    }

    private object GetBoundValue(object value)
    {
        if (value is BindingExpression)
        {
            // ValidationStep was UpdatedValue or CommittedValue (validate after setting)
            // Need to pull the value out of the BindingExpression.
            BindingExpression binding = (BindingExpression)value;

            // Get the bound object and name of the property
            string resolvedPropertyName = binding.GetType().GetProperty("ResolvedSourcePropertyName", BindingFlags.Public | BindingFlags.DeclaredOnly | BindingFlags.Instance).GetValue(binding, null).ToString();
            object resolvedSource = binding.GetType().GetProperty("ResolvedSource", BindingFlags.Public | BindingFlags.DeclaredOnly | BindingFlags.Instance).GetValue(binding, null);

            // Extract the value of the property
            object propertyValue = resolvedSource.GetType().GetProperty(resolvedPropertyName).GetValue(resolvedSource, null);

            return propertyValue;
        }
        else
        {
            return value;
        }
    }
}
于 2013-03-07T16:28:25.940 回答
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 "";
        }
    }
}

主视图模型

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();
    }
}
于 2013-03-07T19:08:31.330 回答