C# 在datagrid中编辑当前选定的项

C# 在datagrid中编辑当前选定的项,c#,mvvm,binding,datagrid,C#,Mvvm,Binding,Datagrid,嘿嘿, 我正在用MVVM创建一个简单的应用程序,偶然发现了一个我发现很难解决的问题。在我的应用程序中,我有datagrid和两个控件来编辑datagrid中当前选定的项。在我的ViewModel中,我有CurrentSequence属性,该属性保存colorsettingsSequenceSequence对象(这些对象的集合用作datagrid的DataContext) 这里是xaml: <DataGrid AutoGenerateColumns="False" ItemsSource="

嘿嘿,

我正在用MVVM创建一个简单的应用程序,偶然发现了一个我发现很难解决的问题。在我的应用程序中,我有datagrid和两个控件来编辑datagrid中当前选定的项。在我的ViewModel中,我有
CurrentSequence
属性,该属性保存
colorsettingsSequenceSequence
对象(这些对象的集合用作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>
这很好,但当我想添加验证时,问题就来了。我想分别验证
StartTemp
EndTemp
,并给出不同的错误。如何分解
colorsettingsSequenceSequence
对象,以便在编辑数据网格中更新的一个值时绑定仍能正常工作

以下是我尝试的内容,我创建了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);
    }
}
我只是将文本框绑定到这些值,而不是直接绑定到
CurrentSequence
。我还添加了在setters中设置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;
        }
    }
}

如果我理解正确,您的问题是即使验证失败,您也希望提交属性值。如果我的假设是错误的,那么解决方案就更简单了,基本上是sine在他的评论中暗示的,即您只需要在
颜色设置序列序列
类中实现
INotifyPropertyChanged

我无法从你的帖子中推断出你采用了什么样的验证方式,但以下是我的做法。即使文本框中的验证失败,更新数据网格的关键是
ValidationStep=“UpdatedValue”
验证规则的一部分(以及规则的实现)

视图:

ValidationRule(另请参见):

公共类TempValidationRule:ValidationRule { //默认值 私有内部最小温度=-273; 私有内部温度_最大温度=2500; 公共int最小温度 { 获取{return\u minimumTemp;} 设置{u minimumTemp=value;} } 公共int最大温度 { 获取{return\u maximumTemp;} 设置{u maximumTemp=value;} } 公共覆盖验证结果验证(对象值,System.Globalization.CultureInfo CultureInfo) { 字符串错误=null; 字符串s=GetBoundValue(值)作为字符串; 如果(!string.IsNullOrEmpty) { 内部温度; 如果(!int.TryParse,out temp)) error=“无有效整数”; 否则如果(温度>此最大温度) error=string.Format(“温度太高,最大值为{0}.”,this.MaximumTemp); 否则如果(温度<此最低温度) error=string.Format(“温度过低。最小值为{0}.”,this.MinimumTemp); } 返回新的ValidationResult(string.IsNullOrEmpty(error),error); } 私有对象GetBoundValue(对象值) { if(值为BindingExpression) { //ValidationStep为UpdatedValue或CommittedValue(设置后验证) //需要从BindingExpression中提取值。 BindingExpression绑定=(BindingExpression)值; //获取绑定对象和属性的名称 字符串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); //提取属性的值 object propertyValue=resolvedSource.GetType().GetProperty(resolvedPropertyName).GetValue(resolvedSource,null); 返回属性值; } 其他的 { 返回值; } } }
我重现了你的问题。但是我找不到任何问题。一切正常

  • 分别验证
    StartTemp
    EndTemp
  • 如果更新了一个值,则还应更新datagrid
所以我在我的项目中解决了以上两个问题

结果

将启动温度更改为40后,datagrid值也已更改

让我们在“开始温度”文本框中创建一个错误

现在是另一个

现在,您可以看到这两个属性分别进行了验证

这是我创建的项目

项目结构

ViewModelBase类

公共类ViewModelBase:INotifyPropertyChanged
{
#INotifyPropertyChanged的区域实现
公共事件属性更改事件处理程序属性更改;
受保护的虚拟void OnPropertyChanged(字符串propertyName)
{
OnPropertyChanged(新PropertyChangedEventArgs(propertyName));
}
PropertyChanged上受保护的虚拟无效(PropertyChangedEventArgs args)
{
var handler=PropertyChanged;
if(处理程序!=null)
处理程序(this,args);
}
#端区
}
颜色设置序列序列类

公共类颜色设置序列:ViewModelBase,IDataErrorInfo
{
#区域声明
私有字符串startColor;
私有字符串起始温度;
私有字符串结束温度;
#端区
#区域属性
/// 
///获取或设置开始颜色。
/// 
/// 
///开始颜色。
/// 
公共字符串StartColor
{
得到
{
返回此.startColor;
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;
        }
    }
}
<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" }
        };
    }

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