MVVM ile WPF'de İletişim Kutularını Kullanma


235

WPF için MVVM modelinde iletişim kutularını işlemek daha karmaşık işlemlerden biridir. Görünüm modeliniz görünüm hakkında hiçbir şey bilmediğinden, iletişim iletişimi ilginç olabilir. Bir ICommandgörünümü açtığında bir iletişim kutusu görünebilir.

Diyalogların sonuçlarını ele almanın iyi bir yolunu bilen var mı? Gibi pencereler hakkında konuşuyorum MessageBox.

Bunu yapmanın yollarından biri, görünüm iletişim kutusunda, bir iletişim kutusu gerektiğinde abone olacağı bir olay olmasıydı.

public event EventHandler<MyDeleteArgs> RequiresDeleteDialog;

Bu Tamam, ama görünüm uzak kalmak istiyorum bir şey olan kod gerektirir anlamına gelir.


Neden Görünüm'deki yardımcı bir nesneye bağlanmıyorsunuz?
Paul Williams

1
Ne demek istediğinden emin değilim.
Ray Booysen

1
Soruyu anlarsam, VM'nin iletişim kutularını açmasını istemezsiniz ve Görünüm'de arka planda kod istemezsiniz. Ayrıca, olaylara komutları tercih ettiğiniz gibi geliyor. Tüm bunları kabul ediyorum, bu yüzden iletişim kutusunda işlemek için bir komut ortaya koyan bir yardımcı sınıf kullanın. Bu soruyu burada başka bir iş parçacığında cevapladım: stackoverflow.com/a/23303267/420400 . Bununla birlikte, son cümle , Görünüm'ün herhangi bir yerinde hiç kod istemediğiniz gibi ses çıkarır . Bu endişeyi anlıyorum, ancak söz konusu kod sadece bir koşullu ve değişmesi muhtemel değil.
Paul Williams

4
Görünüm modeli her zaman iletişim kutusunun oluşturulmasındaki mantıktan sorumlu olmalıdır, ilk etapta var olmasının nedeni budur. Bu, görünümün kendisinin yaratılmasını kaldırdığını (ve yapmaması gerektiğini) söyledi. Bu konuda bir makale yazdım codeproject.com/Articles/820324/… burada iletişim kutularının tüm yaşam döngüsünün düzenli WPF veri bağlama yoluyla ve MVVM modelini bozmadan yönetilebileceğini gösterdim.
Mark Feldman

Yanıtlar:


131

1990'ların kalıcı diyaloglarını sürdürmeyi ve bunun yerine sanal makinede bir boole arkasına bağlı görünürlüğü olan bir bindirme (tuval + mutlak konumlandırma) olarak bir kontrol uygulanmasını öneriyorum. Ajax tipi kontrole daha yakın.

Bu çok faydalı:

<BooleanToVisibilityConverter x:Key="booltoVis" />

de olduğu gibi:

<my:ErrorControl Visibility="{Binding Path=ThereWasAnError, Mode=TwoWay, Converter={StaticResource booltoVis}, UpdateSourceTrigger=PropertyChanged}"/>

Bir kullanıcı kontrolü olarak nasıl uygulandığımı aşağıda görebilirsiniz. 'X' işaretine tıklamak, kontrolün arkasındaki usercontrol kodundaki bir kod satırında kapanmasını sağlar. (Bir dll içinde bir .exe ve ViewModels benim görüşleri var, ben UI manipüle kodu hakkında kötü hissetmiyorum.)

Wpf iletişim kutusu


20
Evet ben de bu fikri seviyorum ama nasıl göstermek ve iletişim sonucu ondan almak vb bu kontrol bazı örnek görmek istiyorum Özellikle Silverlight MVVM senaryoda.
Roboblob

16
Kullanıcının bu iletişim kutusunun altındaki denetimlerle etkileşimde bulunmasını nasıl önlersiniz?
Andrew Garrison

16
Bu yaklaşımla ilgili sorun, en azından kaplama sisteminde bazı ağır değişiklikler yapmadan ilkinden ikinci bir mod iletişim kutusu
açamamanız

6
Bu yaklaşımla ilgili bir diğer sorun da "iletişim kutusunun" taşınamamasıdır. Uygulamalarımızda, kullanıcının arkasında ne olduğunu görebilmesi için hareketli bir diyalogumuz olmalıdır.
JAB

12
Bu yaklaşım benim için korkunç görünüyor. Neyi kaçırıyorum? Bu gerçek bir iletişim kutusundan nasıl daha iyi?
Jonathan Wood

51

Bunun için bir arabulucu kullanmalısınız. Arabulucu, bazı uygulamalarında Messenger olarak da bilinen ortak bir tasarım modelidir . Bu Kayıt / Bildir türünde bir paradigmadır ve ViewModel ve Views'unuzun düşük bağlantılı bir mesajlaşma mekanizması aracılığıyla iletişim kurmasını sağlar.

Google WPF Disciples grubuna göz atmanız ve yalnızca Mediator'ı aramanız gerekir. Cevaplardan çok memnun kalacaksınız ...

Ancak bununla başlayabilirsiniz:

http://joshsmithonwpf.wordpress.com/2009/04/06/a-mediator-prototype-for-wpf-apps/

Zevk almak !

Düzenleme: MVVM Light Toolkit ile bu sorunun cevabını burada görebilirsiniz:

http://mvvmlight.codeplex.com/Thread/View.aspx?ThreadId=209338


2
Marlon Grech, arabulucunun yepyeni bir uygulamasını yayınladı: marlongrech.wordpress.com/2009/04/16/…
Roubachof

21
Sadece bir açıklama: Arabulucu deseni WPF Disciples tarafından tanıtılmadı, klasik bir GoF kalıbı ... ( dofactory.com/Patterns/PatternMediator.aspx ). Aksi takdirde güzel cevap;)
Thomas Levesque

10
Lütfen Tanrım, arabulucu veya lanet bir haberci kullanmayın. Tüm kod tabanınızdaki her olaya abone olan ve her olayı işleyen birçok noktayı bir şekilde hatırlayamazsanız, etrafında uçan düzinelerce mesaj içeren bu tür kodların hatalarını ayıklamak çok zorlaşır. Yeni geliştiriciler için bir kabus haline geliyor. Aslında, tüm MvvMLight kütüphanesinin, asenkronize mesajların yaygın ve gereksiz kullanımı için büyük bir anti-desen olduğunu düşünüyorum. Çözüm basit: tasarımınızın ayrı bir iletişim hizmetini (IDialogService) arayın. Arayüz, geri aramalar için yöntemlere ve olaylara sahiptir.
Chris Bordeman

34

İyi bir MVVM iletişim kutusu:

  1. Yalnızca XAML ile beyan edilmelidir.
  2. Tüm davranışlarını veritabanından alın.

Ne yazık ki, WPF bu özellikleri sağlamamaktadır. Bir iletişim kutusu gösterilmesi için arkasına kod çağrısı gerekir ShowDialog(). Diyalogları destekleyen Window sınıfı, XAML'de bildirilemez, böylece DataContext.

Bunu çözmek için, mantıksal ağaçta oturan ve bir veritabanına veri sağlayan Windowve iletişim kutusunu gösteren ve gizleyen tutamaçları içeren bir XAML saplama kontrolü yazdım . Burada bulabilirsiniz: http://www.codeproject.com/KB/WPF/XAMLDialog.aspx

Kullanımı basittir ve ViewModel'inizde garip değişiklikler gerektirmez ve etkinlik veya mesaj gerektirmez. Temel çağrı şöyle görünür:

<dialog:Dialog Content="{Binding Path=DialogViewModel}" Showing="True" />

Muhtemelen ayarlanan bir stil eklemek istersiniz Showing. Makalemde açıklıyorum. Umarım bu sana yardımcı olur.


2
MVVM'de iletişim pencerelerini gösterme sorununa gerçekten ilginç bir yaklaşım.
dthrasher

2
"Showing a dialog requires a code-behind"mmm bunu ViewModel'de arayabilirsiniz
Brock Hensley

Nokta 3'ü eklerdim - görünümdeki diğer nesnelere bağlanmakta serbestsiniz. İletişim kutusunun kodunu boş bırakmak, görünümde hiçbir yerde C # kodu bulunmadığı anlamına gelir ve veri bağlama VM'ye bağlanma anlamına gelmez.
Paul Williams

25

Bu yaklaşımı MVVM ile diyaloglar için kullanıyorum .

Şimdi tek yapmam gereken, görünüm modelimden aşağıdakileri aramak.

var result = this.uiDialogService.ShowDialog("Dialogwindow title goes here", dialogwindowVM);

uiDialogService hangi kitaplıktan geliyor?
aggietech

1
kütüphane yok. yalnızca küçük bir arayüz ve uygulamadır: stackoverflow.com/questions/3801681/… . Adil olmak gerekirse benim ihtiyaçları için biraz daha fazla
yükleri vardır

16

Mevcut çözümüm, bahsettiğiniz sorunların çoğunu çözüyor, ancak platforma özgü şeylerden tamamen soyutlanmış ve tekrar kullanılabilir. Ayrıca hiçbir kod arkasında sadece ICommand uygulayan DelegateCommands ile bağlama kullanılır. İletişim kutusu temel olarak bir Görünümdür - kendi ViewModel özelliğine sahip ayrı bir denetimdir ve ana ekranın ViewModel'inden gösterilir ancak DelagateCommand bağlamasıyla UI'den tetiklenir.

Tam Silverlight 4 çözümünü buradan görün MVVM ve Silverlight 4 ile kalıcı diyaloglar


@Elad Katz'ın yaklaşımında olduğu gibi, cevabınız bağlantılı içerikten yoksundur - lütfen cevabınızı ekleyerek geliştirin, çünkü burada SO'da iyi bir cevap olarak kabul edilir. Yine de katkınız için teşekkürler! :)
Yoda

6

MVVM'yi öğrenirken (hala öğrenirken) bu kavramla gerçekten bir süre mücadele ettim. Ne karar verdim ve başkalarının zaten karar verdiğini ama benim için net olmayan şey olduğunu düşünüyorum:

Asıl düşüncem, ViewModel'in bir iletişim kutusunu doğrudan çağırmasına izin verilmemesi gerektiğinden, bir iletişim kutusunun nasıl görüneceğine karar veren bir işi yoktur. Bu nedenle MVP (örn. View.ShowSaveFileDialog ()) gibi mesajlar çok nasıl geçirebileceği hakkında düşünmeye başladı. Ancak bunun yanlış bir yaklaşım olduğunu düşünüyorum.

ViewModel için bir iletişim kutusunu doğrudan çağırmak uygundur. Ancak, bir ViewModel'i test ederken, iletişim kutusunun testiniz sırasında açılacağı veya hep birlikte başarısız olacağı anlamına gelir (bunu gerçekten denemedim).

Bu nedenle, test sırasında gerçekleşmesi gereken şey, iletişim kutusunuzun "test" sürümünü kullanmaktır. Bu, herhangi bir iletişim kutusu için bir Arabirim oluşturmanız ve iletişim kutusu yanıtını taklit etmeniz veya varsayılan bir davranışa sahip olacak bir test örneği oluşturmanız gerektiği anlamına gelir.

Bağlama bağlı olarak size doğru sürümü sağlamak için yapılandırabileceğiniz bir tür Hizmet Bulucu ya da IoC kullanıyor olmalısınız.

Bu yaklaşımı kullanarak, ViewModeliniz hala test edilebilir ve iletişim kutularınızı nasıl taklit ettiğinize bağlı olarak davranışı kontrol edebilirsiniz.

Bu yardımcı olur umarım.


6

Bunu yapmanın iki iyi yolu vardır: 1) bir iletişim servisi (kolay, temiz) ve 2) görünüm destekli. Görünüm destekli bazı düzgün özellikler sağlar, ancak genellikle buna değmez.

DİYALOG HİZMETİ

a) kurucu veya bazı bağımlılık konteyneri gibi bir iletişim servisi arayüzü:

interface IDialogService { Task ShowDialogAsync(DialogViewModel dlgVm); }

b) IDialogService uygulamanız bir pencere açmalı (veya etkin pencereye bir miktar denetim enjekte etmeli), verilen dlgVm türünün adına karşılık gelen bir görünüm oluşturmalıdır (kapsayıcı kaydı veya kuralı veya ilişkili DataTemplates türüne sahip bir ContentPresenter kullanın). ShowDialogAsync bir TaskCompletionSource oluşturmalı ve .Task özelliklerini döndürmelidir. DialogViewModel sınıfının kendisi, kapatmak istediğinizde türetilmiş sınıfta çağırabileceğiniz bir olaya ihtiyaç duyar ve iletişim kutusunu gerçekten kapatmak / gizlemek ve TaskCompletionSource'u tamamlamak için iletişim kutusu görünümünde izleyebilirsiniz.

b) Kullanmak için, sadece DialogViewModel'den türetilmiş bir sınıf örneğinizde this.DialogService.ShowDialog (myDlgVm) 'u arayın. Geri gelmeyi bekledikten sonra, ne olduğunu belirlemek için iletişim kutusu sanal makinenize eklediğiniz özelliklere bakın; geri aramaya bile ihtiyacınız yok.

YARDIMLI GÖSTER

Bu, görüşünüzü görünüm modelindeki bir etkinliği dinlemenizi sağlar. Tüm bunlar, eğer eğiminiz varsa arkasındaki kodu ve kaynak kullanımını önlemek için bir Karışım Davranışı'na sarılabilir (FMI, steroidlerde bir tür Blendable ekli özelliği görmek için "Davranış" sınıfını alt sınıf). Şimdilik, bunu her görünümde manuel olarak yapacağız:

a) Özel bir yük (DialogViewModel türetilmiş bir sınıf) ile bir OpenXXXXXDialogEvent oluşturun.

b) Görünümün OnDataContextChanged etkinliğinde olaya abone olmasını sağlayın. Eski değer! = Null ise ve Window'un Unloaded olayında gizlediğinizden ve abonelikten çıktığınızdan emin olun.

c) Etkinlik tetiklendiğinde, sayfanızdaki bir kaynakta olabilecek görünümünüzü açmasını sağlayın veya başka bir yerde (iletişim hizmeti yaklaşımında olduğu gibi) kongre ile bulabilirsiniz.

Bu yaklaşım daha esnektir, ancak daha fazla çalışma gerektirir. Çok kullanmıyorum. Tek güzel avantajı, görünümü fiziksel olarak bir sekmenin içine yerleştirme yeteneğidir. Geçerli kullanıcı denetiminin sınırlarına yerleştirmek için bir algoritma kullandım ya da yeterince büyük değilse, yeterince büyük bir kap bulunana kadar görsel ağacı yukarı doğru hareket ettirdim.

Bu, iletişim kutularının gerçekte kullanıldığı yere yakın olmasına izin verir, uygulamanın geçerli etkinlikle ilgili kısmını yalnızca karartır ve kullanıcının iletişim kutularını manuel olarak itmek zorunda kalmadan uygulama içinde hareket etmesine izin verir, hatta birden fazla yarı kalıcı iletişim kutuları farklı sekmelerde veya alt görünümlerde açılır.


Bir diyalog servisi kesinlikle çok daha kolay ve genelde yaptığım şey. Ayrıca, üst görünüm modeli kapanırken veya iptal edilirken gerekli olan görünümün iletişim kutusunu üst görünüm modelinden kapatmayı da kolaylaştırır.
Chris Bordeman

4

Dondurulabilir komut kullanma

<Grid>
        <Grid.DataContext>
            <WpfApplication1:ViewModel />
        </Grid.DataContext>


        <Button Content="Text">
            <Button.Command>
                <WpfApplication1:MessageBoxCommand YesCommand="{Binding MyViewModelCommand}" />
            </Button.Command>
        </Button>

</Grid>
public class MessageBoxCommand : Freezable, ICommand
{
    public static readonly DependencyProperty YesCommandProperty = DependencyProperty.Register(
        "YesCommand",
        typeof (ICommand),
        typeof (MessageBoxCommand),
        new FrameworkPropertyMetadata(null)
        );


    public static readonly DependencyProperty OKCommandProperty = DependencyProperty.Register(
        "OKCommand",
        typeof (ICommand),
        typeof (MessageBoxCommand),
        new FrameworkPropertyMetadata(null)
        );


    public static readonly DependencyProperty CancelCommandProperty = DependencyProperty.Register(
        "CancelCommand",
        typeof (ICommand),
        typeof (MessageBoxCommand),
        new FrameworkPropertyMetadata(null)
        );


    public static readonly DependencyProperty NoCommandProperty = DependencyProperty.Register(
        "NoCommand",
        typeof (ICommand),
        typeof (MessageBoxCommand),
        new FrameworkPropertyMetadata(null)
        );


    public static readonly DependencyProperty MessageProperty = DependencyProperty.Register(
        "Message",
        typeof (string),
        typeof (MessageBoxCommand),
        new FrameworkPropertyMetadata("")
        );

    public static readonly DependencyProperty MessageBoxButtonsProperty = DependencyProperty.Register(
        "MessageBoxButtons",
        typeof(MessageBoxButton),
        typeof(MessageBoxCommand),
        new FrameworkPropertyMetadata(MessageBoxButton.OKCancel)
        );

    public ICommand YesCommand
    {
        get { return (ICommand) GetValue(YesCommandProperty); }
        set { SetValue(YesCommandProperty, value); }
    }

    public ICommand OKCommand
    {
        get { return (ICommand) GetValue(OKCommandProperty); }
        set { SetValue(OKCommandProperty, value); }
    }

    public ICommand CancelCommand
    {
        get { return (ICommand) GetValue(CancelCommandProperty); }
        set { SetValue(CancelCommandProperty, value); }
    }

    public ICommand NoCommand
    {
        get { return (ICommand) GetValue(NoCommandProperty); }
        set { SetValue(NoCommandProperty, value); }
    }

    public MessageBoxButton MessageBoxButtons
    {
        get { return (MessageBoxButton)GetValue(MessageBoxButtonsProperty); }
        set { SetValue(MessageBoxButtonsProperty, value); }
    }

    public string Message
    {
        get { return (string) GetValue(MessageProperty); }
        set { SetValue(MessageProperty, value); }
    }

    public void Execute(object parameter)
    {
        var messageBoxResult = MessageBox.Show(Message);
        switch (messageBoxResult)
        {
            case MessageBoxResult.OK:
                OKCommand.Execute(null);
                break;
            case MessageBoxResult.Yes:
                YesCommand.Execute(null);
                break;
            case MessageBoxResult.No:
                NoCommand.Execute(null);
                break;
            case MessageBoxResult.Cancel:
                if (CancelCommand != null) CancelCommand.Execute(null); //Cancel usually means do nothing ,so can be null
                break;

        }
    }

    public bool CanExecute(object parameter)
    {
        return true;
    }

    public event EventHandler CanExecuteChanged;


    protected override Freezable CreateInstanceCore()
    {
        throw new NotImplementedException();
    }
}

Bu kodun çalışması gerekir, ancak özellikle dosya veya yazıcı iletişim kutuları gibi sistem iletişim kutuları için bugüne kadarki en iyi fikirdir. İletişim kutuları bir şey varsa Görünüm'e aittir. Dosya iletişim kutuları için sonuç (seçilen dosya adı) parametre olarak iç komuta iletilebilir.
Anton Tykhyy

3

Bir iletişim kutusunun işlenmesinin görüşün sorumluluğu olması gerektiğini ve görüşün bunu destekleyecek kodlara sahip olması gerektiğini düşünüyorum.

İletişim kutularını işlemek için ViewModel - View etkileşimini değiştirirseniz, ViewModel bu uygulamaya bağlıdır. Bu sorunla başa çıkmanın en basit yolu, Görevin görevi gerçekleştirmekten sorumlu olmasını sağlamaktır. Bu bir iletişim kutusu göstermek anlamına gelirse iyi, ancak durum çubuğunda vb. Bir durum mesajı da olabilir.

Benim açımdan, MVVM modelinin tüm amacı iş mantığını GUI'den ayırmaktır, bu nedenle iş katmanında (ViewModel) GUI mantığını (bir iletişim kutusu görüntülemek için) karıştırmamalısınız.


2
VM hiçbir zaman iletişim kutusunu işlemez, benim örneğimde iletişim kutusunun başlatılmasını ve bazı EventArgs formlarında bilgi iletmesini gerektiren bir olaya sahip olacaktı. Görünüm sorumluysa, bilgileri VM'ye nasıl geri gönderir?
Ray Booysen

VM'nin bir şeyleri silmesi gerektiğini varsayalım. VM, View Delete'de bir boole döndüren bir yöntem çağırır. Görünüm daha sonra öğeyi doğrudan silebilir ve true değerini döndürebilir veya kullanıcının yanıtına bağlı olarak bir onay iletişim kutusu gösterebilir ve true / false değerini döndürebilir.
Cameron MacFarland

Sanal Makine iletişim kutusu hakkında hiçbir şey bilmiyor, ancak görünümden yalnızca görünümün onayladığı veya reddettiği bir şeyi silmesini istedi.
Cameron MacFarland

Her zaman MVVM'nin Model: iş mantığı, ViewModel: GUI mantığı ve View: mantık olmadığını düşündüm. Son paragrafınız bir şekilde çelişiyor. Lütfen açıkla!
David Schmitt

2
İlk olarak, ön silme onayı istenmesinin iş mantığı veya görünüm mantığı olup olmadığı belirlenmelidir. İş mantığı ise, modeldeki DeleteFile yöntemi bunu yapmamalı, bunun yerine onay soru nesnesini döndürmelidir. Bu, gerçek silme işlemini gerçekleştiren temsilciye bir başvuru içerir. İş mantığı değilse, VM, iki ICommand üyesi ile birlikte DeleteFileCommand içinde sorunun bir VM'sini oluşturmalıdır. Biri evet, diğeri hayır için. Muhtemelen her iki görüş için de argümanlar vardır ve RL'de kullanımın çoğu muhtemelen her ikisiyle de karşılaşacaktır.
Guge

3

İlginç bir alternatif, görünümleri (diyaloglar) göstermekle sorumlu olan Denetleyicileri kullanmaktır.

Bunun nasıl çalıştığı WPF Uygulama Çerçevesi (WAF) tarafından gösterilir .


3

Neden sadece sanal makinede bir etkinlik oluşturmuyorsunuz ve görünümdeki etkinliğe abone olmuyorsunuz? Bu, uygulama mantığını ve görünümü ayrı tutar ve yine de iletişim kutuları için bir alt pencere kullanmanıza izin verir.


3

ViewModel'den bir İletiyi dinleyen bir Davranış uyguladım. Laurent Bugnion çözümüne dayanıyor, ancak arkasında kod kullanmadığı ve daha fazla kullanılabilir olduğu için daha zarif olduğunu düşünüyorum.

MVPM kutudan çıkmış gibi WPF'nin nasıl davranacağı


1
Tam kodu buraya eklemelisiniz, çünkü SO iyi yanıtlar için gereken şeydir. Bununla birlikte, bağlantılı yaklaşım oldukça düzgün, bu yüzden bunun için teşekkürler! :)
Yoda

2
@yoda tam kod oldukça uzun ve bu yüzden ona bağlamayı tercih ederim. Cevabımı değişiklikleri yansıtmak ve bozulmamış bir bağlantıya işaret etmek için düzenledim
Elad Katz

Gelişme için teşekkürler. Bununla birlikte, SO'da uzun bir süre kod 3 tam sayfa kaydırma sağlamak, bir gün çevrimdışı olabilecek bir bağlantıdan daha iyidir. Karmaşık konular için iyi makaleler her zaman oldukça uzundur - ve yeni bir sekme açmanın, sekmeye geçmenin ve daha önce üzerinde bulunduğum aynı sayfada / sekmede kaydırmak için herhangi bir fayda görmüyorum. ;)
Yoda

@EladKatz WPF uygulamanızın bir kısmını sağladığınız bağlantıda paylaştığınızı gördüm. ViewModel'den yeni bir pencere açmak için herhangi bir çözümünüz var mı? Temelde iki form var ve her biri bir ViewModel vardır. Bir kullanıcı başka bir form açılır ve viewmodel1 nesnesini viewmodel2'ye gönderir. Form 2'de kullanıcı nesneyi değiştirebilir ve pencereyi kapattığında, güncellenen nesne ilk ViewModel'e geri gönderilir. Bunun için bir çözümün var mı?
Ehsan

2

Ben görünüm görünümünden olayı işlemek için kod olabilir düşünüyorum.

Olaya / senaryoya bağlı olarak, model olaylarını görüntülemeye abone olan bir olay tetikleyicisi ve yanıt olarak çağırmak için bir veya daha fazla eylem de olabilir.




1

Karl Shifflett, hizmet yaklaşımını ve Prism InteractionRequest yaklaşımını kullanarak iletişim kutularını göstermek için örnek bir uygulama oluşturdu.

Hizmet yaklaşımını seviyorum - Kullanıcıların bir şeyleri kırma olasılığı daha az bu yüzden daha az esnek :) Uygulamamın WinForms bölümü ile de tutarlı (MessageBox.Show) Ama çok sayıda farklı iletişim kutusu göstermeyi planlıyorsanız, InteractivetionRequest daha iyi bir yol.

http://karlshifflett.wordpress.com/2010/11/07/in-the-box-ndash-mvvm-training/


1

Bunun eski bir soru olduğunu biliyorum, ancak bu aramayı yaptığımda birçok ilgili soru buldum, ama gerçekten net bir cevap bulamadım. Bu yüzden bir iletişim kutusu / mesaj kutusu / popin uygulamamı kendim yapıyorum ve paylaşıyorum!
Ben "MVVM kanıtı" olduğunu düşünüyorum, ve ben basit ve düzgün yapmaya çalışıyorum, ama ben WPF için yeniyim, bu yüzden yorum yapmak, hatta çekme isteği yapmak için çekinmeyin.

https://github.com/Plasma-Paris/Plasma.WpfUtils

Bu şekilde kullanabilirsiniz:

public RelayCommand YesNoMessageBoxCommand { get; private set; }
async void YesNoMessageBox()
{
    var result = await _Service.ShowMessage("This is the content of the message box", "This is the title", System.Windows.MessageBoxButton.YesNo);
    if (result == System.Windows.MessageBoxResult.Yes)
        // [...]
}

Ya da daha karmaşık popin istiyorsanız:

var result = await _Service.ShowCustomMessageBox(new MyMessageBoxViewModel { /* What you want */ });

Ve bunun gibi şeyler gösteriyor:

2


1

Standart yaklaşım

WPF'de bu sorunla uğraşan yıllar geçirdikten sonra, sonunda WPF'de diyalogları uygulamanın standart yolunu anladım . İşte bu yaklaşımın avantajları:

  1. TEMİZ
  2. MVVM tasarım modelini ihlal etmez
  3. ViewModal hiçbir zaman kullanıcı arayüzü kitaplıklarına (WindowBase, PresentationFramework vb.) Başvurmaz.
  4. Otomatik test için mükemmel
  5. İletişim kutuları kolayca değiştirilebilir.

Peki anahtar nedir? Öyle DI + IoC .

İşte böyle çalışır. MVVM Light kullanıyorum, ancak bu yaklaşım diğer çerçevelere de genişletilebilir:

  1. Çözümünüze bir WPF Uygulaması projesi ekleyin. Diyelim App .
  2. Bir ViewModal Sınıf Kütüphanesi ekleyin. Buna VM deyin .
  3. Uygulama VM projesine başvuruyor. VM projesi Uygulama hakkında hiçbir şey bilmiyor.
  4. Her iki projeye de MVGM Light'a NuGet referansı ekleyin . Bugünlerde MVVM Light Standard kullanıyorum , ancak tam Framework sürümü ile de sorun yok.
  5. VM projesine bir arabirim IDialogService ekleyin :

    public interface IDialogService
    {
      void ShowMessage(string msg, bool isError);
      bool AskBooleanQuestion(string msg);
      string AskStringQuestion(string msg, string default_value);
    
      string ShowOpen(string filter, string initDir = "", string title = "");
      string ShowSave(string filter, string initDir = "", string title = "", string fileName = "");
      string ShowFolder(string initDir = "");
    
      bool ShowSettings();
    }
  6. Bir kamu statik özelliği Açığa IDialogServiceGözlerinde farklı tip ViewModelLocator, ancak Görünüm katman için izin kayıt bölümü gerçekleştirmek için. Anahtar budur :

    public static IDialogService DialogService => SimpleIoc.Default.GetInstance<IDialogService>();
  7. Bu projenin bir uygulamasını Uygulama projesine ekleyin.

    public class DialogPresenter : IDialogService
    {
        private static OpenFileDialog dlgOpen = new OpenFileDialog();
        private static SaveFileDialog dlgSave = new SaveFileDialog();
        private static FolderBrowserDialog dlgFolder = new FolderBrowserDialog();
    
        /// <summary>
        /// Displays a simple Information or Error message to the user.
        /// </summary>
        /// <param name="msg">String text that is to be displayed in the MessageBox</param>
        /// <param name="isError">If true, Error icon is displayed. If false, Information icon is displayed.</param>
        public void ShowMessage(string msg, bool isError)
        {
                if(isError)
                        System.Windows.MessageBox.Show(msg, "Your Project Title", MessageBoxButton.OK, MessageBoxImage.Error);
                else
                        System.Windows.MessageBox.Show(msg, "Your Project Title", MessageBoxButton.OK, MessageBoxImage.Information);
        }
    
        /// <summary>
        /// Displays a Yes/No MessageBox.Returns true if user clicks Yes, otherwise false.
        /// </summary>
        /// <param name="msg"></param>
        /// <returns></returns>
        public bool AskBooleanQuestion(string msg)
        {
                var Result = System.Windows.MessageBox.Show(msg, "Your Project Title", MessageBoxButton.YesNo, MessageBoxImage.Question) == MessageBoxResult.Yes;
                return Result;
        }
    
        /// <summary>
        /// Displays Save dialog. User can specify file filter, initial directory and dialog title. Returns full path of the selected file if
        /// user clicks Save button. Returns null if user clicks Cancel button.
        /// </summary>
        /// <param name="filter"></param>
        /// <param name="initDir"></param>
        /// <param name="title"></param>
        /// <param name="fileName"></param>
        /// <returns></returns>
        public string ShowSave(string filter, string initDir = "", string title = "", string fileName = "")
        {
                if (!string.IsNullOrEmpty(title))
                        dlgSave.Title = title;
                else
                        dlgSave.Title = "Save";
    
                if (!string.IsNullOrEmpty(fileName))
                        dlgSave.FileName = fileName;
                else
                        dlgSave.FileName = "";
    
                dlgSave.Filter = filter;
                if (!string.IsNullOrEmpty(initDir))
                        dlgSave.InitialDirectory = initDir;
    
                if (dlgSave.ShowDialog() == DialogResult.OK)
                        return dlgSave.FileName;
                else
                        return null;
        }
    
    
        public string ShowFolder(string initDir = "")
        {
                if (!string.IsNullOrEmpty(initDir))
                        dlgFolder.SelectedPath = initDir;
    
                if (dlgFolder.ShowDialog() == DialogResult.OK)
                        return dlgFolder.SelectedPath;
                else
                        return null;
        }
    
    
        /// <summary>
        /// Displays Open dialog. User can specify file filter, initial directory and dialog title. Returns full path of the selected file if
        /// user clicks Open button. Returns null if user clicks Cancel button.
        /// </summary>
        /// <param name="filter"></param>
        /// <param name="initDir"></param>
        /// <param name="title"></param>
        /// <returns></returns>
        public string ShowOpen(string filter, string initDir = "", string title = "")
        {
                if (!string.IsNullOrEmpty(title))
                        dlgOpen.Title = title;
                else
                        dlgOpen.Title = "Open";
    
                dlgOpen.Multiselect = false;
                dlgOpen.Filter = filter;
                if (!string.IsNullOrEmpty(initDir))
                        dlgOpen.InitialDirectory = initDir;
    
                if (dlgOpen.ShowDialog() == DialogResult.OK)
                        return dlgOpen.FileName;
                else
                        return null;
        }
    
        /// <summary>
        /// Shows Settings dialog.
        /// </summary>
        /// <returns>true if User clicks OK button, otherwise false.</returns>
        public bool ShowSettings()
        {
                var w = new SettingsWindow();
                MakeChild(w); //Show this dialog as child of Microsoft Word window.
                var Result = w.ShowDialog().Value;
                return Result;
        }
    
        /// <summary>
        /// Prompts user for a single value input. First parameter specifies the message to be displayed in the dialog 
        /// and the second string specifies the default value to be displayed in the input box.
        /// </summary>
        /// <param name="m"></param>
        public string AskStringQuestion(string msg, string default_value)
        {
                string Result = null;
    
                InputBox w = new InputBox();
                MakeChild(w);
                if (w.ShowDialog(msg, default_value).Value)
                        Result = w.Value;
    
                return Result;
        }
    
        /// <summary>
        /// Sets Word window as parent of the specified window.
        /// </summary>
        /// <param name="w"></param>
        private static void MakeChild(System.Windows.Window w)
        {
                IntPtr HWND = Process.GetCurrentProcess().MainWindowHandle;
                var helper = new WindowInteropHelper(w) { Owner = HWND };
        }
    }
  8. Bu işlevlerden bazıları genel ( ShowMessage, AskBooleanQuestionvb.) Olsa da, diğerleri bu projeye özgüdür ve özel Windows kullanır . Aynı şekilde daha fazla özel pencere ekleyebilirsiniz. Anahtar, kullanıcı arabirimine özgü öğeleri Görünüm katmanında tutmak ve yalnızca döndürülen verileri VM katmanındaki POCO'ları kullanarak göstermektir .
  9. Bu sınıfı kullanarak Görünüm katmanında IoC Kaydınızı gerçekleştirin. Bunu ana görünümünüzün yapıcısında ( InitializeComponent()çağrıdan sonra ) yapabilirsiniz:

    SimpleIoc.Default.Register<IDialogService, DialogPresenter>();
  10. İşte böyle. Artık hem VM hem de View katmanlarındaki tüm iletişim kutusu işlevlerinize erişebilirsiniz. VM katmanınız şu işlevleri şu şekilde çağırabilir:

    var NoTrump = ViewModelLocator.DialogService.AskBooleanQuestion("Really stop the trade war???", "");
  11. Çok temiz görüyorsun. VM katmanı, kullanıcı arayüzü katmanı tarafından kullanıcıya Evet / Hayır sorusunun nasıl sunulacağı hakkında hiçbir şey bilmez ve iletişim kutusundan döndürülen sonuçla başarılı bir şekilde çalışabilir.

Diğer ücretsiz avantajlar

  1. Birim testi yazmak için aşağıdakilerin özel bir uygulamasını sağlayabilirsiniz: IDialogService için, Test projenizde ve bu sınıfı test sınıfınızda yapıcıda IoC'ye kaydedebilirsiniz.
  2. Microsoft.Win32Aç ve Kaydet iletişim kutularına erişmek gibi bazı ad alanlarını içe aktarmanız gerekir . Bu iletişim kutularının WinForms sürümü de mevcut olduğu için bunları bıraktım, ancak birisi kendi sürümünü oluşturmak isteyebilir. Ayrıca, kullanılan tanımlayıcıların bazılarının DialogPresenterkendi pencerelerimin adları olduğunu unutmayın (örn. SettingsWindow). Bunları hem arayüzden hem de uygulamadan kaldırmanız veya kendi pencerelerinizi sağlamanız gerekir.
  3. VM'niz çoklu iş parçacığı gerçekleştiriyorsa DispatcherHelper.Initialize(), uygulamanızın yaşam döngüsünün başlarında MVVM Light'ı arayın .
  4. DialogPresenterGörünüm katmanına enjekte edilenler dışında , diğer ViewModals kaydedilmeli ViewModelLocatorve ardından Görünüm katmanının tüketmesi için bu tür bir genel statik özellik gösterilmelidir. Bunun gibi bir şey:

    public static SettingsVM Settings => SimpleIoc.Default.GetInstance<SettingsVM>();
  5. Çoğunlukla, iletişim kutularında DataContext vb. Bağlama veya ayarlama gibi şeyler için herhangi bir kod arkası olmamalıdır. XAML bunu sizin için yapabilir:

    <Window x:Class="YourViewNamespace.SettingsWindow"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:local="clr-namespace:YourViewProject"
      xmlns:vm="clr-namespace:YourVMProject;assembly=YourVMProject"
      DataContext="{x:Static vm:ViewModelLocator.Settings}"
      d:DataContext="{d:DesignInstance Type=vm:SettingsVM}" />
  6. Ayar DataContextbu şekilde size böyle IntelliSense ve otomatik tamamlama gibi tasarım zamanı faydaları her türlü verir.

Umarım herkese yardımcı olur.


0

Bir görevin veya iletişim kutusunun görünüm modelinin nasıl görünmesi gerektiğini sorduğumda benzer bir sorunu düşünüyordum .

Mevcut çözümüm şöyle:

public class SelectionTaskModel<TChoosable> : ViewModel
    where TChoosable : ViewModel
{
    public SelectionTaskModel(ICollection<TChoosable> choices);
    public ReadOnlyCollection<TChoosable> Choices { get; }
    public void Choose(TChoosable choosen);
    public void Abort();
}

Görünüm modeli, kullanıcı girişinin gerekli olduğuna karar verdiğinde, kullanıcı SelectionTaskModeliçin olası seçeneklerin bir örneğini çıkarır . Altyapı, uygun zamanda Choose(), kullanıcının seçimiyle fonksiyonu çağıracak olan ilgili görüşü getirmeye özen gösterir .


0

Aynı sorunla mücadele ettim. View ve ViewModel arasında iletişim kurmak için bir yol buldum. Bir ileti kutusu göstermesini bildirmek için ViewModel'den Görünüm'e bir ileti göndermeyi başlatabilirsiniz ve sonuçla birlikte raporlanır. Ardından ViewModel, Görünüm'den döndürülen sonuca yanıt verebilir.

Ben Bunu göstermek blogumda :



0

Üzgünüm, ama içeri girmeliyim. Prism projesinde Prism.Wpf.Interactivity ad alanını bulmadan önce önerilen çözümlerin birçoğunu yaşadım. Özel bir pencereyi döndürmek için etkileşim isteklerini ve açılır pencere eylemini kullanabilirsiniz veya daha basit ihtiyaçlar için Bildirim ve Onay açılır pencereleri bulunur. Bunlar gerçek pencereler oluşturur ve bu şekilde yönetilir. iletişim kutusunda ihtiyacınız olan bağımlılıklara sahip bir bağlam nesnesi iletebilirsiniz. Bulduğumdan beri bu çözümü işimde kullanıyoruz. Burada çok sayıda üst düzey geliştiricimiz var ve kimse daha iyi bir şey bulamadı. Önceki çözümümüz, bir yer paylaşımında iletişim hizmeti ve bunu yapmak için bir sunucu sınıfı kullanmaktı, ancak tüm iletişim kutusu görünüm modelleri için fabrikalara sahip olmanız gerekiyordu.

Bu önemsiz değil, aynı zamanda süper karmaşık da değil. Ve Prizma için yerleşiktir ve bu nedenle en iyi (veya daha iyi) IMHO uygulamasıdır.

2 sentim!


-1

EDIT: evet, bu doğru bir MVVM yaklaşım değil katılıyorum ve şimdi blindmeis tarafından önerilen benzer bir şey kullanıyorum.

Bunu yapabilmenin yollarından biri

Ana Görünüm Modelinizde (modeli açtığınız yer):

void OpenModal()
{
    ModalWindowViewModel mwvm = new ModalWindowViewModel();
    Window mw = new Window();
    mw.content = mwvm;
    mw.ShowDialog()
    if(mw.DialogResult == true)
    { 
        // Your Code, you can access property in mwvm if you need.
    }
}

Ve Kalıcı Pencere Görünümü / ViewModelinizde:

XAML:

<Button Name="okButton" Command="{Binding OkCommand}" CommandParameter="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}">OK</Button>
<Button Margin="2" VerticalAlignment="Center" Name="cancelButton" IsCancel="True">Cancel</Button>

ViewModel:

public ICommand OkCommand
{
    get
    {
        if (_okCommand == null)
        {
            _okCommand = new ActionCommand<Window>(DoOk, CanDoOk);
        }
        return _okCommand ;
    }
}

void DoOk(Window win)
{
    <!--Your Code-->
    win.DialogResult = true;
    win.Close();
}

bool CanDoOk(Window win) { return true; }

veya burada yayınlananlara benzer WPF MVVM: Bir pencereyi kapatma


2
Ben aşağı oylama değildim, ama sanırım bunun nedeni görünüm modelinin görünüme doğrudan bir referansı olması.
Brian Gideon

@BrianGideon, yorumunuz için teşekkürler. Bunun ayrıştırılmış bir çözüm olmadığını kabul ediyorum. Aslında, blindmeis tarafından önerilen iskele benzeri bir şey kullanmıyorum. Tekrar teşekkürler.
Simone

Görmemek çok kolay olduğunda manzaraya ulaşmak kötü bir form.
Chris Bordeman
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.