2013-04-10 14 views
21

Attualmente sto cercando di ottenere la funzionalità di un controllo a schede con le schede nascoste utilizzando un ListView (come schede) e un ContentControl con Binding della proprietà del contenuto.Binding ContentControl Content per contenuto dinamico

ho letto un po 'su questo argomento e se ho capito bene, dovrebbe funzionare in questo modo:

<Grid> 
    <Grid.ColumnDefinitions> 
     <ColumnDefinition Width="20.0*"/> 
     <ColumnDefinition Width="80.0*"/> 
    </Grid.ColumnDefinitions> 
    <ListBox Grid.Column="0"> 
     <ListBoxItem Content="Appearance"/> 
    </ListBox> 

    <ContentControl Content="{Binding SettingsPage}" Grid.Column="1"/> 
</Grid> 
. 
. 
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
       xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> 
    <ContentControl x:Key="AppearancePage"> 
     <TextBlock Text="Test" /> 
    </ContentControl> 
    <ContentControl x:Key="AdvancedPage"> 
     <TextBlock Text="Test2" /> 
    </ContentControl> 
</ResourceDictionary> 

E nel codice dietro:

public partial class MainWindow : MetroWindow 
    { 
    private ContentControl SettingsPage; 
    private ResourceDictionary SettingsPagesDict = new ResourceDictionary(); 

    public MainWindow() 
    { 
     InitializeComponent(); 

     SettingsPagesDict.Source = new Uri("SettingsPages.xaml", UriKind.RelativeOrAbsolute); 
     SettingsPage = SettingsPagesDict["AppearancePage"] as ContentControl; 

Allthough getta alcun errore, non visualizza il TextBlock "Test".

È probabile che abbia ottenuto il concetto di associazione errata, per favore darmi un suggerimento nella giusta direzione.

saluti

+0

E dov'è il ListView lì? Puoi darci un sacco di codice in più come molto di più. Dacci tutto ciò che hai. –

+0

Se si desidera utilizzare le schede, perché non utilizzare invece il controllo TabControl? Per nascondere/mostrare le schede, puoi manipolare la proprietà Visibility dei controlli TabItem (puoi utilizzare l'associazione qui). Inoltre, leggere la Panoramica sul binding dei dati da Microsoft http://msdn.microsoft.com/en-us/library/ms752347.aspx. Ti consiglierei di non collegare UI Elements. Nel tuo esempio, vorrei creare una classe per SettingsPage, che conterrà più proprietà per le impostazioni. Nel xaml, vorrei creare controlli e associare a ciascuna proprietà. – failedprogramming

+0

@ snowy gui hedgehog: ListView in quanto tale non è importante, è lì solo per attivare l'evento changeditem in cui imposterò il contenuto di ContentControl. Fondamentalmente la mia domanda è tutta su come modificare dinamicamente il contenuto di ContentControl dal codice sottostante utilizzando i modelli ContentControl predefiniti. @failedprogramming Il motivo per cui sto provando a farlo è questo post: [collegamento] (http://stackoverflow.com/questions/7010688/wpf-tab-control-with-no-tabs) qui. Perché consigli di non associare UI Elements? – Xaser

risposta

60

Ok ho bussato un semplice esempio per mostrare come è possibile modificare dinamicamente il contenuto del ContentControl utilizzando un approccio MVVM (Model-View-ViewModel), con l'associazione di dati.

Si consiglia di creare un nuovo progetto e caricare questi file per vedere come funziona.

Prima di tutto è necessario implementare l'interfaccia INotifyPropertyChanged. Ciò ti consentirà di definire le tue classi con proprietà che notificheranno all'interfaccia utente quando si verifica una modifica alle proprietà. Creiamo una classe astratta che fornisce questa funzionalità.

ViewModelBase.cs

public abstract class ViewModelBase : INotifyPropertyChanged 
{ 
    public event PropertyChangedEventHandler PropertyChanged; 

    protected void OnPropertyChanged(string propertyName) 
    { 
     this.OnPropertyChanged(new PropertyChangedEventArgs(propertyName)); 
    } 

    protected virtual void OnPropertyChanged(PropertyChangedEventArgs e) 
    { 
     var handler = this.PropertyChanged; 
     if (handler != null) 
     { 
      handler(this, e); 
     } 
    } 
} 

ora abbiamo bisogno di avere i modelli di dati. Per semplicità, ho creato 2 modelli: HomePage e SettingsPage. Entrambi i modelli hanno una sola proprietà, è possibile aggiungere più proprietà come richiesto.

HomePage.cs

public class HomePage 
{ 
    public string PageTitle { get; set; } 
} 

SettingsPage.cs

public class SettingsPage 
{ 
    public string PageTitle { get; set; } 
} 

poi Creo corrispondente ViewModels per avvolgere ciascun modello. Si noti che viewmodels eredita dalla mia classe astratta ViewModelBase.

HomePageViewModel.cs

public class HomePageViewModel : ViewModelBase 
{ 
    public HomePageViewModel(HomePage model) 
    { 
     this.Model = model; 
    } 

    public HomePage Model { get; private set; } 

    public string PageTitle 
    { 
     get 
     { 
      return this.Model.PageTitle; 
     } 
     set 
     { 
      this.Model.PageTitle = value; 
      this.OnPropertyChanged("PageTitle"); 
     } 
    } 
} 

SettingsPageViewModel.cs

public class SettingsPageViewModel : ViewModelBase 
{ 
    public SettingsPageViewModel(SettingsPage model) 
    { 
     this.Model = model; 
    } 

    public SettingsPage Model { get; private set; } 

    public string PageTitle 
    { 
     get 
     { 
      return this.Model.PageTitle; 
     } 
     set 
     { 
      this.Model.PageTitle = value; 
      this.OnPropertyChanged("PageTitle"); 
     } 
    } 
} 

Ora abbiamo bisogno di fornire Visualizzazioni per ogni ViewModel. Ad esempio, HomePageView e SettingsPageView. Ho creato 2 UserControls per questo.

HomePageView.XAML

<UserControl x:Class="WpfApplication3.HomePageView" 
     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" 
     mc:Ignorable="d" 
     d:DesignHeight="300" d:DesignWidth="300"> 
<Grid> 
     <TextBlock FontSize="20" Text="{Binding Path=PageTitle}" /> 
</Grid> 

SettingsPageView.xaml

<UserControl x:Class="WpfApplication3.SettingsPageView" 
     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" 
     mc:Ignorable="d" 
     d:DesignHeight="300" d:DesignWidth="300"> 
<Grid> 
    <TextBlock FontSize="20" Text="{Binding Path=PageTitle}" /> 
</Grid> 

Abbiamo ora bisogno di definire il codice XAML per il MainWindow. Ho incluso 2 pulsanti per aiutare a navigare tra le 2 "pagine". MainWindow.xaml

<Window x:Class="WpfApplication3.MainWindow" 
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    xmlns:local="clr-namespace:WpfApplication3" 
    Title="MainWindow" Height="350" Width="525"> 
<Window.Resources> 
    <DataTemplate DataType="{x:Type local:HomePageViewModel}"> 
     <local:HomePageView /> 
    </DataTemplate> 
    <DataTemplate DataType="{x:Type local:SettingsPageViewModel}"> 
     <local:SettingsPageView /> 
    </DataTemplate> 
</Window.Resources> 
<DockPanel> 
    <StackPanel DockPanel.Dock="Left"> 
     <Button Content="Home Page" Command="{Binding Path=LoadHomePageCommand}" /> 
     <Button Content="Settings Page" Command="{Binding Path=LoadSettingsPageCommand}"/> 
    </StackPanel> 

    <ContentControl Content="{Binding Path=CurrentViewModel}"></ContentControl> 
</DockPanel> 

Abbiamo bisogno anche di un ViewModel per la MainWindow. Ma prima dobbiamo creare un'altra classe in modo da poter associare i nostri pulsanti ai comandi.

DelegateCommand.cs

public class DelegateCommand : ICommand 
{ 
    /// <summary> 
    /// Action to be performed when this command is executed 
    /// </summary> 
    private Action<object> executionAction; 

    /// <summary> 
    /// Predicate to determine if the command is valid for execution 
    /// </summary> 
    private Predicate<object> canExecutePredicate; 

    /// <summary> 
    /// Initializes a new instance of the DelegateCommand class. 
    /// The command will always be valid for execution. 
    /// </summary> 
    /// <param name="execute">The delegate to call on execution</param> 
    public DelegateCommand(Action<object> execute) 
     : this(execute, null) 
    { 
    } 

    /// <summary> 
    /// Initializes a new instance of the DelegateCommand class. 
    /// </summary> 
    /// <param name="execute">The delegate to call on execution</param> 
    /// <param name="canExecute">The predicate to determine if command is valid for execution</param> 
    public DelegateCommand(Action<object> execute, Predicate<object> canExecute) 
    { 
     if (execute == null) 
     { 
      throw new ArgumentNullException("execute"); 
     } 

     this.executionAction = execute; 
     this.canExecutePredicate = canExecute; 
    } 

    /// <summary> 
    /// Raised when CanExecute is changed 
    /// </summary> 
    public event EventHandler CanExecuteChanged 
    { 
     add { CommandManager.RequerySuggested += value; } 
     remove { CommandManager.RequerySuggested -= value; } 
    } 

    /// <summary> 
    /// Executes the delegate backing this DelegateCommand 
    /// </summary> 
    /// <param name="parameter">parameter to pass to predicate</param> 
    /// <returns>True if command is valid for execution</returns> 
    public bool CanExecute(object parameter) 
    { 
     return this.canExecutePredicate == null ? true : this.canExecutePredicate(parameter); 
    } 

    /// <summary> 
    /// Executes the delegate backing this DelegateCommand 
    /// </summary> 
    /// <param name="parameter">parameter to pass to delegate</param> 
    /// <exception cref="InvalidOperationException">Thrown if CanExecute returns false</exception> 
    public void Execute(object parameter) 
    { 
     if (!this.CanExecute(parameter)) 
     { 
      throw new InvalidOperationException("The command is not valid for execution, check the CanExecute method before attempting to execute."); 
     } 
     this.executionAction(parameter); 
    } 
} 

E ora possiamo defind il MainWindowViewModel. CurrentViewModel è la proprietà associata a ContentControl in MainWindow. Quando cambiamo questa proprietà facendo clic sui pulsanti, la schermata cambia in MainWindow. Il MainWindow sa quale schermo (usercontrol) caricare a causa dei DataTemplates che ho definito nella sezione Window.Resources.

MainWindowViewModel.cs

public class MainWindowViewModel : ViewModelBase 
{ 
    public MainWindowViewModel() 
    { 
     this.LoadHomePage(); 

     // Hook up Commands to associated methods 
     this.LoadHomePageCommand = new DelegateCommand(o => this.LoadHomePage()); 
     this.LoadSettingsPageCommand = new DelegateCommand(o => this.LoadSettingsPage()); 
    } 

    public ICommand LoadHomePageCommand { get; private set; } 
    public ICommand LoadSettingsPageCommand { get; private set; } 

    // ViewModel that is currently bound to the ContentControl 
    private ViewModelBase _currentViewModel; 

    public ViewModelBase CurrentViewModel 
    { 
     get { return _currentViewModel; } 
     set 
     { 
      _currentViewModel = value; 
      this.OnPropertyChanged("CurrentViewModel"); 
     } 
    } 

    private void LoadHomePage() 
    { 
     CurrentViewModel = new HomePageViewModel(
      new HomePage() { PageTitle = "This is the Home Page."}); 
    } 

    private void LoadSettingsPage() 
    { 
     CurrentViewModel = new SettingsPageViewModel(
      new SettingsPage(){PageTitle = "This is the Settings Page."}); 
    } 
} 

E, infine, abbiamo bisogno di ignorare l'avvio applicazione in modo che possiamo caricare la nostra classe MainWindowViewModel nella proprietà DataContext del MainWindow.

App.xaml.cs

public partial class App : Application 
{ 
    protected override void OnStartup(StartupEventArgs e) 
    { 
     base.OnStartup(e); 

     var window = new MainWindow() { DataContext = new MainWindowViewModel() }; 
     window.Show(); 
    } 
} 

Sarebbe anche una buona idea per rimuovere il codice StartupUri="MainWindow.xaml" nel tag App.xaml applicazione in modo che non otteniamo 2 MainWindows fase di start up.

Si noti che le classi DelegateCommand e ViewModelBase possono essere copiate in nuovi progetti e utilizzate. Questo è solo un esempio molto semplice. È possibile ottenere una migliore idea da here e here

Modifica nel tuo commento, si voleva sapere se è possibile non avere una classe per ogni vista e relativo codice standard. Per quanto ne so, la risposta è no. Sì, puoi avere un'unica classe gigantesca, ma dovresti comunque chiamare OnPropertyChanged per ogni setter di proprietà. Ci sono anche alcuni inconvenienti a questo. Innanzitutto, la classe risultante sarebbe davvero difficile da mantenere. Ci sarebbe un sacco di codice e dipendenze. In secondo luogo, sarebbe difficile usare DataTemplates per "scambiare" le viste. È ancora possibile utilizzando una chiave x: Inserisci i tuoi DataTemplates e codifica un modello nel tuo usercontrol. In sostanza, non stai rendendo il tuo codice molto più breve, ma lo renderai più difficile per te stesso.

Immagino che la tua lamentela principale sia dover scrivere così tanto codice nel tuo viewmodel per avvolgere le proprietà del tuo modello.Dai un'occhiata a T4 templates. Alcuni sviluppatori lo usano per generare automaticamente il proprio codice di codice (ad esempio classi ViewModel). Non lo uso personalmente, utilizzo uno snippet di codice personalizzato per generare rapidamente una proprietà viewmodel.

Un'altra opzione sarebbe utilizzare un framework MVVM, come Prism o MVVMLight. Non ne ho mai usato uno, ma ho sentito che alcuni di essi hanno funzioni incorporate per semplificare il codice di codice.

Un altro punto da notare è: Se si memorizzano le impostazioni in un database, potrebbe essere possibile utilizzare un framework ORM come Entity Framework per generare i propri modelli dal database, il che significa che tutto ciò che è rimasto è la creazione del viewmodels e visualizzazioni.

+2

Nessun problema. Si prega di consultare la mia modifica sopra per quanto riguarda la tua domanda sul codice eccessivo. – failedprogramming

+0

Grazie mille, ho ottenuto tutto ciò che funziona finora :) – Xaser

+1

@Xaser Preferirei se pubblichi la tua domanda su StackOverflow. Sarai in grado di ottenere più aiuto in questo modo. Puoi inviarmi il link di ogni nuova domanda e cercherò di aiutarti. Grazie – failedprogramming

Problemi correlati