Laden...

MVM: Event aus UserControl mit Command binden

Erstellt von BJA-CH vor 6 Jahren Letzter Beitrag vor 6 Jahren 4.932 Views
B
BJA-CH Themenstarter:in
59 Beiträge seit 2017
vor 6 Jahren
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?

D
985 Beiträge seit 2014
vor 6 Jahren

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.

5.299 Beiträge seit 2008
vor 6 Jahren

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

T
461 Beiträge seit 2013
vor 6 Jahren

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

5.299 Beiträge seit 2008
vor 6 Jahren

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.

T
461 Beiträge seit 2013
vor 6 Jahren

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

D
985 Beiträge seit 2014
vor 6 Jahren

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

T
461 Beiträge seit 2013
vor 6 Jahren

@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... 😄

M
177 Beiträge seit 2009
vor 6 Jahren

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

B
BJA-CH Themenstarter:in
59 Beiträge seit 2017
vor 6 Jahren

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!