Search code examples
c#wpfvalidationmodern-ui

Modern UI for WPF - ModernDialog: Allow to click Ok only on some conditions


I started a new small project, using ModernUI for WPF.

I've to display a Dialog with only two textbox and two button(ok/cancel).

The two textbox have some validation(one is a name that should be >0, the other should be an email).

Currently I've the following:

        var userEditionForm = new UserEditionForm(oneUserConfigurationViewModel);
        var dlg = new ModernDialog
        {
            Title = "User edition",
            Content = userEditionForm,
            Width = 300
        };
        dlg.Buttons = new[] {dlg.OkButton, dlg.CancelButton};
        dlg.ShowDialog();
        return dlg.DialogResult.HasValue && dlg.DialogResult.Value;

The content user control is:

<UserControl x:Class="Test.UserControls.UserEditionForm"
             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"
             mc:Ignorable="d"
             d:DesignHeight="300" d:DesignWidth="300">
    <Grid>
        <StackPanel Orientation="Vertical">
            <StackPanel.Resources>
                <Style TargetType="StackPanel">
                    <Setter Property="Orientation" Value="Horizontal" />
                    <Setter Property="Margin" Value="0,0,0,4" />
                </Style>
                <Style TargetType="Label" BasedOn="{StaticResource {x:Type Label}}">
                    <Setter Property="Width" Value="100" />
                    <Setter Property="VerticalAlignment" Value="Center" />
                </Style>
            </StackPanel.Resources>

            <StackPanel>
                <Label Content="Name" Target="{Binding ElementName=TextFirstName}" />
                <TextBox x:Name="TextFirstName" Width="150"
                         Text="{Binding User.Name, RelativeSource={RelativeSource AncestorType=UserControl}, Mode=TwoWay, ValidatesOnDataErrors=True}" />
            </StackPanel>
            <StackPanel>
                <Label Content="Email" Target="{Binding ElementName=TextEmail}" />
                <TextBox x:Name="TextEmail" Width="150"
                         Text="{Binding User.Email, RelativeSource={RelativeSource AncestorType=UserControl}, Mode=TwoWay, ValidatesOnDataErrors=True}" />
            </StackPanel>
        </StackPanel>
    </Grid>
</UserControl>

The validation is made, but I would like to prevent the user to click "OK" until the validation is good. Is it possible? If yes, how?


Solution

  • This was actually pretty tricky but it worked for me:

    ViewModel

    public class UserInfo : INotifyPropertyChanged, IDataErrorInfo
    {
        private static Regex emailRegex = new Regex(@"^\w+(?:\.\w+)*?@[a-zA-Z_]+?\.[a-zA-Z]{2,3}$");
    
        private string firstname;
        private string email;
    
        public string FirstName
        {
            get { return firstname; }
            set { firstname = value; OnPropertyChanged(); }
        }
    
        public string EMail
        {
            get { return email; }
            set { email = value; OnPropertyChanged(); }
        }
    
        public string Error
        {
            get { return null; }
        }
    
        public string this[string columnName]
        {
            get
            {
                switch (columnName)
                {
                    case "FirstName":
                        return string.IsNullOrWhiteSpace(FirstName) ? "Firstname is required!" : null;
                    case "EMail":
                        return EMail == null || !emailRegex.IsMatch(EMail) ? "The Email Address is not valid!" : null;
                    default:
                        return null;
                }
            }
        }
    
        public LoginCommand LoginCommand { get; private set; }
    
        public UserInfo()
        {
            LoginCommand = new LoginCommand(this);
        }
    
        private void OnPropertyChanged([CallerMemberName]string propertyName = "")
        {
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    
        public event PropertyChangedEventHandler PropertyChanged = delegate { };
    }
    

    Command (No clue what you want to call it...)

    public class LoginCommand : ICommand
    {
        private UserInfo info;
        private ICommand attachedCommand;
    
        public bool CanExecute(object parameter)
        {
            return parameter != null && info["FirstName"] == null && info["EMail"] == null;
        }
    
        public event EventHandler CanExecuteChanged = delegate { };
    
        public void Execute(object parameter)
        {
            Debug.WriteLine("This Works!");
    
            //Add execution logic here
    
            if (attachedCommand != null)
            {
                attachedCommand.Execute(parameter); //should close the window
            }
    
        }
    
        private void Info_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            CanExecuteChanged(this, new EventArgs());
        }
    
        public LoginCommand(UserInfo info)
        {
            this.info = info;
            info.PropertyChanged += Info_PropertyChanged;
        }
    
        public void AttachCommand(ICommand command) //attach the original command here
        {
            attachedCommand = command;
        }
    }
    

    Usage with ModernUI

    UserInfo info = new UserInfo();
    UserInfoForm form = new UserInfoForm(info);
    ModernDialog dialog = new ModernDialog
    {
        Title = "User edition",
        Content = form,
        Width = 300
    };
    Button btnOk = dialog.OkButton;
    ICommand originalCommand = btnOk.Command; //the original command should close the window so i keep it
    btnOk.Command = info.LoginCommand;
    info.LoginCommand.AttachCommand(originalCommand); //and attach it to my command
    dialog.Buttons = new[] { btnOk, dialog.CancelButton };
    dialog.ShowDialog();