3

ねえ、

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

それはうまく機能しますが、検証を追加したいときに問題が発生します。個別に検証StartTempして、さまざまなエラーを発生させたいと思います。データグリッドでも更新される1つの値を編集した場合、バインディングも引き続き機能するよう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);
    }
}

そして、 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

私はあなたの問題を再現しました。しかし、私couldn't findは問題があります。すべて正常に動作します。

  • 検証StartTempしてEndTemp別々に。
  • 1 つの値が更新された場合、データグリッドも更新する必要があります

したがって、プロジェクトで上記の2つの問題を解決しました。

結果

ここに画像の説明を入力

開始温度を 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();
    }
}
于 2013-03-07T19:08:31.330 に答える
2

私が正しく理解していれば、検証が失敗した場合でもプロパティ値をコミットしたいという問題があります。この仮定が間違っている場合、解決策はさらに簡単です。基本的に、sineが彼のコメントで示唆したことは、クラスに実装するだけでよいということ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 に答える