Übergeben von zwei Befehlsparametern mithilfe einer WPF-Bindung

154

Ich habe einen Befehl, den ich aus meiner XAML-Datei mit der folgenden Standardsyntax ausführe:

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

Dies funktionierte einwandfrei, bis mir klar wurde, dass ich ZWEI Informationen aus der Ansicht benötigte, um diesen Vorgang so auszuführen, wie es die Benutzer erwarten (insbesondere die Breite und Höhe der Leinwand).

Es scheint möglich zu sein, ein Array als Argument an meinen Befehl zu übergeben, aber ich sehe keine Möglichkeit, die Bindung an meine beiden Canvas-Eigenschaften im CommandParameter anzugeben:

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

Wie gebe ich sowohl Breite als auch Höhe an meinen Befehl weiter? Es scheint nicht möglich zu sein, dies mit Befehlen von XAML zu tun, und ich muss einen Klick-Handler in meinem Codebehind verkabeln, damit diese Informationen an meine Zoom-Methode übergeben werden.

JasonD
quelle
[ stackoverflow.com/questions/58114752/… die obige Lösung. Ich hatte das gleiche Problem.)
user1482689

Antworten:

239

Erstens, wenn Sie MVVM ausführen, stehen diese Informationen Ihrer VM normalerweise über separate Eigenschaften zur Verfügung, die an die Ansicht gebunden sind. Das erspart Ihnen die Übergabe von Parametern an Ihre Befehle.

Sie können jedoch auch mehrere Bindungen verwenden und einen Konverter verwenden, um die Parameter zu erstellen:

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

In Ihrem Konverter:

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

    ...
}

Dann in Ihrer Befehlsausführungslogik:

public void OnExecute(object parameter)
{
    var values = (object[])parameter;
    var width = (double)values[0];
    var height = (double)values[1];
}
Kent Boogaart
quelle
1
Danke Kent - genau das habe ich gesucht. Ihr erster Ansatz gefällt mir besser, damit die VM den "Status" der Ansicht durch eine Bindung kennt, ohne dass ich überhaupt Parameter übergeben muss, aber ich kann ihn trotzdem testen. Ich bin mir nicht sicher, ob das hier für mich funktionieren wird, da ich die Ansicht brauche, um die Zeichenfläche so groß wie möglich zu machen und diesen Wert an die VM zu übergeben. Wenn ich es binde, muss ich dann nicht die Breite in der VM einstellen? In welchem ​​Fall ist die VM an die Ansicht gebunden?
JasonD
@ Jason: Du kannst es so oder so machen. Das heißt, lassen Sie die Ansicht Änderungen an das Ansichtsmodell zurückschieben oder lassen Sie das Ansichtsmodell Änderungen an der Ansicht verschieben. Eine TwoWay-Bindung führt dazu, dass Ihnen beide Optionen zur Verfügung stehen.
Kent Boogaart
In meinem Programm ist der OnExecute-Methodenparameter ein Array mit Nullwerten, aber im Konverter sind die Werte wie erwartet
Alex David
2
Ich finde, dass der Parameter in der OnExecute-Methode null ist, auch YourConverter.Convert () wurde nach dem Klicken auf die Schaltfläche nicht aufgerufen. Warum?
SubmarineX
3
Dies funktioniert nicht, wenn eine Taste gedrückt wird, sind die Parameter null
adminSoftDK
38

Im Konverter der ausgewählten Lösung sollten Sie Werte hinzufügen.Clone (), andernfalls enden die Parameter im Befehl end null

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

    ...
}
Daniel
quelle
6
Hallo, diese Ergänzung mit Clone () macht es möglich :) Kannst du bitte erklären, welchen Unterschied es macht. Weil ich nicht verstehe, warum Clone () benötigt wird, um zu funktionieren? Danke dir.
adminSoftDK
Ich könnte mich irren, aber dies (Zeile 1267)
scheint
14

Verwenden Sie Tuple in Converter und wandeln Sie in OnExecute das Parameterobjekt zurück in 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
quelle
5

Wenn Ihre Werte statisch sind, können Sie Folgendes verwenden 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
quelle
" Wenn Ihre Werte statisch sind ": Was ist eine statische Ressource? In der Frage werden beispielsweise Leinwandbreite und -höhe erwähnt. Diese Werte sind nicht konstant, aber statisch? Was wäre die XAML in diesem Fall?
Minuten
2
Ich hätte "konstant" statt "statisch" schreiben sollen. Eine statische Ressource ist eine Ressource, die sich während der Ausführung nicht ändert. Wenn Sie SystemColorsbeispielsweise verwenden, sollten Sie DynamicResourcestattdessen verwenden, StaticResourceda der Benutzer die Systemfarben während der Ausführung über die Systemsteuerung ändern kann. Canvas Widthund Heightsind keine Ressourcen und nicht statisch. Es gibt Instanzeigenschaften, die von geerbt wurden FrameworkElement.
Maxence
2

Bei der Verwendung von Tuple in Converter ist es besser, "Objekt" anstelle von "Zeichenfolge" zu verwenden, damit es für alle Objekttypen ohne Einschränkung des Objekts "Zeichenfolge" funktioniert.

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

Dann könnte die Ausführungslogik in Command so aussehen

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

und Multi-Bind mit Konverter, um die Parameter zu erstellen (mit zwei TextBox-Objekten)

<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
quelle
Ich mag dieses, da klarer ist, wie viele Parameter der Konverter unterstützt. Gut für nur zwei Parameter! (Außerdem haben Sie die XAML- und Command Execute-Funktion für eine vollständige Abdeckung gezeigt.)
Caleb W.