观察属性方法在Prism 6中无法观察模型属性

5
我正在尝试学习Prism MVVM,并且正在创建一个带有2个字段和一个按钮的窗口,只有这两个字段不为空时,该按钮才会启用。
问题是我找不到一种方法来使ObservesProperty()方法在对象(在此情况下为Pessoa)上工作。当我编辑文本字段NomeSobrenome时,CanExecuteAtualizar()方法仅在应用程序启动时被调用,按钮没有任何反应并且该方法未被触发...
我尝试在没有模型的情况下进行操作,直接在ViewModel中放置NomeSobrenomeUltimaAtualizacao属性,它可以正常工作,根据CanExecuteAtualizar方法的返回值禁用按钮,但我想改用模型。是否有一种方法可以实现这一点? ViewAViewModel.cs
public class ViewAViewModel : BindableBase
{
    private Pessoa _pessoa;

    public Pessoa Pessoa
    {
        get { return _pessoa; }
        set { SetProperty(ref _pessoa, value); }
    }

    public ICommand CommandAtualizar { get; set; }

    public ViewAViewModel()
    {
        Pessoa = new Pessoa();
        Pessoa.Nome = "Gabriel";
        CommandAtualizar = new DelegateCommand(ExecuteAtualizar, CanExecuteAtualizar).ObservesProperty(() => Pessoa.Nome).ObservesProperty(() => Pessoa.Sobrenome);
    }

    public bool CanExecuteAtualizar()
    {
        return !string.IsNullOrWhiteSpace(Pessoa.Nome) && !string.IsNullOrWhiteSpace(Pessoa.Sobrenome);
    }

    public void ExecuteAtualizar()
    {
        Pessoa.UltimaAtualizacao = DateTime.Now;
    }
}

Pessoa.cs

public class Pessoa : BindableBase
{
    private string _nome;

    public string Nome
    {
        get { return _nome; }
        set { SetProperty(ref _nome, value); }
    }

    private string _sobrenome;

    public string Sobrenome
    {
        get { return _sobrenome; }
        set { SetProperty(ref _sobrenome, value); }
    }

    private DateTime? _ultimaAtualizacao;

    public DateTime? UltimaAtualizacao
    {
        get { return _ultimaAtualizacao; }
        set { SetProperty(ref _ultimaAtualizacao, value); }
    }
}

ViewA.xaml

<UserControl x:Class="PrismDemo.Views.ViewA"
             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"
             xmlns:local="clr-namespace:PrismDemo.Views"
             mc:Ignorable="d"
             d:DesignHeight="100" d:DesignWidth="500">
    <Grid Background="White">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*" />
            <ColumnDefinition Width="2*" />
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="*" />
            <RowDefinition Height="*" />
            <RowDefinition Height="*" />
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>

        <Label Content="Nome:"  Grid.Column="0" Grid.Row="0" HorizontalAlignment="Left" VerticalAlignment="Center" />
        <TextBox Grid.Column="1" Grid.Row="0"  Margin="3" TabIndex="0" Text="{Binding Pessoa.Nome, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
        <Label Content="Sobrenome:"  Grid.Column="0" Grid.Row="1" HorizontalAlignment="Left" VerticalAlignment="Center" />
        <TextBox Grid.Column="1" Grid.Row="1"  Margin="3" TabIndex="1" Text="{Binding Pessoa.Sobrenome, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
        <Label Content="Última atualização:"  Grid.Column="0" Grid.Row="2" HorizontalAlignment="Left" VerticalAlignment="Center" />
        <Label Grid.Column="1" Grid.Row="2"  Margin="3" HorizontalAlignment="Left" Content="{Binding Pessoa.UltimaAtualizacao, Mode=TwoWay}" />
        <Button Content="Atualizar" Grid.Column="1" Grid.Row="3" Width="70" Margin="2,2,3,2" HorizontalAlignment="Right" Command="{Binding CommandAtualizar}" />
    </Grid>
</UserControl>
3个回答

7

DelegateCommand.ObservesProperty不支持复杂对象属性。它只支持在命令定义的ViewModel上存在的属性。这是因为复杂对象的生命周期是未知的,如果创建了许多对象实例,则会创建内存泄漏。我的建议是像这样定义您的属性:

private Pessoa _pessoa;
public Pessoa Pessoa
{
    get { return _pessoa; }
    set 
    {
        if (_pessoa != null)
            _pessoa.PropertyChanged -= PropertyChanged; 

        SetProperty(ref _pessoa, value);


        if (_pessoa != null)
            _pessoa.PropertyChanged += PropertyChanged;
    }
}

然后在PropertyChanged方法中,调用DelegateCommand.RaiseCanExecuteChanged。

编辑: 现在,在Prism for Xamarin.Forms 7.0中支持复杂属性。


@Brian 你试过了吗?这种方法对我似乎不起作用。 - mechanic
@机械师 是的,这是我在这种情况下使用的方法。您模型中的属性必须调用PropertyChanged才能使其正常工作。 - user5420778
感谢@BrianLagunas的更新,我已经升级了Prism.Core版本7.0.0.396(wpf),但仍然没有看到它在表达式具有访问成员时发出通知,例如new DelegateCommand(OnAddStaff, CanAddStaff) .ObservesProperty(() => NewStaffDto.RoleId) - Paulo
你不能仅将WPF更新到v7,因为存在重大变更且不存在二进制兼容性。目前还没有针对WPF的v7版本发布。 - user5420778
再次感谢@BrianLagunas,我希望这些好东西也能应用到WPF中,或者在有重大变化的情况下,创建一个新的Prism发布产品。 对于WPF,命令绑定刷新周期已经通过静态命令管理器解决了,但代价是刷新所有绑定。虽然无法指定刷新顺序,但它确实有效。 同时,我认为使用ObserveCanExecute会更简单,就像我在下面的答案中所提到的。 - Paulo
显示剩余5条评论

1
DelegateCommand.ObservesProperty 不支持复杂对象,但这是使用 Prism 命令的方式。在我看来,手动调用 PropertyChanged 是一种丑陋的 hack,应该避免使用。这也会使代码变得臃肿,而 Prism 则试图减少代码量。
另一方面,将复杂类型的所有属性移动到 ViewModel 中会降低 ViewModel 的可读性。在这种情况下创建复杂类型的原因是为了避免在那里有太多单个属性。
但是,你可以将 Command 定义移动到复杂类型内部。然后,你可以在复杂类型的构造函数中为所有简单属性设置 ObservesProperty,这样一切都能正常工作。 模型:
using Prism.Commands;
using Prism.Mvvm;
using static System.String;

public class LoginData : BindableBase
{
    public LoginData()
    {
        DbAddr = DbName = DbUser = DbPw = "";

        TestDbCommand = new DelegateCommand(TestDbConnection, CanTestDbConnection)
            .ObservesProperty(() => DbAddr)
            .ObservesProperty(() => DbName)
            .ObservesProperty(() => DbUser)
            .ObservesProperty(() => DbPw);
    }

    public DelegateCommand TestDbCommand { get; set; }

    public bool CanTestDbConnection()
    {
        return !IsNullOrWhiteSpace(DbAddr)
            && !IsNullOrWhiteSpace(DbName)
            && !IsNullOrWhiteSpace(DbUser)
            && !IsNullOrWhiteSpace(DbPw);
    }

    public void TestDbConnection()
    {
        var t = new Thread(delegate () {
            Status = DatabaseFunctions.TestDbConnection(this);
        });
        t.Start();
    }

    private string _dbAddr;
    public string DbAddr
    {
        get => _dbAddr;
        set => SetProperty(ref _dbAddr, value);
    }

    ...
}

视图模型:
public class DatabaseConfigurationViewModel
{
    public DatabaseConfigurationViewModel()
    {
        CurrentLoginData = new LoginData(true);
    }

    public LoginData CurrentLoginData { get; set; }
}

视图:
<UserControl x:Class="TestApp.Views.DatabaseConfiguration"
         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"
         xmlns:prism="http://prismlibrary.com/"
         prism:ViewModelLocator.AutoWireViewModel="True"
         mc:Ignorable="d" 
         d:DesignHeight="300" d:DesignWidth="300">
<Grid>
    <StackPanel Orientation="Vertical">
        <Label>IP Adresse oder URL:</Label>
        <TextBox Text="{Binding CurrentLoginData.DbAddr, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"></TextBox>
        ...
        <Button Command="{Binding CurrentLoginData.TestDbCommand}">Teste Verbindung</Button>
       </StackPanel>
   </Grid>


你可以这样做,但我不建议这样做,因为这样会将视图相关的内容与类模型混合在一起。因此,当CanTestDbConnection有不同规则时,您将无法在任何其他视图中使用LoginData。对于这种情况,您必须创建另一个具有相同属性的类,从而破坏了创建模型的目的。 - Gabriel Duarte

0
在我所使用的Prism版本(7.0.0.362)中,你可以使用ObserveCanExecute,并传递属性HasChanges,该属性在实体的每次属性更改时更新。
TestDbCommand = new DelegateCommand(TestDbConnection).ObservesCanExecute(() => HasChanged);
Pessoa = new Pessoa();
Pessoa.PropertyChanged += Pessoa_PropertyChanged;

然后在构造函数中使用验证方法更新HasChanges,并在析构函数中分离该方法。

private void Pessoa_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
    HasChanged = ValidatePessoa(Pessoa);
}

~YourViewModel()
{
    Pessoa.PropertyChanged -= Pessoa_PropertyChanged;
}
bool _hasChanged;
public bool HasChanged { get => _hasChanged; set => SetProperty(ref _hasChanged, value); }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接