Search code examples
c#wpfdatagridmessagebox

Why does MessageBox in CellEditEnding prevent execution of button function in DataGrid?


Sorry if it is trivial but it has been troubling me for past few days now. I have a DataGrid where the user can edit entry of a cell.

To validate the data before passing to the ItemSource (a list) of the grid I have a function bound to the CellEditEnding Event.

todayspecialextra.CellEditEnding += ExtraEntry_CellEditEnding;

private void ExtraEntry_CellEditEnding(object sender, DataGridCellEditEndingEventArgs e)
    {
        if (e.EditAction == DataGridEditAction.Commit)
        {
                   ....
                   // element.Text has the new, user-entered value
                    var element = e.EditingElement as TextBox;
                    int x;
                    if (!(int.TryParse(element.Text, out x) && x >= 0 && x <= 10))
                    {
                        element.Text = "0";
                        MessageBox.Show("Quantity allowed >0 and <10");
                        Console.WriteLine("MessageBox Ended now!");
                    }
         }
    }


private void button_enter_Click(object sender, RoutedEventArgs e)
{
  ...
}

Normally if the user is editing a cell and enters an invalid value. He gets a MessageBox dialogue and the value itself changes to 0. But if the user is still editing the cell and presses Save/Enter Button and the input was invalid there are two cases:

  1. The cellEditEnding event is triggered first(can anyone tell why?) and the above function runs causing the MessageBox to show. But when I close the MessageBox Console.WriteLine("MessageBox Ended now!"); runs and the original Save/Button function doesn't get executed.

  2. If I comment the MessageBox.Show() line. The cellEditEnding event is triggered and the above function runs as usual followed by the code of the Save/Enter button function.

Why does MessageBox prevent execution of the Save/Enter Button function?

P.S: I don't want the execution of the enter button if the input is invalid. But can't understand why using MessageBox helps in achieving this?


Solution

  • My comment should explain what is happening. If i could make a suggestion, I wouldn't use a MessageBox to display incorrect input. MessageBoxes can be a pain.

    Instead I would suggest to take a look at DataGrid control validation. I'm just leaving work so I don't have time to show you an example but I can later once I get home.

    Sorry I completely forgot about this when I got home last night. OK, this is a really simple example I whipped up this morning of some datagrid row validation that you can do. It's fairly simplistic but should give you some ideas. I'm doing the actual validation in the model. If one of the properties is not within 0 - 10 I return an error which is than displayed in a tool tip.

    MainWindow Within the MainWindow I create the datagrid and bind it to an observable collection of type SalesFigures. I also created a style for the datagridrow for displaying the row error to the user.

    <Window x:Class="DataGridValidation.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:DataGridValidation"
        xmlns:vm="clr-namespace:DataGridValidation.ViewModels"
        xmlns:rules="clr-namespace:DataGridValidation.Validation"
        mc:Ignorable="d"
        Title="MainWindow" 
        Height="350" Width="525">
    <Window.DataContext>
        <vm:MainViewModel/>
    </Window.DataContext>
    <Grid>
        <DataGrid ItemsSource="{Binding Sales}"
                  AutoGenerateColumns="False">
            <!-- Row validation rule -->
            <DataGrid.RowValidationRules>
                <rules:SalesValidationRule ValidationStep="UpdatedValue"/>
            </DataGrid.RowValidationRules>
            <DataGrid.Resources>
                <!-- DataGridRow template for displaying the error -->
                <Style TargetType="{x:Type DataGridRow}">
                    <Style.Triggers>
                        <Trigger Property="Validation.HasError" Value="true">
                            <Setter Property="BorderThickness" Value="1"/>
                            <Setter Property="BorderBrush" Value="Red"/>
                            <Setter Property="ToolTip"
                                Value="{Binding RelativeSource={RelativeSource Self},
                                Path=(Validation.Errors)[0].ErrorContent}"/>
                        </Trigger>
                    </Style.Triggers>
                </Style>
            </DataGrid.Resources>
            <DataGrid.Columns>
                <DataGridTextColumn Header="Pie" 
                                    Binding="{Binding Pie, ValidatesOnExceptions=True}"/>
                <DataGridTextColumn Header="Cake" Binding="{Binding Cake, ValidatesOnExceptions=True}"/>
                <DataGridTextColumn Header="Candy" Binding="{Binding Candy, ValidatesOnExceptions=True}"/>
                <DataGridTextColumn Header="Chocolate" Binding="{Binding, ValidatesOnExceptions=True Chocolate}"/>
            </DataGrid.Columns>
        </DataGrid>
    </Grid>
    

    SalesFigures Below is the sales figures object. I implement IDataErrorInfo which exposes an indexer and an Error property for displaying the custom error information to the UI. You should also implement the INotifyPropertyChanged interface if you plan on updating the models properties programmatically.

    using System;
    using System.ComponentModel;
    using System.Linq;
    
    namespace DataGridValidation.Entities
    {
    public class SalesFigures : IDataErrorInfo
    {
        public decimal Pie { get; set; }
        public decimal Cake { get; set; }
        public decimal Candy { get; set; }
        public decimal Chocolate { get; set; }
    
        public string this[ string columnName ]
        {
            get
            {
                string result = null;
                switch( columnName )
                {
                    case nameof( Pie ):
                        result = ValidateValue( nameof( Pie ), Pie );
                        break;
                    case nameof( Cake ):
                        result = ValidateValue( nameof( Cake ), Cake );
                        break;
                    case nameof( Candy ):
                        result = ValidateValue( nameof( Candy ), Candy );
                        break;
                    case nameof( Chocolate ):
                        result = ValidateValue( nameof( Chocolate ), Chocolate );
                        break;
                    default:
                        break;
                }
    
                return result;
            }
        }
    
    
        public string ValidateValue( string name, decimal value )
        {
            if( value < 0 )
            {
                return $"{name} must be greater than or equal to zero";
            }
            if( value > 10 )
            {
                return $"{name} must be equal to or less than 10";
            }
            return string.Empty;
        }
    
        public string Error
        {
            get
            {
                var errors = string.Empty;
                // Get all properties of object.
                var properties = TypeDescriptor.GetProperties( this );
    
                // Iterate through every property and look
                // for errors.
                foreach( var property in properties.Cast<PropertyDescriptor>() )
                {
                    var propertyError = this[ property.Name ];
                    if( !string.IsNullOrWhiteSpace( propertyError ) )
                    {
                        errors += $"{propertyError}{Environment.NewLine}";
                    }
                }
    
                return errors;
            }
        }
    }
    

    }

    SalesValidationRule The rule is used to determine if the model (SalesFigures) encountered any errors.

    using System;
    using System.ComponentModel;
    using System.Globalization;
    using System.Linq;
    using System.Windows.Controls;
    using System.Windows.Data;
    
    namespace DataGridValidation.Validation
    {
    public class SalesValidationRule : ValidationRule
    {
        public override ValidationResult Validate( object value, CultureInfo cultureInfo )
        {
            var bindingGroup = ( BindingGroup )value;
    
            if( bindingGroup == null )
                return null;
    
            var errors = string.Empty;
    
            foreach( var item in bindingGroup.Items.OfType<IDataErrorInfo>() )
            {
                if( !string.IsNullOrWhiteSpace( item.Error ) )
                {
                    errors += $"{item.Error}{Environment.NewLine}";
                }
            }
    
            if( !string.IsNullOrWhiteSpace( errors ) )
            {
                return new ValidationResult( false, errors );
            }
    
            return ValidationResult.ValidResult;
        }
    }
    

    }

    MainViewModel For completeness this is the MainViewModel which the MainWindow is binding to. Here I just populate the DataGrid with some random data.

    using System;
    using System.Collections.ObjectModel;
    using System.ComponentModel;
    using System.Runtime.CompilerServices;
    using DataGridValidation.Annotations;
    using DataGridValidation.Entities;
    
    namespace DataGridValidation.ViewModels
    {
    public class MainViewModel : INotifyPropertyChanged
    {
    
        public MainViewModel( )
        {
            var random = new Random( );
    
            for( int i = 0; i < 15; i++ )
            {
                Sales.Add(
                    new SalesFigures
                    {
                        Cake = random.Next( 0, 11 ),
                        Candy = random.Next( 0, 11 ),
                        Chocolate = random.Next( 0, 11 ),
                        Pie = random.Next( 0, 11 )
                    } );
            }
        }
    
        public ObservableCollection<SalesFigures> Sales { get; } =
            new ObservableCollection<SalesFigures>( );
    
    
    
        public event PropertyChangedEventHandler PropertyChanged;
    
        [NotifyPropertyChangedInvocator]
        protected virtual void OnPropertyChanged( [CallerMemberName] string propertyName = null )
        {
            PropertyChanged?.Invoke( this, new PropertyChangedEventArgs( propertyName ) );
        }
    }
    

    }