web-dev-qa-db-de.com

WPF Binden von UI-Ereignissen an Befehle in ViewModel

Ich arbeite gerade an einer einfachen Anwendung, um MVVM zu folgen, und meine Frage ist, wie ich ein SelectionChanged-Ereignis aus meinem Code in das viewModel verschieben kann. Ich habe mir einige Beispiele von Bindungselementen an Befehlen angesehen, habe es aber nicht ganz verstanden. Kann jemand mithelfen. Vielen Dank!

Kann jemand eine Lösung bereitstellen, die den folgenden Code verwendet? Danke vielmals!  

public partial class MyAppView : Window 
{
    public MyAppView()
    {
        InitializeComponent();

        this.DataContext = new MyAppViewModel ();

        // Insert code required on object creation below this point.
    }

    private void contactsList_SelectionChanged(object sender, System.Windows.Controls.SelectionChangedEventArgs e)
    {
        //TODO: Add event handler implementation here.           
        //for each selected contact get the labels and put in collection 

        ObservableCollection<AggregatedLabelModel> contactListLabels = new ObservableCollection<AggregatedLabelModel>();

        foreach (ContactListModel contactList in contactsList.SelectedItems)
        {
            foreach (AggregatedLabelModel aggLabel in contactList.AggLabels)
            {
                contactListLabels.Add(aggLabel);
            }
        }
        //aggregate the contactListLabels by name
        ListCollectionView selectedLabelsView = new ListCollectionView(contactListLabels);

        selectedLabelsView.GroupDescriptions.Add(new PropertyGroupDescription("Name"));
        tagsList.ItemsSource = selectedLabelsView.Groups;
    }
}
44
Ben

Sie sollten eine EventTrigger zusammen mit InvokeCommandAction aus dem Windows.Interactivity-Namespace verwenden. Hier ist ein Beispiel:

<ListBox ...>
    <i:Interaction.Triggers>
        <i:EventTrigger EventName="SelectionChanged">
            <i:InvokeCommandAction Command="{Binding SelectedItemChangedCommand}"/>
        </i:EventTrigger>
    </i:Interaction.Triggers>
</ListBox>

Sie können auf System.Windows.Interactivity verweisen, indem Sie Add reference > Assemblies > Extensions aufrufen. 

Der vollständige i-Namespace lautet: xmlns:i="clr-namespace:System.Windows.Interactivity;Assembly=System.Windows.Interactivity".

92
Pavlo Glazkov

Diese Frage hat ein ähnliches Problem.

WPF MVVM: Befehle sind einfach. So verbinden Sie View und ViewModel mit RoutedEvent

Ich gehe mit diesem Problem um, wenn ich eine SelectedItem-Eigenschaft im ViewModel habe und dann das SelectedItem Ihrer ListBox oder was auch immer an diese Eigenschaft binde.

9

Ihre beste Wette ist Windows.Interactivity. Verwenden Sie EventTriggers, um eine ICommand an eine RoutedEvent anzuhängen.

Hier ist ein Artikel, mit dem Sie beginnen können: Silverlight und WPF-Verhalten und -Auslöser

7
decyclone

Um dies zu ändern, müssen Sie Ihr Denken ändern. Sie behandeln kein Ereignis "Auswahl geändert" mehr, sondern speichern das ausgewählte Element in Ihrem Ansichtsmodell. Sie würden dann die bidirektionale Datenbindung verwenden, damit das Ansichtsmodell aktualisiert wird, wenn der Benutzer ein Element auswählt. Wenn Sie das ausgewählte Element ändern, wird Ihre Ansicht aktualisiert.

7
M. Dudley
<ListBox SelectionChanged="{eb:EventBinding Command=SelectedItemChangedCommand, CommandParameter=$e}">

</ListBox>

Befehl

{eb: EventBinding} (Ein einfaches Benennungsmuster, um den Befehl zu finden)

{eb: EventBinding-Befehl = Befehlsname}

Befehlsparameter

$ e (EventAgrs)

$ this oder $ this.Property

schnur

https://github.com/JonghoL/EventBindingMarkup

2
jongho

Ich würde der Top-Antwort in dieser Frage folgen

Grundsätzlich enthält Ihr Ansichtsmodell eine Liste aller Elemente und eine Liste der ausgewählten Elemente. Sie können dann an Ihre Listbox ein Verhalten anhängen, das Ihre Liste der ausgewählten Elemente verwaltet.

Wenn Sie dies tun, bedeutet dies, dass Sie nichts im Code hinter sich haben, und das XAML ist ziemlich einfach zu befolgen. Außerdem kann das Verhalten an anderer Stelle in Ihrer App verwendet werden.

<ListBox ItemsSource="{Binding AllItems}" Demo:SelectedItems.Items="{Binding SelectedItems}" SelectionMode="Multiple" />
1
Tom Dudfield

Manchmal funktioniert die Lösung des Bindungsereignisses für den Befehl über den Interaktivitätsauslöser nicht, wenn das Ereignis der benutzerdefinierten Benutzerkontrolle ..__ gebunden werden muss. In diesem Fall können Sie benutzerdefiniertes Verhalten verwenden.

Bindungsverhalten wie folgt deklarieren:

public class PageChangedBehavior
{
    #region Attached property

    public static ICommand PageChangedCommand(DependencyObject obj)
    {
        return (ICommand)obj.GetValue(PageChangedCommandProperty);
    }
    public static void SetPageChangedCommand(DependencyObject obj, ICommand value)
    {
        obj.SetValue(PageChangedCommandProperty, value);
    }

    public static readonly DependencyProperty PageChangedCommandProperty =
        DependencyProperty.RegisterAttached("PageChangedCommand", typeof(ICommand), typeof(PageChangedBehavior),
            new PropertyMetadata(null, OnPageChanged));

    #endregion

    #region Attached property handler

    private static void OnPageChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var control = d as PageControl;
        if (control != null)
        {
            if (e.NewValue != null)
            {
                control.PageChanged += PageControl_PageChanged;
            }
            else
            {
                control.PageChanged -= PageControl_PageChanged;
            }
        }
    }

    static void PageControl_PageChanged(object sender, int page)
    {
        ICommand command = PageChangedCommand(sender as DependencyObject);

        if (command != null)
        {
            command.Execute(page);
        }
    }

    #endregion

}

Und dann binden Sie es an den Befehl in xaml:

        <controls:PageControl
            Grid.Row="2"
            CurrentPage="{Binding Path=UsersSearchModel.Page,Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
            PerPage="{Binding Path=UsersSearchModel.PageSize,Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
            Count="{Binding Path=UsersSearchModel.SearchResults.TotalItemCount}"
            behaviors:PageChangedBehavior.PageChangedCommand="{Binding PageChangedCommand}">
        </controls:PageControl>
1
Fragment

Betrachten Sie Microsoft.Xaml.Behaviors.Wpf , dessen Besitzer Microsoft ist, die Sie auf dieser Seite sehen können.

System.Windows.Interactivity.WPF Besitzer ist mthamil, kann mir jeder sagen, ob es zuverlässig ist?

Beispiel für Microsoft.Xaml.Behaviors.Wpf:

<UserControl ...
             xmlns:behaviors="http://schemas.Microsoft.com/xaml/behaviors"
             ...>

<Button x:Name="button">
    <behaviors:Interaction.Triggers>
        <behaviors:EventTrigger EventName="Click" SourceObject="{Binding ElementName=button}">
            <behaviors:InvokeCommandAction Command="{Binding ClickCommand}" />
        </behaviors:EventTrigger>
    </behaviors:Interaction.Triggers>
</Button>

</UserControl>
1
ifeng

Wie @Cameron MacFarland erwähnt, würde ich einfach eine bidirektionale Bindung an eine Eigenschaft auf dem viewModel vornehmen. Im Eigenschaftssetter können Sie jede gewünschte Logik ausführen, z. B. je nach Ihren Anforderungen zu einer Kontaktliste hinzufügen. 

Ich würde die Eigenschaft jedoch nicht unbedingt 'SelectedItem' nennen, da das viewModel die Ansichtsebene und die Interaktion mit ihren Eigenschaften nicht kennen sollte. Ich würde es so etwas wie CurrentContact oder so nennen. 

Offensichtlich ist dies der Fall, es sei denn, Sie möchten nur Befehle als Übungsaufgabe usw. erstellen.

0
HAdes

Dies ist eine Implementierung mit einem MarkupExtension. Trotz der niedrigen Ebene (die in diesem Szenario erforderlich ist) ist der XAML-Code sehr einfach:

XAML

<SomeControl Click="{local:EventBinding EventToCommand}" CommandParameter="{local:Int32 12345}" />

Marup Extension

public class EventBindingExtension : MarkupExtension
{
    private static readonly MethodInfo EventHandlerImplMethod = typeof(EventBindingExtension).GetMethod(nameof(EventHandlerImpl), new[] { typeof(object), typeof(string) });
    public string Command { get; set; }

    public EventBindingExtension()
    {
    }
    public EventBindingExtension(string command) : this()
    {
        Command = command;
    }

    // Do not use!!
    public static void EventHandlerImpl(object sender, string commandName)
    {
        if (sender is FrameworkElement frameworkElement)
        {
            object dataContext = frameworkElement.DataContext;

            if (dataContext?.GetType().GetProperty(commandName)?.GetValue(dataContext) is ICommand command)
            {
                object commandParameter = (frameworkElement as ICommandSource)?.CommandParameter;
                if (command.CanExecute(commandParameter)) command.Execute(commandParameter);
            }
        }
    }

    public override object ProvideValue(IServiceProvider serviceProvider)
    {
        if (serviceProvider.GetService(typeof(IProvideValueTarget)) is IProvideValueTarget targetProvider &&
            targetProvider.TargetObject is FrameworkElement targetObject &&
            targetProvider.TargetProperty is MemberInfo memberInfo)
        {
            Type eventHandlerType;
            if (memberInfo is EventInfo eventInfo) eventHandlerType = eventInfo.EventHandlerType;
            else if (memberInfo is MethodInfo methodInfo) eventHandlerType = methodInfo.GetParameters()[1].ParameterType;
            else return null;

            MethodInfo handler = eventHandlerType.GetMethod("Invoke");
            DynamicMethod method = new DynamicMethod("", handler.ReturnType, new[] { typeof(object), typeof(object) });

            ILGenerator ilGenerator = method.GetILGenerator();
            ilGenerator.Emit(OpCodes.Ldarg, 0);
            ilGenerator.Emit(OpCodes.Ldstr, Command);
            ilGenerator.Emit(OpCodes.Call, EventHandlerImplMethod);
            ilGenerator.Emit(OpCodes.Ret);

            return method.CreateDelegate(eventHandlerType);
        }
        else
        {
            throw new InvalidOperationException("Could not create event binding.");
        }
    }
}
0
bytecode77