简体   繁体   中英

WPF- datagrid row validation errors to a textblock/label?

In WPF i have a DataDrid with different validations.

So far i used ToolTip in the DataGrid to show the error validation:

<DataGrid.RowValidationErrorTemplate>
   <ControlTemplate>
      <Grid Margin="0,-2,0,-2" ToolTip="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DataGridRow}},Path=(Validation.Errors)[0].ErrorContent}">
         <Ellipse StrokeThickness="0" Fill="Red" Width="{TemplateBinding FontSize}" Height="{TemplateBinding FontSize}" />
         <TextBlock Text="!" FontSize="{TemplateBinding FontSize}" FontWeight="Bold" Foreground="White" HorizontalAlignment="Center" VerticalAlignment="Center" />
      </Grid>
   </ControlTemplate>
</DataGrid.RowValidationErrorTemplate>

Now the client asked to show the validation error in a different TextBlock/Label in the page- how do i do that? How do i use the: (Validation.Errors)[0].ErrorContent outside the DataGrid ?

You probably have a class like ValidationRules that holds a Validate Function.

due to the magnificent binding that occurs, you no longer need to follow the validation as it is binded. This however sticks to the control.

You can also create an event on the control inside the datagrid that is being used to validate.

<DataGridTemplateColumn Header="MyValue">
    <DataGridTemplateColumn.CellTemplate>
        <DataTemplate>
            <Textbox Text="{Binding MyValue}" OnLeave="tb_OnLeave"/>
        </DataTemplate>
    </DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>

In you behind you can then have something like:

private void tb_OnLeave(object sender, EventArgs e)
{
  //sender defines your value;
  Textbox s = (Textbox)sender;

  //validate the Text property of the sender
  if(ValidationRule.ValidateTextBox(s.Text)); //true
  return;

  //set the focus on your control
  s.Focus(); 

  //set the text of your other control
  YourOtherControl.Text = string.Format("The value of textbox: {0} is not valid", s.Text);
}

The point here is that you will not use the validationBinding anymore. I think there are ways to do it via binding but i'm not strong enough on that topic to give you a better answer.

Perhaps the RelativeSource can be used to point to another control. But as I said, I don't know enough to be sure.

Assuming you have a list (eg MyList ) as your DataGrid.ItemsSource which is an ObservableCollection<ItemVM> where ItemVM represents the view-model of each DataGridRow then You can use a MultiBinding with a multi-value converter to save the last error message in a property of currently invalid ItemVM (eg ErrorMessage ) and then retrieve it in your desired TextBlock.Text through binding.

You can say this is a trick for 3-way binding where TextBlock.Text and CurrentItem.ErrorMessage and (Validation.Errors)[0].ErrorContent are all bound together.

The following codes will clear things up:

In MainWindow:

public ObservableCollection<ItemVM> MyList { get; set; }
public ItemVM CurrentItem { get; set; }

In ItemVM:

public class ItemVM : INotifyPropertyChanged
{
    //implementation of INotifyPropertyChanged ...

    string _errorMessage;
    public string ErrorMessage
    {
        get
        {
            return _errorMessage;
        }
        set
        {
            _errorMessage = value;
            OnPropertyChanged("ErrorMessage");
        }
    }
    // Other Properties ...
}

In Xaml:

<Window.Resources>
    <local:MultiConverter x:Key="multiConv"/>
</Window.Resources>
<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="*"/>
        <RowDefinition Height="Auto"/>
    </Grid.RowDefinitions>
    <DataGrid Grid.Row="0" ItemsSource="{Binding MyList}" >
        <DataGrid.RowValidationErrorTemplate>
            <ControlTemplate >
                <Grid Margin="0,-2,0,-2">
                    <Ellipse StrokeThickness="0" Fill="Red" Width="{TemplateBinding FontSize}" Height="{TemplateBinding FontSize}" />
                    <TextBlock Text="!" FontSize="{TemplateBinding FontSize}" FontWeight="Bold" Foreground="White" HorizontalAlignment="Center" VerticalAlignment="Center" />
                    <Grid.ToolTip>
                        <MultiBinding Converter="{StaticResource multiConv}">
                            <Binding Path="(Validation.Errors)[0].ErrorContent" RelativeSource="{RelativeSource Mode=FindAncestor,
                                                AncestorType=DataGridRow}"/>
                            <Binding Path="DataContext.CurrentItem" RelativeSource="{RelativeSource Mode=FindAncestor,
                                                AncestorType=Window}"/>
                        </MultiBinding>
                    </Grid.ToolTip>
                </Grid>
            </ControlTemplate>
        </DataGrid.RowValidationErrorTemplate>
    </DataGrid>
    <TextBox Grid.Row="1" Text="{Binding CurrentItem.ErrorMessage}"/>
</Grid>

And finally in MultiValueConverter:

public class MultiConverter : IMultiValueConverter
{
    public object Convert(
        object[] values, Type targetType, object parameter, CultureInfo culture)
    {
        ((ItemVM)values[1]).ErrorMessage = values[0].ToString();
        return values[0].ToString();
    }

    public object[] ConvertBack(
        object value, Type[] targetTypes, object parameter, CultureInfo culture)
    {
        throw new NotSupportedException();
    }
}

EDIT

my DataGrid.ItemsSource is DataSet DefaultView- its DB based

Ok, since you are using DataSet.DefaultView as the ItemsSource do it like this:

1) First define a very small simple class that implements INotifyPropertyChanged to wrap your error message:

public class ErrorContainer: INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;
    void OnPropertyChanged(string propertyName)
    {
        if (PropertyChanged != null)
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
    }

    string _message;
    public string Message
    {
        get
        {return _message;}
        set
        {_message = value; OnPropertyChanged("Message");}
    }
}

2) Then use an instance of this class as a property in your MainWindow :

public partial class MainWindow : Window
{

....

    public ErrorContainer Error { get; set; }

        public MainWindow()
    {
        InitializeComponent();
        DataContext = this;

        // initialize ErrorContainer
        Error = new ErrorContainer();

        // some dummy test data (you're probably getting your data from DB instead)
        DataSet ds = new DataSet("MyDataSet");
        DataTable dt = new DataTable("MyDataTable");

        DataColumn propertyName = new DataColumn("Property");
        DataColumn propertyValue = new DataColumn("Value");
        DataColumn propertyDate = new DataColumn("Date", typeof(DateTime));

        dt.Columns.Add(propertyName);
        dt.Columns.Add(propertyValue);
        dt.Columns.Add(propertyDate);

        dt.Rows.Add("Name1", 1, DateTime.Now);
        dt.Rows.Add("Name2", 2, DateTime.Now);
        dt.Rows.Add("Name3", 3, DateTime.Now);

        ds.Tables.Add(dt);

        // bind DataGrid.ItemsSource (do NOT set this again in xaml)
        var srcBinding = new Binding();
        srcBinding.Source = ds.Tables[0].DefaultView;
        myDataGrid.SetBinding(DataGrid.ItemsSourceProperty, srcBinding);

    }
}

3) Now change the second path in the multi-binding to DataContext.Error and also change TextBox.Text binding to Error.Message :

<Grid>
     <DataGrid x:Name=x:Name="myDataGrid">
    ....

                        <MultiBinding Converter="{StaticResource multiConv}">
                            <Binding Path="(Validation.Errors)[0].ErrorContent" RelativeSource="{RelativeSource Mode=FindAncestor,
                                                AncestorType=DataGridRow}"/>
                            <Binding Path="DataContext.Error" RelativeSource="{RelativeSource Mode=FindAncestor,
                                                AncestorType=Window}"/>
                        </MultiBinding>
                    </Grid.ToolTip>
                </Grid>
            </ControlTemplate>
        </DataGrid.RowValidationErrorTemplate>
    </DataGrid>
    <TextBox Grid.Row="1" Text="{Binding Error.Message}"/>
</Grid>

4) And finally change your MultiValueConverter.Convert method to this:

    public object Convert(
        object[] values, Type targetType, object parameter, CultureInfo culture)
    {
        ((ErrorContainer)values[1]).Message = values[0].ToString();
        return values[0].ToString();
    }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM