Binding ContentControl Content for dynamic content

Obecnie próbuję uzyskać funkcjonalność tabcontrol z ukrytymi kartami za pomocą ListView (jako karty) i ContentControl z powiązaniem właściwości Content.

Przeczytałem trochę na ten temat i jeśli dobrze zrozumiałem, to powinno działać w ten sposób:

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

I w kodzie za:

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;

Mimo że nie wyświetla błędu, nie wyświetla bloku tekstowego "Test".

Its likely I got the concept of binding wrong, please give me a hint in the right kierunek.

Pozdrawiam

Author: DhruvJoshi, 2013-04-11

1 answers

Ok mam prosty przykład, aby pokazać, jak można dynamicznie zmieniać zawartość ContentControl za pomocą podejścia MVVM (Model-View-ViewModel) z powiązaniem danych.

Zalecałbym utworzenie nowego projektu i załadowanie tych plików, aby zobaczyć, jak to wszystko działa.

Najpierw musimy zaimplementować interfejs INotifyPropertyChanged. Pozwoli to na zdefiniowanie własnych klas z właściwościami, które będą powiadamiać interfejs użytkownika, gdy nastąpi zmiana właściwości. My Utwórz abstrakcyjną klasę, która zapewnia tę funkcjonalność.

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

Teraz musimy mieć modele danych. Dla uproszczenia stworzyłem 2 modele-HomePage i SettingsPage. Oba modele mają tylko jedną właściwość, możesz dodać więcej właściwości w razie potrzeby.

Strona główna.cs

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

SettingsPage.cs

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

Następnie tworzę odpowiednie Viewmodele, aby zawinąć każdy model. Zauważ, że viewmodels Dziedzicz z mojej klasy abstrakcyjnej 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");
        }
    }
}

Teraz musimy dostarczyć widoki dla każdego modelu ViewModel. tj. HomePageView i SettingsPageView. Stworzyłem do tego 2 kontrolki UserControls.

Strona główna.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>

Teraz musimy zdefiniować xaml dla głównego okna. I zawierają 2 przyciski ułatwiające nawigację między 2 "stronami". 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>

Potrzebujemy również ViewModel dla głównego okna. Ale zanim to nastąpi, musimy utworzyć kolejną klasę, aby móc powiązać nasze przyciski z poleceniami.

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

I teraz możemy defind MainWindowViewModel. CurrentViewModel jest właściwością, która jest powiązana z ContentControl w oknie głównym. Gdy zmienimy tę właściwość przez klikając na przyciski, ekran zmienia się na głównym oknie. MainWindow wie, który ekran (usercontrol) ma się załadować ze względu na tablice danych, które zdefiniowałem w oknie.Dział zasobów.

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

I na koniec, musimy nadpisać uruchomienie aplikacji tak, że możemy załadować naszą klasę MainWindowViewModel do Właściwości DataContext 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();
    }
}

Byłoby również dobrym pomysłem jest usunięcie kodu StartupUri="MainWindow.xaml" w aplikacji.znacznik aplikacji xaml, dzięki któremu nie otrzymamy 2 okien MainWindows przy starcie.

Zauważ, że Klasy DelegateCommand i viewmodelbase mogą być kopiowane do nowych projektów i używane. To tylko bardzo prosty przykład. Możesz uzyskać lepszy pomysł z tutaj i tutaj

Edit W komentarzu chciałeś wiedzieć, czy jest możliwe, aby nie mieć klasy dla każdego widoku i powiązanego kotła kod. O ile wiem, odpowiedź brzmi nie. Tak, możesz mieć jedną gigantyczną klasę, ale nadal będziesz musiał wywołać OnPropertyChanged dla każdego settera właściwości. Istnieje również sporo wad tego. Po pierwsze, wynikająca z tego Klasa byłaby naprawdę trudna do utrzymania. Byłoby dużo kodu i zależności. Po drugie, trudno byłoby użyć tablic danych do "zamiany" widoków. Jest to nadal możliwe, używając klucza x:w płytach danych i kodując na twardo powiązanie szablonu w kontrolerze użytkownika. W istocie, tak naprawdę nie czyni Twój kod znacznie krótszy, ale będzie to trudniejsze dla siebie.

Domyślam się, że twoim głównym problemem jest to, że musisz napisać tyle kodu w swoim viewmodelu, aby zawinąć właściwości modelu. Spójrz na szablony T4 . Niektórzy programiści używają tego do automatycznego generowania kodu boilerplate (np. klas ViewModel). Nie używam tego osobiście, używam niestandardowego fragmentu kodu, aby szybko wygenerować właściwość viewmodel.

Inna opcja to być użycie frameworku MVVM, takiego jak Prism lub MVVMLight. Sam nie używałem żadnego, ale słyszałem, że niektóre z nich mają wbudowane funkcje, aby ułatwić kod boilerplate.

Kolejna uwaga to: Jeśli przechowujesz ustawienia w bazie danych, może być możliwe użycie frameworka ORM, takiego jak Framework Entity, do generowania modeli z bazy danych, co oznacza, że pozostało Ci tylko tworzenie modeli i widoków.

 68
Author: failedprogramming,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-04-13 07:58:18