Manipulando caixas de diálogo no WPF com MVVM

235

No padrão MVVM para WPF, manipular diálogos é uma das operações mais complexas. Como seu modelo de visão não sabe nada sobre a visão, a comunicação por diálogo pode ser interessante. Eu posso expor ICommandque, quando a visualização é chamada, uma caixa de diálogo pode aparecer.

Alguém sabe uma boa maneira de lidar com os resultados dos diálogos? Estou falando de diálogos do Windows, como MessageBox.

Uma das maneiras pelas quais fizemos isso foi ter um evento no modelo de visualização no qual a visualização seria assinada quando um diálogo fosse necessário.

public event EventHandler<MyDeleteArgs> RequiresDeleteDialog;

Isso é bom, mas significa que a visualização requer código, algo que eu gostaria de evitar.

Ray Booysen
fonte
Por que não vincular a um objeto auxiliar na Visualização?
Paul Williams
1
Não tenho certeza do que você quer dizer.
Ray Booysen
1
Se eu entendi a pergunta, você não quer que a VM apareça nas caixas de diálogo e não queira codificar na Visualização. Além disso, parece que você prefere comandos a eventos. Eu concordo com tudo isso; portanto, uso uma classe auxiliar na exibição que expõe um comando para manipular a caixa de diálogo. Respondi a esta pergunta em outro segmento aqui: stackoverflow.com/a/23303267/420400 . No entanto, a última frase faz parecer que você não deseja nenhum código, em nenhum lugar da Visualização. Entendo essa preocupação, mas o código em questão é apenas condicional e provavelmente não será alterado.
Paul Williams
4
O modelo de visualização deve sempre ser responsável pela lógica por trás da criação da caixa de diálogo; essa é toda a razão de sua existência em primeiro lugar. Dito isto, ele não faz (e não deveria) fazer o esforço de criar a própria visão. Escrevi um artigo sobre esse assunto em codeproject.com/Articles/820324/…, onde mostro que todo o ciclo de vida das caixas de diálogo pode ser gerenciado por meio de ligação de dados WPF regular e sem quebrar o padrão MVVM.
Mark Feldman

Respostas:

131

Sugiro renunciar às caixas de diálogo modais dos anos 90 e, em vez disso, implementar um controle como uma sobreposição (tela + posicionamento absoluto) com visibilidade vinculada a um booleano na VM. Mais próximo de um controle do tipo ajax.

Isso é muito útil:

<BooleanToVisibilityConverter x:Key="booltoVis" />

como em:

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

Aqui está como eu tenho um implementado como controle de usuário. Clicar no 'x' fecha o controle em uma linha de código no código do controle do usuário por trás. (Como tenho meus modos de exibição em .exe e ViewModels em uma dll, não me sinto mal com o código que manipula a interface do usuário.)

Diálogo Wpf

Jeffrey Knight
fonte
20
Sim, eu também gosto dessa idéia, mas gostaria de ver algum exemplo desse controle em termos de como mostrá-lo e recuperar o resultado do diálogo, etc. Especialmente no cenário MVVM no Silverlight.
Roboblob
16
Como você evita que o usuário interaja com os controles abaixo dessa sobreposição de caixa de diálogo?
Andrew Garrison
17
O problema com esta abordagem é que você não pode abrir uma segunda caixa de diálogo modal a partir da primeira, pelo menos não sem algumas modificações pesadas para o sistema de sobreposição ...
Thomas Levesque
6
Outro problema com essa abordagem é que a "caixa de diálogo" não pode ser movida. Em nossos aplicativos, precisamos ter um diálogo móvel para que o usuário possa ver o que está por trás dele.
JAB
13
Essa abordagem parece terrível para mim. o que estou perdendo? Como isso é melhor do que uma caixa de diálogo real?
Jonathan Madeira
51

Você deve usar um mediador para isso. O mediador é um padrão de design comum, também conhecido como Messenger, em algumas de suas implementações. É um paradigma do tipo Register / Notify e permite que o seu ViewModel e Views se comuniquem através de um mecanismo de mensagens com pouco acoplamento.

Você deve conferir o grupo de discípulos do WPF do Google e procurar por mediador. Você ficará muito feliz com as respostas ...

No entanto, você pode começar com isso:

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

Aproveitar !

Editar: você pode ver a resposta para esse problema com o MVVM Light Toolkit aqui:

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

Roubachof
fonte
2
Marlon grech acaba de postar uma nova implementação do mediador: marlongrech.wordpress.com/2009/04/04/16/…
Roubachof
21
Apenas uma observação: o padrão Mediador não foi introduzido pelos Discípulos do WPF, é um padrão clássico do GoF ... ( dofactory.com/Patterns/PatternMediator.aspx ). Resposta agradável de outra forma;)
Thomas Levesque
10
Por favor, Deus, não use um mediador ou um mensageiro maldito. Esse tipo de código com dezenas de mensagens circulando se torna muito difícil de depurar, a menos que você possa se lembrar de todos os muitos pontos em toda a sua base de código que se inscreve e gerencia todos os eventos. Torna-se um pesadelo para novos desenvolvedores. De fato, considero toda a biblioteca MvvMLight um antipadrão massivo por seu uso generalizado e desnecessário de mensagens assíncronas. A solução é simples: chame um serviço de diálogo separado (ou seja, IDialogService) do seu design. A interface possui métodos e eventos para retornos de chamada.
precisa saber é o seguinte
34

Um bom diálogo MVVM deve:

  1. Ser declarado com apenas XAML.
  2. Obtenha todo o seu comportamento na ligação de dados.

Infelizmente, o WPF não fornece esses recursos. Mostrar uma caixa de diálogo requer uma chamada de code-behind para ShowDialog(). A classe Window, que oferece suporte a diálogos, não pode ser declarada em XAML, portanto, não pode ser facilmente vinculada a dados DataContext.

Para resolver isso, escrevi um controle de stub XAML que fica na árvore lógica e retransmite a ligação de dados para Windowe manipula a exibição e ocultação da caixa de diálogo. Você pode encontrá-lo aqui: http://www.codeproject.com/KB/WPF/XAMLDialog.aspx

É realmente simples de usar e não requer alterações estranhas no seu ViewModel e não requer eventos ou mensagens. A chamada básica é assim:

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

Você provavelmente deseja adicionar um estilo definido Showing. Eu explico no meu artigo. Espero que isso ajude você.

user92541
fonte
2
Essa é uma abordagem realmente interessante para o problema de mostrar janelas de diálogo no MVVM.
Dthrasher
2
"Showing a dialog requires a code-behind"mmm você pode chamar que, em ViewModel
Brock Hensley
Eu acrescentaria o ponto 3 - você é livre para se ligar a outros objetos dentro da visualização. Deixar o código da caixa de diálogo para trás vazio significa que não há código C # em qualquer lugar da exibição, e a ligação de dados não implica a ligação à VM.
Paul Williams
25

Eu uso essa abordagem para diálogos com o MVVM.

Tudo o que tenho a fazer agora é chamar o seguinte no meu modelo de exibição.

var result = this.uiDialogService.ShowDialog("Dialogwindow title goes here", dialogwindowVM);
blindmeis
fonte
de qual biblioteca o uiDialogService vem?
aggietech
1
nenhuma biblioteca. é apenas uma pequena interface e implementação: stackoverflow.com/questions/3801681/… . para ser justo atm tem mais algumas sobrecargas para as minhas necessidades :) (altura, largura propertysettings e assim por diante)
blindmeis
16

Minha solução atual resolve a maioria dos problemas mencionados, mas é completamente abstraída de itens específicos da plataforma e pode ser reutilizada. Também usei nenhuma vinculação code-behind apenas com DelegateCommands que implementam ICommand. O diálogo é basicamente um modo de exibição - um controle separado que possui seu próprio ViewModel e é mostrado no ViewModel da tela principal, mas acionado a partir da interface do usuário através da ligação DelagateCommand.

Veja a solução completa do Silverlight 4 aqui Diálogos modais com MVVM e Silverlight 4

Roboblob
fonte
Assim como a abordagem de @Elad Katz, sua resposta não possui o conteúdo vinculado - melhore sua resposta inserindo-a, pois é isso que é considerado uma boa resposta aqui no SO. No entanto, obrigado pela sua contribuição! :)
Yoda
6

Eu realmente lutei com esse conceito por um tempo ao aprender (ainda aprendendo) MVVM. O que eu decidi e o que acho que outros já decidiram, mas que não estava claro para mim, é o seguinte:

Meu pensamento original era que um ViewModel não deveria chamar uma caixa de diálogo diretamente, pois não é necessário decidir como uma caixa de diálogo deve aparecer. Por causa disso, comecei a pensar em como eu poderia passar mensagens como faria no MVP (por exemplo, View.ShowSaveFileDialog ()). No entanto, acho que essa é a abordagem errada.

Não há problema em um ViewModel chamar uma caixa de diálogo diretamente. No entanto, quando você está testando um ViewModel, isso significa que a caixa de diálogo será exibida durante o teste ou falhará totalmente (nunca realmente tentei isso).

Portanto, o que precisa acontecer é que durante o teste, use uma versão de "teste" da sua caixa de diálogo. Isso significa que, para sempre em qualquer caixa de diálogo, você precisa criar uma Interface e simular a resposta da caixa de diálogo ou criar uma simulação de teste que terá um comportamento padrão.

Você já deve estar usando algum tipo de Service Locator ou IoC que você pode configurar para fornecer a versão correta, dependendo do contexto.

Usando essa abordagem, seu ViewModel ainda pode ser testado e, dependendo de como você zomba de suas caixas de diálogo, pode controlar o comportamento.

Espero que isto ajude.

Mike Rowley
fonte
6

Há duas boas maneiras de fazer isso: 1) um serviço de diálogo (fácil, limpo) e 2) visualização assistida. A exibição assistida fornece alguns recursos interessantes, mas geralmente não vale a pena.

SERVIÇO DE DIÁLOGO

a) uma interface de serviço de diálogo como via construtor ou algum contêiner de dependência:

interface IDialogService { Task ShowDialogAsync(DialogViewModel dlgVm); }

b) Sua implementação do IDialogService deve abrir uma janela (ou injetar algum controle na janela ativa), criar uma exibição correspondente ao nome do tipo dlgVm fornecido (use convenção ou registro de contêiner ou um ContentPresenter com o DataTemplates associados ao tipo). ShowDialogAsync deve criar um TaskCompletionSource e retornar sua propriedade .Task. A própria classe DialogViewModel precisa de um evento que você possa chamar na classe derivada quando desejar fechar e observe na exibição da caixa de diálogo para realmente fechar / ocultar a caixa de diálogo e concluir o TaskCompletionSource.

b) Para usar, basta ligar para aguardar this.DialogService.ShowDialog (myDlgVm) em sua instância de alguma classe derivada de DialogViewModel. Depois de aguardar retorno, consulte as propriedades que você adicionou na sua VM de diálogo para determinar o que aconteceu; você nem precisa de um retorno de chamada.

VIEW ASSISTED

Isso faz com que sua visualização ouça um evento no modelo de visualização. Tudo isso pode ser envolvido em um Blend Behavior para evitar o código por trás e o uso de recursos, se você quiser (FMI, subclasse a classe "Behavior" para ver uma espécie de propriedade anexada ao Blendable em esteróides). Por enquanto, faremos isso manualmente em cada visualização:

a) Crie um OpenXXXXXDialogEvent com uma carga útil personalizada (uma classe derivada de DialogViewModel).

b) Faça com que a visualização assine o evento em seu evento OnDataContextChanged. Certifique-se de ocultar e cancelar a assinatura se o valor antigo! = Null e no evento Descarregado da Janela.

c) Quando o evento for disparado, abra a visualização, que pode estar em um recurso da sua página, ou você poderá localizá-lo por convenção em outro local (como na abordagem do serviço de diálogo).

Essa abordagem é mais flexível, mas requer mais trabalho para usar. Eu não uso muito. A única vantagem interessante é a capacidade de colocar a visualização fisicamente dentro de uma guia, por exemplo. Eu usei um algoritmo para colocá-lo nos limites do controle de usuário atual ou, se não for grande o suficiente, percorrer a árvore visual até encontrar um contêiner grande o suficiente.

Isso permite que as caixas de diálogo fiquem próximas ao local em que são realmente usadas, apenas diminua a parte do aplicativo relacionada à atividade atual e permita que o usuário se mova dentro do aplicativo sem ter que empurrar manualmente as caixas de diálogo, mesmo tendo várias diálogos modais são abertos em diferentes guias ou sub-visualizações.

Chris Bordeman
fonte
Um serviço de diálogo é muito mais fácil, certamente, e o que eu costumo fazer. Também facilita fechar o diálogo da vista a partir do modelo de vista pai, o que é necessário quando o modelo de vista pai está fechando ou cancelando.
Chris Bordeman
4

Use um comando congelável

<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();
    }
}
Maxm007
fonte
Esse código precisa de algum trabalho, mas é de longe a melhor idéia, especialmente para as caixas de diálogo do sistema, como caixas de diálogo de arquivos ou impressoras. Os diálogos pertencem ao modo de exibição, se algo acontecer. Para caixas de diálogo de arquivo, o resultado (nome do arquivo selecionado) pode ser passado para o comando interno como parâmetro.
Anton Tykhyy 30/03
3

Penso que o manuseio de um diálogo deve ser de responsabilidade da visão, e a visão precisa ter código para dar suporte a isso.

Se você alterar a interação ViewModel - View para manipular caixas de diálogo, o ViewModel dependerá dessa implementação. A maneira mais simples de lidar com esse problema é tornar o View responsável pela execução da tarefa. Se isso significa mostrar uma caixa de diálogo, tudo bem, mas também pode ser uma mensagem de status na barra de status etc.

O que quero dizer é que todo o objetivo do padrão MVVM é separar a lógica de negócios da GUI, portanto, você não deve misturar a lógica da GUI (para exibir um diálogo) na camada de negócios (o ViewModel).

Cameron MacFarland
fonte
2
A VM nunca lidaria com a caixa de diálogo; no meu exemplo, simpy teria um evento que exigiria que a caixa de diálogo fosse acionada e transmitisse informações de alguma forma de EventArgs. Se a exibição for responsável, como ela passa informações de volta para a VM?
Ray Booysen
Digamos que a VM precise excluir algo. A VM chama um método na Exibir Excluir, que retorna um booleano. O modo de exibição pode excluir o item diretamente e retornar verdadeiro, ou mostrar uma caixa de diálogo de confirmação e retornar verdadeiro / falso, dependendo da resposta do usuário.
Cameron MacFarland
A VM não sabe nada sobre a caixa de diálogo, mas apenas solicita que a exibição exclua algo, que a exibição confirmou ou negou.
Cameron MacFarland
Eu sempre pensei que o objetivo do MVVM era Modelo: lógica de negócios, ViewModel: lógica da GUI e View: no logic. O que é de alguma forma contradito pelo seu último parágrafo. Por favor explique!
David Schmitt
2
Primeiro, é necessário determinar se a solicitação de confirmação de pré-exclusão é lógica comercial ou visualiza a lógica. Se for lógica de negócios, o método DeleteFile no modelo não deve fazer isso, mas retornar o objeto de pergunta de confirmação. Isso incluirá uma referência ao delegado que faz a exclusão real. Se não for lógica de negócios, a VM deve criar uma VM da pergunta no DeleteFileCommand, com dois membros ICommand. Um para sim e outro para não. Provavelmente, existem argumentos para ambas as visualizações e, na RL, a maior parte do uso provavelmente encontrará ambas.
Guge
3

Uma alternativa interessante é usar controladores responsáveis ​​por mostrar as visualizações (caixas de diálogo).

Como isso funciona é mostrado pelo WPF Application Framework (WAF) .

jbe
fonte
3

Por que não apenas criar um evento na VM e assinar o evento na exibição? Isso manteria a lógica do aplicativo e a exibição separadas e ainda permitiria o uso de uma janela filho para diálogos.

Eric Grover
fonte
3

Eu implementei um comportamento que escuta uma mensagem do ViewModel. É baseado na solução Laurent Bugnion, mas como não usa código por trás e é mais reutilizável, acho que é mais elegante.

Como fazer o WPF se comportar como se o MVVM fosse suportado imediatamente

Elad Katz
fonte
1
Você deve incluir o código completo aqui, pois é isso que o SO exige para obter boas respostas. No entanto, a abordagem vinculada é bem organizada, então obrigado por isso! :)
Yoda
2
@yoda o código completo é bastante longo, e é por isso que prefiro vincular a ele. Eu editei a minha resposta para refletir as mudanças e para apontar para um link que não está quebrado
Elad Katz
Obrigado pela melhoria. No entanto, é melhor fornecer rolagens de página inteira do código 3 por muito tempo aqui no SO do que um link que possa estar offline algum dia. Os bons artigos para tópicos complexos são sempre bastante longos - e não vejo nenhum benefício em abrir uma nova guia, alternar para ela e rolar para cima, rolando a mesma página / guia em que eu estava antes disso. ;)
Yoda
@EladKatz Vi que você compartilhou parte da sua implementação do WPF no link fornecido. Você tem alguma solução para abrir uma nova janela no ViewModel? Basicamente, tenho dois formulários e cada um tem um ViewModel. Um usuário clica em um botão e outro formulário aparece e o viewmodel1 envia seu objeto para o viewmodel2. No formulário 2, o usuário pode alterar o objeto e, quando fechar a janela, o objeto atualizado será enviado de volta ao primeiro ViewModel. Você tem alguma solução para isso?
Ehsan 12/02
2

Eu acho que a visão poderia ter código para manipular o evento do modelo de visão.

Dependendo do evento / cenário, ele também pode ter um gatilho de evento que se assina para visualizar eventos de modelo e uma ou mais ações para chamar em resposta.

Nikhil Kothari
fonte
1

Karl Shifflett criou um aplicativo de exemplo para mostrar caixas de diálogo usando a abordagem de serviço e a abordagem Prism InteractionRequest.

Eu gosto da abordagem de serviço - é menos flexível, portanto é menos provável que os usuários quebrem algo :) Também é consistente com a parte WinForms do meu aplicativo (MessageBox.Show). Mas se você planeja mostrar muitas caixas de diálogo diferentes, o InteractionRequest é um melhor caminho a percorrer.

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

surfen
fonte
1

Sei que é uma pergunta antiga, mas quando fiz essa pesquisa, encontrei muitas perguntas relacionadas, mas não encontrei uma resposta muito clara. Então, eu faço minha própria implementação de uma caixa de diálogo / messagebox / popin e a compartilho!
Eu acho que é "prova MVVM" e tento torná-lo simples e adequado, mas sou novo no WPF, portanto, fique à vontade para comentar ou até fazer solicitação de recebimento.

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

Você pode usá-lo assim:

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)
        // [...]
}

Ou assim, se você quiser popin mais sofisticado:

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

E está mostrando coisas assim:

2

Xav987
fonte
1

A abordagem padrão

Depois de passar anos lidando com esse problema no WPF, finalmente descobri a maneira padrão de implementar diálogos no WPF. Aqui estão as vantagens dessa abordagem:

  1. LIMPAR \ LIMPO
  2. Não viola o padrão de design do MVVM
  3. O ViewModal nunca faz referência a nenhuma das bibliotecas da interface do usuário (WindowBase, PresentationFramework etc.)
  4. Perfeito para testes automatizados
  5. Os diálogos podem ser substituídos facilmente.

Então, qual é a chave? É DI + IoC .

Aqui está como isso funciona. Estou usando o MVVM Light, mas essa abordagem também pode ser estendida a outras estruturas:

  1. Adicione um projeto de aplicativo WPF à sua solução. Chame de App .
  2. Adicione uma biblioteca de classes ViewModal. Chame isso de VM .
  3. O aplicativo faz referência ao projeto da VM. O projeto da VM não sabe nada sobre o aplicativo.
  4. Adicione a referência do NuGet ao MVVM Light nos dois projetos . Estou usando o MVVM Light Standard , mas você também concorda com a versão completa do Framework.
  5. Adicione uma interface IDialogService ao projeto da VM:

    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. Exponha uma propriedade estática pública do IDialogServicetipo no seu ViewModelLocator, mas deixe a parte de registro para a camada Visualizar executar. Esta é a chave .

    public static IDialogService DialogService => SimpleIoc.Default.GetInstance<IDialogService>();
  7. Adicione uma implementação dessa interface no projeto App.

    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. Enquanto algumas dessas funções são genéricas ( ShowMessage, AskBooleanQuestionetc.), outras são específicas para este projeto e usam Windows personalizados . Você pode adicionar mais janelas personalizadas da mesma maneira. A chave é manter elementos específicos da interface do usuário na camada Exibir e apenas expor os dados retornados usando POCOs na camada VM .
  9. Execute o registro IoC da sua interface na camada View usando esta classe. Você pode fazer isso no construtor da visualização principal (após a InitializeComponent()chamada):

    SimpleIoc.Default.Register<IDialogService, DialogPresenter>();
  10. Ai está. Agora você tem acesso a todas as suas funcionalidades de diálogo nas camadas VM e View. Sua camada de VM pode chamar essas funções assim:

    var NoTrump = ViewModelLocator.DialogService.AskBooleanQuestion("Really stop the trade war???", "");
  11. Tão limpo que você vê. A camada da VM não sabe nada sobre como uma pergunta Sim / Não será apresentada ao usuário pela camada da interface do usuário e ainda pode trabalhar com êxito com o resultado retornado da caixa de diálogo.

Outras vantagens gratuitas

  1. Para escrever teste de unidade, você pode fornecer uma implementação personalizada IDialogServiceno seu projeto de Teste e registrar essa classe no IoC no construtor de sua classe de teste.
  2. Você precisará importar alguns espaços para nome, como Microsoft.Win32acessar as caixas de diálogo Abrir e Salvar. Eu os deixei de fora porque também há uma versão WinForms dessas caixas de diálogo disponível, além de alguém querer criar sua própria versão. Observe também que alguns dos identificadores usados DialogPresentersão nomes de minhas próprias janelas (por exemplo SettingsWindow). Você precisará removê-los da interface e da implementação ou fornecer suas próprias janelas.
  3. Se sua VM executar multiencadeamento, ligue para o MVVM Light no DispatcherHelper.Initialize()início do ciclo de vida do seu aplicativo.
  4. Exceto pelo DialogPresenterque é injetado na camada View, outros ViewModals devem ser registrados ViewModelLocatore, em seguida, uma propriedade estática pública desse tipo deve ser exposta para a camada View consumir. Algo assim:

    public static SettingsVM Settings => SimpleIoc.Default.GetInstance<SettingsVM>();
  5. Na maioria das vezes, suas caixas de diálogo não devem ter nenhum código por trás de coisas como ligação ou configuração do DataContext etc. Você nem deve passar as coisas como parâmetros do construtor. O XAML pode fazer tudo isso por você, assim:

    <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. Definir DataContextdessa maneira oferece todos os tipos de benefícios em tempo de design, como Intellisense e preenchimento automático.

Espero que ajude a todos.

ponto Net
fonte
0

Eu estava pensando em um problema semelhante ao perguntar como deveria ser o modelo de exibição de uma tarefa ou diálogo .

Minha solução atual é assim:

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

Quando o modelo de visualização decide que a entrada do usuário é necessária, ele abre uma instância SelectionTaskModelcom as opções possíveis para o usuário. A infraestrutura cuida da exibição da visualização correspondente, que em tempo oportuno chamará a Choose()função com a escolha do usuário.

David Schmitt
fonte
0

Eu lutei com o mesmo problema. Eu criei uma maneira de intercomunicar entre o View e o ViewModel. Você pode iniciar o envio de uma mensagem do ViewModel para o View para solicitar que ele mostre uma caixa de mensagens e ele informará o resultado. Em seguida, o ViewModel pode responder ao resultado retornado da exibição.

Eu demonstro isso no meu blog :

Dan está brincando à luz do fogo
fonte
0

Escrevi um artigo bastante abrangente sobre esse mesmo tópico e também desenvolvi uma biblioteca pop-in para MVVM Dialogs. A aderência estrita ao MVVM não é apenas possível, mas muito limpa quando implementada corretamente, e pode ser facilmente estendida a bibliotecas de terceiros que não aderem a elas mesmas:

https://www.codeproject.com/Articles/820324/Implementing-Dialog-Boxes-in-MVVM

Mark Feldman
fonte
0

Desculpe, mas eu tenho que concordar. Passei por várias das soluções sugeridas antes de encontrar o espaço para nome Prism.Wpf.Interactivity no projeto Prism. Você pode usar solicitações de interação e ação da janela pop-up para rolar uma janela personalizada ou, para necessidades mais simples, são criadas pop-ups de Notificação e Confirmação. Isso cria janelas verdadeiras e é gerenciado como tal. você pode passar um objeto de contexto com quaisquer dependências necessárias na caixa de diálogo. Usamos essa solução no meu trabalho desde que a encontrei. Temos vários desenvolvedores seniores aqui e ninguém criou nada melhor. Nossa solução anterior foi o serviço de diálogo em uma sobreposição e o uso de uma classe de apresentador para que isso acontecesse, mas era necessário ter fábricas para todos os modelos de exibição de diálogo etc.

Isso não é trivial, mas também não é super complicado. E é incorporado ao Prism e, portanto, é a melhor (ou melhor) prática do IMHO.

Meus 2 centavos!

jogi
fonte
-1

EDIT: sim, eu concordo que esta não é uma abordagem correta do MVVM e agora estou usando algo semelhante ao sugerido por blindmeis.

Uma das maneiras que você pode fazer isso é

No seu Main View Model (onde você abre o modal):

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

E no seu Modal Window View / ViewModel:

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

ou semelhante ao postado aqui WPF MVVM: Como fechar uma janela

Simone
fonte
2
Não fui o voto negativo, mas suspeito que seja porque o modelo de exibição tem uma referência direta à exibição.
Brian Gideon
@BrianGideon, obrigado pelo seu comentário. Concordo que esta não é uma solução dissociada. Na verdade, não estou usando algo parecido com o sugerido por blindmeis. Obrigado novamente.
Simone
É uma má forma chegar à vista quando é tão fácil não.
21415 Chris Bordeman