2013-01-24 4 views
10

Supponiamo che io sono i seguenti modelli di vista:Esiste uno schema per la sottoscrizione alle modifiche di proprietà gerarchiche con l'interfaccia utente reattiva?

public class AddressViewModel : ReactiveObject 
{ 
    private string line; 

    public string Line 
    { 
     get { return this.line; } 
     set { this.RaiseAndSetIfChanged(x => x.Line, ref this.line, value); } 
    } 
} 

public class EmployeeViewModel : ReactiveObject 
{ 
    private AddressViewModel address; 

    public AddressViewModel Address 
    { 
     get { return this.address; } 
     set { this.RaiseAndSetIfChanged(x => x.Address, ref this.address, value); } 
    } 
} 

Supponiamo ora che nel EmployeeViewModel voglio esporre una proprietà con l'ultimo valore di Address.Line:

public EmployeeViewModel() 
{ 
    this.changes = this.ObservableForProperty(x => x.Address) 
     .Select(x => x.Value.Line) 
     .ToProperty(this, x => x.Changes); 
} 

private readonly ObservableAsPropertyHelper<string> changes; 
public string Changes 
{ 
    get { return this.changes.Value; } 
} 

questo sarà spuntare solo quando un cambiamento la proprietà Address viene creata, ma non quando si verifica una modifica a Line all'interno di Address. Se io invece faccio questo:

public EmployeeViewModel() 
{ 
    this.changes = this.Address.Changed 
     .Where(x => x.PropertyName == "Line") 
     .Select(x => this.Address.Line)  // x.Value is null here, for some reason, so I use this.Address.Line instead 
     .ToProperty(this, x => x.Changes); 
} 

Questo sarà spuntare solo quando si verifica un cambiamento Line all'interno della corrente AddressViewModel, ma non tiene conto stabilendo un nuovo AddressViewModel del tutto (né ospitare un nullAddress).

Sto cercando di capire come affrontare correttamente questo problema. Sono nuovo di RxUI quindi potrei mancare qualcosa di ovvio. I potrebbe corrispondere manualmente alle modifiche dell'indirizzo e impostare un abbonamento secondario, ma questo sembra brutto e soggetto a errori.

Esiste uno schema o un helper standard che dovrei utilizzare per ottenere questo risultato?

Ecco po 'di codice che può essere la copia/incollato di provare questo:

ViewModels.cs:

namespace RxUITest 
{ 
    using System; 
    using System.Reactive.Linq; 
    using System.Threading; 
    using System.Windows.Input; 
    using ReactiveUI; 
    using ReactiveUI.Xaml; 

    public class AddressViewModel : ReactiveObject 
    { 
     private string line1; 

     public string Line1 
     { 
      get { return this.line1; } 
      set { this.RaiseAndSetIfChanged(x => x.Line1, ref this.line1, value); } 
     } 
    } 

    public class EmployeeViewModel : ReactiveObject 
    { 
     private readonly ReactiveCommand changeAddressCommand; 
     private readonly ReactiveCommand changeAddressLineCommand; 
     private readonly ObservableAsPropertyHelper<string> changes; 
     private AddressViewModel address; 
     private int changeCount; 

     public EmployeeViewModel() 
     { 
      this.changeAddressCommand = new ReactiveCommand(); 
      this.changeAddressLineCommand = new ReactiveCommand(); 

      this.changeAddressCommand.Subscribe(x => this.Address = new AddressViewModel() { Line1 = "Line " + Interlocked.Increment(ref this.changeCount) }); 
      this.changeAddressLineCommand.Subscribe(x => this.Address.Line1 = "Line " + Interlocked.Increment(ref this.changeCount)); 

      this.Address = new AddressViewModel() { Line1 = "Default" }; 

      // Address-only changes 
      this.changes = this.ObservableForProperty(x => x.Address) 
       .Select(x => x.Value.Line1 + " CHANGE") 
       .ToProperty(this, x => x.Changes); 

      // Address.Line1-only changes 
      //this.changes = this.Address.Changed 
      // .Where(x => x.PropertyName == "Line1") 
      // .Select(x => this.Address.Line1 + " CHANGE")  // x.Value is null here, for some reason, so I use this.Address.Line1 instead 
      // .ToProperty(this, x => x.Changes); 
     } 

     public ICommand ChangeAddressCommand 
     { 
      get { return this.changeAddressCommand; } 
     } 

     public ICommand ChangeAddressLineCommand 
     { 
      get { return this.changeAddressLineCommand; } 
     } 

     public AddressViewModel Address 
     { 
      get { return this.address; } 
      set { this.RaiseAndSetIfChanged(x => x.Address, ref this.address, value); } 
     } 

     public string Changes 
     { 
      get { return this.changes.Value; } 
     } 
    } 
} 

MainWindow.cs:

using System.Windows; 

namespace RxUITest 
{ 
    public partial class MainWindow : Window 
    { 
     public MainWindow() 
     { 
      InitializeComponent(); 

      this.DataContext = new EmployeeViewModel(); 
     } 
    } 
} 

MainWindow.xaml:

<Window x:Class="RxUITest.MainWindow" 
     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
     Title="MainWindow" Height="350" Width="525"> 
    <StackPanel> 
     <TextBlock Text="{Binding Changes}"/> 
     <Button Command="{Binding ChangeAddressCommand}">Change Address</Button> 
     <Button Command="{Binding ChangeAddressLineCommand}">Change Address.Line1</Button> 
    </StackPanel> 
</Window> 

risposta

15

No, non c'è modo in cui un modo più semplice per fare questo:

this.WhenAny(x => x.Address.Line, x => x.Value) 
    .Subscribe(x => Console.WriteLine("Either Address or Address.Line changed!")); 
+2

È geniale! Ho appena provato e ha funzionato. Avevo frainteso l'operatore di "When Any" osservando un numero qualsiasi di proprietà "tail" ('Line' nell'esempio sopra). Non avevo capito che aveva anche l'intelligenza di osservare qualsiasi proprietà nella catena. Grazie Paolo. –

+0

è this.RaiseAndSetIfChanged (x => x.Address, ref this.address, value); la sintassi è ancora supportata? Sono in grado di ottenere solo la seguente sintassi; this.RaiseAndSetIfChanged (ref this.address, value); – Elangesh

+1

No, la vecchia sintassi è deprecata, dovresti usare solo la nuova sintassi ora. –

0

Se si considera il vostro indirizzo potrebbe essere una semplice raccolta di linee vale a dire

class AddressViewModel 
: ObservableCollection<LineViewModel>, INotifyPropertyChanged 

allora si sarebbe probabilmente collegare un gestore di eventi per propagare le modifiche

this.CollectionChanged += this.NotifyPropertyChanged(...); 

Quindi vorrei fare lo stesso genere di cose per ReactiveObject ma usando Rx

public class EmployeeViewModel : ReactiveObject 
{ 
private AddressViewModel address; 
private IDisposable addressSubscription; 

public AddressViewModel Address 
{ 
    get { return this.address; } 
    set 
    { 
     if (addressSubscription != null) 
     { 
      addressSubscription.Dispose(); 
      addressSubscription =null; 
     } 
     if (value != null) 
     { 
      addressSubscription = value.Subscribe(...); 
     } 
     this.RaiseAndSetIfChanged(x => x.Address, ref this.address, value); 
    } 
} 

}

+0

Questo è il tipo di approccio mia base di codice esistente prende. Tuttavia, poiché sto integrando RxUI, immagino ci debba essere un modo molto più pulito per farlo. –

+0

Fatemi sapere se trovate :-) – AlSki

+4

Non scrivete mai nulla nel setter in ReactiveUI diverso da RaiseAndSetIfChanged - se lo siete, siete sicuramente Doing It Wrong ™ –

Problemi correlati