Laden...

MVM: Event aus UserControl mit Command binden

Letzter Beitrag vor 7 Jahren 10 Posts 5.041 Views
MVM: Event aus UserControl mit Command binden

Hallo zäme
Ich habe ein UserControl, welches mit einem Event ein Zustand an das aufrufende Element (XAML) weitergibt. Eine "normal" Eventübergabe in den Code-Behind funktioniert.
Nach dem Konzept des MVVM soll aber ein Event mit Commands übergeben werden. Dies funktioniert in den Steuerelementen, z.B. bei einem Mouse-Trigger etwa wie folgt:


            <i:Interaction.Triggers>
                <i:EventTrigger EventName="MouseEnter">
                    <i:InvokeCommandAction Command="{Binding Path=MouseEventCommand}" CommandParameter="{Binding}"/>
                </i:EventTrigger>
            </i:Interaction.Triggers>

Wenn ich dies mit meinem Event aus meinem UserControl auch tue, funktioniert nichts. Das hat vermutlich zu tun, dass ein Event und ein ICommand wohl nicht das gleiche ist.

Wie kann ich in einem UserControl ein Event bauen, damit ich diesen mit einem Command-Befehl in mein ViewModel bringen kann?

Weiss jemand hier weiter, diese Problem müsste doch schon längstens gelöst sein.
Überhaupt, ich finde auch das propangierte Konzept mit dem "Interaction.Trigger" etwas fragwürdig. Ist das wirklich das Konzept, das man heute für solch zentrale Aufgaben verwendet?

Wenn du uns mehr über das erzählst was du wirklich machen möchtest (anstatt uns nur davon zu erzählen wie du es nicht lösen kannst) könnten wir dir wohl besser weiterhelfen.

Überhaupt, ich finde auch das propangierte Konzept mit dem "Interaction.Trigger" etwas fragwürdig. Ist das wirklich das Konzept, das man heute für solch zentrale Aufgaben verwendet? Jo, viele tun das, und finden das toll.
Manche aber auch nicht.
Ich zB. finde das so dermassen umständlich, dass ich mir lieber ein Control ins Viewmodel hole (ganz böse!! :evil: ), damit ich dessen Events behandeln kann.
Denn darum gehts ja bei der Interaction.Event-Trigger-Binding-auf-Commands: dass Events eines Controls behandelt werden.
Also den Vorteil, dass ich die Events so behandeln kann, wie ich sie brauche, erkaufe ich mir durch 2 nachteile:*geht nicht, wenn mehrere Controls ans selbe Viewmodel binden *soll wohl Probleme beim UnitTesting aufwerfen (ich habs noch nicht ersnsthaft probiert)

vlt. gibts noch mehr Nachteile - grad nicht im Kopf.
Ich wollte nur kundtun, dassichdas mit dem "fragwürdig" gut verstehe (oder gut zu verstehen glaube)

Der frühe Apfel fängt den Wurm.

Ja, welcher WPF'ler stand noch nicht vor diesem Problem 😃

Meine Lösung, derzeit nur für Window.Closing: (ist teilweise noch verbesserungswürdig...)


public static class WindowEventsBinding
    {
        #region Closing

        /// <summary>
        /// Closing Event.
        /// </summary>
        public static readonly DependencyProperty ClosingProperty = DependencyProperty.RegisterAttached( "Closing", typeof( ICommand ), typeof( WindowEventsBinding ), new UIPropertyMetadata( new PropertyChangedCallback( WindowEventsBinding.ClosingChanged ) ) );

        public static ICommand GetClosing(DependencyObject obj)
        {
            return obj.GetValue( WindowEventsBinding.ClosingProperty ) as ICommand;
        }

        public static void SetClosing(DependencyObject obj, ICommand value)
        {
            obj.SetValue( WindowEventsBinding.ClosingProperty, value );
        }

        private static void ClosingChanged( DependencyObject target, DependencyPropertyChangedEventArgs e )
        {
            Window window = target as Window;
            if (window == null) return;

            if (e.NewValue != null) window.Closing += WindowEventsBinding.Window_Closing;
            else window.Closing -= WindowEventsBinding.Window_Closing;
        }

        private static void Window_Closing( object sender, global::System.ComponentModel.CancelEventArgs e )
        {
            Window window = sender as Window;
            if (window == null) return;

            ICommand closingBinding = window.GetValue( WindowEventsBinding.ClosingProperty ) as ICommand;
            if (closingBinding == null) return;

            if (closingBinding.CanExecute( null )) closingBinding.Execute( null );
            else e.Cancel = true;
        }

        #endregion
    }

und im XAML:


<Window x:Class="[Namespace].MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:wb="clr-namespace:[Namespace];assembly=[assembly]"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        mc:Ignorable="d"
        wb:WindowEventsBinding.Closing="{Binding Path=ClosingCmd}">
</Window>

Ist immer die Frage welchen Umweg man lieber macht...

Ich habe den Titel mal angepasst, so dass Suchende auch etwas damit anfangen können. EDIT: Ich sollte beim Wort "Shift" im Titel das "f" nicht vergessen... 😄

also eine extra-Klasse, > 30 Zeilen Code, mit DependancyProperty - nur für ein popeliges Window-Closing-Event?
wie gesagt: fragwürdig.

Der frühe Apfel fängt den Wurm.

Naja, für eine Einmalverwendung sicher unnötig aber wer programmiert schon so... 😉

Ich habe den Titel mal angepasst, so dass Suchende auch etwas damit anfangen können. EDIT: Ich sollte beim Wort "Shift" im Titel das "f" nicht vergessen... 😄

@ThomasE.

Du hast da einen kleinen Bug drin


public static class WindowEventsBinding
    {
        // ...
        private static void ClosingChanged( DependencyObject target, DependencyPropertyChangedEventArgs e )
        {
            Window window = target as Window;
            if (window == null) return;

            if (e.OldValue == null && e.NewValue != null) window.Closing += WindowEventsBinding.Window_Closing;
            
            if (e.NewValue == null && e.OldValue != null) window.Closing -= WindowEventsBinding.Window_Closing;
        }
    }

Kommt wahrscheinlich nie zum Vorschein, weil du die Command-Eigenschaft nur einmal setzt.

BTW: Bei Setter/Getter könntest du noch statt DependencyObject den Typ als Window festlegen, dann kann diese Eigenschaft wirklich nur an einem Window gesetzt werden.

@Sir Rufo,

besten Dank für die Info, werd mir das gleich notieren!

Grüße

Ich habe den Titel mal angepasst, so dass Suchende auch etwas damit anfangen können. EDIT: Ich sollte beim Wort "Shift" im Titel das "f" nicht vergessen... 😄

Ich finde die Variante von ThomasE. legitim sowie die Verwendung der Interactions vom Blend SDK.

<Window x:Class="WpfApp1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
        xmlns:local="clr-namespace:WpfApp1"
        mc:Ignorable="d" 
        Title="MainWindow" Height="350" Width="525">
    <i:Interaction.Triggers>
        <i:EventTrigger EventName="Closing">
            <i:InvokeCommandAction Command="{Binding Path=CloseCommand}" CommandParameter="{Binding}"/>
        </i:EventTrigger>
    </i:Interaction.Triggers>

Es macht durchaus Sinn für Events, bei denen man öfter ein Command ausführen will, eine Helper Klasse mit Attached Properties anzulegen. Die Helper Klasse kann man ja in mehreren Projekten wiederverwenden. Die Zeitersparnis und der Komfort zahlt sich jedenfalls aus.

Wenn ich dies mit meinem Event aus meinem UserControl auch tue, funktioniert nichts.

Kannst du das bitte präzisieren? Wird der Command nicht ausgelöst?

Wie kann ich in einem UserControl ein Event bauen...

RoutedEvent - https://msdn.microsoft.com/de-de/library/ms752288(v=vs.110).aspx

, damit ich diesen mit einem Command-Befehl in mein ViewModel bringen kann?

Keine Ahnung was du damit meinst, ich kann es nur erahnen. Jedenfalls wenn du ein RoutedEvent erstellst, kannst du dieses dann im i:EventTrigger EventName="DeinCustomRoutedEvent" angeben.

PS: Passt zum Thema: https://docs.microsoft.com/en-us/dotnet/framework/wpf/advanced/routed-events-overview#routing-strategies

Besten Dank für eure Hinweise. Tatsächlich habe ich meinen Fehler gefunden.
Ich hatte im UserControl nur ein Delagte/Event gebaut und kein RoutedEvent.
Mit dem RoutedEvent wird der Interaction EventTrigger angesprochen.

Besten Dank für eure Beiträge!