Come associare correttamente a una proprietà di dipendenza di un usercontrol in un framework MVVM

Non sono riuscito a trovare un semplice esempio di implementazione corretta di un controllo utente con WPF che ha una proprietà di dipendenza all’interno del framework MVVM. Il mio codice di seguito fallisce ogni volta che assegno l’usercontrol a un datacontext.

Sto provando a:

  1. Impostare la proprietà di dipendenza dal ItemsControl chiamante e
  2. Rendi disponibile il valore di tale proprietà di dipendenza su ViewModel del chiamato usercontrol.

Ho ancora molto da imparare e apprezzo sinceramente qualsiasi aiuto.

Questo è il comando ItemsControl nel più alto usercontrol che sta effettuando la chiamata al controllo usso di InkStringView con la proprietà di dipendenza TextInControl (esempio da un’altra domanda).

                  

Ecco l’usercontrol di InkStringView con la proprietà di dipendenza.

  XAML:          

 Code-Behind file: namespace Nova5.UI.Views.Ink { public partial class InkStringView : UserControl { public InkStringView() { InitializeComponent(); this.DataContext = new InkStringViewModel(); <--THIS PREVENTS CORRECT BINDING, WHAT } --ELSE TO DO????? public String TextInControl { get { return (String)GetValue(TextInControlProperty); } set { SetValue(TextInControlProperty, value); } } public static readonly DependencyProperty TextInControlProperty = DependencyProperty.Register("TextInControl", typeof(String), typeof(InkStringView)); } 

}

Questo è uno dei tanti motivi per cui non devi mai impostare DataContext direttamente dallo UserControl stesso.

Quando si esegue questa operazione, non è più ansible utilizzare alcun altro DataContext perché il DataContext di UserControl è codificato in base a un’istanza a cui ha accesso solo UserControl , che sconfigge uno dei maggiori vantaggi di WPF di avere UI e livelli dati separati.

Esistono due modi principali per utilizzare UserControls in WPF

  1. Un UserControl autonomo che può essere utilizzato ovunque senza che sia richiesto uno specifico DataContext .

    Questo tipo di UserControl espone normalmente DependencyProperties a qualsiasi valore necessario e verrà utilizzato in questo modo:

      

    Esempi tipici a cui posso pensare sono qualsiasi cosa generica come un controllo Calendar o un controllo Popup.

  2. Un UserControl che deve essere utilizzato solo con un Model o con un ViewModel specifico.

    Questi UserControls sono molto più comuni per me, ed è probabilmente quello che stai cercando nel tuo caso. Un esempio di come userò un tale UserControl sarebbe questo:

      

    O più frequentemente, sarebbe usato con un DataTemplate implicito. Un DataTemplate implicito è un DataTemplate con un DataType e nessuna Key e WPF utilizzerà automaticamente questo modello ogni volta che desidera eseguire il rendering di un object del tipo specificato.

              

    Nessun ContentPresenter.ItemTemplate o ItemsControl.ItemTemplate è necessario quando si utilizza questo metodo.

Non mescolare questi due metodi, non va bene 🙂


Ma comunque, per spiegare il tuo problema specifico in modo più dettagliato

Quando crei il tuo UserControl in questo modo

  

stai praticamente dicendo

 var vw = new InkStringView() vw.TextInControl = vw.DataContext.text; 

vw.DataContext non è specificato da nessuna parte in XAML, quindi viene ereditato dall’elemento padre, il che si traduce in

 vw.DataContext = Strings[x]; 

quindi l’associazione che imposta TextInControl = vw.DataContext.text è valida e si risolve perfettamente in fase di runtime.

Tuttavia quando si esegue questo nel costruttore UserControl

 this.DataContext = new InkStringViewModel(); 

DataContext è impostato su un valore, quindi non viene più automaticamente ereditato dal genitore.

Così ora il codice che viene eseguito assomiglia a questo:

 var vw = new InkStringView() vw.DataContext = new InkStringViewModel(); vw.TextInControl = vw.DataContext.text; 

e, naturalmente, InkStringViewModel non ha una proprietà chiamata text , quindi l’associazione non riesce in fase di runtime.

Sembra che tu stia mescolando il modello della vista genitore con il modello dell’UC.

Ecco un esempio che corrisponde al tuo codice:

Il MainViewModel:

 using System.Collections.Generic; namespace UCItemsControl { public class MyString { public string text { get; set; } } public class MainViewModel { public ObservableCollection Strings { get; set; } public MainViewModel() { Strings = new ObservableCollection { new MyString{ text = "First" }, new MyString{ text = "Second" }, new MyString{ text = "Third" } }; } } } 

The MainWindow che lo utilizza:

                       

UC (nessun set di DataContext):

 public partial class InkStringView : UserControl { public InkStringView() { InitializeComponent(); } public String TextInControl { get { return (String)GetValue(TextInControlProperty); } set { SetValue(TextInControlProperty, value); } } public static readonly DependencyProperty TextInControlProperty = DependencyProperty.Register("TextInControl", typeof(String), typeof(InkStringView)); } 

(Il tuo XAML è OK)

Con ciò posso ottenere quello che immagino sia il risultato atteso, un elenco di valori:

 First I am row 1 Second I am row 1 Third I am row 1 

Devi fare 2 cose qui (suppongo che Strings sia un ObservableCollection ).

1) Rimuovi this.DataContext = new InkStringViewModel(); dal costruttore InkStringView. DataContext sarà un elemento di Strings ObservableCollection.

2) Cambia

  

a

  

Lo xaml che hai sta cercando una proprietà “Text” su ItemsControl per associare il valore TextInControl a. Il xaml che ho messo usando DataContext (che sembra essere una stringa) per associare TextInControl a. Se Strings è in realtà un ObservableCollection con una proprietà String di SomeProperty a cui si desidera associare, quindi modificarlo in questo caso.

  

Ci sei quasi. Il problema è che stai creando un ViewModel per il tuo UserControl. Questo è un odore

UserControls dovrebbe apparire e comportarsi come qualsiasi altro controllo, visto dall’esterno. Si dispone correttamente di proprietà esposte sul controllo e sono i controlli interni vincolanti a queste proprietà. È tutto corretto

Dove fallisci stai cercando di creare un ViewModel per tutto. Quindi mollate lo stupido InkStringViewModel e lasciate che chiunque stia usando il controllo leghi il loro modello di visualizzazione ad esso.

Se sei tentato di chiedere “che dire della logica nel modello di vista? Se me ne sbarazzerò dovrò inserire il codice nel codebehind!” Rispondo, “è una logica aziendale? Non dovrebbe comunque essere incorporata nel tuo UserControl. E MVVM! = No codebehind. Utilizza codebehind per la logica dell’interfaccia utente.