wpf ObservesProperty 方法不在 Prism 6 观察模型的属性

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/33313190/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-13 13:30:07  来源:igfitidea点击:

ObservesProperty method isn't observing model's properties at Prism 6

c#wpfmvvmprism

提问by Gabriel Duarte

I'm trying to learn Prism MVVM, and i'm making a window with 2 fields and a button, that gets enabled when this two fields aren't empty.

我正在尝试学习 Prism MVVM,并且我正在制作一个带有 2 个字段和一个按钮的窗口,当这两个字段不为空时会启用该窗口。

The problem is that i can't find a way to make the method ObservesProperty()work on an object (Pessoain that case). The CanExecuteAtualizar()method only gets called at the app startup, and when i edit the textfields Nomeor Sobrenomenothing happens to the button and the method isn't fired...

问题是我找不到使该方法ObservesProperty()对对象起作用的方法(Pessoa在这种情况下)。该CanExecuteAtualizar()方法仅在应用程序启动时被调用,当我编辑文本字段NomeSobrenome按钮没有任何反应并且该方法不会被触发时......

I tried to work without a model, putting the Nome, Sobrenomeand UltimaAtualizacaoproperties directly in the ViewModel and it works fine, disabling the button according to the return of the method CanExecuteAtualizar, but i wanted to use it with a model instead. Is there a way to do this?

我尝试在没有模型的情况下工作,将Nome,SobrenomeUltimaAtualizacao属性直接放在 ViewModel 中,它工作正常,根据方法的返回禁用按钮CanExecuteAtualizar,但我想将它与模型一起使用。有没有办法做到这一点?

ViewAViewModel.cs

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

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

视图A.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>

回答by Brian Lagunas

DelegateCommand.ObservesPropery doesn't support complex object properties. It only supports properties that exist on the ViewModel in the command is defined. This is because the lifecycle of complex objects are unknown, and a memory leak would be created if many instances of the object was created. My recommendation would be to define you property like this:

DelegateCommand.ObservesPropery 不支持复杂的对象属性。它只支持在命令定义中存在于 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;
    }
}

Then in the PropertyChanged method, call DelegateCommand.RaiseCanExecuteChanged

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

EDIT:Complex property support is now available in Prism for Xamarin.Forms 7.0.

编辑:Xamarin.Forms 7.0 的 Prism 中现在提供复杂属性支持。

回答by RonnyR

It is true that DelegateCommand.ObservesPropery does not support complex objects, but its the way Commands are meant to be used with Prism. Manually calling PropertyChanged is an ugly hack in my opinion and should be avoided. Also it bloates the code again which Prism tries to reduce.

DelegateCommand.ObservesPropery 确实不支持复杂对象,但它的方式 Commands 旨在与 Prism 一起使用。在我看来,手动调用 PropertyChanged 是一种丑陋的技巧,应该避免。它还使 Prism 试图减少的代码再次膨胀。

Moving all properties of the complex type into the ViewModel on the other hand would reduce the readability of the ViewModel. The very reason you create complex types in such scenarios is to avoid having too many single properties there in the first place.

另一方面,将复杂类型的所有属性移动到 ViewModel 中会降低 ViewModel 的可读性。在这种情况下创建复杂类型的真正原因是首先要避免在那里有太多的单一属性。

But instead you could move the Command definition inside the complex type. Then you can set ObservesProperty for all simple properties in the constructor of the complex type and everything works as expected.

但是,您可以将 Command 定义移动到复杂类型中。然后,您可以在复杂类型的构造函数中为所有简单属性设置 ObservesProperty,一切都按预期工作。

Model:

模型:

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);
    }

    ...
}

ViewModel:

视图模型:

public class DatabaseConfigurationViewModel
{
    public DatabaseConfigurationViewModel()
    {
        CurrentLoginData = new LoginData(true);
    }

    public LoginData CurrentLoginData { get; set; }
}

View:

看法:

<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>

回答by Paulo

In the Prism version that I have (7.0.0.362), you can use ObserveCanExecute, and pass property HasChangesthat is updated on every property change of your entity.

在我拥有的 Prism 版本 (7.0.0.362) 中,您可以使用 ObserveCanExecute,并传递在实体的每个属性更改时更新的属性HasChanges

TestDbCommand = new DelegateCommand(TestDbConnection).ObservesCanExecute(() => HasChanged);
Pessoa = new Pessoa();
Pessoa.PropertyChanged += Pessoa_PropertyChanged;

Then update HasChangeswith a validation method in the constructor, and detach the method in the destructor.

然后在构造函数中使用验证方法更新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); }