通过WPF MVVM中的RelayCommand防止插入空值?

huangapple go评论74阅读模式
英文:

Preventing null value insertion through a RelayCommand in WPF MVVM?

问题

以下是您提供的代码的翻译部分:

在你的代码中,你试图使用CanExecuteAddTodoCommand()方法来控制AddTodoCommand的可执行性,确保只有当NewTodo不为空时才能执行该命令。你尝试了两种不同的实现方法。

首先,你尝试了以下方法:

public bool CanExecuteAddTodoCommand()
{
    if (NewTodo == string.Empty)
    {
        MessageBox.Show("请填写备注!");
        return false;
    }
    else
    {
        return true;
    }
}

这个方法几乎正确,但有一个问题。如果NewTodonull,它会通过验证并显示消息框。你只想在按钮被点击时才显示提示框,而不是在输入框为空时就显示。此外,虽然它在按下“Enter”键时可以通过,但似乎无法通过文本框输入来触发按钮。

然后,你尝试了以下方法:

public bool CanExecuteAddTodoCommand()
{
    if (string.IsNullOrEmpty(NewTodo)) 
    {
        MessageBox.Show("请填写备注!");
        return false;
    }
    else
    {
        return true;
    }
}

这个方法会在NewTodo为空时显示消息框,但同样存在无法通过文本框输入触发按钮的问题,因为它会一直阻止按钮的执行。

对于你的问题,你可以尝试以下修改来解决它:

public bool CanExecuteAddTodoCommand()
{
    // 只有当NewTodo不为空并且不为null时,才允许执行命令
    return !string.IsNullOrWhiteSpace(NewTodo);
}

这个修改将允许在文本框中输入内容后才能触发按钮,并且只有当NewTodo既不为空也不为null时才允许执行命令。这应该符合你的期望行为。

英文:

Good day, I am new to C# and WPF. Currently, I am trying to create a Todo list, but I am having problems with the CanExecuteAddTodoCommand() for my RelayCommand. I used MVVMlight for this and Material design in XAML for the XAML.

This is the code I'm working with.

Model:

using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.Command;
using System.Collections.ObjectModel;
using System.Windows;
using ToDoV3.Model;

namespace ToDoV3.ViewModel
{
    /// <summary>
    /// This class contains properties that the main View can data bind to.
    /// <para>
    /// Use the <strong>mvvminpc</strong> snippet to add bindable properties to this ViewModel.
    /// </para>
    /// <para>
    /// You can also use Blend to data bind with the tool's support.
    /// </para>
    /// <para>
    /// See http://www.galasoft.ch/mvvm
    /// </para>
    /// </summary>
    public class MainViewModel : ViewModelBase
    {
        private ObservableCollection<TodoModel> _todoList;

        public ObservableCollection<TodoModel> TodoList
        {
            get { return _todoList; }
            set { Set(ref _todoList, value); }
        }

        public MainViewModel()
        {
            TodoList = new ObservableCollection<TodoModel>();
        }

        private string _newTodo;
        public string NewTodo
        {
            get { return _newTodo; }
            set { Set(ref _newTodo, value); }
 

        //Button Press Trigger
        private RelayCommand _addTodoCommand;
        public RelayCommand AddTodoCommand
        {
            get
            {
                return _addTodoCommand
                    ?? (_addTodoCommand = new RelayCommand(ExecuteAddTodoCommand, CanExecuteAddTodoCommand));
            }
        }

        //Adds the todo on clicking the button or pressing enter
        public void ExecuteAddTodoCommand()
        {
            TodoList.Add(new TodoModel { TaskTodo = NewTodo, IsDone = false });
            //Clears the box after pressing plus or enter
            if (NewTodo != string.Empty)
            {
                NewTodo = string.Empty;
            }
        }

        public bool CanExecuteAddTodoCommand()
        {
            if (NewTodo == string.Empty)
            {
                MessageBox.Show("please enter a note!");
                return false;
            }
            else
            {
                return true;
            }

        }

    }
}

View

<Window x:Class="ToDoV3.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:ToDoV3"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800"
        
        DataContext="{Binding Main, Source={StaticResource Locator}}"
        
        xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
        TextElement.Foreground="{DynamicResource MaterialDesignBody}"
        Background="{DynamicResource MaterialDesignPaper}"
        TextElement.FontWeight="Medium"
        TextElement.FontSize="14"
        FontFamily="{materialDesign:MaterialDesignFont}">



    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="*"/>
            <RowDefinition Height="20"/>
            <RowDefinition Height="85"/>
        </Grid.RowDefinitions>

        <!--Where tasks are displayed-->

        <ScrollViewer>
            <ItemsControl
                Margin="12,0,12,0"
                Grid.IsSharedSizeScope="True"
                ItemsSource="{Binding TodoList}">
                <ItemsControl.ItemTemplate>
                    <DataTemplate
                        >
                        <Border
                            x:Name="Border"
                            Padding="8">
                            <Grid>
                                <Grid.ColumnDefinitions>
                                    <ColumnDefinition
                                        SharedSizeGroup="Checkerz" />
                                    <ColumnDefinition />
                                </Grid.ColumnDefinitions>
                                <CheckBox
                                    VerticalAlignment="Center"
                                    IsChecked="{Binding IsDone}" />
                                <StackPanel
                                    Grid.Column="1"
                                    Margin="8,0,0,0">
                                    <TextBlock
                                        FontWeight="Bold"
                                        Text="{Binding TaskTodo}" />
                                </StackPanel>
                            </Grid>
                        </Border>
                        <DataTemplate.Triggers>
                            <DataTrigger
                                Binding="{Binding IsSelected}"
                                Value="True">
                                <Setter
                                    TargetName="Border"
                                    Property="Background"
                                    Value="{DynamicResource MaterialDesignSelection}" />
                            </DataTrigger>
                        </DataTemplate.Triggers>
                    </DataTemplate>
                </ItemsControl.ItemTemplate>
            </ItemsControl>
        </ScrollViewer>




        <!--This holds the status count-->
        <Grid Grid.Row="1" Background="#212121" >
            <TextBlock Foreground="#FFF"
                   Margin="10 7 0 0"
                   FontSize="12"
                   Text="{Binding CountStatus}"/>
        </Grid>


        <!--This holds the textbox and button-->
        <Grid Grid.Row="2" Background="#212121">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="*"/>
                <ColumnDefinition Width="80"/>
            </Grid.ColumnDefinitions>

            <TextBox
                
                Grid.Column="0"
                Margin="10 0 10 0"
                Background="#FFF"
                Height="70"
                VerticalAlignment="Center"
                materialDesign:HintAssist.Hint="Type your todo here"
                IsEnabled="{Binding Path=IsChecked, ElementName=MaterialDesignOutlinedTextBoxEnabledComboBox}"
                Style="{StaticResource MaterialDesignOutlinedTextBox}"
                TextWrapping="Wrap"
                VerticalScrollBarVisibility="Auto" >

                <TextBox.Text>
                    <Binding  Path="NewTodo" UpdateSourceTrigger="PropertyChanged"/>
                </TextBox.Text>
                <TextBox.InputBindings>
                    <KeyBinding Command="{Binding AddTodoCommand}" Key="Enter"/>
                </TextBox.InputBindings>
            </TextBox>


            <Button 
                Command="{Binding AddTodoCommand}"
                
                Grid.Column="1" 
                IsEnabled="{Binding DataContext.ControlsEnabled, RelativeSource={RelativeSource FindAncestor, AncestorType=Window}}"
                Style="{StaticResource MaterialDesignFloatingActionLightButton}"
                ToolTip="MaterialDesignFloatingActionLightButton">
                <materialDesign:PackIcon Kind="Plus" Width="24" Height="24"/>
            </Button>
            

        </Grid>

    </Grid>
</Window>

This is where I specifically have a problem with. This is the first method I tried.

public bool CanExecuteAddTodoCommand()
        {
            if (NewTodo == string.Empty)
            {
                MessageBox.Show("please enter a note!");
                return false;
            }
            else
            {
                return true;
            }

        }

The output I expect is that whenever the add button is pressed it would only work if NewTodo is not empty. It works but it would pass through a blank and would appear in the note list after that, it would not pass through input if the textbox were blank.

I've tried switching it to null since I think the reason it passes it's because it's null rather than empty. So, I changed it to something like this.

public bool CanExecuteAddTodoCommand()
        {
            if (string.IsNullOrEmpty(NewTodo)) 
            {
                MessageBox.Show("please enter a note!");
                return false;
            }
            else
            {
                //return NewTodo != string.Empty || NewTodo == null;
                return true;
            }

        }

It now sees that the textbox is null and sent the message box, but I only want this prompt to appear only when I triggered a push. The message box would immediately show, and the button cannot be pressed. Also, for some reason I can pass through what's inside the textbox to the observableobject and it would be displayed by pressing enter but as mentioned earlier, the code above does disable the button so I can't send anything through it. Lastly, there are parts of the code mentioning a checkbox but that's not related to this question.

Any thoughts on this would be highly appreciated.

TLDR:
Tried two ways of implementing the CanExecuteAddTodoCommand().

This one is almost correct albeit with one problem. It would pass a null since the if else statement only checked for empty not null.

The second CanExecuteAddTodoCommand() was changed to see if the NewTodo was null, the message would send by pressing enter but it would not allow me to send using the textbox.

答案1

得分: 0

如果要在TextBox中没有文本时禁用命令,您应该实现CanExecuteAddTodoCommand方法,类似于以下方式:

public bool CanExecuteAddTodoCommand() => !string.IsNullOrEmpty(NewTodo);

但如果您希望在"只有在触发推送时才出现提示",您应该始终启用命令,并在Execute方法中显示MessageBox

public void ExecuteAddTodoCommand()
{
    if (string.IsNullOrEmpty(NewTodo))
    {
        MessageBox.Show("请输入一个注释!");
        return false;
    }

    TodoList.Add(new TodoModel { TaskTodo = NewTodo, IsDone = false });
    // 在按加号或回车后清除文本框
    if (NewTodo != string.Empty)
    {
        NewTodo = string.Empty;
    }
}

public bool CanExecuteAddTodoCommand() => true;

无法控制框架何时调用CanExecute方法,因此在此方法中显示MessageBox是一个不好的主意。

还记得在NewTodo属性的setter中刷新状态命令:

private string _newTodo;
public string NewTodo
{
    get { return _newTodo; }
    set
    {
        Set(ref _newTodo, value);
        _addTodoCommand.RaiseCanExecuteChanged();
    }
}

这会引发CanExecuteChanged事件,这将导致框架调用CanExecute方法以确定命令是否仍然应启用或禁用。

英文:

If you want to disable the command when there is no text in the TextBox, you should implement the CanExecuteAddTodoCommand method something like this:

public bool CanExecuteAddTodoCommand() => !string.IsNullOrEmpty(NewTodo);

But if you want a "prompt to appear only when you triggered a push", you should always enable the command and display the MessageBox in the Execute method:

public void ExecuteAddTodoCommand()
{
    if (string.IsNullOrEmpty(NewTodo))
    {
        MessageBox.Show("please enter a note!");
        return false;
    }

    TodoList.Add(new TodoModel { TaskTodo = NewTodo, IsDone = false });
    //Clears the box after pressing plus or enter
    if (NewTodo != string.Empty)
    {
        NewTodo = string.Empty;
    }
}

public bool CanExecuteAddTodoCommand() => true;

You cannot control when the framework calls the CanExecute method so displaying a MessageBox in this method is a bad idea.

Also remember to refresh the status command in the setter of the NewTodo property:

private string _newTodo;
public string NewTodo
{
    get { return _newTodo; }
    set
    {
        Set(ref _newTodo, value);
        _addTodoCommand.RaiseCanExecuteChanged();
    }
}

This raises the CanExecuteChanged event which will cause the framework to call the CanExecute method to determine whether the command should still be enabled or disabled.

huangapple
  • 本文由 发表于 2023年6月2日 14:05:22
  • 转载请务必保留本文链接:https://go.coder-hub.com/76387525.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定