Twee opdrachtparameters doorgeven met een WPF-binding

Ik heb een opdracht die ik uitvoer vanuit mijn XAML-bestand met de volgende standaardsyntaxis:

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

Dit werkte prima totdat ik me realiseerde dat ik TWEE stukjes informatie uit de weergave nodig had om deze bewerking te voltooien zoals gebruikers verwachten (specifiek de breedte en hoogte van het canvas).

Het lijkt mogelijk om een ​​array als argument aan mijn opdracht door te geven, maar ik zie geen manier om de binding aan mijn twee canvaseigenschappen in de CommandParameter te specificeren:

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

Hoe geef ik zowel Breedte als Hoogte door aan mijn opdracht? Het lijkt erop dat dit niet mogelijk is met commando’s van XAML en ik moet een klik-handler in mijn codebehind aansluiten om deze informatie door te geven aan mijn zoommethode.


Antwoord 1, autoriteit 100%

Ten eerste, als u MVVM gebruikt, heeft u deze informatie doorgaans beschikbaar voor uw VM via afzonderlijke eigenschappen die zijn gekoppeld aan de weergave. Zo hoef je helemaal geen parameters meer door te geven aan je commando’s.

U kunt echter ook multi-binden en een converter gebruiken om de parameters te maken:

<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 je converter:

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

Vervolgens, in de logica van je commando-uitvoering:

public void OnExecute(object parameter)
{
    var values = (object[])parameter;
    var width = (double)values[0];
    var height = (double)values[1];
}

Antwoord 2, autoriteit 15%

In de converter van de gekozen oplossing moet u waarden toevoegen.Clone() anders eindigen de parameters in de opdracht null

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

Antwoord 3, autoriteit 5%

Gebruik Tuple in Converter en cast in OnExecute het parameterobject terug naar 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;
}

Antwoord 4, autoriteit 3%

Als uw waarden statisch zijn, kunt u x:Array:

. gebruiken

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

Antwoord 5, autoriteit 2%

Over het gebruik van Tuple in Converter, zou het beter zijn om ‘object’ te gebruiken in plaats van ‘string’, zodat het voor alle soorten objecten werkt zonder beperking van het ‘string’-object.

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

Dan zou de uitvoeringslogica in Command zo kunnen zijn

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

en multi-bind met converter om de parameters te maken (met twee TextBox-objecten)

<Button Content="Zip/City paste" Command="{Binding PasteClick}" >
    <Button.CommandParameter>
        <MultiBinding Converter="{StaticResource YourConvert}">
            <Binding ElementName="txtZip"/>
            <Binding ElementName="txtCity"/>
        </MultiBinding>
    </Button.CommandParameter>
</Button>

Antwoord 6

Deze taak kan ook op een andere manier worden opgelost. In plaats van een converter te programmeren en de code in de XAML te vergroten, kun je ook de verschillende parameters in het ViewModel aggregeren. Als resultaat heeft het ViewModel dan nog een eigenschap die alle parameters bevat.

Een voorbeeld van mijn huidige aanvraag, waarmee ik ook het onderwerp kan behandelen.
Een generiek RelayCommand is vereist: https://stackoverflow.com/a/22286816/7678085

De ViewModelBase wordt hier uitgebreid met een commando SaveAndClose. Het generieke type is een genoemde tuple die de verschillende parameters vertegenwoordigt.

public ICommand SaveAndCloseCommand => saveAndCloseCommand ??= new RelayCommand<(IBaseModel Item, Window Window)>
    (execute =>
    {
        execute.Item.Save();
        execute.Window?.Close(); // if NULL it isn't closed.
    },
    canExecute =>
    {
        return canExecute.Item?.IsItemValide ?? false;
    });
private ICommand saveAndCloseCommand;

Dan bevat het een eigenschap volgens het generieke type:

public (IBaseModel Item, Window Window) SaveAndCloseParameter 
{ 
    get => saveAndCloseParameter ; 
    set 
    {
        SetProperty(ref saveAndCloseParameter, value);
    }
}
private (IBaseModel Item, Window Window) saveAndCloseParameter;

De XAML-code van de weergave ziet er dan als volgt uit:
(Let op de klassieke klikgebeurtenis)

<Button 
    Command="{Binding SaveAndCloseCommand}" 
    CommandParameter="{Binding SaveAndCloseParameter}" 
    Click="ButtonApply_Click" 
    Content="Apply"
    Height="25" Width="100" />
<Button 
    Command="{Binding SaveAndCloseCommand}" 
    CommandParameter="{Binding SaveAndCloseParameter}" 
    Click="ButtonSave_Click" 
    Content="Save"
    Height="25" Width="100" />

en in de code achter de weergave, en vervolgens de klikgebeurtenissen evalueren, die vervolgens de parametereigenschap instellen.

private void ButtonApply_Click(object sender, RoutedEventArgs e)
{
    computerViewModel.SaveAndCloseParameter = (computerViewModel.Computer, null);
}
private void ButtonSave_Click(object sender, RoutedEventArgs e)
{
    computerViewModel.SaveAndCloseParameter = (computerViewModel.Computer, this);
}

Persoonlijk denk ik dat het gebruik van de klikgebeurtenissen geen breuk is met het MVVM-patroon. De programmastroombesturing bevindt zich nog steeds in het gebied van het ViewModel.

Other episodes