다른 ViewModel의 메소드 호출 방법이 있을까요?

다른 ViewModel에 있는 커멘드를 실행 시키는 방법이 있는지 궁금합니다.
ChatGPT는 UserControl에서 이벤트가 발생할 때 CommandParameter로 MainViewModel를 바인딩 시키면 된다고 하는데, 확인해 보니 바인딩이 안되는거 같습니다.(null로 전달 됨)
혹시 방법이 있을까요?

Window에 있는 DataContext인 MainViewModel은 PageChange 커멘드를 가지고 있으며, PageChange 는 CommandParameter로 전달받은 이름과 동일한 UserControl를 CurrentPage에 할당합니다. CurrentPage가 변경되면, UserControl 를 동적으로 변경 시킵니다.

//MainWindow.xaml 중 일부
<Window x:Name="window" x:Class="UI.MainWindow"
        xmlns:local="clr-namespace:UI">
    <Window.DataContext>
        <local:MainViewModel/>
    </Window.DataContext>
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="90"/>
            <ColumnDefinition Width="*"/>
        </Grid.ColumnDefinitions>
    </Grid>
    <Grid Grid.Column="0" >
        <Button Command="{Binding PageChange}" CommandParameter="변경할UC명"/>
    </Grid>
    <Grid Grid.Column="1" >
        <UserControl x:Name="pageView" Content="{Binding CurrentPage}"  />
    </Grid>
</Window>
//MainViewModel.cs 중 일부
private UserControl _currentPage = new();
public UserControl CurrentPage
{
    get => _currentPage;
    private set { _currentPage = value; OnPropertyChanged(); }
}

private ICommand? _PageChange = null;
public ICommand PageChange
{
    get
    {
        _PageChange ??= new Command.RelayCommand(PageChangAction);
        return _PageChange;
    }
}
private void PageChangAction(object obj)
{
    //obj와 일치하는 이름을 가진 UserControl를 CurrentPage에 할당
}

CurrentPage에 표시되는 UserControl 중 하나는 다음과 같습니다.

<UserControl x:Class="UI.SubPage.SubUC"
             xmlns:local="clr-namespace:DeviceMangerUI.Engineering"
             xmlns:root="clr-namespace:UI" >
    <UserControl.DataContext>
        <local:SubViewModel/>
    </UserControl.DataContext>
    <Grid>
        // 버튼을 클릭하면, 내용을 처리 후 MainViewModel의 PageChange를 실행시키고 싶어요.
        // ChatGPT가 CommandParameter에 바인딩 하면 된다는데,,, CommandParameter가 Null로 전달되요
        <Button Command="{Binding CallMainCommand}"
                CommandParameter="{Binding DataContext, RelativeSource={RelativeSource AncestorType=Window}}" /> 
    </Grid>
</UserControl>
// SubViewModel.cs 중 일부
private ICommand? _callMainCommand = null;
public ICommand CallMainCommand
{
    get
    {
        _callMainCommand ??= new Command.RelayCommand(ExecuteCallMainCommand, (object obj) => true);
        return _callMainCommand ;
    }
}
private void ExecuteCallMainCommand(object parameter)
{
    // SubViewModel에서 필요한 작업을 수행
            
    // 이제 MainViewModel의 PageChange 커맨드를 실행하고 싶은데,
    // parameter가 null로 전달 되요 ㅠ
    if (parameter is MainViewModel mainViewModel)
    {
        mainViewModel.PageChange.Execute("변경할UC명");
    }
}

CurrentPage에 표시되는 UserControl 의 CommandParameter를 아래와 같이 변경해서 동작을 확인 했습니다 …
두개 차이가 뭔지 설명 해주실 분 계신가요?..

// 변경 전
CommandParameter="{Binding DataContext, RelativeSource={RelativeSource AncestorType=Window}}" /> 
// 변경 후
CommandParameter="{Binding DataContext, RelativeSource={RelativeSource AncestorType={x:Type root:MainWindow}}}"
  • 변경 전: AncestorType=Window을 사용하여 상위 컨텍스트에서 DataContext를 찾았으나, DataContext가 예상대로 MainViewModel을 가리키지 못함.
  • 변경 후: AncestorType={x:Type root:MainWindow}을 사용하여 MainWindow 타입의 상위 요소에서 DataContext를 정확하게 참조함. 이를 통해 CommandParameter가 항상 MainViewModel의 인스턴스를 올바르게 받을 수 있게 되었음.

따라서, 두 번째 방법(AncestorType={x:Type root:MainWindow} 사용)이 첫 번째 방법보다 더 효과적이며, CommandParameter를 올바르게 설정하여 MainViewModelPageChange 커맨드를 실행할 수 있게 합니다.

라고 합니다.
from: https://www.phind.com/search?cache=yi6yzltiy4yqt0j3qlwo5qwy

FindAncestor은 특정한 형식이나 상속받은 하위 형식을 선택하고, {x:Type ~} 마크업 확장은 생략 가능하므로 이론상 두 구문은 root:MainWindow에 대해서 동일하게 동작합니다. 실제로 올려주신 첫 번째 포스트의 코드를 구동해보면 AncestorType=Window로도 DataContext를 잘 가져옵니다.

올려 주신 코드 이전에 뭔가 잘못된 부분이 있지 않았나 추측해봅니다.

(혹시 RelayCommand를 직접 구현하셨다면 이 부분이 의심됩니다.)

1 Like