2014-09-04 24 views
9

Non riesco a trovare un semplice esempio di come in modo corretto implementare un usercontrol 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.Come associare correttamente a una proprietà di dipendenza di un usercontrol in un framework MVVM

sto provando a:

  1. Impostare la proprietà di dipendenza dal chiamante ItemsControl, e
  2. Fai il valore di quella proprietà di dipendenza a disposizione del ViewModel della chiamata UserControl.

Ho ancora molto da imparare e sinceramente apprezzo 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).

<ItemsControl 
       ItemsSource="{Binding Strings}" x:Name="self" > 

     <ItemsControl.ItemsPanel> 
      <ItemsPanelTemplate> 
       <StackPanel HorizontalAlignment="Left" VerticalAlignment="Top" Orientation="Vertical" /> 
      </ItemsPanelTemplate> 
     </ItemsControl.ItemsPanel> 


     <ItemsControl.ItemTemplate> 
      <DataTemplate> 
       <DataTemplate.Resources> 
        <Style TargetType="v:InkStringView"> 
         <Setter Property="FontSize" Value="25"/> 
         <Setter Property="HorizontalAlignment" Value="Left"/> 
        </Style> 
       </DataTemplate.Resources> 

       <v:InkStringView TextInControl="{Binding text, ElementName=self}" /> 
      </DataTemplate> 
     </ItemsControl.ItemTemplate> 
    </ItemsControl> 

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

XAML: 
<UserControl x:Class="Nova5.UI.Views.Ink.InkStringView" 
     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" 
     x:Name="mainInkStringView" 
     mc:Ignorable="d" 
     d:DesignHeight="300" d:DesignWidth="300"> 
<Grid> 
    <Grid.RowDefinitions> 
     <RowDefinition/> 
     <RowDefinition/> 
    </Grid.RowDefinitions> 

    <TextBlock Grid.Row="0" Text="{Binding TextInControl, ElementName=mainInkStringView}" /> 
    <TextBlock Grid.Row="1" Text="I am row 1" /> 
</Grid> 

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

} 

}

+0

Il DataContext dei controlli nel ItemTemplate di un ItemsControl (o qualsiasi classe derivata) viene automaticamente assegnato da WPF all'elemento rispettivo nella raccolta di origine. Nel tuo caso questo sarebbe un elemento dalla collezione 'String'. Se 'Stringhe' è una raccolta di oggetti che hanno una proprietà' text', devi semplicemente scrivere il binding nel DataTemplate come 'TextInControl =" {Binding text} "' e * non impostare esplicitamente * nessun altro DataContext. Suggerirei di leggere su questo argomento. – Clemens

+0

Vedere [Panoramica di associazione dati] (http://msdn.microsoft.com/en-us/library/ms752347.aspx) e [Panoramica Templatura dati] (http://msdn.microsoft.com/en-us/library /ms742521.aspx). – Clemens

+0

@Clemens Ciao. L'eliminazione di ElementName = ... dal ItemsControl chiamante continua a essere visualizzato con "I am row 1" e una riga vuota dove dovrebbe essere il testo sulla riga 0. ??? Idee? –

risposta

12

Questo è uno dei tanti motivi non si dovrebbe mai impostare il DataContext direttamente dallo UserControl stesso.

Quando lo fa, non è più possibile utilizzare qualsiasi altra DataContext con esso, perché il DataContext UserControl è codificato a un'istanza che solo il UserControl ha accesso a, che tipo di sconfitte uno dei più grandi vantaggi di WPF di avere separato UI e livelli di dati.

Ci sono due modi principali di usare UserControls in WPF

  1. Un standalone UserControl che può essere utilizzato ovunque senza una specifica DataContext che sia necessaria.

    Questo tipo di UserControl normalmente espone DependencyProperties per tutti i valori di cui ha bisogno, e sarebbe stato utilizzato in questo modo:

    <v:InkStringView TextInControl="{Binding SomeValue}" /> 
    

    Esempi tipici mi viene in mente sarebbe qualcosa di generico come un controllo Calendar o un controllo Popup.

  2. A UserControl che deve essere utilizzato solo con uno specifico Model o ViewModel.

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

    <v:InkStringView DataContext="{Binding MyInkStringViewModelProperty}" /> 
    

    o più frequentemente, sarebbe essere utilizzato con un implicito DataTemplate. Un numero implicito DataTemplate è un DataTemplate con un DataType e nessun Key e WPF utilizzerà automaticamente questo modello ogni volta che desidera eseguire il rendering di un oggetto del tipo specificato.

    <Window.Resources> 
        <DataTemplate DataType="{x:Type m:InkStringViewModel}"> 
         <v:InkStringView /> 
        </DataTemplate> 
    <Window.Resources> 
    
    <!-- Binding to a single ViewModel --> 
    <ContentPresenter Content="{Binding MyInkStringViewModelProperty}" /> 
    
    <!-- Binding to a collection of ViewModels --> 
    <ItemsControl ItemsSource="{Binding MyCollectionOfInkStringViewModels}" /> 
    

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

Non mescolare questi due metodi in su, non va bene :)


Ma comunque, per spiegare il tuo problema specifico in un po 'più in dettaglio

Quando si creare il tuo UserControl come questo

<v:InkStringView TextInControl="{Binding text}" /> 

si sono sostanzialmente dicendo

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

vw.DataContext non è specificato in qualsiasi parte del XAML, quindi diventa ereditato dalla voce genitore, che si traduce in

vw.DataContext = Strings[x]; 

in modo che il legame che imposta TextInControl = vw.DataContext.text sia valido e risolve bene in fase di esecuzione.

Tuttavia quando si esegue questo nella tua UserControl costruttore

this.DataContext = new InkStringViewModel(); 

il DataContext è impostato su un valore, quindi non viene 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, in modo che il legame non riesce in fase di esecuzione.

+0

qualche buona risorsa da imparare sui due metodi che hai spiegato sopra? –

+0

@EhsanSajjad Non riesco a pensare a nessuno al momento, ma sono concetti facili da ricordare una volta capito come funziona. O ** A) ** scrivi il tuo UserControl XAML in modo tale che non si romperà a prescindere da ciò che 'DataContext' è (di solito fatto usando DependencyProperties, come OP ha), o ** B) ** scrivi il tuo UserControl XAML con l'assunto che 'DataContext' è di un tipo di dati specifico (tipicamente un modello o ViewModel). E in nessun momento dovresti mai impostare la proprietà 'DataContext' dallo UserControl stesso :) – Rachel

+0

@Rachel Quindi ... Nel caso 2, ViewModel di UserControl è in realtà lo stesso ViewModel del modulo di chiamata ?? Non ho familiarità con ContentPresenter ... dove si adatta? –

1

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

UserControls deve 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 si fallisce, si sta tentando 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 si è tentati 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.

+0

Logicamente ogni componente ha un modello, a volte è diviso in n proprietà (FirstName, LastName, Indirizzo ...), a volte si ha un singolo oggetto composito (Persona), a volte un intero modello con dati eterogenei. – Pragmateek

+1

@Pragmateek ea volte la mucca salta sopra la luna, ma anche questo non ha niente a che fare con la domanda o questa risposta. – Will

+0

Stavo aggiungendo questo commento perché questa è la prima osservazione della tua risposta ("questo è un odore") che non è sempre vera, volevo solo chiarire. E per favore non lasciatevi ingannare dalla coincidenza Non sono il downvoter (non ho mai downvote come il mio profilo potrebbe mostrarti e soprattutto non qualcuno con la stessa reputazione che hai) :) – Pragmateek

2

È necessario eseguire 2 operazioni qui (presumo che le stringhe siano ObservableCollection<string>).

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

2) Variazione

<v:InkStringView TextInControl="{Binding text, ElementName=self}" /> 

a

<v:InkStringView TextInControl="{Binding }" /> 

XAML avete è alla ricerca di un "testo" proprietà sul ItemsControl di impegnare la TextInControl valore. 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.

<v:InkStringView TextInControl="{Binding SomeProperty}" /> 
+0

L'ipotesi è sbagliata. 'Stringhe' è una raccolta di oggetti con una proprietà pubblica' text' di tipo string. – Clemens

+0

sì ... quindi utilizzare l'opzione inferiore –

+0

l'ho provato in entrambi i modi usando il suo codice e questo lo risolve come si presupponeva nei commenti. Il binding per TextInControl va bene come è. –

3

Sembra che si mischi il modello della vista padre con il modello di 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<MyString> Strings { get; set; } 

     public MainViewModel() 
     { 
      Strings = new ObservableCollection<MyString> 
      { 
       new MyString{ text = "First" }, 
       new MyString{ text = "Second" }, 
       new MyString{ text = "Third" } 
      }; 
     } 
    } 
} 

Il MainWindow che lo utilizza:

<Window x:Class="UCItemsControl.MainWindow" 
     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
     xmlns:v="clr-namespace:UCItemsControl" 
     Title="MainWindow" Height="350" Width="525"> 
    <Window.DataContext> 
     <v:MainViewModel></v:MainViewModel> 
    </Window.DataContext> 
    <Grid> 
     <ItemsControl 
       ItemsSource="{Binding Strings}" x:Name="self" > 

      <ItemsControl.ItemsPanel> 
       <ItemsPanelTemplate> 
        <StackPanel HorizontalAlignment="Left" VerticalAlignment="Top" Orientation="Vertical" /> 
       </ItemsPanelTemplate> 
      </ItemsControl.ItemsPanel> 


      <ItemsControl.ItemTemplate> 
       <DataTemplate> 
        <DataTemplate.Resources> 
         <Style TargetType="v:InkStringView"> 
          <Setter Property="FontSize" Value="25"/> 
          <Setter Property="HorizontalAlignment" Value="Left"/> 
         </Style> 
        </DataTemplate.Resources> 

        <v:InkStringView TextInControl="{Binding text}" /> 
       </DataTemplate> 
      </ItemsControl.ItemTemplate> 
     </ItemsControl> 
    </Grid> 
</Window> 

tuo UC (nessun insieme 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 che posso ottenere quello che credo sia il risultato atteso, un elenco di valori:

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