2013-04-10 1 views
21

현재 ListView (탭)를 사용하여 숨겨진 탭이있는 tabcontrol과 ContentControl을 바인딩하는 ContentControl의 기능을 구현하려고합니다. ,바인딩 콘텐츠 컨트롤 내용 동적 콘텐츠

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 :

은 그 주제에 비트를 읽고 내가 바로 그것을 가지고 있다면,이 방식으로 작동합니다 :

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

을 그리고 뒤에 코드 "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의 컨텐트를 설정할 변경 이벤트를 트리거 할 수 있습니다. 기본적으로 내 질문에 동적으로 미리 정의 된 ContentControl 템플릿을 사용하여 코드에서 ContentControl 콘텐츠를 동적으로 변경하는 방법에 대한 모든 것입니다. @failedprogramming 내가 이것을하려고하는 이유는이 게시물입니다. [link] (http://stackoverflow.com/questions/7010688/wpf-tab-control-with-no-tabs) 여기. UI 요소를 바인딩하지 않는 이유는 무엇입니까? – Xaser

답변

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

우리는 이제 데이터 모델이 필요합니다. 간단하게하기 위해 HomePage와 SettingsPage라는 두 가지 모델을 만들었습니다. 두 모델 모두 하나의 속성 만 가지고 있으므로 필요에 따라 더 많은 속성을 추가 할 수 있습니다.

HomePage.cs

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

SettingsPage.cs

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

나는 각 모델을 포장 ViewModels 해당 만듭니다. 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"); 
     } 
    } 
} 

이제 우리는 각각의 뷰 모델에 대한 뷰를 제공해야합니다. 즉 HomePageView 및 SettingsPageView. 이를 위해 2 개의 UserControls를 만들었습니다.

HomePageView.

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

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

우리는 이제 MainWindow를위한 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> 
우리는 또한 MainWindow를위한 뷰 모델이 필요합니다. 그러나 그 전에 우리는 버튼을 명령에 묶어 둘 수있는 다른 클래스를 만들어야합니다.

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

그리고 마지막으로, 우리는 우리가 MainWindow를의의 DataContext 속성에 우리의 MainWindowViewModel 클래스를로드 할 수 있도록 응용 프로그램 시작을 오버라이드 (override) 할 필요가있다.

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 응용 프로그램 태그에 StartupUri="MainWindow.xaml" 코드를 제거하는 것이 좋습니다 것입니다.

새 프로젝트에 복사하여 사용할 수있는 DelegateCommand 및 ViewModelBase 클래스에 유의하십시오. 이것은 아주 간단한 예일뿐입니다. 당신은 당신의 코멘트에 herehere

편집 에서 더 나은 아이디어를 얻을 수 있습니다, 당신은 각 뷰 및 관련 상용구 코드를위한 클래스가 필요하지 할 수 있는지 알고 싶었다. 내가 아는 한, 대답은 '아니오'입니다. 예, 하나의 거대한 클래스를 가질 수 있지만 각 Property setter에 대해 OnPropertyChanged를 호출해야합니다. 이것에는 몇 가지 단점이 있습니다. 첫째, 결과 클래스는 유지하기가 매우 어려울 것입니다. 많은 코드와 의존성이있을 것입니다. 둘째, 뷰를 "스왑"하기 위해 DataTemplates를 사용하는 것은 어려울 것입니다. DataTemplates에서 x : Key를 사용하고 사용자 정의 컨트롤에서 템플릿 바인딩을 하드 코딩하여 여전히 가능합니다. 본질적으로 코드를 훨씬 짧게 만들지는 못하지만 코드를 더 어렵게 만들 것입니다.

여러분의 주요 불만이 여러분의 모델에 많은 코드를 작성해야만 모델 속성을 감쌀 것 같아요.T4 templates을보십시오. 일부 개발자는 이것을 사용하여 상용구 코드 (예 : ViewModel 클래스)를 자동 생성합니다. 이 코드는 개인적으로 사용하지 않으며, 사용자 정의 코드 스 니펫을 사용하여 viewmodel 속성을 빠르게 생성합니다.

또 다른 옵션은 Prism 또는 MVVMLight와 같은 MVVM 프레임 워크를 사용하는 것입니다. 나는 하나도 사용하지 않았지만, 그들 중 일부는 상용구 코드를 쉽게 만들 수있는 기능을 내장하고 있다고 들었습니다. 데이터베이스에 설정을 저장하는 경우 엔티티 프레임 워크와 같은 ORM 프레임 워크를 사용하여 데이터베이스에서 모델을 생성 할 수 있습니다. 뷰 모델 및 뷰.

+2

걱정하지 마십시오. 과도한 코드에 대한 질문은 위의 편집을 참조하십시오. – failedprogramming

+0

고마워, 내가 지금까지 모든 일을 가지고있어 :) – Xaser

+1

@ Xaser 내가 StackOverflow에 질문을 게시 선호합니다. 이 방법으로 더 많은 도움을 얻을 수 있습니다. 새로운 질문에 대한 링크를 보내 주시면 도와 드리겠습니다. 감사합니다 – failedprogramming