ViewModel은 어떻게 양식을 닫아야합니까?


247

WPF와 MVVM 문제를 배우려고 노력하고 있지만 걸림돌을 겪었습니다. 이 질문은이 질문과 비슷하지만 완전히 같지는 않습니다 (wpf-with-mvvm 처리-대화) ...

MVVM 패턴을 사용하여 작성된 "로그인"양식이 있습니다.

이 양식에는 사용자 이름과 비밀번호를 보유한 ViewModel이 있으며, 이는 일반 데이터 바인딩을 사용하여 XAML의보기에 바인딩됩니다. 또한 일반 데이터 바인딩을 사용하여 폼에있는 "로그인"버튼에 바인딩 된 "로그인"명령이 있습니다.

"로그인"명령이 실행되면 ViewModel에서 기능을 호출하여 로그 오프하는 네트워크를 통해 데이터를 전송합니다.이 기능이 완료되면 2 가지 작업이 있습니다.

  1. 로그인이 잘못되었습니다-MessageBox 만 표시하면됩니다.

  2. 로그인이 유효합니다. 로그인 양식을 닫고 true로 리턴해야합니다 DialogResult.

문제는 ViewModel이 실제 뷰에 대해 아무것도 모르므로 뷰를 닫고 특정 DialogResult를 반환하도록 어떻게 말할 수 있습니까? CodeBehind에 일부 코드를 붙일 수 있고 View를 ViewModel에 전달할 수는 있지만 MVVM의 요점을 완전히 물리 칠 것 같습니다 ...


최신 정보

결국 나는 방금 MVVM 패턴의 "순도"를 위반하고 View가 Closed이벤트를 게시 하고 Close메소드를 공개하도록했습니다 . 그런 다음 ViewModel은을 호출 view.Close합니다. 보기는 인터페이스를 통해서만 알려져 있으며 IOC 컨테이너를 통해 연결되므로 테스트 가능성이나 유지 관리 성이 손실되지 않습니다.

허용 된 답변이 -5 표인 것보다는 어리석은 것 같습니다! "순수한"상태에서 문제를 해결함으로써 얻는 좋은 감정을 잘 알고 있지만 확실히 한 줄 방법을 피하기 위해 200 줄의 이벤트, 명령 및 행동을 생각하는 유일한 사람은 아닙니다. "패턴"과 "순도"의 이름은 조금 말도 안됩니다 ...


2
나는 받아 들인 대답을 downvote하지 않았지만 downvotes의 이유는 그것이 하나의 경우에 효과가 있더라도 일반적으로 도움이되지 않기 때문이라고 생각합니다. "로그인 양식이 '두 개의 필드'대화 상자 인 동안 훨씬 복잡하고 MVVM을 보증하지만 여전히 폐쇄해야하는 다른 필드가 많이 있습니다."
Joe 화이트

1
나는 당신의 요점을 알지만, 개인적인 경우에도 간단한 Close방법이 여전히 가장 좋은 해결책 이라고 생각합니다 . 다른 더 복잡한 대화 상자의 다른 모든 것은 MVVM과 데이터 바인딩이지만 단순한 방법 대신 여기에 거대한 "솔루션"을 구현하는 것은 어리석은 것처럼 보였습니다 ...
Orion Edwards

2
대화 상자 결과 asimsajjad.blogspot.com/2010/10/…에 대한 다음 링크를 확인할 수 있습니다. 그러면 대화 상자 resutl이 반환되고 viewModel에서보기가 닫힙니다.
Asim Sajjad

3
이 질문에 대한 답변을 변경하십시오. 이 기능에 MVVM 사용을 의심하는 사람보다 훨씬 더 좋은 솔루션이 많이 있습니다. 그것은 대답이 아니라 회피입니다.
ScottCher

2
@OrionEdwards 나는 당신이 여기서 패턴을 깨는 것이 옳았다 고 생각합니다. 디자인 패턴의 주요 목적은 전체 팀을 동일한 규칙에 따라 개발주기를 단축하고 유지 보수성을 높이며 코드를 단순화하는 것입니다. 이것은 외부 라이브러리에 대한 의존성을 추가하고 수백 개의 코드 라인을 구현하여 작업을 수행함으로써 달성되지는 않습니다. 단지 패턴의 "순도"를 희생하기 위해 고집을 부딪히기 때문에 훨씬 더 간단한 솔루션이 있다는 것을 완전히 무시합니다. 그냥 다 your've 무엇을 문서로 보장하고 KISS 코드 ( 케이 EEP를 t 읽힌와 imple).
M463

답변:


324

나는 Thejuan의 대답 에서 영감을 얻어 더 단순한 부착물 을 작성했습니다. 스타일도없고 트리거도 없습니다. 대신, 당신은 이것을 할 수 있습니다 :

<Window ...
        xmlns:xc="clr-namespace:ExCastle.Wpf"
        xc:DialogCloser.DialogResult="{Binding DialogResult}">

이것은 WPF 팀이 올바르게 이해하고 DialogResult를 종속성 속성으로 만든 것처럼 거의 깨끗합니다. bool? DialogResultViewModel에 속성을 넣고 INotifyPropertyChanged를 구현하면 ViewModel 은 속성을 설정하여 창을 닫고 DialogResult를 설정할 수 있습니다. 그대로 MVVM.

DialogCloser의 코드는 다음과 같습니다.

using System.Windows;

namespace ExCastle.Wpf
{
    public static class DialogCloser
    {
        public static readonly DependencyProperty DialogResultProperty =
            DependencyProperty.RegisterAttached(
                "DialogResult",
                typeof(bool?),
                typeof(DialogCloser),
                new PropertyMetadata(DialogResultChanged));

        private static void DialogResultChanged(
            DependencyObject d,
            DependencyPropertyChangedEventArgs e)
        {
            var window = d as Window;
            if (window != null)
                window.DialogResult = e.NewValue as bool?;
        }
        public static void SetDialogResult(Window target, bool? value)
        {
            target.SetValue(DialogResultProperty, value);
        }
    }
}

나는 또한 이것을 내 블로그에 게시했습니다 .


3
이것은 내가 가장 좋아하는 답변입니다! 첨부 된 재산을 잘 작성했습니다.
Jorge Vargas

2
좋은 옵션이지만이 솔루션에는 미묘한 버그가 있습니다. 대화 상자의 뷰 모델이 싱글 톤 인 경우 DialogResult 값은 다음 대화 상자 사용으로 이월됩니다. 즉, 대화 상자가 두 번째로 표시되지 않으므로 스스로 표시하기 전에 즉시 취소하거나 수락합니다.
코딩 사라지다

13
@HiTech Magic, 버그가 싱글 톤 ViewModel을 처음 사용하는 것처럼 들립니다. 진지하게 왜 지구상에서 싱글 톤 ViewModel을 원할까요? 전역 변수에서 변경 가능한 상태를 유지하는 것은 좋지 않습니다. 테스트는 악몽이되며 테스트는 MVVM을 처음 사용하는 이유 중 하나입니다.
Joe White

3
MVVM이 로직을 특정 UI에 밀접하게 연결하지 않는 것이 중요하지 않습니까? 이 경우 부울? WinForm과 같은 다른 UI에서는 사용할 수 없으며 DialogCloser는 WPF에만 해당됩니다. 그렇다면 이것이 솔루션뿐만 아니라 어떻게 적합합니까? 또한 바인딩을 통해 창을 닫기 위해 2x-10x 코드를 작성하는 이유는 무엇입니까?
David Anderson

2
@DavidAnderson, 나는 WinForms로 MVVM을 시도하지 않을 것입니다. 데이터 바인딩 지원이 너무 약하고 MVVM은 잘 고려 된 바인딩 시스템에 의존합니다. 그리고 2x-10x 코드 근처에는 없습니다. 모든 창에 대해 한 번 이 아니라 해당 코드를 한 번 작성하십시오 . 그 후에는 한 줄 바인딩과 알림 속성으로, 뷰의 다른 모든 것에 이미 사용하는 것과 동일한 메커니즘을 사용합니다 (예를 들어, 창문). 당신은 다른 트레이드 오프를 환영하지만, 일반적으로 나에게 좋은 거래처럼 보인다.
Joe White

64

내 관점에서 볼 때, 동일한 접근 방식이 "로그인"창뿐만 아니라 모든 종류의 창에 사용되기 때문에 질문은 꽤 좋습니다. 나는 많은 제안을 검토했지만 아무도 괜찮습니다. MVVM 디자인 패턴 기사 에서 가져온 제안을 검토 하십시오 .

각 ViewModel 클래스는 유형 WorkspaceViewModelRequestClose이벤트 및 CloseCommand속성 이있는 클래스를 상속해야 ICommand합니다. CloseCommand속성 의 기본 구현은 RequestClose이벤트를 발생시킵니다.

창을 닫으 OnLoaded려면 창의 방법을 재정의해야합니다.

void CustomerWindow_Loaded(object sender, RoutedEventArgs e)
{
    CustomerViewModel customer = CustomerViewModel.GetYourCustomer();
    DataContext = customer;
    customer.RequestClose += () => { Close(); };
}

또는 OnStartup당신의 앱 방법 :

    protected override void OnStartup(StartupEventArgs e)
    {
        base.OnStartup(e);

        MainWindow window = new MainWindow();
        var viewModel = new MainWindowViewModel();
        viewModel.RequestClose += window.Close;
        window.DataContext = viewModel;

        window.Show();
    }

나는 RequestClose이벤트 및 CloseCommand속성 구현 WorkspaceViewModel이 매우 분명하다고 생각하지만 일관성이 있음을 보여줄 것입니다.

public abstract class WorkspaceViewModel : ViewModelBase
// There's nothing interesting in ViewModelBase as it only implements the INotifyPropertyChanged interface
{
    RelayCommand _closeCommand;
    public ICommand CloseCommand
    {
        get
        {
            if (_closeCommand == null)
            {
                _closeCommand = new RelayCommand(
                   param => Close(),
                   param => CanClose()
                   );
            }
            return _closeCommand;
        }
    }

    public event Action RequestClose;

    public virtual void Close()
    {
        if ( RequestClose != null )
        {
            RequestClose();
        }
    }

    public virtual bool CanClose()
    {
        return true;
    }
}

그리고 소스 코드 RelayCommand:

public class RelayCommand : ICommand
{
    #region Constructors

    public RelayCommand(Action<object> execute, Predicate<object> canExecute)
    {
        if (execute == null)
            throw new ArgumentNullException("execute");

        _execute = execute;
        _canExecute = canExecute;
    }
    #endregion // Constructors

    #region ICommand Members

    [DebuggerStepThrough]
    public bool CanExecute(object parameter)
    {
        return _canExecute == null ? true : _canExecute(parameter);
    }

    public event EventHandler CanExecuteChanged
    {
        add { CommandManager.RequerySuggested += value; }
        remove { CommandManager.RequerySuggested -= value; }
    }

    public void Execute(object parameter)
    {
        _execute(parameter);
    }

    #endregion // ICommand Members

    #region Fields

    readonly Action<object> _execute;
    readonly Predicate<object> _canExecute;

    #endregion // Fields
}

추신 그 소스에 대해 나에게 나쁜 취급하지 마십시오! 어제 그들을 가지고 있다면 몇 시간을 절약 할 수있을 것입니다 ...

PPS 의견이나 제안을 환영합니다.


2
음, customer.RequestCloseXAML 파일 뒤에있는 코드에서 이벤트 핸들러에 연결 한 사실이 MVVM 패턴을 위반하지 않습니까? Click어쨌든 코드 뒤에 손을 대고 this.Close()! 를 수행 한 것을 볼 때 닫기 버튼 의 이벤트 핸들러에 바인딩 할 수도 있습니다 . 권리?
GONeale

1
이벤트 접근 방식에 너무 많은 문제가 없지만 RequestClose라는 단어는 마음에 들지 않습니다. 왜냐하면 그것은 여전히 ​​View 구현에 대한 많은 지식을 암시하기 때문입니다. 컨텍스트가 더 의미가 있고 응답에서 뷰가 수행 해야하는 작업에 대해 덜 암시하는 IsCancelled와 같은 속성을 노출하는 것을 선호합니다.
jpierson

18

첨부 된 동작을 사용하여 창을 닫았습니다. ViewModel의 "signal"속성을 연결된 비헤이비어에 바인딩 (실제로 트리거를 사용합니다) true로 설정하면 비헤이비어가 창을 닫습니다.

http://adammills.wordpress.com/2009/07/01/window-close-from-xaml/


이것은 지금까지 유일한 해결책이며 Window에서 코드 숨김이 필요하지 않습니다 (또 다른 접근법을 제안하는 대신 모달 Window를 실제로 닫습니다). 동정심은 스타일과 방아쇠와 그 모든 멍청이로 인해 너무 많은 복잡성이 필요합니다. 한 줄로 연결된 행동으로 실제로 가능 해야하는 것처럼 보입니다.
Joe White

4
이제 한 줄로 연결된 동작으로 가능합니다. 내 답변보기 : stackoverflow.com/questions/501886/…
Joe White

15

여기에 MVVM의 장단점을 주장하는 의견이 많이 있습니다. 저에게는 Nir에 동의합니다. 패턴을 적절히 사용하는 문제이며 MVVM이 항상 적합하지는 않습니다. 사람들은 MVVM에 맞추기 위해 소프트웨어 디자인의 가장 중요한 원칙을 모두 기꺼이 희생하려고 한 것 같습니다.

즉, 귀하의 사례는 약간의 리팩토링에 적합하다고 생각합니다.

내가 접한 대부분의 경우 WPF를 사용하면 여러없이 사용할 수 있습니다 Window. 아마도 s를 사용하는 Windows 대신 Frames와 Pages를 사용해 볼 수 DialogResult있습니다.

귀하의 경우 내 제안은 LoginFormViewModel처리 할 수 있으며 LoginCommand로그인이 유효하지 않은 경우 속성을 LoginFormViewModel적절한 값 ( false또는 같은 열거 형 값 UserAuthenticationStates.FailedAuthentication)으로 설정하십시오. 성공적인 로그인 ( true또는 다른 열거 형 값)에 대해서도 동일하게 수행합니다 . 그런 다음 DataTrigger다양한 사용자 인증 상태에 응답하는를 사용하고 SetterSource속성 을 변경 하는 간단한 방법 을 사용할 수 있습니다 Frame.

로그인 창을 반환하면 DialogResult혼란 스러울 수 있습니다. 그것은 DialogResult실제로 ViewModel의 속성입니다. 내 생각에 WPF에 대한 경험이 제한적이지만, WinForms에서 어떻게했는지에 대해 생각하고 있기 때문에 일반적으로 옳지 않은 느낌이들 때.

희망이 도움이됩니다.


10

로그인 대화 상자가 생성되는 첫 번째 창이라고 가정하면 LoginViewModel 클래스에서 다음을 시도하십시오.

    void OnLoginResponse(bool loginSucceded)
    {
        if (loginSucceded)
        {
            Window1 window = new Window1() { DataContext = new MainWindowViewModel() };
            window.Show();

            App.Current.MainWindow.Close();
            App.Current.MainWindow = window;
        }
        else
        {
            LoginError = true;
        }
    }

남자 이것은 간단하고 훌륭하게 작동합니다. 현재이 접근법을 사용하고 있습니다.
Erre Efe

MAIN 창에서만 작동합니다. 따라서 다른 창에서는 사용하지 마십시오.
Oleksii

7

이것은 간단하고 깨끗한 솔루션입니다. ViewModel에 이벤트를 추가하고 해당 이벤트가 시작될 때 창을 닫도록 지시합니다.

자세한 내용은 내 블로그 게시물, ViewModel에서 창 닫기를 참조하십시오 .

XAML :

<Window
  x:Name="this"
  xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"  
  xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions">
  <i:Interaction.Triggers>
    <i:EventTrigger SourceObject="{Binding}" EventName="Closed">
      <ei:CallMethodAction
        TargetObject="{Binding ElementName=this}"
        MethodName="Close"/>
    </i:EventTrigger>
  </i:Interaction.Triggers>
<Window>

뷰 모델 :

private ICommand _SaveAndCloseCommand;
public ICommand SaveAndCloseCommand
{
  get
  {
    return _SaveAndCloseCommand ??
      (_SaveAndCloseCommand = new DelegateCommand(SaveAndClose));
  }
}
private void SaveAndClose()
{
  Save();
  Close();
}

public event EventHandler Closed;
private void Close()
{
  if (Closed != null) Closed(this, EventArgs.Empty);
}

참고 :이 예에서는 Prism을 사용 하지만 DelegateCommand( Prism : Commanding 참조 ) 해당 ICommand구현에 모든 구현을 사용할 수 있습니다.

공식 패키지 에서 동작을 사용할 수 있습니다 .


2
+1이지만 답변 자체에 자세한 내용을 제공해야합니다. 예를 들어이 솔루션에는 Expression Blend Interactivity 어셈블리에 대한 참조가 필요합니다.
surfen

6

내가 처리하는 방법은 내 ViewModel에 이벤트 핸들러를 추가하는 것입니다. 사용자가 성공적으로 로그인하면 이벤트가 발생합니다. 내보기 에서이 이벤트에 첨부하고 이벤트가 발생하면 창을 닫습니다.


2
그게 내가 보통하는 일입니다. 그럼에도 불구하고 모든 새로운 wpf 명령 명령을 고려하면 약간 더러워 보입니다.
Botz3000

4

처음에 내가 한 일이 작동하지만 다소 오래 걸리고 추한 것처럼 보입니다 (전역 정적은 결코 좋지 않습니다)

1 : App.xaml.cs

public partial class App : Application
{
    // create a new global custom WPF Command
    public static readonly RoutedUICommand LoggedIn = new RoutedUICommand();
}

2 : LoginForm.xaml

// bind the global command to a local eventhandler
<CommandBinding Command="client:App.LoggedIn" Executed="OnLoggedIn" />

3 : LoginForm.xaml.cs

// implement the local eventhandler in codebehind
private void OnLoggedIn( object sender, ExecutedRoutedEventArgs e )
{
    DialogResult = true;
    Close();
}

4 : LoginFormViewModel.cs

// fire the global command from the viewmodel
private void OnRemoteServerReturnedSuccess()
{
    App.LoggedIn.Execute(this, null);
}

나중에이 코드를 모두 제거 LoginFormViewModel하고 Close 메서드를 호출했습니다. 결국 훨씬 더 좋고 따라 가기가 쉬워졌습니다. 패턴의 요점은 사람들이 앱이하는 일을 더 쉽게 이해할 수 있도록하는 것입니다.이 경우 MVVM은 사용하지 않은 것보다 이해하기가 훨씬 어려워졌으며 이제는 안티 패턴 이었습니다 .


3

참고로, 나는이 같은 문제에 부딪 쳤고 최선의 대답은 아니지만 전역이나 정적이 필요없는 해결 방법을 찾았습니다. 나는 너희들이 스스로 결정하도록 내버려 두었다.

필자의 경우 표시 할 창을 인스턴스화하는 ViewModel (ViewModelMain이라고 함)은 LoginFormViewModel에 대해서도 알고 있습니다 (위의 상황을 예로 사용).

그래서 내가 한 일은 LoginCommandViewModel에서 ICommand 유형의 속성을 만드는 것입니다 (CloseWindowCommand라고 부릅니다). 그런 다음 Window에서 .ShowDialog ()를 호출하기 전에 LoginFormViewModel의 CloseWindowCommand 속성을 인스턴스화 한 Window의 Close.) 메서드로 설정했습니다. 그런 다음 LoginFormViewModel 내부에서 CloseWindowCommand.Execute ()를 호출하여 창을 닫으면됩니다.

그것은 내가 생각한 약간의 해결 방법 / 해킹이지만 실제로 MVVM 패턴을 손상시키지 않고 잘 작동합니다.

이 과정을 원하는만큼 자유롭게 비판하십시오. :)


나는 그것을 완전히 이해하지 못했지만 이것이 LoginWindow 전에 MainWindow를 인스턴스화해야한다는 것을 의미하지 않습니까? 가능하다면 피하고 싶은 것입니다.
Orion Edwards

3

이것은 아마도 매우 늦었지만 같은 문제가 발생하여 나에게 맞는 해결책을 찾았습니다.

대화 상자없이 앱을 만드는 방법을 알 수 없습니다 (마음 블록 일 수도 있습니다). 그래서 나는 MVVM에 방해가되어 대화 상자를 표시했습니다. 그래서이 CodeProject 기사를 보았습니다.

http://www.codeproject.com/KB/WPF/XAMLDialog.aspx

이것은 기본적으로 윈도우가 다른 윈도우의 시각적 트리 내에있을 수있게하는 UserControl입니다 (xaml에서는 허용되지 않음). 또한 IsShowing이라는 부울 DependencyProperty를 노출합니다.

일반적으로 리소스 사전에서와 같은 스타일을 설정하여 기본적으로 컨트롤의 Content 속성이 트리거를 통해! = null 일 때 대화 상자를 표시합니다.

<Style TargetType="{x:Type d:Dialog}">
    <Style.Triggers>
        <Trigger Property="HasContent"  Value="True">
            <Setter Property="Showing" Value="True" />
        </Trigger>
    </Style.Triggers>
</Style>

대화 상자를 표시하려는보기에서 간단히 다음을 수행하십시오.

<d:Dialog Content="{Binding Path=DialogViewModel}"/>

그리고 ViewModel에서해야 할 일은 속성을 값으로 설정하는 것입니다 (참고 : ViewModel 클래스는 INotifyPropertyChanged를 지원해야보기에 어떤 일이 발생했는지 알 수 있습니다).

이렇게 :

DialogViewModel = new DisplayViewModel();

ViewModel과 View를 일치 시키려면 리소스 사전에 다음과 같은 내용이 있어야합니다.

<DataTemplate DataType="{x:Type vm:DisplayViewModel}">
    <vw:DisplayView/>
</DataTemplate>

이 모든 것을 통해 대화 상자를 표시하는 단일 코드가 제공됩니다. 당신이 얻는 문제는 위의 코드로 대화 상자를 실제로 닫을 수 없다는 것입니다. 따라서 DisplayViewModel이 상속하는 ViewModel 기본 클래스의 이벤트를 위의 코드 대신 입력 해야하는 이유는 다음과 같습니다.

        var vm = new DisplayViewModel();
        vm.RequestClose += new RequestCloseHandler(DisplayViewModel_RequestClose);
        DialogViewModel = vm;

그런 다음 콜백을 통해 대화 상자 결과를 처리 할 수 ​​있습니다.

이것은 약간 복잡해 보일 수 있지만 일단 토대가 마련되면 매우 간단합니다. 다시 이것은 이것이 나의 구현이며, 나는 다른 것들이 있다고 확신한다 :)

이것이 도움이되기를 바랍니다.


3

자,이 질문은 거의 6 살이되었지만 여전히 정답이라고 생각하는 것을 찾을 수 없으므로 "2 센트"를 공유 할 수 있습니다.

실제로 두 가지 방법이 있습니다. 첫 번째 방법은 간단한 방법입니다. 두 번째 방법은 올바른 방법이므로 올바른 것을 찾고 있다면 # 1을 건너 뛰고 # 2로 이동하십시오 .

1. 빠르고 쉬움 (완료되지는 않음)

작은 프로젝트가있는 경우 때로는 ViewModel에서 CloseWindowAction 을 만듭니다 .

        public Action CloseWindow { get; set; } // In MyViewModel.cs

그리고 View를 작성하거나 View의 코드 뒤에있는 사람은 Action이 호출하는 Method를 설정했습니다.

(MVVM은 View와 ViewModel의 분리에 관한 것임을 기억하십시오 ... View의 코드 Behins는 여전히 View이며 적절한 분리가있는 한 패턴을 위반하지 않습니다)

일부 ViewModel이 새 창을 생성하는 경우 :

private void CreateNewView()
{
    MyView window = new MyView();
    window.DataContext = new MyViewModel
                             {
                                 CloseWindow = window.Close,
                             }; 
    window.ShowDialog();
}

또는 메인 창에서 원하는 경우보기의 생성자 아래에 배치하십시오.

public MyView()
{
    InitializeComponent();           
    this.DataContext = new MainViewModel
                           {
                                CloseWindow = this.Close
                           };
}

창을 닫으려면 ViewModel에서 Action을 호출하십시오.


2. 올바른 방법

이제 올바른 방법은 프리즘 (IMHO)을 사용하는 것입니다 . . .

Interaction Request를 작성하고 , 새로운 Window에 필요한 데이터로 채우고, 점심을 먹으며, 닫고, 데이터를 다시받을 수도 있습니다. 이 모든 캡슐화 및 MVVM이 승인되었습니다. 심지어 사용자 또는 (확인 버튼) 창과 같은 경우 창을 닫은 방법의 상태필요한 경우 데이터를 다시 가져옵니다.CanceledAccepted . 좀 더 복잡하고 답변 # 1이지만 훨씬 더 완벽하며 Microsoft의 권장 패턴입니다.

내가 준 링크에는 모든 코드 스 니펫과 예제가 있으므로 여기에 코드를 넣을 필요가 없으며 Prism Quick Start 다운로드 기사를 읽고 실행하면됩니다. 작동하지만 창을 닫는 것보다 이점이 더 큽니다.


좋은 방법이지만 ViewModel의 해상도 및 할당이 항상 그렇게 쉬운 것은 아닙니다. 동일한 뷰 모델이 많은 Windows의 DataContext 인 경우 어떻게합니까?
Kylo Ren

그런 다음 한 번에 모든 창을 닫고 한 번에 많은 대의원을 트리거 할 수 있음을 기억하고 대의원 +=을 추가하는 데 사용 하고 해당 작업을 호출하면 모든 사람이 해고됩니다. VM에 특별한 로직을 만들어야 어떤 창을 닫을 지 알 수 있습니다 (닫기 작업 모음이있을 수 있음) ...하지만 여러 뷰를 하나의 VM에 바인딩하는 것이 가장 좋은 방법은 아니라고 생각합니다. 하나의 View와 하나의 VM 인스턴스를 서로 바인딩하고 모든 View에 바인딩 된 모든 하위 VM을 관리하는 상위 VM을 관리하는 것이 좋습니다.
mFeinstein

3
public partial class MyWindow: Window
{
    public ApplicationSelection()
    {
      InitializeComponent();

      MyViewModel viewModel = new MyViewModel();

      DataContext = viewModel;

      viewModel.RequestClose += () => { Close(); };

    }
}

public class MyViewModel
{

  //...Your code...

  public event Action RequestClose;

  public virtual void Close()
  {
    if (RequestClose != null)
    {
      RequestClose();
    }
  }

  public void SomeFunction()
  {
     //...Do something...
     Close();
  }
}

2

ViewModel이 View가 등록한 이벤트를 노출하도록 할 수 있습니다. 그런 다음 ViewModel이 뷰를 닫을 시간을 결정하면 해당 이벤트를 시작하여 뷰를 닫습니다. 특정 결과 값을 다시 전달하려면 ViewModel에 해당 속성이 있어야합니다.


나는 이것에 동의합니다-단순성은 가치가 있습니다. 다음 주니어 개발자가이 프로젝트를 인수하기 위해 고용되면 어떻게 될지 생각해야합니다. 내 생각에 그는 당신이 묘사 한대로 이것을 올바르게 얻을 수있는 훨씬 좋은 기회를 가질 것입니다. 이 코드를 영원히 유지한다고 생각하지 않는다면? +1
Dean

2

방대한 수의 답변에 추가하기 위해 다음을 추가하고 싶습니다. ViewModel에 ICommand가 있고 해당 명령이 창을 닫거나 해당 문제에 대한 다른 조치를 원한다고 가정하면 다음과 같은 것을 사용할 수 있습니다.

var windows = Application.Current.Windows;
for (var i=0;i< windows.Count;i++ )
    if (windows[i].DataContext == this)
        windows[i].Close();

완벽하지는 않으며 테스트하기가 어려울 수 있지만 (정적을 조롱 / 스터 빙하기 어렵 기 때문에) 다른 솔루션보다 깨끗합니다 (IMHO).

에릭


나는 당신의 간단한 대답을 보았을 때 매우 기뻤습니다! 그러나 그것은 작동하지 않습니다! Visual Basic으로 열고 닫을 필요가 있습니다. VB에서 (windows [i] .DataContext == this)의 동등성을 알고 있습니까?
Ehsan

나는 마침내 그것을 얻었다! :) 감사. windows (i) .DataContext가 나인 경우
Ehsan

창을 여는 동일한 방법을 알고 있습니까? 자식 뷰 모델에서 일부 데이터를 송수신해야하며 그 반대도 마찬가지입니다.
Ehsan

1

Joe White의 솔루션을 구현했지만 가끔 " DialogResult는 Window가 생성되어 대화 상자 오류" 로 표시된 후에 만 발생할 수 있습니다.

View가 닫힌 후 ViewModel을 유지하고 때로는 동일한 VM을 사용하여 새 View를 열었습니다. 이전보기 전에 새보기를 닫으면 쓰레기 결과 수집했던 것으로 보인다 DialogResultChanged 세트에 노력하고 DialogResult를에 따라서 오류를 자극, 닫힌 창에서 속성을.

내 솔루션은 DialogResultChanged 를 변경 하여 창의 IsLoaded 속성 을 확인하는 것입니다 .

private static void DialogResultChanged(
    DependencyObject d,
    DependencyPropertyChangedEventArgs e)
{
    var window = d as Window;
    if (window != null && window.IsLoaded)
        window.DialogResult = e.NewValue as bool?;
}

이렇게 변경하면 닫힌 대화 상자에 대한 첨부 파일이 무시됩니다.


감사합니다. 같은 문제가있었습니다
DJ Burb

1

프로그래밍 방식으로 작성된 창에 사용자 정의 컨트롤을 표시해야했기 때문에 Joe White의 답변Adam Mills의 답변의 일부 코드를 혼합했습니다 . 따라서 DialogCloser는 창에있을 필요가 없으며 사용자 컨트롤 자체에있을 수 있습니다.

<UserControl ...
    xmlns:xw="clr-namespace:Wpf"
    xw:DialogCloser.DialogResult="{Binding DialogResult}">

그리고 DialogCloser는 윈도우 자체에 부착되지 않은 경우 사용자 컨트롤의 윈도우를 찾습니다.

namespace Wpf
{
  public static class DialogCloser
  {
    public static readonly DependencyProperty DialogResultProperty =
        DependencyProperty.RegisterAttached(
            "DialogResult",
            typeof(bool?),
            typeof(DialogCloser),
            new PropertyMetadata(DialogResultChanged));

    private static void DialogResultChanged(
        DependencyObject d,
        DependencyPropertyChangedEventArgs e)
    {
      var window = d.GetWindow();
      if (window != null)
        window.DialogResult = e.NewValue as bool?;
    }

    public static void SetDialogResult(DependencyObject target, bool? value)
    {
      target.SetValue(DialogResultProperty, value);
    }
  }

  public static class Extensions
  {
    public static Window GetWindow(this DependencyObject sender_)
    {
      Window window = sender_ as Window;        
      return window ?? Window.GetWindow( sender_ );
    }
  }
}

1

행동이 가장 편리한 방법입니다.

  • 한 손으로, 주어진 뷰 모델에 바인딩 될 수 있습니다 ( "폼을 닫으십시오!"

  • 한편으로는 양식 자체에 액세스 할 수 있으므로 필요한 양식 별 이벤트를 구독하거나 확인 대화 상자 또는 기타 항목을 표시 할 수 있습니다.

필요한 행동을 쓰는 것이 처음에는 지루한 것을 볼 수 있습니다. 그러나 이제부터는 정확히 하나의 라이너 XAML 스 니펫으로 필요한 모든 단일 양식에서 재사용 할 수 있습니다. 필요한 경우 별도의 어셈블리로 추출하여 원하는 다음 프로젝트에 포함시킬 수 있습니다.


0

왜 창을 명령 매개 변수로 전달하지 않습니까?

씨#:

 private void Cancel( Window window )
  {
     window.Close();
  }

  private ICommand _cancelCommand;
  public ICommand CancelCommand
  {
     get
     {
        return _cancelCommand ?? ( _cancelCommand = new Command.RelayCommand<Window>(
                                                      ( window ) => Cancel( window ),
                                                      ( window ) => ( true ) ) );
     }
  }

XAML :

<Window x:Class="WPFRunApp.MainWindow"
        x:Name="_runWindow"
...
   <Button Content="Cancel"
           Command="{Binding Path=CancelCommand}"
           CommandParameter="{Binding ElementName=_runWindow}" />

VM을 창 유형으로 제한하는 것은 좋은 생각이 아닙니다.
Shimmy Weitzhandler 1

2
VM을 Window"순수한"MVVM이 아닌 유형으로 VM을 제한하는 것은 좋은 생각이 아닙니다. VM이 객체로 제한되지 않는 답변을 참조하십시오 Window.
Shimmy Weitzhandler

이렇게하면 항상 상황이 될 수없는 Button에 종속성이 적용됩니다. 또한 UI 유형을 ViewModel에 전달하는 것은 나쁜 습관입니다.
Kylo Ren

0

또 다른 해결책은 DialogResult와 같은 View Model에서 INotifyPropertyChanged를 사용하여 속성을 만든 다음 Code Behind에서 다음과 같이 작성하는 것입니다.

public class SomeWindow: ChildWindow
{
    private SomeViewModel _someViewModel;

    public SomeWindow()
    {
        InitializeComponent();

        this.Loaded += SomeWindow_Loaded;
        this.Closed += SomeWindow_Closed;
    }

    void SomeWindow_Loaded(object sender, RoutedEventArgs e)
    {
        _someViewModel = this.DataContext as SomeViewModel;
        _someViewModel.PropertyChanged += _someViewModel_PropertyChanged;
    }

    void SomeWindow_Closed(object sender, System.EventArgs e)
    {
        _someViewModel.PropertyChanged -= _someViewModel_PropertyChanged;
        this.Loaded -= SomeWindow_Loaded;
        this.Closed -= SomeWindow_Closed;
    }

    void _someViewModel_PropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        if (e.PropertyName == SomeViewModel.DialogResultPropertyName)
        {
            this.DialogResult = _someViewModel.DialogResult;
        }
    }
}

가장 중요한 조각은 _someViewModel_PropertyChanged입니다. DialogResultPropertyName에 공개 const 문자열이 될 수 있습니다 SomeViewModel.

ViewModel에서 수행하기 어려운 경우에 이러한 종류의 트릭을 사용하여 View Control에서 일부 변경을 수행하십시오. ViewModel에서 OnPropertyChanged를 사용하면 View에서 원하는 모든 작업을 수행 할 수 있습니다. ViewModel은 여전히 ​​'단위 테스트 가능'이며 코드 뒤의 작은 코드 줄은 차이가 없습니다.


0

나는 이런 식으로 갈 것입니다 :

using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.Command;    
using GalaSoft.MvvmLight.Messaging; 

// View

public partial class TestCloseWindow : Window
{
    public TestCloseWindow() {
        InitializeComponent();
        Messenger.Default.Register<CloseWindowMsg>(this, (msg) => Close());
    }
}

// View Model

public class MainViewModel: ViewModelBase
{
    ICommand _closeChildWindowCommand;

    public ICommand CloseChildWindowCommand {
        get {
            return _closeChildWindowCommand?? (_closeChildWindowCommand = new RelayCommand(() => {
                Messenger.Default.Send(new CloseWindowMsg());
        }));
        }
    }
}

public class CloseWindowMsg
{
}

0

나는 모든 대답을 읽었지만 말해야합니다. 대부분의 대답은 충분하지 않거나 더 나쁩니다.

대화 상자 창을 표시하고 대화 상자 결과를 반환하는 DialogService 클래스를 사용 하여 이를 아름답게 처리 할 수 있습니다. 구현 및 사용법을 보여주는 샘플 프로젝트를 만들었습니다 .

가장 중요한 부분은 다음과 같습니다.

//we will call this interface in our viewmodels
public interface IDialogService
{
    bool? ShowDialog(object dialogViewModel, string caption);
}

//we need to display logindialog from mainwindow
public class MainWindowViewModel : ViewModelBase
{
    public string Message {get; set;}
    public void ShowLoginCommandExecute()
    {
        var loginViewModel = new LoginViewModel();
        var dialogResult = this.DialogService.ShowDialog(loginViewModel, "Please, log in");

        //after dialog is closed, do someting
        if (dialogResult == true && loginViewModel.IsLoginSuccessful)
        {
            this.Message = string.Format("Hello, {0}!", loginViewModel.Username);
        }
    }
}


public class DialogService : IDialogService
{
    public bool? ShowDialog(object dialogViewModel, string caption)
    {
        var contentView = ViewLocator.GetView(dialogViewModel);
        var dlg = new DialogWindow
        {
            Title = caption
        };
        dlg.PART_ContentControl.Content = contentView;

        return dlg.ShowDialog();
    }
}

이게 더 간단하지 않습니까? EventAggregator 또는 다른 유사한 솔루션보다 더 간결하고 읽기 쉽고 마지막이지만 디버깅하기가 쉽지 않습니까?

보시다시피, 내 뷰 모델에서 여기 내 게시물에 설명 된 ViewModel 첫 번째 접근 방식을 사용 했습니다 .WPF의 ViewModel에서 View를 호출하는 모범 사례

물론 실제 DialogService.ShowDialog환경에서는 대화 상자를 구성 할 수있는 추가 옵션 (예 : 실행해야하는 버튼 및 명령)이 있어야합니다. 그렇게하는 다른 방법이 있지만 범위를 벗어납니다 :)


0

이것은 뷰 모델을 통해이 작업을 수행하는 방법에 대한 질문에는 대답하지 않지만 XAML + 블렌드 SDK 만 사용하여 수행하는 방법을 보여줍니다.

Blend SDK에서 두 파일을 다운로드하여 사용하기로 선택했습니다. 둘 다 NuGet을 통해 Microsoft에서 패키지로 사용할 수 있습니다. 파일은 다음과 같습니다

System.Windows.Interactivity.dll 및 Microsoft.Expression.Interactions.dll

Microsoft.Expression.Interactions.dll은 viewmodel 또는 다른 대상에서 속성을 설정하거나 메서드를 호출하는 기능과 같은 멋진 기능을 제공하며 내부에 다른 위젯도 있습니다.

일부 XAML :

<Window x:Class="Blah.Blah.MyWindow"
    ...
    xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
    xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
  ...>
 <StackPanel>
    <Button x:Name="OKButton" Content="OK">
       <i:Interaction.Triggers>
          <i:EventTrigger EventName="Click">
             <ei:ChangePropertyAction
                      TargetObject="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}"
                      PropertyName="DialogResult"
                      Value="True"
                      IsEnabled="{Binding SomeBoolOnTheVM}" />                                
          </i:EventTrigger>
    </Button>
    <Button x:Name="CancelButton" Content="Cancel">
       <i:Interaction.Triggers>
          <i:EventTrigger EventName="Click">
             <ei:ChangePropertyAction
                      TargetObject="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}"
                      PropertyName="DialogResult"
                      Value="False" />                                
          </i:EventTrigger>
    </Button>

    <Button x:Name="CloseButton" Content="Close">
       <i:Interaction.Triggers>
                <i:EventTrigger EventName="Click">
                    <!-- method being invoked should be void w/ no args -->
                    <ei:CallMethodAction
                        TargetObject="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}"
                        MethodName="Close" />
                </i:EventTrigger>
            </i:Interaction.Triggers>
    </Button>
 <StackPanel>
</Window>

간단한 OK / Cancel 동작을 수행하려는 경우 Window.ShowDialog ()에 창이 표시되어 있으면 IsDefault 및 IsCancel 속성을 사용하여 벗어날 수 있습니다.
개인적으로 IsDefault 속성이 true로 설정된 버튼에 문제가 있었지만 페이지가로드 될 때 숨겨졌습니다. 표시 된 후에 멋지게 재생하고 싶지 않은 것 같으므로 위의 그림과 같이 Window.DialogResult 속성을 설정하고 나에게 효과적입니다.


0

여기에 간단한 버그가없는 솔루션 (소스 코드 포함)이 있습니다.

  1. 에서 ViewModel 파생 INotifyPropertyChanged

  2. ViewModel에서 관찰 가능한 속성 CloseDialog 만들기

    public void Execute()
    {
        // Do your task here
    
        // if task successful, assign true to CloseDialog
        CloseDialog = true;
    }
    
    private bool _closeDialog;
    public bool CloseDialog
    {
        get { return _closeDialog; }
        set { _closeDialog = value; OnPropertyChanged(); }
    }
    
    public event PropertyChangedEventHandler PropertyChanged;
    
    private void OnPropertyChanged([CallerMemberName]string property = "")
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(property));
        }
    }

    }

  3. 이 속성 변경을 위해 뷰에 처리기 연결

        _loginDialogViewModel = new LoginDialogViewModel();
        loginPanel.DataContext = _loginDialogViewModel;
        _loginDialogViewModel.PropertyChanged += OnPropertyChanged;
  4. 이제 거의 끝났습니다. 이벤트 핸들러에서DialogResult = true

    protected void OnPropertyChanged(object sender, PropertyChangedEventArgs args)
    {
        if (args.PropertyName == "CloseDialog")
        {
            DialogResult = true;
        }
    }

0

/ any에 (또는 닫으려면)을 만드 Dependency Property십시오 . 아래처럼 :ViewUserControlWindow

 public bool CloseTrigger
        {
            get { return (bool)GetValue(CloseTriggerProperty); }
            set { SetValue(CloseTriggerProperty, value); }
        }

        public static readonly DependencyProperty CloseTriggerProperty =
            DependencyProperty.Register("CloseTrigger", typeof(bool), typeof(ControlEventBase), new PropertyMetadata(new PropertyChangedCallback(OnCloseTriggerChanged)));

        private static void OnCloseTriggerChanged(DependencyObject dp, DependencyPropertyChangedEventArgs e)
        {
            //write Window Exit Code
        }

그리고 ViewModel의 속성 에서 바인딩하십시오 .

<Window x:Class="WpfStackOverflowTempProject.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow"  Width="525"
        CloseTrigger="{Binding Path=CloseWindow,Mode=TwoWay}"

부동산 VeiwModel:

private bool closeWindow;

    public bool CloseWindow
    {
        get { return closeWindow; }
        set 
        { 
            closeWindow = value;
            RaiseChane("CloseWindow");
        }
    }

이제 CloseWindowViewModel 에서 값 을 변경하여 닫기 작업을 트리거하십시오 . :)


-2

창을 닫아야 할 경우 간단히 뷰 모델에 넣으십시오.

따다

  foreach (Window window in Application.Current.Windows)
        {
            if (window.DataContext == this)
            {
                window.Close();
                return;
            }
        }

ViewModel은 어떤 방식으로도 UIElement 를 포함해서는 안됩니다. 버그를 생성 할 수 있기 때문입니다
WiiMaxx

DataContext가 상속되는 경우 여러 창입니까?
Kylo Ren

ta-da, 이것은 완전히 MVVM이 아닙니다.
Alexandru Dicu

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.