ViewModel formu nasıl kapatmalıdır?


247

WPF ve MVVM problemini öğrenmeye çalışıyorum, ama bir engelle karşılaştım. Bu soru benzer ancak bu soru ile tamamen aynı değil (wpf-in-mvvm ile iletişim-diyalogları) ...

MVVM deseni kullanılarak yazılmış bir "Giriş" formu var.

Bu formda, normal veri bağlamaları kullanılarak XAML'deki görünüme bağlı olan Kullanıcı Adını ve Parolayı içeren bir ViewModel bulunur. Ayrıca, normal veritabanını kullanarak, formdaki "Oturum Aç" düğmesine bağlı bir "Oturum Aç" komutuna sahiptir.

"Oturum Aç" komutu tetiklendiğinde, ViewModel'de kapanıp oturum açmak için ağ üzerinden veri gönderen bir işlevi çağırır. Bu işlev tamamlandığında 2 eylem vardır:

  1. Giriş geçersizdi - sadece bir MessageBox gösteriyoruz ve her şey yolunda

  2. Giriş geçerliydi, Giriş formunu kapatmamız ve doğru olarak geri dönmesi gerekiyor DialogResult...

Sorun, ViewModel gerçek görünüm hakkında hiçbir şey bilmiyor, bu yüzden nasıl görünümü kapatabilir ve belirli bir DialogResult dönmek için söyleyebilirim? Ben CodeBehind bazı kod sopa ve / veya ViewModel görünümden geçmek, ama bu tamamen MVVM bütün noktasını yenecek gibi görünüyor ...


Güncelleme

Sonunda MVVM modelinin "saflığını" ihlal ettim ve View'e bir Closedolay yayınlamasını ve bir Closeyöntemi ortaya koymasını sağladım . ViewModel daha sonra arayacaktır view.Close. Görünüm yalnızca bir arabirim aracılığıyla bilinir ve bir IOC kapsayıcısı aracılığıyla kablolanır, bu nedenle test edilebilirlik veya bakım kolaylığı kaybolmaz.

Kabul edilen cevabın -5 oy olduğu çok aptalca görünüyor! Birinin "saf" olurken bir sorunu çözerek elde ettiği iyi duyguların farkında olduğum halde, şüphesiz ki 200 satır olay, komut ve davranışın sadece bir satır yönteminden kaçınmasını düşünen tek kişi ben değilim "desen" ve "saflık" adı biraz saçma ....


2
Kabul edilen cevabı küçümsemedim, ama aşağı oyların nedeninin, bir durumda çalışsa bile, genel olarak yararlı olmadığı tahmin ediyorum. Başka bir yorumda kendiniz söylediniz: "Giriş formu bir 'iki alan' iletişim kutusu olsa da, çok daha karmaşık (ve dolayısıyla MVVM'yi garanti eder), ancak yine de kapatılması gereken birçok başka var ..."
Joe Beyaz

1
Ne Closedemek istediğini anlıyorum, ama şahsen genel durum için bile basit bir yöntemin hala en iyi çözüm olduğunu düşünüyorum. Diğer daha karmaşık diyaloglardaki diğer her şey MVVM ve databound'dur, ancak burada basit bir yöntem yerine devasa "çözümleri" uygulamak aptalca görünüyordu ...
Orion Edwards

2
Diyalog sonucunu döndürecek ve görünümü görünümden kapatacak olan asimsajjad.blogspot.com/2010/10/… iletişim kutusu sonucu için aşağıdaki bağlantıyı kontrol edebilirsinizModel
Asim Sajjad

3
Lütfen bu sorunun kabul edilen cevabını değiştirin. Bu işlevsellik için MVVM kullanımını sorgulayan birinden çok daha iyi olan birçok iyi çözüm var. Bu bir cevap değil, kaçınma.
ScottCher

2
@OrionEdwards Bence burada kalıbı kırmaya haklısın. Bir tasarım modelinin temel amacı, tüm ekibi aynı kurallara uyarak geliştirme döngülerini hızlandırmak, sürdürülebilirliği artırmak ve kodunuzu basitleştirmektir. Bu, dış kütüphanelere bağımlılıklar ekleyerek ve bir görevi yerine getirmek için yüzlerce kod satırı uygulayarak, sadece desenin "saflığını" feda etmek için inatçı olduğu için çok daha basit bir çözüm olduğunu göz ardı ederek elde edilmez. Sadece yapılması your've Ne belgeye sağlamak ve KISS kodunuzu ( k EEP i t s hort ve s uygu).
M463

Yanıtlar:


324

Thejuan'ın basit bir ekli mülk yazma cevabından ilham aldım . Stil yok, tetikleyici yok; bunun yerine, bunu yapabilirsiniz:

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

Bu neredeyse WPF ekibi doğru yapmış ve DialogResult'u bir bağımlılık özelliği haline getirmiş gibi temiz. Sadece bool? DialogResultViewModel'inize bir özellik koyun ve INotifyPropertyChanged'i uygulayın ve voilà, ViewModel'iniz bir özellik ayarlayarak Pencereyi kapatabilir (ve DialogResult'u ayarlayabilir). Olması gerektiği gibi MVVM.

DialogCloser kodu şöyledir:

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

Ben de bu gönderdiniz blogumda .


3
Bu en çok sevdiğim cevap! Ekli mülkü yazmak iyi iş.
Jorge Vargas

2
Güzel bir seçenek, ancak bu çözümde ince bir hata var. İletişim kutusunun Modeli Görüntüle tek bir ton ise, DialogResult değeri iletişim kutusunun bir sonraki kullanımına taşınır. Bu, kendini göstermeden önce anında iptal edeceği veya kabul edeceği, böylece iletişim kutusunun ikinci kez görünmeyeceği anlamına gelir.
Kodlama gitti

13
@HiTech Magic, hata ilk başta tek bir ViewModel kullanıyor gibi geliyor. (sırıtma) Cidden, neden dünyada tek bir ViewModel istesin ki? Değişken durumu küresel değişkenlerde tutmak kötü bir fikirdir. Testi kabus haline getirir ve test MVVM'yi ilk başta kullanmanızın nedenlerinden biridir.
Joe White

3
MVVM'nin mantığınızı belirli bir kullanıcı arayüzüyle sıkı bir şekilde birleştirmemesi değil mi? Bu durumda bool? kesinlikle WinForm gibi başka bir kullanıcı arayüzü tarafından kullanılamaz ve DialogCloser WPF'ye özgüdür. Peki bu bir çözüme nasıl uyuyor? Ayrıca, neden sadece bir Bağlama yoluyla bir Pencereyi kapatmak için 2x-10x kodu yazmalısınız?
David Anderson

2
@DavidAnderson, MVVM WinForms ile hiçbir durumda denemek olmaz; veri tabanı desteği çok zayıf ve MVVM iyi düşünülmüş bir bağlama sistemine güveniyor. Ve 2x-10x koduna yakın bir yerde değil. Bu kodu her pencere için bir kez değil, bir kez yazarsınız . Bundan sonra, görünümünüzdeki diğer her şey için zaten kullandığınız aynı mekanizmayı kullanarak tek satırlık bir bağlayıcı artı bir bildirim özelliği (örneğin, yalnızca, pencere). Diğer ödünleşmeler yapabilirsiniz, ancak bu benim için genellikle iyi bir anlaşma gibi görünüyor.
Joe White

64

Benim bakış açımdan bu soru oldukça iyi, çünkü aynı yaklaşım sadece "Giriş" penceresi için değil, her türlü pencere için de kullanılacak. Bir sürü öneriyi gözden geçirdim ve hiçbiri benim için uygun değil. Lütfen MVVM tasarım deseni makalesinden alınan önerimi inceleyin .

Her ViewModel sınıfı , türün olay ve özelliğine WorkspaceViewModelsahip olandan miras almalıdır . Mülkün varsayılan uygulaması etkinliği artıracaktır .RequestCloseCloseCommandICommandCloseCommandRequestClose

Pencere kapalı olsun amacıyla, OnLoadedpencerenizin yöntemi geçersiz kılınan olmalıdır:

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

veya OnStartupuygulamanızın yöntemi:

    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();
    }

Ben RequestCloseolay ve CloseCommandmülkiyet uygulama WorkspaceViewModeloldukça açık sanırım , ama tutarlı olmasını onlara göstereceğim:

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;
    }
}

Ve kaynak kodu 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
}

PS : Bu kaynaklar için bana kötü davranma! Dün olsaydım bu beni birkaç saat kurtaracaktı ...

PPS Herhangi bir yorum veya öneri kabul edilir.


2
Umm, customer.RequestCloseXAML dosyanızın arkasındaki kodda olay işleyicisine bağlandığınız gerçeği MVVM modelini ihlal etmiyor mu? ClickZaten arkadaki koda dokunduğunuz ve yaptığınız ilk bakışta kapatma düğmenizdeki olay işleyicisine bağlanabilirsiniz this.Close()! Sağ?
GONeale

1
Olay yaklaşımı ile ilgili çok fazla sorunum yok ama RequestClose kelimesini sevmiyorum çünkü bana göre hala Görünümün uygulanması hakkında çok fazla bilgi var. Bağlam göz önüne alındığında daha anlamlı olma eğiliminde olan ve görüşün yanıt olarak ne yapması gerektiği konusunda daha az ima eden IsCancelled gibi özellikleri göstermeyi tercih ederim.
jpierson

18

Pencereyi kapatmak için ekli davranışlar kullandım. ViewModel üzerinde bir "sinyal" özelliği ekli davranışa bağlama (aslında bir tetikleyici kullanıyorum) true olarak ayarlandığında, davranış pencereyi kapatır.

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


Bu, Pencerede herhangi bir kod bağlama gerektirmeyen (ve başka bir yaklaşım önermek yerine aslında kalıcı bir Pencereyi kapatmayan) tek cevaptır. Yazık ki, Stil ve Tetikleyici ve tüm bu muck ile çok karmaşıklık gerektirir - bu gerçekten tek satırlı bir davranışla yapılabilir gibi görünüyor.
Joe White

4
Şimdi tek satırlık bir davranış ile yapılabilir. Cevabımı gör: stackoverflow.com/questions/501886/…
Joe White

15

MVVM'nin artılarını ve eksilerini burada tartışan birçok yorum var. Benim için Nir ile aynı fikirdeyim; bu modeli uygun bir şekilde kullanma meselesidir ve MVVM her zaman uymaz. İnsanlar MVVM'ye uyması için SADECE yazılım tasarımının en önemli ilkelerinin tümünü feda etmeye istekli görünüyorlar.

Dedi ki, .. Bence davanız biraz yeniden düzenleme ile iyi bir uyum olabilir.

Karşılaştığım çoğu durumda, WPF birden fazla OLMADAN geçmenizi sağlar Window. Belki Windows s yerine Frames ve Pages kullanmayı deneyebilirsiniz DialogResult.

Sizin durumunuzda benim önerim LoginFormViewModelişlemek LoginCommandolacaktır ve giriş geçersizse, bir özelliği LoginFormViewModeluygun bir değere ( falseveya gibi bir numaralandırma değerine UserAuthenticationStates.FailedAuthentication) ayarlayın. Başarılı bir giriş ( trueveya başka bir numaralandırma değeri) için de aynısını yaparsınız . Daha sonra DataTrigger, çeşitli kullanıcı kimlik doğrulama durumlarına yanıt veren ve öğesinin özelliğini Setterdeğiştirmek için bir basit kullanabilirsiniz . SourceFrame

Ben senin giriş Penceresi bir dönüşü olması DialogResult, nerede kafanız karıştığını düşünüyorum; bu DialogResultgerçekten ViewModel cihazınızın bir özelliği. WPF ile kuşkusuz sınırlı deneyimimde, bir şey doğru hissetmediğinde, çünkü WinForms'da aynı şeyi nasıl yapacağımı düşünürüm.

Umarım yardımcı olur.


10

Giriş iletişim kutusunun ilk oluşturulan pencere olduğunu varsayarsak, bunu LoginViewModel sınıfınızın içinde deneyin:

    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;
        }
    }

Erkekler bu basit ve harika çalışıyor. Şu anda bu yaklaşımı kullanıyorum.
Erre Efe

Sadece ANA pencerede çalışır. Bu yüzden başka pencereler için kullanmayın.
Oleksii

7

Bu basit ve temiz bir çözümdür - ViewModel'e bir olay eklersiniz ve o olay tetiklendiğinde Pencereyi kapatmasını söylersiniz.

Daha fazla bilgi için blog gönderime bakın , ViewModel'den pencereyi kapat .

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>

ViewModel:

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

Not: Örnekte Prizma kullanılır DelegateCommand(bkz. Prizma: Komut ), ancak ICommandbu konuda herhangi bir uygulama kullanılabilir.

Bu resmi paketteki davranışları kullanabilirsiniz .


2
+1 ancak cevabın kendisinde daha fazla ayrıntı vermelisiniz, örneğin bu çözüm, İfade Karışımı Etkileşimi derlemesine başvuru gerektiriyor.
surfen

6

Bunu ele alma şeklim, ViewModel'ime bir olay işleyici eklemektir. Kullanıcı başarıyla giriş yaptığında olayı tetiklerdim. Benim görüşüme göre bu olaya eklenirdim ve kovulduğunda pencereyi kapatırdım.


2
Ben de genellikle bunu yaparım. Her ne kadar bu yeni çıkmış wpf komuta şeyler göz önüne alındığında biraz kirli gibi görünüyor.
Botz3000

4

İşte başlangıçta yaptığım şey, bu işe yarıyor, ancak oldukça uzun ve çirkin görünüyor (küresel statik bir şey asla iyi değil)

1: Uygulama.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);
}

Daha sonra tüm bu kod kaldırıldı ve sadece LoginFormViewModelgörünümde Kapat yöntemi çağrı vardı . Çok daha hoş ve takip edilmesi daha kolay oldu. IMHO örüntülerin amacı, insanlara uygulamanızın ne yaptığını anlamaları için daha kolay bir yol sağlamaktır ve bu durumda MVVM, onu kullanmamış olmama göre anlamayı çok daha zorlaştırıyordu ve şimdi bir anti- desen oldu.


3

FYI, ben de aynı problemle karşılaştım ve bence en iyi cevap olmasa da, küresel veya statik gerektirmeyen bir çalışma buldum. Buna siz karar vermenize izin verdim.

Benim durumumda, görüntülenecek Pencereyi başlatan ViewModel (buna ViewModelMain diyelim) LoginFormViewModel'i de bilir (yukarıdaki durumu örnek olarak kullanarak).

Yani ne yaptım ICommand türü olan LoginFormViewModel üzerinde bir özellik oluşturmak oldu (Lets CloseWindowCommand diyelim). Sonra, pencerede .ShowDialog () çağırmadan önce, ben başlattığım pencerenin Window.Close () yöntemine LoginFormViewModel üzerinde CloseWindowCommand özelliği ayarlayın. Sonra LoginFormViewModel içinde tek yapmam gereken pencereyi kapatmak için CloseWindowCommand.Execute () çağırmak.

Sanırım bir geçici çözüm / kesmek biraz, ama gerçekten MVVM desen kırmadan iyi çalışıyor.

Bu süreci istediğiniz kadar eleştirmekten çekinmeyin, alabilirim! :)


Tamamen baktığımdan emin değilim, ancak bu, MainWindow'unuzun LoginWindow'unuzdan önce başlatılması gerektiği anlamına gelmiyor mu? Mümkünse kaçınmak istediğim bir şey var
Orion Edwards

3

Bu muhtemelen çok geç, ama aynı problemle karşılaştım ve benim için çalışan bir çözüm buldum.

İletişim kutusu olmadan nasıl uygulama oluşturacağımı anlayamıyorum (belki de sadece bir zihin bloğu). Ben MVVM ile çıkmaza girmiştim ve bir diyalog gösteriyordum. Bu yüzden bu CodeProject makalesine rastladım:

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

Temel olarak bir pencerenin başka bir pencerenin (xaml içinde izin verilmez) görsel ağacında olmasını sağlayan bir UserControl. Ayrıca IsShowing adlı bir boolean DependencyProperty'yi de ortaya koyar.

Denetimin Content özelliği her tetiklendiğinde temelde iletişim kutusunu görüntüleyen, tipik olarak, kaynaklardan oluşan bir dosya gibi bir stil ayarlayabilirsiniz: = null

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

İletişim kutusunu görüntülemek istediğiniz görünümde aşağıdakilere sahip olmanız yeterlidir:

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

ViewModel'inizde tek yapmanız gereken özelliği bir değere ayarlamaktır (Not: görünümün bir şey olduğunu bilmek için ViewModel sınıfı INotifyPropertyChanged'i desteklemelidir).

şöyle:

DialogViewModel = new DisplayViewModel();

ViewModel'i View ile eşleştirmek için, kaynakta birictionary için böyle bir şeye sahip olmalısınız:

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

Tüm bunlar ile iletişim kutusunu göstermek için tek satırlık bir kod alırsınız. Aldığınız sorun, iletişim kutusunu yalnızca yukarıdaki kodla gerçekten kapatamamanızdır. Bu nedenle, DisplayViewModel'in yukarıdaki koddan miras aldığı ve yerine yukarıdaki kodu yazdığı bir ViewModel temel sınıfına bir olay eklemeniz gerekir.

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

Ardından, geri arama yoluyla iletişim kutusunun sonucunu işleyebilirsiniz.

Bu biraz karmaşık görünebilir, ancak zemin döşendikten sonra oldukça basittir. Yine bu benim uygulama, eminim başkaları var :)

Umarım bu yardımcı olur, beni kurtardı.


3

Tamam, bu soru neredeyse 6 yaşında ve hala burada doğru cevap olduğunu düşündüğüm şeyi bulamıyorum, bu yüzden "2 sent" i paylaşmama izin ver ...

Aslında bunu yapmanın 2 yolu var, birincisi basit olanı ... ikincisi sağdaki, bu yüzden doğru olanı arıyorsanız, # 1'i atlayın ve # 2'ye atlayın :

1. Hızlı ve Kolay (ancak tam değil)

Sadece küçük bir projem varsa, bazen yalnızca ViewModel'de bir CloseWindowAction oluştururum :

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

Ve her kim View görünümü sandığında, ya da View'in arkasındaki kodda Eylemin çağıracağı Yöntemi ayarladım:

(MVVM'nin View ve ViewModel'i ayırmakla ilgili olduğunu unutmayın ... View'un kod davranışları hala View'dir ve uygun ayırma olduğu sürece deseni ihlal etmezsiniz)

Bazı ViewModel'ler yeni bir pencere oluşturursa:

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

Veya Ana Pencerenizde isterseniz, Görünümünüzün yapıcısının altına yerleştirin:

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

pencereyi kapatmak istediğinizde, ViewModel'inizde Eylem'i çağırmanız yeterlidir.


2. Doğru yol

Şimdi bunu yapmanın uygun yolu Prizma (IMHO) kullanmaktır ve bununla ilgili her şeyi burada bulabilirsiniz .

Bir Etkileşim İsteği yapabilir , yeni Pencerenizde ihtiyacınız olan verilerle doldurabilir , öğle yemeği yiyebilir, kapatabilir ve hatta verileri geri alabilirsiniz . Tüm bunlar kapsüllenmiş ve MVVM onaylanmıştır. Hatta Pencere kapatıldı nasıl bir statü elde Kullanıcı eğer gibi Canceledya Accepted(Tamam düğmesi) Pencere ve de gerekirse veri geri . Biraz daha karmaşık ve Cevap # 1, ancak çok daha eksiksiz ve Microsoft tarafından Önerilen Bir Model.

Verdiğim bağlantı tüm kod snippet'lerine ve örneklere sahip, bu yüzden buraya herhangi bir kod yerleştirmek için uğraşmayacağım, sadece Prism Hızlı Başlangıç'ı indirme ve çalıştırma makalesini okuyun, sadece biraz daha ayrıntılı anlamak için gerçekten basit işe yarar, ancak faydaları sadece bir pencereyi kapatmaktan daha büyüktür.


Güzel bir yol, ancak ViewModels'ın çözünürlüğü ve atanması her zaman bu kadar basit olamaz. Aynı görünüm modeli birçok Windows'un DataContext'i ise ne olur?
Kylo Ren

Sonra sanırım tüm pencereleri bir kerede kapatmak zorunda kalacaksınız, bir Eylemin aynı anda birçok delege tetikleyebileceğini unutmayın, sadece +=bir temsilci eklemek için kullanın ve Eylem'i arayın, hepsini tetikleyecektir .... Ya da VM'nizde hangi pencerenin kapatılacağının farkında olması için özel bir mantık yapmak zorundasınız (belki de Kapat Eylemlerinin bir koleksiyonuna sahip olabilirsiniz) .... Ama bence birden fazla görünümün bir VM'ye bağlı olması en iyi uygulama değil, bir View ve bir VM örneğine sahip olmak, birbirine bağlı ve belki de tüm Views'a bağlı tüm alt VM'leri yöneten bir ana VM'ye sahip olmak daha iyi.
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 uygulamasının, View uygulamasının kaydettiği bir etkinliği göstermesini sağlayabilirsiniz. Ardından, ViewModel görünümü kapatma zamanına karar verdiğinde, görünümün kapanmasına neden olan olayı tetikler. Belirli bir sonuç değerinin geri iletilmesini istiyorsanız, bunun için ViewModel'de bir özelliğiniz olur.


Buna katılıyorum - basitlik değerlidir. Bir sonraki küçük geliştirici bu projeyi üstlenmek için işe alındığında ne olacağını düşünmem gerekiyor. Tahminimce, tarif ettiğiniz gibi bunu doğru yapmak için çok daha iyi bir şansı olacak. Bu kodu sonsuza kadar koruyacağınızı düşünmedikçe? +1
Dean

2

Sadece çok sayıda cevaba eklemek için aşağıdakileri eklemek istiyorum. ViewModel'inizde bir ICommand bulunduğunu ve bu komutun penceresini (veya bu konudaki herhangi bir işlemi) kapatmak istediğinizi varsayarsak, aşağıdakine benzer bir şey kullanabilirsiniz.

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

Mükemmel değildir ve test edilmesi zor olabilir (bir statiki taklit etmek / saplamak zor olduğu için), ancak diğer çözümlerden daha temizdir (IMHO).

Erick


Basit cevabını gördüğümde çok mutlu oldum! ama o da işe yaramıyor! Visual basic ile açılıp kapanmam gerekiyor. VB'de (windows [i] .DataContext == this) denkliğini biliyor musunuz?
Ehsan

Sonunda anladım! :) Teşekkürler. Windows (i) .DataContext benimse
Ehsan

Bir pencere açmak için de aynı basit yolu biliyor musunuz? Çocuk vizöründe bazı veriler de göndermem ve almam gerekiyor.
Ehsan

1

Joe White'ın çözümünü uyguladım, ancak ara sıra " DialogResult yalnızca Pencere oluşturulduktan ve iletişim kutusu olarak gösterildikten sonra ayarlanabilir " hatalarıyla karşılaştım.

Görünüm kapatıldıktan sonra ViewModel'i saklıyordum ve daha sonra aynı VM'yi kullanarak yeni bir Görünüm açtım. Eski Görünümün çöp toplanmadan önce yeni Görünümü kapatmanın , DialogResultChanged'in DialogResult özelliğini kapalı pencerede ayarlamaya çalışmasıyla sonuçlandığını ve böylece hatayı provoke ettiği anlaşılıyor .

Benim çözüm DialogResultChanged pencerenin IsLoaded özelliğini kontrol etmek için değiştirmek oldu :

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

Bu değişiklik yapıldıktan sonra, kapalı iletişim kutularına yapılan ekler yok sayılır.


Teşekkürler bayım. Aynı sorunu yaşadım
DJ Burb

1

Programlı olarak oluşturulmuş bir pencerede bir kullanıcı kontrolü göstermem gerektiğinden, Joe White'ın cevabını ve Adam Mills'in cevabından bazı kodları harmanladım . DialogCloser'ın pencerede olması gerekmez, kullanıcı kontrolünün kendisinde olabilir

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

DialogCloser, pencerenin kendisine bağlı değilse, kullanıcı denetiminin penceresini bulur.

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

Davranış burada en uygun yoldur.

  • Bir yandan, verilen görünüm modeline bağlanabilir ("formu kapat!" Sinyalini verebilir)

  • Başka bir elden, formun kendisine erişimi vardır, böylece gerekli forma özel olaylara abone olabilir veya onay iletişim kutusunu veya başka bir şeyi gösterebilir.

Gerekli davranışı yazmak ilk kez sıkıcı görülebilir. Ancak, bundan sonra, tam olarak tek katmanlı XAML snippet'i ile ihtiyacınız olan her formda yeniden kullanabilirsiniz. Ve gerekirse, ayrı bir montaj olarak çıkarabilirsiniz, böylece istediğiniz herhangi bir projeye dahil edilebilir.


0

Pencereyi neden komut parametresi olarak geçirmiyorsunuz?

C #:

 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'yi bir Pencere türüyle sınırlamanın iyi bir fikir olduğunu düşünmüyorum.
Shimmy Weitzhandler

2
VM'yi Window"saf" MVVM olmayan bir türle sınırlamanın iyi bir fikir olduğunu düşünmüyorum . VM'nin bir nesneyle sınırlı olmadığı bu cevaba bakınız Window.
Shimmy Weitzhandler

bu şekilde bağımlılık her zaman durum olamayacak bir Düğmeye konur. Ayrıca UI türünü ViewModel'e geçirmek Kötü bir uygulamadır.
Kylo Ren

0

Başka bir çözüm, DialogResult gibi Görünüm Modelinde INotifyPropertyChanged ile özellik oluşturmak ve ardından Arkasındaki Kodda şunu yazmaktır:

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;
        }
    }
}

En önemli parça _someViewModel_PropertyChanged. DialogResultPropertyNamebazı genel const dizesi olabilir SomeViewModel.

Bu tür bir numarayı ViewModel'de zor olduğunda Görünüm Denetimlerinde bazı değişiklikler yapmak için kullanıyorum. ViewModel'de, View'da istediğiniz her şeyi yapabilirsiniz. ViewModel hala 'birim test edilebilir' ve arkasındaki koddaki bazı küçük kod satırları hiçbir fark yaratmıyor.


0

Ben şu şekilde giderdim:

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

Tüm cevapları okudum ama söylemeliyim ki, çoğu yeterince iyi ya da daha kötü değil.

Bu sorumluluğu diyalog penceresini göstermek ve diyalog sonucunu döndürmek olan DialogService sınıfıyla güzel bir şekilde halledebilirsiniz . Ben yarattımUygulama ve kullanımını gösteren örnek bir proje .

İşte en önemli parçalar:

//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();
    }
}

Bu daha basit değil mi? EventAggregator veya diğer benzer çözümlerden daha sağlam, daha okunabilir ve son fakat hata ayıklamak daha kolay değil mi?

Gördüğünüz gibi, benim görünüm modellerimde, burada yazımda açıklanan ViewModel ilk yaklaşımını kullandım: çağırmak için en iyi uygulama

Tabii ki, gerçek dünyada, DialogService.ShowDialogiletişim kutusunu yapılandırmak için daha fazla seçeneğe sahip olmalıdır, örneğin yürütmeleri gereken düğmeler ve komutlar. Bunu yapmanın farklı yolları vardır, ancak kapsam dışıdır :)


0

Bu, viewmodel aracılığıyla nasıl yapılacağı sorusunu yanıtlamasa da, bu yalnızca XAML + harman SDK'sını kullanarak nasıl yapılacağını gösterir.

Her ikisi de Microsoft'tan NuGet aracılığıyla bir paket olarak kullanabileceğiniz Blend SDK'dan iki dosya indirmeyi ve kullanmayı seçtim. Dosyalar:

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

Microsoft.Expression.Interactions.dll, görünüm ayarlama veya görünüm modunuzda veya başka bir hedefte bir yöntem başlatma ve ayrıca başka widget'lara sahip olma gibi güzel özellikler sunar.

Bazı 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>

Sadece basit Tamam / İptal davranışına gidiyorsanız, pencere Window.ShowDialog () ile gösterildiği sürece IsDefault ve IsCancel özelliklerini kullanarak uzaklaşabileceğinizi unutmayın.
Şahsen IsDefault özelliği true olarak ayarlanmış bir düğme w / sorunları vardı, ama sayfa yüklendiğinde gizlendi. Gösterildikten sonra güzel oynamak istemiyor gibi görünüyordu, bu yüzden sadece bunun yerine yukarıda gösterilen Window.DialogResult özelliğini ayarlıyorum ve benim için çalışıyor.


0

İşte basit hata ücretsiz çözüm (kaynak kodu ile), Benim için çalışıyor.

  1. ViewModel'inizi türet INotifyPropertyChanged

  2. ViewModel'de gözlemlenebilir özellik CloseDialog oluşturma

    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. Bu özellik değişikliği için Görünümde bir İşleyici ekleyin

        _loginDialogViewModel = new LoginDialogViewModel();
        loginPanel.DataContext = _loginDialogViewModel;
        _loginDialogViewModel.PropertyChanged += OnPropertyChanged;
  4. Şimdi neredeyse bitti. Olay işleyicisindeDialogResult = true

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

0

Bir oluşturma Dependency Propertyiçinde senin View/ herhangi UserControl(veya Windowsize yakın istiyorsunuz). Aşağıdaki gibi:

 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
        }

Ve ViewModel mülkünüzden bağlayın :

<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}"

Emlak VeiwModel:

private bool closeWindow;

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

Şimdi CloseWindowViewModel'deki değeri değiştirerek kapatma işlemini tetikleyin . :)


-2

Pencereyi kapatmanız gereken yere, bunu yalnızca görünüm modeline koyun:

ta-da

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

Bir ViewModel hiçbir şekilde bir UIElement içermemelidir , çünkü bu hata yaratabilir
WiiMaxx

DataContext devralınırsa birkaç pencere olursa ne olur?
Kylo Ren

ta-da, bu tamamen MVVM değil.
Alexandru Dicu

-10
Application.Current.MainWindow.Close() 

Bu yeterli!


3
-1 Sadece kapatmak istediğiniz pencere ana pencereyse doğrudur ... Giriş iletişim kutusu için çok olası bir varsayım ...
surfen
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.