[C#] ViewModel은 어떻게 양식을 닫아야합니까?

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 줄의 이벤트, 명령 및 행동을 생각하는 유일한 사람은 아닙니다. “패턴”과 “순도”의 이름은 조금 말도 안됩니다 …



답변

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

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


답변

내 관점에서 볼 때, 동일한 접근 방식이 “로그인”창뿐만 아니라 모든 종류의 창에 사용되기 때문에 질문은 꽤 좋습니다. 나는 많은 제안을 검토했지만 아무도 괜찮습니다. 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 의견이나 제안을 환영합니다.


답변

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

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


답변

여기에 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에서 어떻게했는지에 대해 생각하고 있기 때문에 일반적으로 옳지 않은 느낌이들 때.

희망이 도움이됩니다.


답변

로그인 대화 상자가 생성되는 첫 번째 창이라고 가정하면 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;
        }
    }


답변

이것은 간단하고 깨끗한 솔루션입니다. 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구현에 모든 구현을 사용할 수 있습니다.

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


답변

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