ViewModel에서 창 닫기


95

window control사용자 WPF가 내가 만들고 있는 응용 프로그램 에 로그인 할 수 있도록를 사용하여 로그인을 만듭니다.

지금까지 사용자가 로그인 화면 에서 usernamepassword에 대한 올바른 자격 증명을 입력했는지 확인하는 방법을 만들었습니다.textboxbindingproperties.

나는 bool이와 같은 방법 을 만들어서 이것을 달성했다 .

public bool CheckLogin()
{
    var user = context.Users.Where(i => i.Username == this.Username).SingleOrDefault();

    if (user == null)
    {
        MessageBox.Show("Unable to Login, incorrect credentials.");
        return false;
    }
    else if (this.Username == user.Username || this.Password.ToString() == user.Password)
    {
        MessageBox.Show("Welcome " + user.Username + ", you have successfully logged in.");

        return true;
    }
    else
    {
        MessageBox.Show("Unable to Login, incorrect credentials.");
        return false;
    }
}

public ICommand ShowLoginCommand
{
    get
    {
        if (this.showLoginCommand == null)
        {
            this.showLoginCommand = new RelayCommand(this.LoginExecute, null);
        }
        return this.showLoginCommand;
    }
}

private void LoginExecute()
{
    this.CheckLogin();
} 

나는 또한 같은 내 버튼에 commandthat I 이 있습니다.bindxaml

<Button Name="btnLogin" IsDefault="True" Content="Login" Command="{Binding ShowLoginCommand}" />

사용자 이름과 비밀번호를 입력하면 옳든 틀리 든 적절한 코드를 실행합니다. 그러나 사용자 이름과 암호가 모두 정확할 때 ViewModel에서이 창을 어떻게 닫을 수 있습니까?

이전에 a를 사용해 보았지만 dialog modal제대로 작동하지 않았습니다. 또한 내 app.xaml 내에서 다음과 같은 작업을 수행하여 로그인 페이지를 먼저로드 한 다음 true가되면 실제 애플리케이션을로드합니다.

private void ApplicationStart(object sender, StartupEventArgs e)
{
    Current.ShutdownMode = ShutdownMode.OnExplicitShutdown;

    var dialog = new UserView();

    if (dialog.ShowDialog() == true)
    {
        var mainWindow = new MainWindow();
        Current.ShutdownMode = ShutdownMode.OnMainWindowClose;
        Current.MainWindow = mainWindow;
        mainWindow.Show();
    }
    else
    {
        MessageBox.Show("Unable to load application.", "Error", MessageBoxButton.OK);
        Current.Shutdown(-1);
    }
}

질문 : Window controlViewModel 에서 로그인 을 닫으려면 어떻게 해야합니까?

미리 감사드립니다.


답변:


149

창을 ViewModel에 전달할 수 있습니다. CommandParameter . 아래 내 예를 참조하십시오.

CloseWindowWindows를 매개 변수로 사용하고 닫는 메서드를 구현 했습니다. 창은를 통해 ViewModel에 전달됩니다 CommandParameter. x:Name닫아야하는 창에 대해 를 정의 해야합니다. 내 XAML 창에서이 메서드를 통해 호출 Command하고 .NET을 사용하여 창 자체를 매개 변수로 ViewModel에 전달합니다 CommandParameter.

Command="{Binding CloseWindowCommand, Mode=OneWay}" 
CommandParameter="{Binding ElementName=TestWindow}"

ViewModel

public RelayCommand<Window> CloseWindowCommand { get; private set; }

public MainViewModel()
{
    this.CloseWindowCommand = new RelayCommand<Window>(this.CloseWindow);
}

private void CloseWindow(Window window)
{
    if (window != null)
    {
       window.Close();
    }
}

전망

<Window x:Class="ClientLibTestTool.ErrorView"
        x:Name="TestWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:localization="clr-namespace:ClientLibTestTool.ViewLanguages"
        DataContext="{Binding Main, Source={StaticResource Locator}}"
        Title="{x:Static localization:localization.HeaderErrorView}"
        Height="600" Width="800"
        ResizeMode="NoResize"
        WindowStartupLocation="CenterScreen">
    <Grid> 
        <Button Content="{x:Static localization:localization.ButtonClose}" 
                Height="30" 
                Width="100" 
                Margin="0,0,10,10" 
                IsCancel="True" 
                VerticalAlignment="Bottom" 
                HorizontalAlignment="Right" 
                Command="{Binding CloseWindowCommand, Mode=OneWay}" 
                CommandParameter="{Binding ElementName=TestWindow}"/>
    </Grid>
</Window>

MVVM 라이트 프레임 워크를 사용하고 있지만 원칙은 모든 wpf 애플리케이션에 적용됩니다.

이 솔루션은 MVVM 패턴을 위반합니다. 뷰 모델은 UI 구현에 대해 알 수 없기 때문입니다. MVVM 프로그래밍 패러다임을 엄격하게 따르려면 인터페이스로 뷰 유형을 추상화해야합니다.

MVVM 준수 솔루션 (이전 EDIT2)

사용자 Crono 는 주석 섹션에서 유효한 점을 언급합니다.

Window 개체를 뷰 모델에 전달하면 MVVM 패턴 IMHO가 깨집니다. vm이 무엇을보고 있는지 알도록하기 때문입니다.

close 메소드가 포함 된 인터페이스를 도입하여이 문제를 해결할 수 있습니다.

상호 작용:

public interface ICloseable
{
    void Close();
}

리팩터링 된 ViewModel은 다음과 같습니다.

ViewModel

public RelayCommand<ICloseable> CloseWindowCommand { get; private set; }

public MainViewModel()
{
    this.CloseWindowCommand = new RelayCommand<IClosable>(this.CloseWindow);
}

private void CloseWindow(ICloseable window)
{
    if (window != null)
    {
        window.Close();
    }
}

ICloseable뷰 에서 인터페이스 를 참조하고 구현 해야합니다.

보기 (코드 숨김)

public partial class MainWindow : Window, ICloseable
{
    public MainWindow()
    {
        InitializeComponent();
    }
}

원래 질문에 대한 답변 : (이전 EDIT1)

로그인 버튼 (추가 된 CommandParameter) :

<Button Name="btnLogin" IsDefault="True" Content="Login" Command="{Binding ShowLoginCommand}" CommandParameter="{Binding ElementName=LoginWindow}"/>

코드 :

 public RelayCommand<Window> CloseWindowCommand { get; private set; } // the <Window> is important for your solution!

 public MainViewModel() 
 {
     //initialize the CloseWindowCommand. Again, mind the <Window>
     //you don't have to do this in your constructor but it is good practice, thought
     this.CloseWindowCommand = new RelayCommand<Window>(this.CloseWindow);
 }

 public bool CheckLogin(Window loginWindow) //Added loginWindow Parameter
 {
    var user = context.Users.Where(i => i.Username == this.Username).SingleOrDefault();

    if (user == null)
    {
        MessageBox.Show("Unable to Login, incorrect credentials.");
        return false;
    }
    else if (this.Username == user.Username || this.Password.ToString() == user.Password)
    {
        MessageBox.Show("Welcome "+ user.Username + ", you have successfully logged in.");
        this.CloseWindow(loginWindow); //Added call to CloseWindow Method
        return true;
    }
    else
    {
        MessageBox.Show("Unable to Login, incorrect credentials.");
        return false;
    }
 }

 //Added CloseWindow Method
 private void CloseWindow(Window window)
 {
     if (window != null)
     {
         window.Close();
     }
 }

1
@Joel 업데이트 주셔서 감사합니다. 마지막 질문은 Window의 매개 변수를받는 메소드로 인해, 내 명령 내에서 해당 메소드를 호출 할 때 매개 변수를 예상합니다. 메소드에 대해 호출되는 로컬 Window 매개 변수를 작성합니까? private void LoginExecute(){this.CheckLogin();}<-CheckLogin은 매개 변수를 받아야합니다.
WPFNoob 2013

이해가 안 돼서 죄송합니다. 질문에 대해 설명해 주시겠습니까?
Joel

14
창 이름 지정이 마음에 들지 않으면 다음과 같이 매개 변수를 바인딩 할 수도 있습니다.CommandParameter="{Binding RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
Jacco Dieleman 2014-04-29

33
Window객체를 뷰 모델에 전달하면 MVVM 패턴 IMHO가 깨집니다. vm이 무엇을보고 있는지 알도록 강제하기 때문입니다. 뷰가 대신 MDI 인터페이스에 도킹 된 탭이면 어떻게 될까요? 이 IMHO를 수행하는 적절한 방법은 Close 메서드를 구현하는 일종의 IUIHost 인터페이스를 전달하고 vm이 구현하는 뷰를 표시하는 것입니다.
Crono

2
인터페이스가 ViewModel에 대한 구체적인 구현을 숨기므로 괜찮습니다. ViewModel은 Close () 메서드를 구현한다는 점을 제외하고는 뷰에 대해 아무것도 모릅니다. 따라서보기는 WPF 창, WinForms 양식, UWP 응용 프로그램 또는 WPF 그리드 등 무엇이든 될 수 있습니다. 뷰 모델에서 뷰를 분리합니다.
Joel

34

MVVM을 유지하면서 Blend SDK (System.Windows.Interactivity)의 Behaviors 또는 Prism의 사용자 지정 상호 작용 요청을 사용하면 이런 종류의 상황에서 정말 잘 작동 할 수 있다고 생각합니다.

Behavior 경로를 사용하는 경우 일반적인 아이디어는 다음과 같습니다.

public class CloseWindowBehavior : Behavior<Window>
{
    public bool CloseTrigger
    {
        get { return (bool)GetValue(CloseTriggerProperty); }
        set { SetValue(CloseTriggerProperty, value); }
    }

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

    private static void OnCloseTriggerChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var behavior = d as CloseWindowBehavior;

        if (behavior != null)
        {
            behavior.OnCloseTriggerChanged();
        }
    }

    private void OnCloseTriggerChanged()
    {
        // when closetrigger is true, close the window
        if (this.CloseTrigger)
        {
            this.AssociatedObject.Close();
        }
    }
}

그런 다음 창에서 CloseTrigger를 창을 닫고 싶을 때 설정되는 부울 값에 바인딩하면됩니다.

<Window x:Class="TestApp.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
        xmlns:local="clr-namespace:TestApp"
        Title="MainWindow" Height="350" Width="525">
    <i:Interaction.Behaviors>
        <local:CloseWindowBehavior CloseTrigger="{Binding CloseTrigger}" />
    </i:Interaction.Behaviors>

    <Grid>

    </Grid>
</Window>

마지막으로 DataContext / ViewModel에는 다음과 같이 창을 닫고 싶을 때 설정 한 속성이 있습니다.

public class MainWindowViewModel : INotifyPropertyChanged
{
    private bool closeTrigger;

    /// <summary>
    /// Gets or Sets if the main window should be closed
    /// </summary>
    public bool CloseTrigger
    {
        get { return this.closeTrigger; }
        set
        {
            this.closeTrigger = value;
            RaisePropertyChanged(nameof(CloseTrigger));
        }
    }

    public MainWindowViewModel()
    {
        // just setting for example, close the window
        CloseTrigger = true;
    }

    protected void RaisePropertyChanged(string propertyName)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;
}

(Window.DataContext = new MainWindowViewModel () 설정)


@Steve 답장에 감사드립니다 boolean. CloseTrigger를 값 에 바인딩하는 것에 대해 언급하셨습니다 . 당신이 그렇게 말했을 때, DataTrigger그것을 달성하기 위해 내가 창조한다는 뜻 입니까?
WPFNoob 2013

죄송합니다. 좀 더 명시 적이어야합니다. 뷰 모델 (위의 예에서는 CloseTrigger라고하는 속성)에 속성이 있어야 true로 설정되어 동작을 트리거하게됩니다. 나는 답변을 업데이트했습니다
Steve Van Treeck 2013-04-24

이것은 효과가 있었지만 애플리케이션이로드되는 방식을 변경해야했습니다. 주 응용 프로그램에 Window를 사용했기 때문에 모든 자식 창도 종료되었습니다. 감사.
WPFNoob 2013

작업을 수행하기 위해 속성을 true로 설정하는 것은 냄새 나는 IMO입니다.
Josh Noe

33

일반적으로이 작업을 수행해야 할 때 뷰 모델에 이벤트를 넣은 다음 Window.Close()뷰 모델을 창에 바인딩 할 때 연결합니다.

public class LoginViewModel
{
    public event EventHandler OnRequestClose;

    private void Login()
    {
        // Login logic here
        OnRequestClose(this, new EventArgs());
    }
}

그리고 로그인 창을 만들 때

var vm = new LoginViewModel();
var loginWindow = new LoginWindow
{
    DataContext = vm
};
vm.OnRequestClose += (s, e) => loginWindow.Close();

loginWindow.ShowDialog(); 

11
익명 대리자는 신속하게 작성되지만 이벤트 등록을 취소 할 수 없다는 점에 유의할 가치가 있습니다 (문제가 될 수도 있고 아닐 수도 있음). 일반적으로 본격적인 이벤트 핸들러를 사용하는 것이 좋습니다.
Mathieu Guindon

나는 이것을 가장 좋아한다. (예를 들어 창을 표시 할 때 특수 처리를 피하기 위해 어쨌든 하드가 Loaded, ContentRendered나에게로 꽤 깨끗, 메인 윈도우, 대화 서비스 등)의 ViewModel 이벤트를 통해 그것에 조금 추가. 3 줄의 코드는 실제로 재사용 솔루션이 필요하지 않습니다. 추신 : 순수한 MVVM은 어쨌든 바보입니다.
Sinatr

소년 이것은 나를 도왔다.
Dimitri

이것은 MVVM 패턴을 깨지 않기 때문에 받아 들여지는 대답보다 훨씬 낫습니다.
Spook

22

늦을 수도 있지만 여기에 내 대답이 있습니다.

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

1
이것이 실제 답이 아닌 이유는 무엇입니까?
user2529011

1
@ user2529011 일부, 적어도, 뷰 모델이 Application.Current.Windows에 대해 아무것도 모르는 것을 불평 것
gusmally 모니카 지원

-1. 뷰 모델은 뷰에 대해 전혀 알지 못합니다. 그 문제를 위해 코드 뒤에 작성하는 것이 좋습니다.
Alejandro

13

여기 제가 여러 프로젝트에서 사용한 것이 있습니다. 해킹처럼 보일 수 있지만 제대로 작동합니다.

public class AttachedProperties : DependencyObject //adds a bindable DialogResult to window
{
    public static readonly DependencyProperty DialogResultProperty = 
        DependencyProperty.RegisterAttached("DialogResult", typeof(bool?), typeof(AttachedProperties), 
        new PropertyMetaData(default(bool?), OnDialogResultChanged));

    public bool? DialogResult
    {
        get { return (bool?)GetValue(DialogResultProperty); }
        set { SetValue(DialogResultProperty, value); }
    }

    private static void OnDialogResultChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var window = d as Window;
        if (window == null)
            return;

        window.DialogResult = (bool?)e.NewValue;
    }
}

이제 DialogResultVM에 바인딩 하고 속성 값을 설정할 수 있습니다 . 는 Window값이 설정되어있는 경우, 닫습니다.

<!-- Assuming that the VM is bound to the DataContext and the bound VM has a property DialogResult -->
<Window someNs:AttachedProperties.DialogResult={Binding DialogResult} />

이것은 우리의 프로덕션 환경에서 실행되는 것의 요약입니다.

<Window x:Class="AC.Frontend.Controls.DialogControl.Dialog"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:DialogControl="clr-namespace:AC.Frontend.Controls.DialogControl" 
        xmlns:hlp="clr-namespace:AC.Frontend.Helper"
        MinHeight="150" MinWidth="300" ResizeMode="NoResize" SizeToContent="WidthAndHeight"
        WindowStartupLocation="CenterScreen" Title="{Binding Title}"
        hlp:AttachedProperties.DialogResult="{Binding DialogResult}" WindowStyle="ToolWindow" ShowInTaskbar="True"
        Language="{Binding UiCulture, Source={StaticResource Strings}}">
        <!-- A lot more stuff here -->
</Window>

보시다시피, 네임 스페이스를 xmlns:hlp="clr-namespace:AC.Frontend.Helper"먼저 선언 하고 그 후에 binding hlp:AttachedProperties.DialogResult="{Binding DialogResult}".

AttachedProperty모습이 맘에. 어제 게시 한 것과 같지는 않지만 IMHO는 아무런 효과가 없어야합니다.

public class AttachedProperties
{
    #region DialogResult

    public static readonly DependencyProperty DialogResultProperty =
        DependencyProperty.RegisterAttached("DialogResult", typeof (bool?), typeof (AttachedProperties), new PropertyMetadata(default(bool?), OnDialogResultChanged));

    private static void OnDialogResultChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var wnd = d as Window;
        if (wnd == null)
            return;

        wnd.DialogResult = (bool?) e.NewValue;
    }

    public static bool? GetDialogResult(DependencyObject dp)
    {
        if (dp == null) throw new ArgumentNullException("dp");

        return (bool?)dp.GetValue(DialogResultProperty);
    }

    public static void SetDialogResult(DependencyObject dp, object value)
    {
        if (dp == null) throw new ArgumentNullException("dp");

        dp.SetValue(DialogResultProperty, value);
    }

    #endregion
}

아니요, 어리석은 질문이 아닙니다. <Window />내 스니핑에서 설명한 것처럼 바인딩 선언을 요소 에 넣으십시오 . 나는 보통 거기에 선언 된 나머지 (네임 스페이스 선언 등)를 작성하기에는 너무 게으르다.
DHN 2013

1
Pls는 내 편집을 참조하십시오. 프로덕션 코드를 게시 했으므로 제대로 작동하는지 확신합니다. 약간 다르게 보이지만 어제 게시 한 코드도 작동합니다.
DHN 2013

정리해 주셔서 감사합니다. 내가 잘못된 이름 공간을 호출하고 있음이 밝혀졌습니다. 을 생성 datatrigger하고 버튼에 할당 하기 만하면 작동합니까? nooby 질문에 다시 한 번 죄송합니다.
WPFNoob 2013

고마워요. 저는 제가 너무 많은 질문을하고 어리 석고 어리석은 것처럼 보일 수 있고 사람들의 시간을 낭비하고 있다는 것을 알고 있습니다! 그러나 내 질문으로 돌아갑니다. 당신이 언급 한 모든 후에 어떻게 창을 닫습니까? DataTrigger¬ and setting value true`를 사용 합니까?
WPFNoob 2013

1
그게 부분입니다. 저는 여러분에게 떠날 것입니다. ; O)에 대해 생각 DataContextDialog. 나는 같이 VM을 설정하는 것이, 기대하는 DataContext속성을 설정하는 명령, 제공 DialogResult또는 당신이 무엇에 바인딩 한 true또는 false, 그래서 것을 Dialog닫히고가.
DHN 2013

13

쉬운 방법

public interface IRequireViewIdentification
{
    Guid ViewID { get; }
}

ViewModel에 구현

public class MyViewVM : IRequireViewIdentification
{
    private Guid _viewId;

    public Guid ViewID
    {
        get { return _viewId; }
    }

    public MyViewVM()
    {
        _viewId = Guid.NewGuid();
    }
}

일반 창 관리자 도우미 추가

public static class WindowManager
{
    public static void CloseWindow(Guid id)
    {
        foreach (Window window in Application.Current.Windows)
        {
            var w_id = window.DataContext as IRequireViewIdentification;
            if (w_id != null && w_id.ViewID.Equals(id))
            {
                window.Close();
            }
        }
    }
}

뷰 모델에서 이렇게 닫습니다

WindowManager.CloseWindow(ViewID);

아주 좋은 솔루션입니다.
DonBoitnott 2017-06-12

Win을 닫을 때 dialogresult를 설정하기 위해 WindowManager를 약간 변경했습니다. public static void CloseWindow (Guid id, bool dialogResult) {foreach (Application.Current.Windows의 창 창) {var w_id = window.DataContext as IRequireViewIdentification; if (w_id! = null && w_id.ViewID.Equals (id)) {window.DialogResult = dialogResult; window.Close (); }}} 다음과 같이 호출합니다. WindowManager.CloseWindow (_viewId, true);
lebhero

좋은 솔루션이지만 viewmodel과 사이의 긴밀한 결합을 만들어 주며 WindowManager, 이는 View(의 측면에서 PresentationFramework) 밀접하게 결합됩니다 . WindowManager서비스가 인터페이스를 통해 viewmodel에 전달 되면 더 좋을 것 입니다. 그러면 솔루션을 다른 플랫폼으로 쉽게 마이그레이션 할 수 있습니다.
Spook

4

다음은 이벤트 대신 MVVM Light Messenger를 사용하는 간단한 예입니다. 보기 모델은 버튼을 클릭하면 닫기 메시지를 보냅니다.

    public MainViewModel()
    {
        QuitCommand = new RelayCommand(ExecuteQuitCommand);
    }

    public RelayCommand QuitCommand { get; private set; }

    private void ExecuteQuitCommand() 
    {
        Messenger.Default.Send<CloseMessage>(new CloseMessage());
    }

그런 다음 창 뒤에있는 코드로 수신됩니다.

    public Main()
    {   
        InitializeComponent();
        Messenger.Default.Register<CloseMessage>(this, HandleCloseMessage);
    }

    private void HandleCloseMessage(CloseMessage closeMessage)
    {
        Close();
    }

CloseMessage의 구현을 어디서 찾을 수 있는지 조언 해 주시겠습니까?
Roman O

CloseMessage는 전송되는 메시지 유형을 식별하는 데 사용되는 빈 클래스입니다. (여기에는 필요하지 않은 복잡한 메시지 정보도 포함될 수 있습니다.)
IngoB

4

이건 어때 ?

ViewModel :

class ViewModel
{
    public Action CloseAction { get; set; }
    private void Stuff()
    {
       // Do Stuff
       CloseAction(); // closes the window
    }
}

ViewModel에서 CloseAction ()을 사용하여 위의 예제와 같이 창을 닫습니다.

전망:

public View()
{
    InitializeComponent();
    ViewModel vm = new ViewModel (); // this creates an instance of the ViewModel
    this.DataContext = vm; // this sets the newly created ViewModel as the DataContext for the View
    if (vm.CloseAction == null)
        vm.CloseAction = new Action(() => this.Close());
}

3

나는 이것이 오래된 게시물이라는 것을 알고 있으며, 아마도 아무도 여기까지 스크롤하지 않을 것입니다. 그래서 몇 시간 동안 다른 것을 시도한 후이 블로그를 발견하고 친구가 그것을 죽였습니다. 이를 수행하는 가장 간단한 방법은 시도해 보았고 매력처럼 작동합니다.

블로그

ViewModel에서 :

...

public bool CanClose { get; set; }

private RelayCommand closeCommand;
public ICommand CloseCommand
{
    get
    {
        if(closeCommand == null)
        (
            closeCommand = new RelayCommand(param => Close(), param => CanClose);
        )
    }
}

public void Close()
{
    this.Close();
}

...

ViewModel에 Action 속성을 추가하되 View의 코드 숨김 파일에서 정의합니다. 이를 통해 View를 가리키는 ViewModel에 대한 참조를 동적으로 정의 할 수 있습니다.

ViewModel에서 간단히 다음을 추가합니다.

public Action CloseAction { get; set; }

그리고 뷰에서 다음과 같이 정의합니다.

public View()
{
    InitializeComponent() // this draws the View
    ViewModel vm = new ViewModel(); // this creates an instance of the ViewModel
    this.DataContext = vm; // this sets the newly created ViewModel as the DataContext for the View
    if ( vm.CloseAction == null )
        vm.CloseAction = new Action(() => this.Close());
}

링크가 깨진 : /
gusmally 모니카 지원

@gusmally 확실합니까? 나는 normaly, 다시 시도 열 jkshay.com/...
Serlok

2

이와 같이 ViewModel에서 새로운 이벤트 핸들러를 생성 할 수 있습니다.

public event EventHandler RequestClose;

    protected void OnRequestClose()
    {
        if (RequestClose != null)
            RequestClose(this, EventArgs.Empty);
    }

그런 다음 ExitCommand에 대한 RelayCommand를 정의합니다.

private RelayCommand _CloseCommand;
    public ICommand CloseCommand
    {
        get
        {
            if(this._CloseCommand==null)
                this._CloseCommand=new RelayCommand(CloseClick);
            return this._CloseCommand;
        }
    }

    private void CloseClick(object obj)
    {
        OnRequestClose();
    }

그런 다음 XAML 파일 세트에서

<Button Command="{Binding CloseCommand}" />

xaml.cs 파일에 DataContext를 설정하고 생성 한 이벤트를 구독합니다.

public partial class MainWindow : Window
{
    private ViewModel mainViewModel = null;
    public MainWindow()
    {
        InitializeComponent();
        mainViewModel = new ViewModel();
        this.DataContext = mainViewModel;
        mainViewModel.RequestClose += delegate(object sender, EventArgs args) { this.Close(); };
    }
}

이벤트 대신 MVVM Light Messenger를 사용했습니다.
Hamish Gunn

1

내 제안 된 방법은 ViewModel에서 이벤트를 선언하고 아래와 같이 블렌드 InvokeMethodAction을 사용하는 것입니다.

샘플 ViewModel

public class MainWindowViewModel : BindableBase, ICloseable
{
    public DelegateCommand SomeCommand { get; private set; }
    #region ICloseable Implementation
    public event EventHandler CloseRequested;        

    public void RaiseCloseNotification()
    {
        var handler = CloseRequested;
        if (handler != null)
        {
            handler.Invoke(this, EventArgs.Empty);
        }
    }
    #endregion

    public MainWindowViewModel()
    {
        SomeCommand = new DelegateCommand(() =>
        {
            //when you decide to close window
            RaiseCloseNotification();
        });
    }
}

I Closeable 인터페이스는 다음과 같지만이 작업을 수행 할 필요가 없습니다. ICloseable은 일반 뷰 서비스를 만드는 데 도움이되므로 종속성 주입으로 뷰와 ViewModel을 구성하면 할 수있는 작업은 다음과 같습니다.

internal interface ICloseable
{
    event EventHandler CloseRequested;
}

ICloseable 사용

var viewModel = new MainWindowViewModel();
        // As service is generic and don't know whether it can request close event
        var window = new Window() { Content = new MainView() };
        var closeable = viewModel as ICloseable;
        if (closeable != null)
        {
            closeable.CloseRequested += (s, e) => window.Close();
        }

그리고 아래는 Xaml입니다. 인터페이스를 구현하지 않아도이 xaml을 사용할 수 있습니다. CloseRquested를 발생시키기 위해 뷰 모델 만 필요합니다.

<Window 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:WPFRx"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity" 
xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions" 
xmlns:ViewModels="clr-namespace:WPFRx.ViewModels" x:Name="window" x:Class="WPFRx.MainWindow"
    mc:Ignorable="d"
    Title="MainWindow" Height="350" Width="525" 
d:DataContext="{d:DesignInstance {x:Type ViewModels:MainWindowViewModel}}">

<i:Interaction.Triggers>
    <i:EventTrigger SourceObject="{Binding Mode=OneWay}" EventName="CloseRequested" >
        <ei:CallMethodAction TargetObject="{Binding ElementName=window}" MethodName="Close"/>
    </i:EventTrigger>
</i:Interaction.Triggers>

<Grid>
    <Button Content="Some Content" Command="{Binding SomeCommand}" Width="100" Height="25"/>
</Grid>


1

MessengerMVVMLight 툴킷에서 사용할 수 있습니다 . 다음 ViewModel과 같은 메시지를 보내 십시오 :
Messenger.Default.Send(new NotificationMessage("Close"));
그런 다음 Windows 코드에서 다음 InitializeComponent과 같이 해당 메시지를 등록하십시오.

Messenger.Default.Register<NotificationMessage>(this, m=>{
    if(m.Notification == "Close") 
    {
        this.Close();
    }
   });

MVVMLight 툴킷에 대한 자세한 내용은 여기에서 찾을 수 있습니다 : Codeplex의 MVVMLight 툴킷

MVVM에는 "코드 숨김 없음 규칙"이 없으며보기 코드 숨김에서 메시지를 등록 할 수 있습니다.


0

간단 해. Login-LoginViewModel에 대한 고유 한 ViewModel 클래스를 만들 수 있습니다. 보기를 만들 수 있습니다. var dialog = new UserView (); LoginViewModel 내부. 그리고 Command LoginCommand를 버튼에 설정할 수 있습니다.

<Button Name="btnLogin" IsDefault="True" Content="Login" Command="{Binding LoginCommand}" />

<Button Name="btnCancel" IsDefault="True" Content="Login" Command="{Binding CancelCommand}" />

ViewModel 클래스 :

public class LoginViewModel
{
    Window dialog;
    public bool ShowLogin()
    {
       dialog = new UserView();
       dialog.DataContext = this; // set up ViewModel into View
       if (dialog.ShowDialog() == true)
       {
         return true;
       }

       return false;
    }

    ICommand _loginCommand
    public ICommand LoginCommand
    {
        get
        {
            if (_loginCommand == null)
                _loginCommand = new RelayCommand(param => this.Login());

            return _loginCommand;
        }
    }

    public void CloseLoginView()
    {
            if (dialog != null)
          dialog.Close();
    }   

    public void Login()
    {
        if(CheckLogin()==true)
        {
            CloseLoginView();         
        }
        else
        {
          // write error message
        }
    }

    public bool CheckLogin()
    {
      // ... check login code
      return true;
    }
}

3
예, 또한 유효한 솔루션입니다. 그러나 MVVM과 VM과 뷰의 분리를 고수하려면 패턴을 깨뜨릴 것입니다.
DHN 2013

안녕하세요 @misak-솔루션을 구현하려고 시도하면 (다른 답변과 마찬가지로) Object reference not set to an instance of an object.CloseLoginView 메서드에 대해 a 가 발생합니다. 그 문제를 해결하는 방법에 대한 제안이 있습니까?
WPFNoob

@WPFNoob-이 솔루션을 다시 트레이싱합니다. 예제가 올바르게 작동합니다. 완전한 Visual Studio 솔루션을 이메일로 보내시겠습니까?
misak 2013 년

@WPFNoob-문제가 보입니다. var dialog = new UserView ();로 인스턴스를 만들고 있습니다. 지우기 키워드 var (로컬 인스턴스)가 LoginViewModel의 전역 인스턴스를 덮어 씁니다
misak

0

이것은 내가 아주 간단하게 한 방법입니다.

YourWindow.xaml.cs

//In your constructor
public YourWindow()
{
    InitializeComponent();
    DataContext = new YourWindowViewModel(this);
}

YourWindowViewModel.cs

private YourWindow window;//so we can kill the window

//In your constructor
public YourWindowViewModel(YourWindow window)
{
    this.window = window;
}

//to close the window
public void CloseWindow()
{
    window.Close();
}

선택한 답변에 잘못된 것이 없습니다.이 방법이 더 간단한 방법이라고 생각했습니다.


8
이를 위해서는 ViewModel이 View를 알고 참조해야합니다.
AndrewS nov.

@AndrewS 왜 그게 나쁜가요?
thestephenstanton

9
MVVM 패턴을 따르려면 ViewModel이 View에 대해 알아야합니다.
MetalMikester 2015

1
이를 확장하기 위해 MVVM의 요점은 대부분의 GUI 코드 단위를 테스트 가능하게 만드는 것입니다. 뷰에는 단위 테스트를 불가능하게 만드는 수많은 종속성이 있습니다. ViewModel은 단위 테스트가 가능해야하지만 뷰에 대한 직접적인 종속성을 부여하면 그렇지 않습니다.
ILMTitan

그리고이를 더욱 확장하기 위해 올바르게 작성된 MVVM을 사용하면 솔루션을 다른 플랫폼으로 쉽게 마이그레이션 할 수 있습니다. 특히, 변경없이 뷰 모델을 재사용 할 수 있어야합니다. 이 경우 솔루션을 Android로 옮긴 경우 Android에는 창 개념이 없기 때문에 작동하지 않습니다. MVVM 차단 솔루션의 경우 -1입니다.
Spook

0

창을 서비스 (예 : UI 서비스)로 취급하고 다음과 같이 인터페이스를 통해 뷰 모델에 자신을 전달할 수 있습니다 .

public interface IMainWindowAccess
{
    void Close(bool result);
}

public class MainWindow : IMainWindowAccess
{
    // (...)
    public void Close(bool result)
    {
        DialogResult = result;
        Close();
    }
}

public class MainWindowViewModel
{
    private IMainWindowAccess access;

    public MainWindowViewModel(IMainWindowAccess access)
    {
        this.access = access;
    }

    public void DoClose()
    {
        access.Close(true);
    }
}

이 솔루션은 MVVM을 깨뜨리지 않고 뷰 자체를 뷰 모델에 전달하는 가장 좋은 점이 있습니다. 물리적으로 뷰가 뷰 모델에 전달되지만 후자는 여전히 전자에 대해 알지 못하며 일부만 볼 수 있기 때문 IMainWindowAccess입니다. 따라서 예를 들어이 솔루션을 다른 플랫폼으로 마이그레이션하려는 경우 IMainWindowAccess적절한 구현의 문제 일뿐입니다 .Activity .

구현할 이벤트 (연결 / 분리 등)보다 조금 더 간단 해 보이지만 여전히 MVVM 패턴과 잘 맞아 떨어 지므로 이벤트와 다른 접근 방식을 제안하기 위해 여기에 솔루션을 게시하고 있습니다 (실제로 매우 유사하지만).


-1

다음 코드를 사용하여 현재 창을 닫을 수 있습니다.

Application.Current.Windows[0].Close();

6
창이 두 개 이상있는 경우 잘못된 창을 닫을 수 있습니다.
Sasha

17
오 이런! 당신은 MVVM 학살 한
호세인 Shahdoost

-7

System.Environment.Exit (0); 뷰 모델에서 작동합니다.


6
아니요. 응용 프로그램을 종료하고 현재 창을 닫지 않습니다.
Tilak

이것은 내 문제를 해결했습니다. 왜냐하면 mainWindow를 닫으면 (나에게) == 응용 프로그램을 종료하기 때문입니다. 이 메서드를 제외한 모든 제안 된 메서드는 다른 스레드에서 호출 될 때 까다로운 점이있었습니다. 그러나이 접근 방식은 호출자 스레드가 누구인지 실제로 신경 쓰지 않습니다 :) 그게 내가 필요한 전부였습니다!
Hamed

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