2013-04-10 32 views
21

私は現在、ListView(タブとして)とContentControlをコンテンツプロパティのバインドで使用して、非表示のタブ付きタブコントロールの機能を実現しようとしています。バインディング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> 

そして、背後にあるコードで:

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、 "Test" TextBlockは表示されません。

私は間違ったバインディングの概念を持っている可能性があります、正しい方向に私にヒントを与えてください。

よろしく

+0

ListViewはどこにありますか?あなたは私たちに多くのスーパーのようなコードをたくさん与えることができますか?あなたが持っているすべてを私たちに与えてください。 –

+0

タブを使用する場合は、代わりにTabControlコントロールを使用してください。タブを非表示/表示するには、TabItemコントロールのVisibilityプロパティを操作します(ここでバインディングを使用できます)。また、Microsoftのデータバインディングの概要(http://msdn.microsoft.com/en-us/library/ms752347.aspx)をお読みください。 UI要素をバインドしないことをお勧めします。あなたの例では、設定用の複数のプロパティを含むSettingsPage用のクラスを作成します。 xamlでは、コントロールを作成して各プロパティにバインドします。 – failedprogramming

+0

@ snowy gui hedgehog:ListView自体は重要ではなく、ContentControlのコンテンツを設定するchangesitemイベントをトリガーするだけです。基本的に私の質問は、事前定義されたContentControlテンプレートを使用して、コードからContentControlのコンテンツを動的に変更する方法に関するものです。 @failedprogramming私はこれをしようとしている理由は、この記事です:[リンク](http://stackoverflow.com/questions/7010688/wpf-tab-control-with-no-tabs)ここに。 UIエレメントをバインドしないようにアドバイスしているのはなぜですか? – Xaser

答えて

60

[OK]を私はあなたが動的にデータバインディングとMVVM(モデル - ビュー - ViewModelに)アプローチを使用して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); 
     } 
    } 
} 

現在のデータモデルを持っている必要があります。簡単にするために、私はHomePageとSettingsPageという2つのモデルを作成しました。どちらのモデルも単一のプロパティしか持っていないので、必要に応じてプロパティを追加できます。私はその後、各モデルをラップするために、対応のviewmodelsを作成

HomePage.cs

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

SettingsPage.cs

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

。 ViewModelは、私の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"); 
     } 
    } 
} 

は、今、私たちは、それぞれのViewModelのビューを提供する必要があります。すなわち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> 

また、メインウィンドウのためのViewModelを必要としています。しかし、その前に、ボタンをコマンドにバインドできるように別のクラスを作成する必要があります。

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

そして今、我々はMainWindowViewModelをdefindすることができます。 CurrentViewModelは、MainWindowのContentControlにバインドされているプロパティです。ボタンをクリックしてこのプロパティを変更すると、MainWindowの画面が変わります。 MainWindowは、Window.Resourcesセクションで定義したDataTemplatesのために読み込む画面(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."}); 
    } 
} 

そして最後に、私たちは、メインウィンドウのDataContextプロパティに私達のMainWindowViewModelクラスをロードできるように、アプリケーションの起動を無効にする必要があります。

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

また、我々はスタート・アップ時に2 MainWindowsを取得しないように、App.xaml ApplicationタグにStartupUri="MainWindow.xaml"コードを削除するには良いでしょう。

新しいプロジェクトにコピーして使用できるDelegateCommandクラスとViewModelBaseクラスに注意してください。 これは単なる非常に簡単な例です。あなたはあなたのコメントでherehere

編集 から、より良いアイデアを得ることができ、あなたは各ビューおよび関連定型的なコードのためのクラスを持っている必要はないことが可能であるかどうかを知りたいと思いました。私が知る限り、答えは「いいえ」です。はい、あなたは1つの巨大なクラスを持つことができますが、各プロパティセッターに対してOnPropertyChangedを呼び出す必要があります。これにはかなりの欠点もあります。まず、結果として得られるクラスは維持するのが難しいでしょう。多くのコードと依存関係が存在します。第2に、ビューを「スワップ」するためにDataTemplateを使用するのは難しいでしょう。 DataTemplatesでx:Keyを使用し、usercontrolでテンプレートバインディングをハードコーディングすることによっても可能です。本質的に、あなたはコードをもっと短くしているわけではありませんが、あなたはそれをもっと難しくしています。

あなたの主な悩みは、あなたのモデルプロパティをラップするためにあなたのビューモデルに多くのコードを書く必要があると思います。T4 templatesをご覧ください。いくつかの開発者は、これを使用してボイラープレートコード(ViewModelクラス)を自動生成します。私はこれを個人的に使用しません。カスタムコードスニペットを使用して、すぐにviewmodelプロパティを生成します。

また、PrismやMVVMLightなどのMVVMフレームワークを使用することもできます。私は自分で使ったことはありませんが、定型コードを簡単に作成できる機能が組み込まれていると聞いてきました。

もう1つの注意点は次のとおりです。 データベースに設定を保存する場合、Entity FrameworkのようなORMフレームワークを使用してデータベースからモデルを生成することができます。ビューモデルとビュー。

+2

心配しないでください。余分なコードに関するご質問については上記の私の編集をご覧ください。 – failedprogramming

+0

ありがとう、私はこれまで働いているすべてを持っています:) – Xaser

+1

@ Xaser StackOverflowであなたの質問を投稿する場合は、私は好きです。あなたはこのように多くの助けを得ることができます。あなたは私に新しい質問のリンクを送ることができ、私は助けようとします。ありがとう、 – failedprogramming

関連する問題