【问题标题】:Editing currently selected item in datagrid编辑数据网格中当前选定的项目
【发布时间】:2013-02-16 21:36:34
【问题描述】:

嘿,

我正在使用 MVVM 创建简单的应用程序,但偶然发现了一个我发现难以解决的问题。在我的应用程序中,我有数据网格和几个控件来编辑数据网格中当前选定的项目。在我的 ViewModel 中,我有 CurrentSequence 属性,其中包含 ColorSettingsSequencesSequence 对象(这些对象的集合用作数据网格的 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);
    }
}

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

【问题讨论】:

  • 好的,所以您的更改不会被推送到原始集合是吗?这是唯一的问题吗?
  • 基本上是的,我需要分解对象,以便我可以将该对象的属性单独绑定到控件,以便我的验证工作。也许我的方法完全有缺陷并且不正确,所以我希望有人能告诉我这样做的正确方法是什么。
  • 您在更改CurrentSequenceEndTempCurrentSequenceStartTemp 中的每个值后是否尝试使用RaisePropertyChanged("CurrentSequence"); 值得一试?
  • @DanielWardin,是的,我试过了,没有帮助。
  • 你的 ColorSettingsSequencesSequence 是否实现了 INotifyPropertyChanged?

标签: c# mvvm binding datagrid


【解决方案1】:

我已经重现了您的问题。但我couldn't find 有任何问题。一切正常。

  • 分别验证 StartTempEndTemp
  • 如果一个值被更新,数据网格也应该被更新

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

结果

将起始温度更改为 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();
    }
}

【讨论】:

  • 是的,这也是我最终得到的解决方案,在 ViewModel 中再次创建了 ColorSettingsSequencesSequence 类。在此之前,我使用了模型层的类。
  • @hs2d:我尽量不介意,但请您评论一下为什么这与我 3 小时前发布的解决方案不同?
  • @adabyron,问题不是提交无效数据。问题是数据没有被推回原始模型,这就是数据网格中的值没有改变的原因。
  • @hs2d:这正是我在回答的第一段中评论的内容,并在代码中展示了如何做。
  • @hs2d:您需要在 ColorSettingsSequencesSequence 上实现 INotifyPropertyChanged。至少要对他的评论投赞成票,因为这确实是你的全部问题。
【解决方案2】:

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

我无法从您的帖子中推断出您采用了哪种验证方式,但这是我的做法。即使文本框中的验证失败,更新数据网格的关键是ValidationRuleValidationStep="UpdatedValue" 部分(以及规则的实现)。

查看:

<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(另见this thread):

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

【讨论】:

    猜你喜欢
    • 2011-04-12
    • 2011-11-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-10-26
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多