结合ContentControl中内容动态内容 [英] Binding ContentControl Content for dynamic content

查看:1385
本文介绍了结合ContentControl中内容动态内容的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

目前,我正在尝试使用一个ListView(如标签),并结合内容属性一个ContentControl中实现与隐藏标签一个TabControl的功能。

I'm currently trying to achieve the functionality of a tabcontrol with hidden tabs by using a ListView (as tabs) and a ContentControl with Binding the Content Property.

我读了这个话题一点,如果我得到它的权利,它应该以这种方式工作:

I read a bit on that topic and if I got it right, it should work this way:

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

而在code背后:

And in the code behind:

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它抛出任何错误,不显示测试的TextBlock。

Allthough it throws no error, it doesn't display the "Test" TextBlock.

它可能我的错结合的概念,请给我一个正确的方向提示。

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

问候

推荐答案

好,我敲了一个简单的例子来告诉你如何可以使用MVVM动态改变ContentControl中的内容(模型 - 视图 - 视图模型)的方法使用数据绑定。

Ok I've knocked up a simple example to show you how you can dynamically change the content of the ContentControl using a MVVM(Model-View-ViewModel) approach with data binding.

我建议你创建一个新的项目,并加载这些文件,看看它是如何工作的。

I would recommend that you create a new project and load these files to see how it all works.

我们首先需要实现INotifyPropertyChanged接口。这将允许您使用时更改的属性发生时将通知UI特性定义自己的类。我们创建了一个抽象类,提供此功能。

We first need to implement INotifyPropertyChanged interface. This will allow you to define your own classes with properties that will notify the UI when a change to the properties occurs. We create an abstract class that provides this functionality.

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

我们现在需要有数据模型。为简单起见,我创建了2个型号 - 主页和SettingsPage。这两款机型只有一个属性,你可以根据需要添加更多的属性。

We now need to have the data models. For simplicity, I've created 2 models - HomePage and SettingsPage. Both models only have a single property, you can add more properties as required.

HomePage.cs

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

SettingsPage.cs

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

我再建立相应的ViewModels包裹每个模型。请注意,从的ViewModels我ViewModelBase抽象类继承。

I then create corresponding ViewModels to wrap each model. Note that the viewmodels inherit from my ViewModelBase abstract class.

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

现在,我们需要为每个视图模型视图。即主页和SettingsPageView。我创建2用户控件这一点。

Now we need to provide Views for each ViewModel. i.e. The HomePageView and the SettingsPageView. I created 2 UserControls for this.

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>

我们现在需要定义的主窗口的XAML。我已经包括2个按钮,帮助2页面之间导航。
MainWindow.xaml

We now need to define the xaml for the MainWindow. I have included 2 buttons to help navigate between the 2 "pages". 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>

我们还需要在主窗口一个ViewModel。但在此之前,我们需要创建另一个类,使我们可以将我们的按钮绑定到命令。

We also need a ViewModel for the MainWindow. But before that we need create another class so that we can bind our Buttons to Commands.

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

现在我们可以defind的MainWindowViewModel。 CurrentViewModel是必然要在主窗口的ContentControl中的财产。当我们通过点击按钮,在主窗口屏幕的改变而改变这个属性。在主窗口知道哪个屏幕(用户控件),因为我在Window.Resources部分已经定义了的DataTemplates的加载。

And now we can defind the MainWindowViewModel. CurrentViewModel is the property that is bound to the ContentControl on the MainWindow. When we change this property by clicking on the buttons, the screen changes on the MainWindow. The MainWindow knows which screen(usercontrol) to load because of the DataTemplates that I have defined in the Window.Resources section.

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

最后,我们需要重写应用程序的启动,使我们可以在我们的MainWindowViewModel类加载到主窗口的DataContext属性。

And finally, we need to override the application startup so that we can load our MainWindowViewModel class into the DataContext property of the 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();
    }
}

这也将是一个好主意,删除的StartupUri =MainWindow.xaml code在App.xaml中应用的标签,这样我们不得到启动时2 MainWindows。

It would also be a good idea to remove the StartupUri="MainWindow.xaml" code in the App.xaml Application tag so that we don't get 2 MainWindows on start up.

注意,仅仅可以被复制到新的项目和所用的DelegateCommand和ViewModelBase类。
这只是一个很简单的例子。你可以从这里 和的here

Note that the DelegateCommand and ViewModelBase classes that just can be copied into new projects and used. This is just a very simple example. You can get a better idea from here and here

修改
在你的评论,你想知道是否有可能不必为每个视图和相关的样板code类。据我所知,答案是否定的。是的,你可以有一个巨大的类,但你仍然需要调用OnPropertyChanged每个属性的制定者。也有不少缺点了这一点。首先,结果类将是非常难以维持。会有很多code和依赖。其次,这将是很难用的DataTemplates来交换的观点。它仍然可以通过使用X:输入您的DataTemplates和硬编码模板在你的用户控件绑定。从本质上讲,你是不是真的让你的code要短得多,但你会使其难以自己。

Edit In your comment, you wanted to know if it is possible to not have to have a class for each view and related boilerplate code. As far as I know, the answer is no. Yes, you can have a single gigantic class, but you would still need to call OnPropertyChanged for each Property setter. There are also quite a few drawbacks to this. Firstly, the resulting class would be really hard to maintain. There would be a lot of code and dependencies. Secondly, it would be hard to use DataTemplates to "swap" views. It is still possible by using a x:Key in your DataTemplates and hardcoding a template binding in your usercontrol. In essence, you aren't really making your code much shorter, but you will be making it harder for yourself.

我猜你的主要抱怨是对你的视图模型写了这么多code来包装你的模型属性。看看 T4模板。一些开发人员使用这个自动生成的样板code(即视图模型类)。我不亲自用这个,我使用自定义的code段,以快速生成视图模型属性。

I'm guessing your main gripe is having to write so much code in your viewmodel to wrap your model properties. Have a look at T4 templates. Some developers use this to auto-generate their boilerplate code (i.e. ViewModel classes). I don't use this personally, I use a custom code snippet to quickly generate a viewmodel property.

另一种选择是使用一个MVVM框架,如棱镜或MVVMLight。我没有用一个自己,但我听说他们中的一些已经内置的功能,使样板code容易。

Another option would be to use a MVVM framework, such as Prism or MVVMLight. I haven't used one myself, but I've heard some of them have built in features to make boilerplate code easy.

另外要注意的一点是:
如果您在数据库中存储的设置,有可能使用ORM框架,像实体框架从数据库中,这意味着你就只剩下创建的ViewModels和视图生成您的模型。

Another point to note is: If you are storing your settings in a database, it might be possible to use an ORM framework like Entity Framework to generate your models from the database, which means all you have left is creating the viewmodels and views.

这篇关于结合ContentControl中内容动态内容的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆