Przekazywanie dwóch parametrów polecenia przy użyciu powiązania WPF

155

Mam polecenie, które wykonuję z mojego pliku XAML przy użyciu następującej standardowej składni:

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

To działało dobrze, dopóki nie zdałem sobie sprawy, że potrzebuję DWÓCH informacji z widoku, aby ta operacja zakończyła się zgodnie z oczekiwaniami użytkowników (w szczególności szerokość i wysokość płótna).

Wygląda na to, że możliwe jest przekazanie tablicy jako argumentu do mojego polecenia, ale nie widzę sposobu na określenie powiązania z moimi dwoma właściwościami płótna w CommandParameter:

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

Jak przekazać do polecenia zarówno szerokość, jak i wysokość? Wydaje się, że nie jest to możliwe przy użyciu poleceń z XAML i muszę połączyć procedurę obsługi kliknięć w moim za kodem, aby uzyskać te informacje, które zostaną przekazane do mojej metody powiększania.

JasonD
źródło
[ stackoverflow.com/questions/58114752/ ... powyższe rozwiązanie. Miałem ten sam problem.)
user1482689

Odpowiedzi:

240

Po pierwsze, jeśli robisz MVVM, zazwyczaj te informacje są dostępne dla maszyny wirtualnej za pośrednictwem oddzielnych właściwości powiązanych z widokiem. Dzięki temu nie musisz przekazywać żadnych parametrów do swoich poleceń.

Możesz jednak również połączyć multi-bind i użyć konwertera do utworzenia parametrów:

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

W twoim konwerterze:

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

    ...
}

Następnie w logice wykonywania poleceń:

public void OnExecute(object parameter)
{
    var values = (object[])parameter;
    var width = (double)values[0];
    var height = (double)values[1];
}
Kent Boogaart
źródło
1
Dzięki, Kent - właśnie tego szukałem. Lepiej podoba mi się twoje pierwsze podejście, dzięki czemu maszyna wirtualna zna „stan” widoku poprzez powiązanie bez konieczności przekazywania parametrów w ogóle, ale nadal mogę to przetestować. Nie jestem pewien, czy to zadziała w moim przypadku, ponieważ potrzebuję widoku, aby maksymalnie powiększyć płótno i przekazać tę wartość do maszyny wirtualnej. Jeśli to powiążę, czy nie będę musiał ustawiać szerokości w maszynie wirtualnej? W takim przypadku maszyna wirtualna jest powiązana z widokiem?
JasonD
@Jason: możesz to zrobić w dowolny sposób. To znaczy, niech widok wypycha zmiany z powrotem do modelu widoku lub model widoku powinien wypychać zmiany do widoku. Wiązanie TwoWay spowoduje, że jedna z opcji będzie dostępna.
Kent Boogaart
w moim programie parametr metody OnExecute jest tablicą z wartościami null, ale w konwerterze wartości są zgodne z oczekiwaniami
Alex David
2
Uważam, że ten parametr ma wartość null w metodzie OnExecute, również YourConverter.Convert () nie został wywołany po kliknięciu przycisku. Czemu?
SubmarineX
3
To nie działa, gdy przycisk jest wciśnięty, parametry są zerowe
adminSoftDK
38

W konwerterze wybranego rozwiązania należy dodać wartości, Clone () w przeciwnym razie parametry w poleceniu kończą się null

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

    ...
}
Daniel
źródło
6
Cześć, ten dodatek z Clone () sprawia, że ​​działa :) Czy możesz wyjaśnić, jaką różnicę robi. Ponieważ nie rozumiem, dlaczego Clone () musi działać? Dziękuję Ci.
adminSoftDK
Mogę się mylić, ale wygląda na to, że to (wiersz 1267) może być dla mnie powodem: referenceource.microsoft.com/#PresentationFramework/src/ ...
maxp
14

Użyj Tuple w Converter, aw OnExecute rzutuj obiekt parametru z powrotem na 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
źródło
5

Jeśli twoje wartości są statyczne, możesz użyć 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
źródło
Jeśli wartości są statyczne ”: co to jest zasób statyczny? Na przykład pytanie dotyczy szerokości i wysokości płótna. Te wartości nie są stałe, ale czy są statyczne? Jaki byłby kod XAML w tym przypadku?
min
2
Powinienem był napisać „stały” zamiast „statyczny”. Zasób statyczny to zasób, który nie zmienia się podczas wykonywania. Jeśli używasz SystemColorsna przykład, powinieneś użyć DynamicResourcezamiast, StaticResourceponieważ użytkownik może zmieniać kolory systemowe za pośrednictwem Panelu sterowania podczas wykonywania. Canvas Widthi Heightnie są zasobami i nie są statyczne. Istnieją właściwości instancji dziedziczone z FrameworkElement.
Maxence
2

Jeśli chodzi o używanie krotki w konwerterze, lepiej byłoby użyć wyrażenia „object” zamiast „string”, aby działało to dla wszystkich typów obiektów bez ograniczenia obiektu „string”.

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

Wtedy logika wykonania w Command mogłaby wyglądać następująco

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

i multi-bind z konwerterem do tworzenia parametrów (z dwoma obiektami 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
źródło
Podoba mi się ten, ponieważ jest bardziej jasne, ile parametrów obsługuje konwerter. Dobre tylko dla dwóch parametrów! (Dodatkowo pokazałeś XAML i funkcję wykonania polecenia dla pełnego pokrycia)
Caleb W.