2017-10-06 2 views
0

중첩 된 뷰를 사용하는 이미 작동중인 응용 프로그램에서 중첩 된 ViewModels을 구성하려고합니다.MVVM 중첩 된 하위 뷰를 하위 뷰 모델로 연결

MainWindow를보기 :

<Window x:Name="FCTWindow" x:Class="CatalogInterface.MainWindow" 
     xmlns:local="clr-namespace:CatalogInterface" 
     xmlns:vm="clr-namespace:CatalogInterface.ViewModels" 
     mc:Ignorable="d" 
     Title="MainWindow" Height="350" Width="532"> 

    <Window.Resources> 
     <vm:MainWindowViewModel x:Key="ViewModel" /> 
    </Window.Resources> 

    <Grid DataContext="{Binding Path=ViewModel.DirFilesListBoxViewModel}" x:Name="BodyGridLeft" Grid.Row="0" Grid.Column="0"> 
     <local:ctlDirFilesListBox> 
      <!-- 
       Need to access the `ItemsSource="{Binding }"` and 
       `SelectedItem="{Binding Path=}"` of the ListBox in 
       `ctlDirFilesListBox` view --> 
     </local:ctlDirFilesListBox> 
</Window> 

아이보기 : 여기에 내가 뭘 원하는지의 예

<UserControl x:Class="CatalogInterface.ctlDirFilesListBox" 
     xmlns:local="clr-namespace:CatalogInterface" 
     xmlns:vm="clr-namespace:CatalogInterface.ViewModels" 
     mc:Ignorable="d" 
     d:DesignHeight="300" d:DesignWidth="300"> 

<Grid x:Name="MainControlGrid">   
    <ListBox SelectionChanged="ListBoxItem_SelectionChanged" 
         HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Background="#FFFFFF" 
         Grid.Row="2" Grid.Column="1" Grid.ColumnSpan="3" BorderThickness="0"> 
     <ListBox.ItemContainerStyle> 
      <Style TargetType="{x:Type ListBoxItem}" BasedOn="{StaticResource {x:Type ListBoxItem}}"> 
       <EventSetter Event="MouseDoubleClick" Handler="ListBoxItem_MouseDoubleClick"/> 
       <EventSetter Event="KeyDown" Handler="ListBoxItem_KeyDown"/> 
      </Style> 
     </ListBox.ItemContainerStyle> 
    </ListBox> 
</Grid> 
</UserControl> 

MainWindowViewModel

using System; 
using System.Text; 

namespace CatalogInterface.ViewModels 
{ 
    class MainWindowViewModel 
    { 
     public DirFilesViewModel DirFilesViewModel { get; set; } 

     public MainWindowViewModel() 
     { 
      DirFilesViewModel = new DirFilesViewModel(); 
     } 
    } 
} 

그래서, 나는이 ListBox.SelectedItem를 연결해야하고 ListBox.ItemSourceMainWindowViewModel.DirFilesViewModel의 속성으로 바인딩합니다. 캐치는 MainWindow View이 아닌 ctlDirListBox 뷰에서 바인딩을 수행해야합니다.

내 하위보기의 요소에 어떻게 액세스합니까? 내 가장 큰 장벽이라고 생각합니다. 모든 데이터 컨텍스트가 옳다고 생각합니다. 하위 뷰 요소에 대해서는 논쟁 할 수 없습니다.

+0

UserControls는 모델 또는 뷰 모델에 맞게 설계되어야합니다. UserControl에 대한 뷰 모델을 디자인하면 안됩니다. TextBox에 TextBoxViewModel이 있습니까? ** 아니, ** 그리고 아주 좋은 이유가 있습니다. 이 안티 패턴의 실생활 예제와 왜 그렇게 열심히 실패하는지 [이 답변] (https://stackoverflow.com/a/44729258/1228)을 읽어보십시오. – Will

답변

3

DirFilesViewModel은 해당 usercontrol의보기 모델입니다. 그것이 사실이 아니라면, 실제 상황이 무엇인지 알려주고 우리는 그것을 분류 할 것입니다.

이것은 매우 간단한 경우입니다. @ JamieMarshall 귀하가 제공 한 XAML이 UserControl에있는 것이면 어쩌면 usercontrol이 아닐 수도 있습니다. 해당 XAML이 포함 된 DataTemplate을 작성하여 사용하거나 ListBox에 대한 스타일을 작성할 수 있습니다. 이벤트가 필요한 경우 UserControl은 이해가되지만 실제로 이벤트가 필요하지는 않습니다.

그러나 UserControls가 사용되는 방식을 이해하는 데는 최소한의 예가 될 수 있으며 그 목적에 적합합니다.

당신은 윈도우의 생성자에서 메인 윈도우의 DataContext에에 메인 뷰 모델의 인스턴스를 할당 할 수

,

public MainWindow() 
{ 
    InitializeComponent(); 

    DataContext = new MainWindowViewModel(); 
} 

또는

<Window.DataContext> 
    <vm:MainWindowViewModel /> 
<Window.DataContext> 

으로 XAML에서

어느 쪽도 특히 바람직 단지 '돈 없다 UserControl에서 둘 중 하나를 수행하십시오. 메인 윈도우는 뷰 (윈도우가 적절하게 고려 된 뷰)가 자신의 뷰 모델을 생성해야하는 유일한 시간입니다.

리소스를 만드는 것은 아무 것도 추가하지 않습니다. Grid.DataContext에 대한 바인딩은 좋지 않은 아이디어입니다. 아무에게나 다른 사람의 DataContext를 바인딩하는 일은 거의 없습니다. 이것은 다른 문제에 대해 얘기했다 무슨 관련이있다 -하지만 좋은 생각을하더라도, 이것은 같을 것이다 바인딩 것입니다 :

<Grid 
    DataContext="{Binding Source={StaticResource ViewModel}}" 
    > 

하지만 그렇게하지 않습니다!

사용자 정의 컨트롤을 올바른 데이터와 함께 표시하기 위해 할 수있는 한 가지 방법은 이와 같은 부모에 표시 될 뷰 모델에 대한 "암시 적 데이터 형식"을 만드는 것입니다. 예를 들어

:

MainWindow를 App.xaml에이어서

<!-- No x:Key, just DataType: It'll be implicitly used for that type. --> 
<DataTemplate DataType="{x:Type vm:DirFilesViewModel> 
    <local:ctlDirFilesListBox /> 
</DataTemplate> 

.xaml :

<UserControl 
    Grid.Row="0" 
    Grid.Column="0" 
    Content="{Binding DirFilesViewModel}" 
    /> 

XAML은 DirFilesViewModel 속성의 창에 대한 DataContext로 이동합니다. 클래스의 인스턴스이고 또한 DirFilesViewModel이라는 객체가있는 것으로 확인됩니다. 그것은 그 클래스를위한 DataTemplate을 가지고 있다는 것을 알고 있으므로, 그 datatemplate을 사용합니다.

이것은 놀랍도록 강력합니다. ObservableCollection<ViewModelBase>에 서로 다른보기를 가진 10 가지 종류의 뷰 모델 30 개가 있다고 가정하고 사용자가 하나 또는 다른 것을 선택한다고 가정 해보십시오. 선택한보기 모델은 SelectedChildVM이라는 mainviewmodel 속성에 있습니다. 다음은 올바른보기로 SelectedChildVM을 표시하는 XAML입니다.

<ContentControl Content="{Binding SelectedChildVM}" /> 

그게 전부입니다.

를 따라 이동하지 :

 <!-- 
      Need to access the `ItemsSource="{Binding }"` and 
      `SelectedItem="{Binding Path=}"` of the ListBox in 
      `ctlDirFilesListBox` view --> 

을 아니 당신이 할 수 없습니다! 그것이 당신이하고 싶은 마지막 일입니다! 일부 UserControls 뷰 모델 대신 자신의 속성이 있습니다. 그것들을 사용하여 어떤 컨트롤과 마찬가지로 부모의 속성을 바인딩합니다.

이것은 UserControls의 다른 사용 사례입니다. 뷰 모델을 DataContext로 상속하여 "매개 변수화 된"것입니다. 당신이 제공하는 정보는 뷰 모델입니다.

UserControl의 컨트롤은 UserControl의 viewmodel 속성에서 가져 오는 고유 한 바인딩을 가져야합니다.

(나는 그 DirFilesViewModel이 무엇을 추측하고있어)는 Files 특성 ( ObservableCollection<SomeFileClass>)와 SelectedFile 클래스 ( SomeFileClass)가 이제 해당 UserControl의 뷰 모델을 가정 해 봅시다. 아마도 ListBoxItem_SelectionChanged이 필요하지 않을 것입니다.

<UserControl x:Class="CatalogInterface.ctlDirFilesListBox" 
     xmlns:local="clr-namespace:CatalogInterface" 
     xmlns:vm="clr-namespace:CatalogInterface.ViewModels" 
     mc:Ignorable="d" 
     d:DesignHeight="300" d:DesignWidth="300"> 
<Grid x:Name="MainControlGrid">   
    <ListBox 
     ItemsSource="{Binding Files}" 
     SelectedItem="{Binding SelectedFile}" 
     SelectionChanged="ListBoxItem_SelectionChanged" 
     HorizontalAlignment="Stretch" 
     VerticalAlignment="Stretch" 
     Background="#FFFFFF" 
     Grid.Row="2" 
     Grid.Column="1" 
     Grid.ColumnSpan="3" 
     BorderThickness="0" 
     > 
     <ListBox.ItemContainerStyle> 
      <Style TargetType="{x:Type ListBoxItem}" BasedOn="{StaticResource {x:Type ListBoxItem}}"> 
       <EventSetter Event="MouseDoubleClick" Handler="ListBoxItem_MouseDoubleClick"/> 
       <EventSetter Event="KeyDown" Handler="ListBoxItem_KeyDown"/> 
      </Style> 
     </ListBox.ItemContainerStyle> 
    </ListBox> 
</Grid> 
</UserControl> 
+0

감사합니다. @ Plunkett, 이것은 매우 포괄적 인 대답입니다. 하나의 질문 - DataTemplate 트릭은 멋지지만 기본으로 가고 싶다면 어떨까요? 바인딩 컨텍스트없이 자식 뷰 모델을 명시 적으로 참조하려면 어떻게해야합니까? 이것은 내 자신의 덕목을위한 것입니다. 나는 아마 당신의 속임수를 사용하여 끝낼 것입니다, 당신이 Pinto에가 본 적이 없다면 페라리를 이해하는 것이 어렵습니다.:) –

+0

@JamieMarshall 나는 그것을 "속임수"라고 정확하게 부르지 않을 것입니다. 그것은 표류하는 대신 스티어링 휠을 사용하여 커브를 돌기위한 "트릭"이라고 말하는 것과 같습니다. DataTemplates는 반드시 암묵적 일 필요는 없습니다 : 여러분은 그것들에'x : Key' 속성을 부여하고 ListBox 나 ItemsControl의'ItemTemplate'으로 사용할 수 있습니다. 또는 어느 것이 사용할 contentcontrol인지 명시 적으로 알려줄 수 있습니다 :''. –

+0

ViewModel에 대한 암시 적 데이터 템플릿이 있다면 (예 : 속성을 편집 할 때), ListBox에 viewmodel의 목록이있는 경우와 같이 하나 이상의 명시적인 데이터 형식을 가질 수도 있습니다. 'Name' 속성을 표시하십시오. 또는 읽기 전용보기, 소형보기 등이있을 수 있습니다. WPF는 winforms/MFC/등의 UI에 대해 다른 생각을합니다. WPF처럼 생각하는 법을 배워야합니다. –

0

어떻게 내 아이 뷰 내부 요소에 액세스합니까?

당신은 부모 창에 이들에 ctlDirFilesListBox 컨트롤의 코드 숨김 클래스 (이름 예를 ItemsSourceSelectedItem을 위해) 두 종속성 속성을 추가하고 결합 할 수 :

<local:ctlDirFilesListBox ItemsSource="{Binding Property}" SelectedItem="{Binding Property}" /> 

당신은 바인딩도해야

<ListBox SelectionChanged="ListBoxItem_SelectionChanged" 
       HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Background="#FFFFFF" 
       Grid.Row="2" Grid.Column="1" Grid.ColumnSpan="3" BorderThickness="0" 
       ItemsSource="{Binding ItemsSource, RelativeSource={RelativeSource AncestorType=UserControl}}" 
       SelectedItem="{Binding SelectedItem, RelativeSource={RelativeSource AncestorType=UserControl}}"> 
    <ListBox.ItemContainerStyle> 
     <Style TargetType="{x:Type ListBoxItem}" BasedOn="{StaticResource {x:Type ListBoxItem}}"> 
      <EventSetter Event="MouseDoubleClick" Handler="ListBoxItem_MouseDoubleClick"/> 
      <EventSetter Event="KeyDown" Handler="ListBoxItem_KeyDown"/> 
     </Style> 
    </ListBox.ItemContainerStyle> 
</ListBox> 

다음 UserControl에서 이러한 속성에