C# 为动态内容绑定 ContentControl 内容

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/15936428/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-10 18:32:33  来源:igfitidea点击:

Binding ContentControl Content for dynamic content

c#wpfxamlbindingcontentcontrol

提问by Xaser

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.

我目前正在尝试通过使用 ListView(作为选项卡)和具有绑定内容属性的 ContentControl 来实现带有隐藏选项卡的选项卡控件的功能。

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>

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

很可能我对绑定的概念有误解,请给我一个正确方向的提示。

Regards

问候

采纳答案by failedprogramming

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.

好的,我已经举了一个简单的例子来向您展示如何使用数据绑定的 MVVM(Model-View-ViewModel) 方法动态更改 ContentControl 的内容。

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

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

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.

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

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

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.

我们现在需要有数据模型。为简单起见,我创建了 2 个模型 - HomePage 和 SettingsPage。两种模型都只有一个属性,您可以根据需要添加更多属性。

HomePage.cs

主页.cs

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

SettingsPage.cs

设置页面.cs

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

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

然后我创建相应的 ViewModel 来包装每个模型。请注意,视图模型继承自我的 ViewModelBase 抽象类。

HomePageViewModel.cs

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

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

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

现在我们需要为每个 ViewModel 提供视图。即 HomePageView 和 SettingsPageView。我为此创建了 2 个用户控件。

HomePageView.xaml

主页视图.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

设置页面视图.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>

We now need to define the xaml for the MainWindow. I have included 2 buttons to help navigate between the 2 "pages". MainWindow.xaml

我们现在需要为 MainWindow 定义 xaml。我包含了 2 个按钮来帮助在 2 个“页面”之间导航。 主窗口.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>

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.

我们还需要一个用于 MainWindow 的 ViewModel。但在此之前,我们需要创建另一个类,以便我们可以将按钮绑定到命令。

DelegateCommand.cs

委托命令.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);
    }
}

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。CurrentViewModel 是绑定到 MainWindow 上的 ContentControl 的属性。当我们通过单击按钮更改此属性时,MainWindow 上的屏幕会发生变化。由于我在 Window.Resources 部分定义的 DataTemplates,MainWindow 知道要加载哪个屏幕(用户控件)。

MainWindowViewModel.cs

主窗口视图模型.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."});
    }
}

And finally, we need to override the application startup so that we can load our MainWindowViewModel class into the DataContext property of the MainWindow.

最后,我们需要覆盖应用程序启动,以便我们可以将 MainWindowViewModel 类加载到 MainWindow 的 DataContext 属性中。

App.xaml.cs

应用程序.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();
    }
}

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.

删除StartupUri="MainWindow.xaml"App.xaml Application 标记中的代码也是一个好主意,这样我们就不会在启动时获得 2 个 MainWindows。

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

请注意,可以复制到新项目中并使用的 DelegateCommand 和 ViewModelBase 类。这只是一个非常简单的例子。你可以从这里这里得到一个更好的主意

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

编辑在您的评论中,您想知道是否可以不必为每个视图和相关样板代码创建一个类。据我所知,答案是否定的。是的,您可以拥有一个巨大的类,但您仍然需要为每个 Property setter 调用 OnPropertyChanged。这也有很多缺点。首先,生成的类将很难维护。会有很多代码和依赖项。其次,很难使用 DataTemplates 来“交换”视图。仍然可以通过在您的 DataTemplates 中使用 ax:Key 并在您的用户控件中硬编码模板绑定。从本质上讲,您并没有真正使代码更短,但会使您自己变得更难。

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.

我猜您的主要抱怨是必须在您的视图模型中编写如此多的代码来包装您的模型属性。看看T4 模板。一些开发人员使用它来自动生成他们的样板代码(即 ViewModel 类)。我个人不使用它,我使用自定义代码片段来快速生成一个视图模型属性。

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.

另一种选择是使用 MVVM 框架,例如 Prism 或 MVVMLight。我自己没有使用过,但我听说其中一些内置功能可以简化样板代码。

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.

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