编辑datagrid中当前选定的项目

时间:2013-03-02 11:09:53

标签: c# mvvm binding datagrid

平,

我正在使用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");
    }
}

这很好用,但问题出现在我想添加验证时。我想分别验证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);
    }
}

我只是将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;
        }
    }
}

2 个答案:

答案 0 :(得分:2)

如果我已正确理解,您的问题是即使验证失败也要提交属性值。如果我在这个假设中错了,那么解决方案就更容易了,基本上他的评论中暗示了这一点,你只需要在INotifyPropertyChanged类中实现ColorSettingsSequencesSequence

我无法从你的帖子中推断出你使用了什么样的验证,但这是我如何做到的。即使文本框中的验证失败,更新数据网格的关键还是ValidationStep="UpdatedValue"的{​​{1}}部分(以及规则的实施)。

DemoValidation

查看:

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任何问题。一切正常。

  • 分别验证StartTempEndTemp
  • 如果更新了一个值,则还应更新数据网格

所以我在项目中解决了上述两个问题。

结果

enter image description here

将开始温度更改为40后,数据网格值也已更改。

enter image description here

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

enter image description here

现在另一个

enter image description here

您现在可以看到两个属性都是单独验证的。

这是我创建的项目。

项目结构

enter image description here

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