WPF, Caliburn.Micro으로 viewmodel을 다른 view와 연결

안녕하세요.

현재 WPF와 Caliburn.Micro로 MVVM으로 개발중입니다.

view가 없는 viewmodel을 다른 view와 연결하여 사용해야될 떄가 생겨서 문의드립니다.

개발하다보니 NonView_ViewModel을 만들었습니다.

ViewedItem1ViewModel, ViewedItem1View가 현재 동작하고 있습니다.

그 외에도 ViewedItem2ViewModel, ViewedItem2View …이렇게 동작중입니다.

그런데 NonView_ViewModel을 예를 들어 ViewedItem2View와 조합하여 생성할수 있는 방법이 있나요?

정확히 말하자면, ViewedItem2View의 datacontext로 NonView_ViewModel으 바인딩한 다음

와 같은 방법으로 동작시키면, 원하는 view와 원하는 viewModel이 연결되어 작동할수 있을까요?

감사합니다.

좋은하루되세요~!

좋아요 1

@WPF 동작하는 샘플 소스코드를 공유해서 설명해주시면 좋을 것 같습니다! :smile:

좋아요 2

질문에서 이루고자 하는 명확한 목표가 무엇인지 정확히 판단이 되지 않아서 …

일단은 ViewedItem2View <—> ViewedItem2ViewModel 매칭으로 View와 ViewModel이 구성되어 있는데

ViewedItem2View <----> X ViewModel 과 같이 다른 ViewModel로 설정할 수 있는지에 대한 것으로 이해 했습니다. (N : 1 매칭)

그런데 이해가 안되는것이 그냥 특정 뷰에 DataContext를 지정해주면 되면 되지 않나요?
혹시 이렇게 했을때 구조적으로 문제가 되는 건지 …

그래도 답변을 드려보면 App Resources에 뷰와 뷰모델 매칭을 미리 정의해 놓고
ContentControl에 정의된 DataType의 뷰모델을 바인딩으로 처리하면 해당 뷰의 DataContext로 지정할 수 있습니다.

가령 이런식 입니다.

<Application x:Class="WpfApp.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:views="clr-namespace:WpfApp.Views"
             xmlns:viewModels="clr-namespace:WpfApp.ViewModels">
    <Application.Resources>
      <DataTemplate DataType="{x:Type viewModels:뷰1ViewModel}">
		    <views:뷰1유저컨트롤/>
	    </DataTemplate>
      
      <DataTemplate DataType="{x:Type viewModels:뷰2ViewModel}">
		    <views:뷰2유저컨트롤/>
	    </DataTemplate>
      
      
      ... [View DataTemplate 3] ...
    </Application.Resources>
</Application>
<Grid>
    <ContentControl Content="{Binding .}" />  <- 위에서 정의된 DataType을 바인딩
</Grid>

혹시 제가 잘못 이해 했다면 고민되는 최소 샘플 코드를 공유해 주시면 다시 한번 같이 고민해 보겠습니다.

그럼 좋은 하루 보내세요!

좋아요 3

소스정리해서 다시 올려볼께요~ 감사합니다.

좋아요 1

제가 다시 정리해서 올려보겠습니다.~
감사합니다.

좋아요 1

목적

NonViewModel을 서로 다른 View에 보일 수 있도록 합니다.

1. NonViewModel - OneView에 연결

PersonModel.cs

namespace Caliburn.Micro.Basic.Project.Models
{
    public class PersonModel
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public string Address { get; set; }
        public string Phone { get; set; }
    }
}

NonView_ViewModel.cs

namespace Caliburn.Micro.Basic.Project.ViewModels
{
    public class NonView_ViewModel
        : Screen
    {
        private PersonModel _model;

        public NonView_ViewModel()
        {
            _model = new PersonModel();
            _model.Id = 1;
            _model.Name = "Caliburn.Micro";
            _model.Address = "...";
            _model.Phone = "111";
        }

        public int Id
        {
            get { return _model.Id; }
            set 
            { 
                _model.Id = value;
                NotifyOfPropertyChange(() => Id);
            }
        }

        public string Name
        {
            get { return _model.Name; }
            set 
            { 
                _model.Name = value;
                NotifyOfPropertyChange(() => Name);
            }
        }

        public string Address
        {
            get { return _model.Address; }
            set
            {
                _model.Address = value;
                NotifyOfPropertyChange(() => Address);
            }
        }

        public string Phone
        {
            get { return _model.Phone; }
            set
            {
                _model.Phone = value;
                NotifyOfPropertyChange(() => Phone);
            }
        }
    }
}

OneView.xaml

<UserControl x:Class="Caliburn.Micro.Basic.Project.Views.OneView"
             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" 
             xmlns:local="clr-namespace:Caliburn.Micro.Basic.Project.Views"
             Background="SlateBlue"
             mc:Ignorable="d"
             d:DesignHeight="450"
             d:DesignWidth="800">
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition />
            <ColumnDefinition />
            <ColumnDefinition />
            <ColumnDefinition />
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition />
            <RowDefinition />
            <RowDefinition />
            <RowDefinition />
            <RowDefinition />
        </Grid.RowDefinitions>
        <TextBlock Grid.Row="0"
                   Grid.Column="1"
                   FontSize="20"
                   VerticalAlignment="Center"
                   TextAlignment="Center"
                   Text="아이디" />
        <TextBlock Grid.Row="1"
                   Grid.Column="1"
                   FontSize="20"
                   VerticalAlignment="Center"
                   TextAlignment="Center"
                   Text="이름" />
        <TextBlock Grid.Row="2"
                   Grid.Column="1"
                   FontSize="20"
                   VerticalAlignment="Center"
                   TextAlignment="Center"
                   Text="주소" />
        <TextBlock Grid.Row="3"
                   Grid.Column="1"
                   FontSize="20"
                   VerticalAlignment="Center"
                   TextAlignment="Center"
                   Text="전번" />
        <TextBox Grid.Row="0"
                 Grid.Column="2"
                 FontSize="20"
                 VerticalAlignment="Center"
                 TextAlignment="Center"
                 Text="{Binding Id}" />
        <TextBox Grid.Row="1"
                 Grid.Column="2"
                 FontSize="20"
                 VerticalAlignment="Center"
                 TextAlignment="Center"
                 Text="{Binding Name}" />
        <TextBox Grid.Row="2"
                 Grid.Column="2"
                 FontSize="20"
                 VerticalAlignment="Center"
                 TextAlignment="Center"
                 Text="{Binding Address}" />
        <TextBox Grid.Row="3"
                 Grid.Column="2"
                 FontSize="20"
                 VerticalAlignment="Center"
                 TextAlignment="Center"
                 Text="{Binding Phone}" />
    </Grid>
</UserControl>

##위 내용이 시현 윈도우

ShellViewModel.cs

namespace Caliburn.Micro.Basic.Project.ViewModels
{
    /// <summary>
    /// The shell view model.
    /// </summary>
    public class ShellViewModel : Conductor<object>
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="ShellViewModel"/> class.
        /// </summary>
        public ShellViewModel()
        {
            ContentControl=IoC.Get<NonView_ViewModel>();
            //??어떻게 할당해야 View를 선택적으로 할당가능한지...
        }

        private NonView_ViewModel _contentControl;

        public NonView_ViewModel ContentControl
        {
            get { return _contentControl; }
            set 
            {
                _contentControl = value;
                NotifyOfPropertyChange(() => ContentControl);
            }
        }
    }
}

ShellView.xaml

<Window x:Class="Caliburn.Micro.Basic.Project.Views.ShellView"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:Caliburn.Micro.Basic.Project.Views"
        mc:Ignorable="d"
        Title="ShellView" Height="450" Width="800">
    <Grid>
        <ContentControl x:Name="ContentControl" />
    </Grid>
</Window>
  1. NonView_ViewModel을 AnotherView에 연결

AnotherView.xaml

<UserControl x:Class="Caliburn.Micro.Basic.Project.Views.AnotherView"
             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" 
             xmlns:local="clr-namespace:Caliburn.Micro.Basic.Project.Views"
             mc:Ignorable="d" 
             d:DesignHeight="450" d:DesignWidth="800">
    <Grid>
        <Grid>
            <Grid.ColumnDefinitions>
                <ColumnDefinition />
                <ColumnDefinition />
                <ColumnDefinition />
                <ColumnDefinition />
            </Grid.ColumnDefinitions>
            <Grid.RowDefinitions>
                <RowDefinition />
                <RowDefinition />
                <RowDefinition />
                <RowDefinition />
                <RowDefinition />
            </Grid.RowDefinitions>
            <TextBlock Grid.Row="2"
                       Grid.Column="1"
                       FontSize="20"
                       VerticalAlignment="Center"
                       TextAlignment="Center"
                       Text="아이디" />
            
            <TextBox Grid.Row="2"
                     Grid.Column="2"
                     FontSize="20"
                     VerticalAlignment="Center"
                     TextAlignment="Center"
                     Text="{Binding Id}" />
            

        </Grid>
    </Grid>
</UserControl>

##원하는 결과
1.OneView에 연결 시…

2.AnotherVIew에 연결 시…

참고로 해당관련 내용이 Prism으로 강의가 있더라고요.

참고영상

Caliburn.Micro로는 없네요.

좋아요 1

결국은 제가 위에서 답변 달았던 방식인 것 같습니다.

App Resources에 미리 데이터템플릿을 정의해 두고서

[ShellView.xaml]

<Grid>
        <ContentControl x:Name="ContentControl" Content="{Binding ContentControl}" />
</Grid>

[ShellViewModel.cs]

public ViewModelBase ContentControl
{
            get { return _contentControl; }
            set 
            {
                _contentControl = value;
                NotifyOfPropertyChange(() => ContentControl);
            }
}

위 ContentControl 에 공통으로 사용 가능한 ViewModelBase 타입으로 하고

ContentControl = new NonView_ViewModel (); 로 처리하면 되지 않을까요?
(뷰모델은 ViewModelBase 상속 받도록 구현하구요)

그리고선 ContentControl 에 DataContext 설정은 외부에서 IoC를 통해 설정하면 될 것 같습니다.

좋아요 2

예 감사합니다.

Caliburn.micro에서 동작하는지 잘 모르겠지만, 확인해보겠습니다~

좋은하루되세요

동일 타입의 ViewModel을 복수의 View에서 사용하고 싶으신 것으로 이해되는데 맞나요?

맞다면 단순히 첨부하신 코드를 기준으로 봤을 때 Constructor Injection으로든 Ioc Container로든 NonViewModel을 주입받아 원하는 View의 DataContext로 할당하거나 Service Locator 패턴 등을 이용해 xaml에서 할당해주면 해결되지 않을까요?

OneView에는 OneViewModel이 있을 테고 AnotherView에는 AnotherViewModel이 있을 테니 View의 최상위 DataContext에는 할당하면 안되고 데이터가 표현되는 Grid의 DataContext에 NonViewModel을 할당하시면 될 것 같습니다.

좋아요 1

아 그런 방법으로 접근할 수 있겠네요…
감사합니다.
:grinning:

좋아요 1

혹은 사용하는 Model이 동일하고 View로 노출하는 Property 수의 차이일 뿐이라면, 그리고 첨부하신 사진과 같이 View에서 Property를 표현하는 방식이 동일하다면 Control로 분리해서 사용하는 방법도 있습니다.

가령 첨부하신 사진처럼 PersonModel을 이용하는 복수의 View에 대해 어떤 View에서는 모든 Property를 노출하고 싶고 어떤 View에서는 아이디만 노출하고 싶은 것이라면, 그리고 양 View에서 Model을 표현하는 방식이 Textbox로 동일하다면 해당 Control을 UserControlA로 분리한 다음 UserControlAViewModel로부터 사용하는 Property의 종류를 enum으로든 bool으로든 받아서 Visibility를 관리하던지, 각 Visibility를 속성으로 노출하고 OneView나 AnotherView에서 UserControlA를 포함하면서 Visibility를 설정해주는 게 좀 더 깔끔한 방법이지 않을까 싶어요.

전자로 간다면 DataTemplate으로 매핑해두고 ViewModel에서 해당 UsercontrolAViewModel을 들고 있다가 ContentControl과 바인딩된 Property에 넣어주든지, Visibility를 활성화하는 방식으로 가면 될 것 같네요.

감사합니다.

저건 사실 예시이고요.

지금 하고 있는 프로젝트에서 ViewModel을 속성 값으로 보는 View부분도 필요하고, Canvas위에 Symbol로 표현할때도 필요하고, 혹은 TreeView에 Node로 표현할 필요도 있어서요.

1개의 ViewModel을 한 화면에서 약 3가지 이상으로 다른 View입혀서 표현할 수 있을까 싶어서요.

그리고 해당 ViewModel만 바꾸면 나머지 view는 따라서 PropertyChange되도록 하는게 가장 이상적일 것으로 판단되는 상황입니다.

좋아요 1