2013-04-10 206 views
21

我目前正在试图通过使用ListView(作为选项卡)和ContentControl与绑定内容属性来实现与隐藏标签tabcontrol的功能。动态内容绑定ContentControl内容

我阅读有关该主题的一点,如果我这样做是正确,它应该以这种方式工作:

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

而且在后面的代码:Allthough它抛出任何错误

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; 

,它不显示“测试”TextBlock。

它可能我得到了绑定错误的概念,请给我一个正确方向的提示。

问候

+0

那里的ListView在哪里?你能给我们更多的代码吗?给我们你有的一切。 – 2013-04-10 21:51:29

+0

如果你想使用标签,你为什么不使用TabControl控件呢?要隐藏/显示选项卡,请操作TabItem控件的Visibility属性(您可以在此处使用绑定)。此外,请阅读Microsoft的数据绑定概述http://msdn.microsoft.com/zh-cn/library/ms752347.aspx。我会建议你不要绑定UI元素。在你的例子中,我会为SettingsPage创建一个类,它将包含设置的多个属性。在xaml中,我会创建控件并绑定到每个属性。 – failedprogramming 2013-04-10 22:10:24

+0

@ snowy gui hedgehog:ListView本身并不重要,它只是在那里触发changeditem事件,我将在其中设置ContentControl的内容。基本上我的问题是如何从使用预定义的ContentControl模板的代码中动态更改ContentControl的内容。 @failedprogramming我试图做到这一点的原因是这篇文章:[链接](http://stackoverflow.com/questions/7010688/wpf-tab-control-with-no-tabs)在这里。 为什么你建议不要绑定UI元素? – Xaser 2013-04-11 20:54:38

回答

60

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

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

我们首先需要实现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); 
     } 
    } 
} 

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

HomePage.cs

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

SettingsPage.cs

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

然后创建对应的ViewModels包裹每个模型。请注意,viewmodels继承自我的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"); 
     } 
    } 
} 

现在,我们需要为每个视图模型视图。即HomePageView和SettingsPageView。我为此创建了2个UserControls。

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

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

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

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

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属性。

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

这也将是一个好主意,除去在App.xaml中应用标签的StartupUri="MainWindow.xaml"代码,所以我们没有得到启动了2个MainWindows。

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

编辑 在您的评论得到一个更好的主意,你想知道是否有可能不必为每个视图和相关的样板代码的类。据我所知,答案是否定的。是的,你可以有一个巨大的类,但你仍然需要为每个属性设置器调用OnPropertyChanged。这也有不少缺点。首先,最终的课程将很难维持。会有很多代码和依赖关系。其次,很难使用DataTemplates来“交换”视图。仍然可以在DataTemplates中使用x:Key并在usercontrol中对模板绑定进行硬编码。本质上,你并不是真的让你的代码更短,但是你会让自己更难。

我猜你的主要抱怨是不得不在你的viewmodel编写这么多的代码来包装你的模型属性。看看T4 templates。一些开发人员使用它来自动生成他们的样板代码(即ViewModel类)。我个人不使用这个,我使用自定义代码片段来快速生成viewmodel属性。

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

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

+2

不用担心。请参阅我上面有关您的代码过多的问题的编辑。 – failedprogramming 2013-04-13 08:00:25

+0

非常感谢,我得到了一切工作到目前为止:) – Xaser 2013-04-13 11:22:04

+1

@Xaser我宁愿如果您发布您的问题在StackOverflow。你将能够通过这种方式获得更多的帮助。你可以给我发送任何新问题的链接,我会尽力帮忙。谢谢 – failedprogramming 2013-04-19 01:30:54