Passando dois parâmetros de comando usando uma ligação WPF

155

Eu tenho um comando que estou executando no meu arquivo XAML usando a seguinte sintaxe padrão:

<Button Content="Zoom" Command="{Binding MyViewModel.ZoomCommand}"/>

Isso funcionou bem até que eu percebi que precisava de DUAS informações na visualização para concluir esta operação da maneira que os usuários esperam (a largura e a altura da tela, especficamente).

Parece que é possível passar uma matriz como argumento para o meu comando, mas não vejo uma maneira de especificar a ligação para minhas duas propriedades de tela no CommandParameter:

<Button Content="Zoom" 
        Command="{Binding MyViewModel.ZoomCommand" 
        CommandParameter="{Binding ElementName=MyCanvas, Path=Width}"/>

Como passo largura e altura ao meu comando? Parece que isso não é possível usando comandos do XAML e eu preciso conectar um manipulador de cliques no meu codebehind para que essas informações passem para o meu método de zoom.

JasonD
fonte
[ stackoverflow.com/questions/58114752/… a solução acima. Eu tive o mesmo problema.)
user1482689 09/10

Respostas:

240

Em primeiro lugar, se você estiver executando o MVVM, normalmente terá essas informações disponíveis para sua VM por meio de propriedades separadas ligadas à visualização. Isso evita que você precise passar parâmetros para seus comandos.

No entanto, você também pode vincular várias vezes e usar um conversor para criar os parâmetros:

<Button Content="Zoom" Command="{Binding MyViewModel.ZoomCommand">
    <Button.CommandParameter>
        <MultiBinding Converter="{StaticResource YourConverter}">
             <Binding Path="Width" ElementName="MyCanvas"/>
             <Binding Path="Height" ElementName="MyCanvas"/>
        </MultiBinding>
    </Button.CommandParameter>
</Button>

No seu conversor:

public class YourConverter : IMultiValueConverter
{
    public object Convert(object[] values, ...)
    {
        return values.Clone();
    }

    ...
}

Então, na sua lógica de execução de comandos:

public void OnExecute(object parameter)
{
    var values = (object[])parameter;
    var width = (double)values[0];
    var height = (double)values[1];
}
Kent Boogaart
fonte
1
Obrigado Kent - era exatamente o que eu estava procurando. Eu gosto mais da sua primeira abordagem, para que a VM conheça o "estado" da exibição por meio de uma ligação sem que eu precise passar parâmetros, mas ainda posso testá-la. Não sei se isso funcionará para mim aqui, pois preciso da visualização para tornar a tela o maior possível e passar esse valor para a VM. Se eu ligá-lo, não precisarei definir a largura na VM? Nesse caso, a VM está vinculada à exibição?
JasonD
@ Jason: você pode fazê-lo de qualquer maneira. Ou seja, fazer com que o push da visualização mude de volta para o modelo de visualização ou faça com que o modelo de visualização mude para a visualização. Uma ligação de duas vias resultará em uma opção disponível para você.
Kent Boogaart 29/08/09
no meu parâmetro de método OnExecute programa é um array com valores nulos, mas, no conversor os valores são como esperado
Alex David
2
Acho que esse parâmetro é nulo no método OnExecute, também YourConverter.Convert () não foi chamado depois de clicar no botão. Por quê?
SubmarineX:
3
Isso não funciona, quando um botão é pressionado, os parâmetros são nulos
adminSoftDK
38

No conversor da solução escolhida, você deve adicionar values.Clone () caso contrário, os parâmetros no comando end null

public class YourConverter : IMultiValueConverter
{
    public object Convert(object[] values, ...)
    {
        return values.Clone();
    }

    ...
}
Daniel
fonte
6
Oi, esta adição ao Clone () faz com que funcione :) Você pode explicar a diferença. Porque eu não entendo por que ele precisa que Clone () funcione? Obrigado.
precisa saber é o seguinte
Eu posso estar errado, mas isso (linha 1267) parece que poderia ser o motivo para mim: referencesource.microsoft.com/#PresentationFramework/src/…
maxp
14

Use Tuple no conversor e, no OnExecute, converta o objeto de parâmetro novamente em Tuple.

public class YourConverter : IMultiValueConverter 
{      
    public object Convert(object[] values, ...)     
    {   
        Tuple<string, string> tuple = new Tuple<string, string>(
            (string)values[0], (string)values[1]);
        return (object)tuple;
    }      
} 

// ...

public void OnExecute(object parameter) 
{
    var param = (Tuple<string, string>) parameter;
}
Melinda
fonte
5

Se seus valores são estáticos, você pode usar x:Array:

<Button Command="{Binding MyCommand}">10
  <Button.CommandParameter>
    <x:Array Type="system:Object">
       <system:String>Y</system:String>
       <system:Double>10</system:Double>
    </x:Array>
  </Button.CommandParameter>
</Button>
Maxence
fonte
" Se seus valores são estáticos ": o que é um recurso estático? Por exemplo, a pergunta menciona Largura e Altura da Tela. Esses valores não são constantes, mas são estáticos? Qual seria o XAML nesse caso?
mins
2
Eu deveria ter escrito "constante" em vez de "estático". Um recurso estático é um recurso que não muda durante a execução. Se você usar, SystemColorspor exemplo, deverá usar em DynamicResourcevez de, StaticResourceporque o usuário pode alterar as cores do sistema via Painel de Controle durante a execução. Canvas Widthe Heightnão são recursos e não são estáticos. Existem propriedades de instância herdadas de FrameworkElement.
Maxence
2

Sobre o uso da Tupla no Converter, seria melhor usar 'objeto' em vez de 'string', para que funcione para todos os tipos de objetos sem limitação do objeto 'string'.

public class YourConverter : IMultiValueConverter 
{      
    public object Convert(object[] values, ...)     
    {   
        Tuple<object, object> tuple = new Tuple<object, object>(values[0], values[1]);
        return tuple;
    }      
} 

Então a lógica de execução em Command pode ser assim

public void OnExecute(object parameter) 
{
    var param = (Tuple<object, object>) parameter;

    // e.g. for two TextBox object
    var txtZip = (System.Windows.Controls.TextBox)param.Item1;
    var txtCity = (System.Windows.Controls.TextBox)param.Item2;
}

e multi-bind com conversor para criar os parâmetros (com dois objetos TextBox)

<Button Content="Zip/City paste" Command="{Binding PasteClick}" >
    <Button.CommandParameter>
        <MultiBinding Converter="{StaticResource YourConvert}">
            <Binding ElementName="txtZip"/>
            <Binding ElementName="txtCity"/>
        </MultiBinding>
    </Button.CommandParameter>
</Button>
alex
fonte
Eu gosto deste, pois fica mais claro quantos parâmetros o conversor suporta. Bom para apenas dois parâmetros! (Além disso, você mostrou a função de execução XAML e Command para obter cobertura total)
Caleb W.