【问题标题】:Struggling to pick up MVVM Basics努力学习 MVVM 基础知识
【发布时间】:2017-07-12 11:19:20
【问题描述】:

我是 MVVM 的新手(对 WPF 有一些经验),我对我认为的基础知识感到非常困惑。我正在尝试制作一个简单的注册表单。用户输入他们的姓名、用户名和密码。为了学习 MVVM 而不是过于复杂,我对密码所做的唯一检查是它是否包含大写字母。暂时没有散列、加密等。

所以我有一个从实体框架生成的User 模型。这是我的第一个困惑。看起来是这样的:

public partial class User : INotifyPropertyChanged
{
    public short Id { get; set; }
    public string LastName { get; set; }
    public string Username { get; set; }
    public string Password { get; set; }

    private string _firstName;
    public string FirstName
    {
        get { return _firstName; }
        set
        {
            if (_firstName == value)
            {
                return;
            }

            _firstName = value;
            OnPropertyChanged("FirstName");
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;

    [NotifyPropertyChangedInvocator]
    protected virtual void OnPropertyChanged(string propertyName)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

如您所见,它有点不匹配。第一部分是由 Entity 生成的,我对 FirstName 进行了修改,使其看起来更像是 MVVM。模型是我创建 get/set 的位置吗?它们也需要在 ViewModel 中吗?

转到我的 ViewModel,我很困惑是否需要再次重新创建 User (FirstName, LastName ....) 的属性,或者它们是否可以通过模型访问。我可以在 ViewModel 中创建一个User 来公开User 的所有属性吗?到目前为止,这是我的ViewModel 的代码:

internal class NewUserViewModel : BaseViewModel
{
    private User _newUser;
    public User NewUser
    {
        get => _newUser;
        set
        {
            if (_newUser == value)
            {
                return;
            }

            _newUser = value;
            OnPropertyChanged("NewUser");
        }
    }

    private string _password;
    public string Password
    {
        get => _password;
        set
        {
            if (_password == value)
                return;
            _password = value;
            OnPasswordChanged();
            OnPropertyChanged("Password");
        }
    }

    #region RegisterCommand

    private DelegateCommand _registerCommand;
    public ICommand RegisterCommand
    {
        get
        {
            _registerCommand = new DelegateCommand(param => Register(), param => CanRegister());
            return _registerCommand;
        }
    }

    private bool CanRegister()
    {
        return _isPasswordValid;
    }

    private bool _isPasswordValid;
    public void OnPasswordChanged()
    {
        _isPasswordValid = Password.Any(char.IsUpper);
    }

    private void Register()
    {
        using (var context = new WorkstreamContext())
        {
            var users = context.Set<User>();
            users.Add(_newUser);
            context.SaveChanges();
        }
    }

    #endregion
}

到目前为止,我已经重新创建了 Password 属性,以便我可以访问它,但是这对我来说很不安,我觉得我要么公开所有属性,要么使用 NewUser,但是我不确定该怎么做这。目前代码一半有效。 Save 按钮显示为灰色,但是当密码包含我所期望的大写字母时,它不会启用。实际报名表:

<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="Auto"/>
        <RowDefinition Height="Auto"/>
        <RowDefinition Height="Auto"/>
        <RowDefinition Height="Auto"/>
        <RowDefinition Height="Auto"/>
        <RowDefinition Height="Auto"/>
        <RowDefinition Height="Auto"/>
    </Grid.RowDefinitions>

    <Image Source="pack://application:,,,/Resources/NewUserForm/NewUser.jpg" HorizontalAlignment="Center" VerticalAlignment="Center" Height="100"/>
    <TextBlock Grid.Row="1" Text="Please Enter Your Details" HorizontalAlignment="Center" Foreground="DarkSlateGray" FontSize="16"/>

    <Grid Grid.Row="2">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="Auto"/>
            <ColumnDefinition Width="*"/>
        </Grid.ColumnDefinitions>
        <Image Height="{Binding ActualHeight, ElementName=FirstNameTextBox}" Source="pack://application:,,,/Resources/NewUserForm/FirstName.jpg" Margin="5,5,0,5"/>
        <xctk:WatermarkTextBox Grid.Column="1" x:Name="FirstNameTextBox" Watermark="first name" Text="{Binding NewUser.FirstName, Mode=TwoWay}" />
    </Grid>

    <Grid Grid.Row="3">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="Auto"/>
            <ColumnDefinition Width="*"/>
        </Grid.ColumnDefinitions>
        <Image Height="{Binding ActualHeight, ElementName=LastNameTextBox}" Source="pack://application:,,,/Resources/NewUserForm/LastName.jpg" Margin="5,5,0,5"/>
        <xctk:WatermarkTextBox Grid.Column="1" x:Name="LastNameTextBox"  Watermark="last name" Text="{Binding NewUser.LastName, Mode=TwoWay}" />
    </Grid>

    <Grid Grid.Row="4">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="Auto"/>
            <ColumnDefinition Width="*"/>
        </Grid.ColumnDefinitions>
        <Image Height="{Binding ActualHeight, ElementName=UsernameTextBox}" Source="pack://application:,,,/Resources/NewUserForm/User.jpg" Margin="5,5,0,5"/>
        <xctk:WatermarkTextBox Grid.Column="1" x:Name="UsernameTextBox"  Watermark="username" Text="{Binding NewUser.Username, Mode=TwoWay}" />
    </Grid>

    <Grid Grid.Row="5">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="Auto"/>
            <ColumnDefinition Width="*"/>
        </Grid.ColumnDefinitions>
        <Image Height="{Binding ActualHeight, ElementName=PasswordTextBox}" Source="pack://application:,,,/Resources/NewUserForm/Password.jpg" Margin="5,5,0,5"/>
        <xctk:WatermarkTextBox Grid.Column="1" x:Name="PasswordTextBox"  Watermark="password" Text="{Binding NewUser.Password, Mode=TwoWay}"/>
    </Grid>

    <DockPanel Grid.Row="6">
        <Button Content="Save" Command="{Binding RegisterCommand, Mode=OneWay, Source={StaticResource NewUserViewModel}}" HorizontalAlignment="Right"/>
        <Button HorizontalAlignment="Right"/>
    </DockPanel>

</Grid>

我将 TextBoxes 绑定到 ViewModel 上暴露的 NewUser 的方式是在 MVVM 中操作的正确方式吗?我很欣赏有很多关于 MVVM 的教程,我已经阅读/查看了很多。但是,我已经到了越来越感到困惑的阶段,如果有人能给我详细介绍我的代码并指出我哪里出错、为什么代码不工作以及我可以在哪里工作,我将不胜感激改进。

【问题讨论】:

  • 我不会像 User 这样混合使用 EF 和 UI 类。我建议改用两个类:一个用于 DB,另一个用于 UI,例如 UserViewModel
  • 正是第一条评论所说的 - 你混淆了担忧。 MVVM/MVC 中的 M 用于表示视图在任何给定上下文中呈现给用户的数据的 POCO(或具有绑定胶水的 POCO)。您所说的 EF 类在 DAL 中,在特定的有界域中。
  • 我推荐的是研究 MVVM 框架,如 MVVMLite、Caliburn 或 Prism。 Prism 非常好documented,我建议您研究一下。
  • 另外你的密码不工作的原因是因为你没有通知任何人你需要做的更改 RegisterCommand.RaiseCanExecuteChanged();并且不是每次调用属性时都只创建一次 _registerCommand 。您可以查看this
  • 此外,密码文本框绑定到 NewUser.Password,因此当您检查密码时,您正在检查视图模型中的 Password 属性,这与 NewUser.Password 不同,因此将永远不要设置。

标签: c# wpf entity-framework mvvm


【解决方案1】:

您的问题非常广泛。但是如果User 类实现了INotifyPropertyChanged 接口,那么它实际上是一种视图模型,您可以像您正在做的那样直接绑定到这个模型的属性:

{Binding NewUser.FirstName}

如果NewUser 是某种 DTO 对象,您可以将其包装在您的视图模型中并绑定到视图模型属性:

public string Password
{ 
    get { return _user.Password; }
    set { return _user.Password = value; OnNotifyPropertyChanged(); }
}

真正的“模型”是一种服务或某种业务逻辑对象。

保存按钮显示为灰色,但是当密码包含我所期望的大写字母时,它不会启用。

你的Password 属性的设置器是否被击中?绑定到视图模型的Password 属性

Text="{Binding Password}"

...并调用命令的RaiseCanExecuteChanged()刷新其状态:

private string _password;
public string Password
{
    get => _password;
    set
    {
        if (_password == value)
            return;
        _password = value;
        OnPasswordChanged();
        OnPropertyChanged("Password");

        _registerCommand.RaiseCanExecuteChanged(); //<--
    }
}

【讨论】:

    猜你喜欢
    • 2010-12-08
    • 2020-08-03
    • 2011-04-20
    • 1970-01-01
    • 2011-01-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多