Laden...

Markieren mehrerer Items in ListView bei MouseMove

Erstellt von BJA-CH vor 6 Jahren Letzter Beitrag vor 6 Jahren 4.303 Views
B
BJA-CH Themenstarter:in
59 Beiträge seit 2017
vor 6 Jahren
Markieren mehrerer Items in ListView bei MouseMove

Hat jemand eine Idee wie ich folgendes hinkriege?
Ich habe ein ListView mit ListViewItems (UnDo-Liste). Nun möchte ich die Einträge von Beginn der Liste bis zur Cursorposition (MouseMove) markieren. Wenn ich die Maus verschiebe, soll die Markierung sich mit ändern und wieder von Beginn der Liste (oben) bis zur neue Cursorposition (ListViewItem) führen.
Habe momentan keine Idee, wie ich dies machen könnte....
Besten Dank!

5.299 Beiträge seit 2008
vor 6 Jahren

Naja - musste halt in irgendeiner Weise die entsprechenden Events verarbeiten, die betroffenen Daten ermitteln und iwei markieren.
Schon zu ersterem gibts 2 prinzipielle Vorgehensweisen:

  1. good old Codebehind-Code
  2. iwelche "EventToCommands" - Klimmzüge, um den View-Zustand ins Viewmodel zu melden.

Nach meiner Erfahrung ist das eine wie das andere nicht ganz einfach, Crux ist halt, dass View und Viewmodel stärker miteinander verbunden werden müssen, als es normale DelegateCommand-Bindings hergeben.

Mit meim ersten Satz ist übrigens auch gleich gesagt, dass du im Viewmodel ühaupt etwas vorsehen musst, dass Daten auch als selektiert markiert werden können.

(Und was noch ganz aussen vor steht ist die Frage, ob du ühaupt nach dem MVVM-Pattern entwickelst - sonst ist das erstmal das Thema, denn was anneres als MVVM zu verzapfen kommt nach glaub einhelliger Ansicht nicht in Betracht.)

Der frühe Apfel fängt den Wurm.

301 Beiträge seit 2009
vor 6 Jahren

Deine Spezifikation ist recht ungenau. Mousemove bedeutet du hältst den Mousebutton dabei nicht gedrückt? Beginn der Liste heißt ab dem aktuell markierten Datensatz oder wirklich erst Position? Was passiert beim scrollen? Soll der Anwendungsfall ähnlich dem im Windows Explorer beim markieren von Dateien sein? Mach dir genau Gedanken darüber was du machen möchtest und recherchiere an welche Events du dich anhängen musst.

Zur Umsetzung :

Ich würde an deiner Stelle auf Behaviors setzen. Mit Codebehind wirst du dieselbe Umsetzung immer wieder neu schreiben müssen. Mit Commands wird das ganze extrem umständlich da die Mouseevents viel zu häufig getriggert werden. Behaviors hingegen erweitern die Funktionalität eines Steuerelements und du kannst dich im Code des Behaviors an die passenden Events dran hängen. Mit Hilfe von Command DependencyProperties in deinem Behavior kannst du dich dann an Funktionalität deines Behaviors dranhängen.

Die Frage ist hier aber auch wie vertraut du mit WPF bist und wie sauber deine Umsetzung werden soll.

Zur Selektierung von Elementen in einer ListView wirst du im Netz aber bestimmt auch ein paar Beispiele und Open Source Libraries finden. Oder du setzt ggf. auf eine fertige Gratis Lösung. Auch hier gibt es ein paar gute.

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

Also... ich habe es nun eine einfache Lösung gefunden. Ob es schön (!) programmiert ist, das werdet ihr mir nun sicher gleich sagen...


    <UserControl.Resources>

        <Style x:Key="itemStyle" TargetType="{x:Type ListViewItem}">
            <EventSetter Event="MouseMove" Handler="listItem_MouseMove"/>
        </Style>
        
    </UserControl.Resources>
    
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>
        <ScrollViewer Name="PART_ScrollViewer" Grid.Row="0"
                                                  HorizontalScrollBarVisibility="Hidden"
                                                  VerticalScrollBarVisibility="Auto">
            <ListView x:Name="PART_ListListe" MinWidth="150"
                      ItemContainerStyle="{StaticResource itemStyle}"
                      MouseLeave="ListView_MouseLeave"
                      SelectionMode="Multiple"
                      SelectionChanged="OnSelectedChanged">
                <ListView.View>
                    <GridView>
                        <GridViewColumn DisplayMemberBinding="{Binding Name}"/>
                    </GridView>
                </ListView.View>
                <ListView.Resources>
                    <Style TargetType="GridViewColumnHeader">
                        <Setter Property="Visibility" Value="Collapsed"/>
                    </Style>
                </ListView.Resources>
            </ListView>
        </ScrollViewer>
        <Button Grid.Row="1" Name="PART_ButtonDelete" Content="Alle Loeschen"
                Click="buttonDelete_Clicked"/>
    </Grid>
</UserControl>

Und der Code dazu:


        private void listItem_MouseMove(object sender, MouseEventArgs e)
        {
            this._isMarkieren = true;
            for (int i = 0; i < this.PART_ListListe.Items.Count; i++)
                (this.PART_ListListe.ItemContainerGenerator.ContainerFromIndex(i) as ListViewItem).IsSelected = false;

            ListViewItem item = (ListViewItem)sender;
            int index = this.PART_ListListe.ItemContainerGenerator.IndexFromContainer(item);
            for (int i = 0; i < index; i++)
            {
                (this.PART_ListListe.ItemContainerGenerator.ContainerFromIndex(i) as ListViewItem).IsSelected = true;
            }
            this._isMarkieren = false;
        }


        private void ListView_MouseLeave(object sender, MouseEventArgs e)
        {
            this._isMarkieren = true;
            for (int i = 0; i < this.PART_ListListe.Items.Count; i++)
                (this.PART_ListListe.ItemContainerGenerator.ContainerFromIndex(i) as ListViewItem).IsSelected = false;
            this._isMarkieren = false;
        }

        private void OnSelectedChanged(object sender, SelectionChangedEventArgs e)
        {
            if (this._isMarkieren)
                return;

            ListView list = (ListView)sender;
            int nr = list.SelectedItems.Count - 1;
            RoutedPropertyChangedEventArgs<int> ev =
                        new RoutedPropertyChangedEventArgs<int>(0, nr, UnDoRowEvent);
            OnUnDoRow(ev);
        }

Danke für eure Unterstützung und Ratschläge...

301 Beiträge seit 2009
vor 6 Jahren

Kann man wohl so machen 😃 . Mit Behaviors könntest du es wiederverwendbar implementieren. Ist aber kein Muss. Wenn es so für dich funktioniert würde ich es so lassen.

Hinweis :

Die "PART_" Naming terminologie verwendet man eigentlich nur im Templating Bereich um zu signalisieren das ein Steuerelement ein Teil des Gesamten Controls ist. Du kannst das natürlich so lassen aber wollte dich da nur in Kenntnis setzen damit dir der Unterschied bewußt ist.

Der Code selbst scheint funktional zu sein, ist aber recht umständlich zu lesen. Du könntest um das ganze lesbarer zu machen deine for Schleifen in separate Methoden extrahieren und diese "UnselectItemAfterIndex(x)" oder sowas in der Richtung nennen.

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

Hallo KroaX
Besten Dank für deine Würdigung. Nehme deine Anregungen gerne auf.
Die Behaviors habe ich bis jetzt leide nicht gekannt. Doch ich beschäftige mich nun damit. Vielleicht bringe ich dann die "gute" Lösung auch noch hin.

Darf ich dazu was fragen:
Ich habe das Assembly System.Windows.Interactivity.dll bzw. der Verweis "System.Windows.Interactivity" auf meinem System gesucht und nicht gefunden. Musste es bei einem anderen Programm, mit dem es mitgeliefert wurde entnehmen. Was muss ich auf Visual Studio (2015) installieren und wie tue ich das, damit ich mit diesen Behaviors wirklich arbeiten kann?
Scheint eine tolle Sache zu sein... das in den Fachbüchern kaum in Erscheinung tritt....

Danke dir!

301 Beiträge seit 2009
vor 6 Jahren

Die System.Interactivity Dll bekommst du in der Regel über den NuGet Package Manager von Visual Studio. Einfach mal nach Suchen.

Behaviors sind nach meinem Bauchgefühl her wirklich nicht so verbreitet. Warum weiß ich leider nicht. Im Grund genommen sind sie den Attached Properties sehr ähnlich, funktionieren aber auf Instanzebene und nicht auf statischer Klassenerweiterungsebene.

Im Netz findet man einiges dazu. In Büchern ist es meist nur angerissen oder in Form von attached Properties erwähnt.

5.299 Beiträge seit 2008
vor 6 Jahren

Jo, Behaviors hatte ich zu erwähnen vergessen.
Aber muss man auch aufpassen - oder zumindest überlegen, obs was bringt.
Der Sinn eines Behaviors ist ja, ein bestimmtes GUI-"Verhalten" darzustellen. Nun sind aber viele Anforderungen ganz ganz spezifisch für die eine Anwendung, oder auch nur für eine einzige View, und sind prinzipell nicht auf andere Anwendungen übertragbar.

Was nützt in dem Falle ein Behavior aus dem Codebehind auszulagern, wenn mans eh nur für genau dieses eine Gui-Element, in Verbindung mit genau dem dazugehörigen Viewmodel-Element verwenden kann?

Jo - das ist gewissermassen meine "Definition" eines Behaviors: Eine Klasse, die das Verarbeiten von ViewElement-Events aus dem Codebehind auslagert in eine eigene Klassem die dann per Xaml ans View attacht werden kann.

Wie gesagt: hat nur Sinn, wenn man das ausgekoppelte iwo anners auch wieder "einkoppeln" kann - ansonsten kann mans eiglich auch da lassen, wo's ursprünglich herkommt - dann hat man einfacheres Xaml.

Der frühe Apfel fängt den Wurm.

D
985 Beiträge seit 2014
vor 6 Jahren

Wie gesagt: hat nur Sinn, wenn man das ausgekoppelte iwo anners auch wieder "einkoppeln" kann - ansonsten kann mans eiglich auch da lassen, wo's ursprünglich herkommt - dann hat man einfacheres Xaml.

Wenn ein Behavior die ganze Sache vereinfacht, dann ist es egal ob man es nur einmal braucht oder x-mal.

Eine Attached Property für die ListBox (um sich dort einzuhängen) und eine für das ListBoxItem (für die Markierung) und dann 2 Styles

Behavior:


public static class UndoListBehavior
{
    public static readonly DependencyProperty IsUndoListProperty =
        DependencyProperty.RegisterAttached(
            "IsUndoList",
            typeof( bool ),
            typeof( UndoListBehavior ),
            new PropertyMetadata( false, OnIsUndoListChanged ) );

    private static void OnIsUndoListChanged( DependencyObject d, DependencyPropertyChangedEventArgs e )
    {
        var listBox = d as ListBox;
        var isUndoList = (bool) e.NewValue;

        if ( isUndoList )
        {
            listBox.MouseLeave += ListBox_MouseLeave;
            listBox.MouseMove += ListBox_MouseMove;
        }
        else
        {
            listBox.MouseLeave -= ListBox_MouseLeave;
            listBox.MouseMove -= ListBox_MouseMove;
        }
    }

    private static void ListBox_MouseMove( object sender, System.Windows.Input.MouseEventArgs e )
    {
        var listBox = sender as ListBox;
        var listBoxItem = listBox.GetContainerAtPoint<ListBoxItem>( e.GetPosition( listBox ) );
            
        bool isSelected = false;


        for ( int i = 0; i < listBox.Items.Count; i++ )
        {
            var item = listBox.ItemContainerGenerator.ContainerFromIndex( i ) as ListBoxItem;

            SetIsPreviewSelected( item, isSelected );

            if ( item == listBoxItem )
            {
                isSelected = true;
            }
        }
    }

    private static void ListBox_MouseLeave( object sender, System.Windows.Input.MouseEventArgs e )
    {
        var listBox = sender as ListBox;
        for ( int i = 0; i < listBox.Items.Count; i++ )
        {
            var item = listBox.ItemContainerGenerator.ContainerFromIndex( i ) as ListBoxItem;
            SetIsPreviewSelected( item, false );
        }
    }

    public static bool GetIsUndoList( ListBox d )
    {
        return (bool) d.GetValue( IsUndoListProperty );
    }

    public static void SetIsUndoList( ListBox d, bool value )
    {
        d.SetValue( IsUndoListProperty, value );
    }

    public static readonly DependencyProperty IsPreviewSelectedProperty =
        DependencyProperty.RegisterAttached(
            "IsPreviewSelected",
            typeof( bool ),
            typeof( UndoListBehavior ),
            new PropertyMetadata( false ) );

    public static bool GetIsPreviewSelected( ListBoxItem d )
    {
        return (bool) d.GetValue( IsPreviewSelectedProperty );
    }

    public static void SetIsPreviewSelected( ListBoxItem d, bool value )
    {
        d.SetValue( IsPreviewSelectedProperty, value );
    }

    public static ItemContainer GetContainerAtPoint<ItemContainer>( this ItemsControl control, Point p )
        where ItemContainer : DependencyObject
    {
        HitTestResult result = VisualTreeHelper.HitTest( control, p );
        DependencyObject obj = result.VisualHit;

        while ( VisualTreeHelper.GetParent( obj ) != null && !( obj is ItemContainer ) )
        {
            obj = VisualTreeHelper.GetParent( obj );
        }

        // Will return null if not found
        return obj as ItemContainer;
    }
}

Styles


<Style x:Key="UndoListBoxItemStyle" TargetType="ListBoxItem">
    <Style.Triggers>
        <Trigger Property="local:UndoListBehavior.IsPreviewSelected" Value="true">
            <Setter Property="Background" Value="{x:Static SystemColors.InactiveSelectionHighlightBrush}"/>
        </Trigger>
    </Style.Triggers>
</Style>
<Style x:Key="UndoListBoxStyle" TargetType="ListBox">
    <Setter Property="local:UndoListBehavior.IsUndoList" Value="True"/>
    <Setter Property="ItemContainerStyle" Value="{StaticResource UndoListBoxItemStyle}"/>
</Style>

und in der XAML selber dann


<ListBox
    ItemsSource="{Binding UndoItems}"
    SelectedItem="{Binding UndoItem}"
    Style="{StaticResource UndoListBoxStyle}">

Wenn wir also von "einfacheres XAML" sprechen, dann sieht das für mich so aus 😁

[EDIT]Code korrigiert[/EDIT]

5.299 Beiträge seit 2008
vor 6 Jahren

Hmm - sieht interessant aus, aber wenn ichs einbinde laufe ich auf eine InvalidcastException:> Fehlermeldung:

Das Objekt des Typs "PersonListboxBehaviorTester.ViewModel.Person" kann nicht in Typ "System.Windows.Controls.ListBoxItem" umgewandelt werden. bei foreach in


   public static class UndoListBehavior {
//...
      private static void ListBox_MouseMove(object sender, System.Windows.Input.MouseEventArgs e) {
         var listBox = sender as ListBox;
         var listBoxItem = listBox.GetContainerAtPoint<ListBoxItem>(e.GetPosition(listBox));
         bool isSelected = false;
         foreach (ListBoxItem item in listBox.Items) {
            SetIsPreviewSelected(item, isSelected);
            if (item == listBoxItem) { isSelected = true; }
         }
      }

Ist auch iwie logisch, weil ich hab keine ListBoxItem in meine listBox.Items, sondern - in meim Falle - Instanzen meiner Person-Viewmodelklasse.

mein Xaml noch:

 <ListBox Grid.Row="2"  Grid.Column="2" ItemsSource="{Binding UndoItems}"  DisplayMemberPath="Name" Padding="4"
        Style="{StaticResource UndoListBoxStyle}"/>

Also die als UndoItems angebundene Mainviewmodel-Property ist eine ListcollectionView mit Personen drinne, nicht ListboxItems

Der frühe Apfel fängt den Wurm.

301 Beiträge seit 2009
vor 6 Jahren

In meinen Augen ist das was ihr das macht ein Attached Property. Sieht man am einfachsten daran, dass ihr das bestehende Control um ein Property erweitert welches dann dazu führt, dass ein Verhalten ausgelöst wird. Außerdem ist alles in der Klasse auf statischer Ebene implementiert. Zugriff auf eine bestimmte Klasseninstanz ist praktisch nicht vorhanden. Behaviors werden aber ausschließlich über die System.Interactivity.BehaviorCollection an ein Control angehangen.

Ich hab jetzt grade leider nicht so die Zeit euer Beispiel nach meiner Behavior Interpretation umzusetzen aber vielleicht hänge ich das nächste Woche mal hier an.

Hab hier das noch dazu gefunden auch wenn es schon was älter ist :

http://briannoyesblog.azurewebsites.net/2012/12/20/attached-behaviors-vs-attached-properties-vs-blend-behaviors/

PS : Nicht falsch verstehen, ich will damit nicht sagen das das eine schlechter / besser als das andere ist. Aber genau wie der Artikel es bestätigt gibt es keinen namentlichen Konsens darüber was ein Behavior eigentlich ist.

5.299 Beiträge seit 2008
vor 6 Jahren

Jo, das ist genau mein Begriff eines Behaviors: Eine attached Property, die durch Event-Verarbeitung das Verhalten des Controls, an das sie attached ist, modifiziert.

Ich wusste garnet, dass Blend ebenfalls eine Behavior-Technologie bereitstellt, halt eine andere.
Weil ich hab kein Blend.
Daher verstund ich unter "Behavior" im Wpf-Kontext bislang immer attachedBehavior.

Behaviors werden aber ausschließlich über die System.Interactivity.BehaviorCollection an ein Control angehangen. "ausschließlich" ist nu bischen iwie begrifflich "unverschämT" 😉 , weil Behaviors im hier gezeigten Sinne gibts ja schon viel länger und unabhängig von dem Kram, den man zum Framework hinzuinstallieren muss, um System.Interactivity ühaupt verfügbar zu haben.

Aber wenn ich dein Link richtig verstehe, steht das dort auch garnet so. Sondern dort wird zw. attached-Behaviors und blend-Behaviors unterschieden. (Oder? Ich habs nur überflogen, weil englisch lesen ist für mich sehr aufwändig)

Wie dem auch sei - aktuell primäres Problem ist, dass das hier gezeigte Behavior so ühaupt nicht funktioniert (zumindest ich kriegs nicht hin).

Der frühe Apfel fängt den Wurm.

301 Beiträge seit 2009
vor 6 Jahren

Ja vielleicht besteht dort auch der Konflikt. Mir waren sie bisher immer nur als Attached Properties geläufig und nie als Attached Behaviors.

Das Behaviors ausschließlich über die Behaviorcollection angehangen werden bezog sich auf eben solche Blend Behaviors. Denke wer sie noch nicht kennt sollte sie doch mal betrachten auch wenn sie Teil eines externen SDK's sind. Man kann damit viele Problem grade im Bezug auf Memory leaks in WPF lösen.

Aber back 2 topic.

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

Müsste die Problemlösung mit der Blend Behaviors etwa so aussehen?


    <ListView>
        <i:Interaction.Behaviors>
            <local:StackListControlBehavior Farbe="CornflowerBlue"/>
        </i:Interaction.Behaviors>
    </ListView>

Und die Behavior-Klasse dann etwas so:


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Interactivity;
using System.Windows.Media;
using System.Windows.Input;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;

namespace UserBehavoirControls
{
    internal static class Extensions
    {
        public static ListViewItem GetItemAt(this ListView listView, Point clientRelativePosition)
        {
            var hitTestResult = VisualTreeHelper.HitTest(listView, clientRelativePosition);
            var selectedItem = hitTestResult.VisualHit;
            while (selectedItem != null)
            {
                if (selectedItem is ListViewItem)
                {
                    break;
                }
                selectedItem = VisualTreeHelper.GetParent(selectedItem);
            }
            return selectedItem != null ? ((ListViewItem)selectedItem) : null;
        }
    }

    public class StackListControlBehavior : Behavior<ListView>
    {
        private Brush _aktivBrush = Brushes.Transparent;

        #region DependencyProperties

        public static readonly DependencyProperty FarbeProperty =
          DependencyProperty.Register("Farbe", typeof(Color), typeof(StackListControl), 
              new FrameworkPropertyMetadata(Colors.Blue, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
                    null));

        public Color Farbe
        {
            get { return (Color)GetValue(FarbeProperty); }
            set { SetValue(FarbeProperty, value); }
        }

        #endregion

        protected override void OnAttached()
        {
            base.OnAttached();
            this.AssociatedObject.Loaded += this.AssociatedObjectLoaded;
            this.AssociatedObject.MouseMove += this.AssociatedObjectItemMouseOver;
            this.AssociatedObject.MouseLeave += this.AssociatedObjectMouseLeave;
        }

        protected override void OnDetaching()
        {
            base.OnDetaching();
            this.AssociatedObject.Loaded -= this.AssociatedObjectLoaded;
            this.AssociatedObject.MouseMove -= this.AssociatedObjectItemMouseOver;
            this.AssociatedObject.MouseLeave -= this.AssociatedObjectMouseLeave;
        }

        private void AssociatedObjectLoaded(object sender, System.Windows.RoutedEventArgs e)
        {
            LinearGradientBrush gradient = new LinearGradientBrush();
            gradient.StartPoint = new Point(0, 0);
            gradient.EndPoint = new Point(0, 0.5);
            gradient.SpreadMethod = GradientSpreadMethod.Reflect;

            gradient.GradientStops.Add(new GradientStop(this.Farbe, 0));
            gradient.GradientStops.Add(new GradientStop(Colors.White, 1));
            this._aktivBrush = gradient;
        }

        private void AssociatedObjectItemMouseOver(object sender, MouseEventArgs e)
        {
            ListView view = (ListView)sender;
            ListViewItem item = Extensions.GetItemAt(this.AssociatedObject, e.GetPosition(this.AssociatedObject));
            if (item == null)
                return;
            int index = this.AssociatedObject.ItemContainerGenerator.IndexFromContainer(item) + 1;
            this.SetzeBackgrundToItem(0, index, this._aktivBrush);
            this.SetzeBackgrundToItem(index, this.AssociatedObject.Items.Count, null);
        }

        private void AssociatedObjectMouseLeave(object sender, System.Windows.RoutedEventArgs e)
        {
            this.SetzeBackgrundToItem(0, this.AssociatedObject.Items.Count, null);
        }

        private void SetzeBackgrundToItem(int von,  int anzahl, Brush farbe)
        {
            if (von < 0)
                return;

            for (int i = von; i < anzahl; i++)
            {
                (this.AssociatedObject.ItemContainerGenerator.ContainerFromIndex(i) as ListViewItem).Background = farbe;
            }
        }

    }
}

Das richtige Item in der ListView auf Grund der Cursor-Position zu finden war nicht ganz einfach...

5.299 Beiträge seit 2008
vor 6 Jahren

Müsste die Problemlösung mit der Blend Behaviors etwa so aussehen? jo - täte mich auch interessieren. Ich kanns ja nicht testen, weil hab kein Blend.

Zwei weitere Fragen an dich ätte ich:

  1. funktioniert das, wie's soll? Und wie solls eiglich funzen - ist mir immer noch nicht klar: soll einfach das Item unter der Maus gehighlighted sein?
  2. welchen konkreten Gewinn hast du davon gegenüber deiner Lösung hier, von vor 2 Tagen? Die ist immerhin nur ca. 1/3 des Aufwandes.

Der frühe Apfel fängt den Wurm.

16.807 Beiträge seit 2008
vor 6 Jahren

Microsoft Blend (bzw die gesamte Microsoft Expression Suite) ist doch schon ewig (2012) abgekündigt und die Features soweit in VS integriert worden.
Wieso jetzt noch auf Blend-Zeug setzen? Oder ist das davon abgekapselt?

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

Beantworte ich gerne.
Ja es tut das, was ich möchte.
Vielleicht ganz kurz nochmals erklärt was ich wollte. Ich hatte in meiner Applikation eine UnDo- bzw. ReDo-Funktion eingebaut. Ich habe im Anhang ein Bild eingefügt, wie es bei Microsoft z.B. im Excel funktioniert. Im Bild leider nicht zu sehen wie diese Auflistung funktioniert, wenn man mit dem Cursor über die Einträge fährt. Aber dies kann man ja selber ausprobieren.

Übrigens, du benötigst dieses Blend nicht. Du kannst wie von KroaX beschrieben, das notwendige DLL mit NuGet Package Manager von Visual Studio holen. Das reicht...

Es funktionieren beide Version, die ich gemacht habe. Ich sehe den grosse Vorteil in diesem Behavior, dass ich ohne eine Veränderung meiner Funktion das Verhalten eines Elementes verändern kann. Im Prinzip kann ich meine UnDo-Funktion in meine Applikation einbauen, diese testen und fertigstellen und erst nachher mich um das Verhalten der ListView kümmern. Oder ich kann mit einer Zeile ein neues Verhalten einbauen, ohne meine grundsätzliche Funktion ändern zu müssen. Ich glaube dies macht durchaus Sinn und ergibt eine Sicherheit, dass mein Programm bzw. Funktion immer noch funktionieren wird, trotz verändertem sichtbarem Verhalten.

Ich habe was spannendes neu kennengelernt.

301 Beiträge seit 2009
vor 6 Jahren

Wieso jetzt noch auf Blend-Zeug setzen? Oder ist das davon abgekapselt?){gray}

Zu Expression Blend:
Expression Blend als Anwendung existiert jetzt als Blend Suite for Visual Studio und wird seit 2015 ( glaub ich ) direkt mit ausgeliefert.

Zum SDK:
Im WPF Bereich wird sie mittlerweile in jedem großen Framework eingesetzt. Das SDK ist losgelöst vom eigentlichen .NET WPF Framework und dient mehr als Erweiterung.

Zur Umsetzung:
Sieht gut aus. Würde ich glaub ich genauso umsetzen und trifft das wovon ich gesprochen hatte. Vorteil hier : Das Behavior lässt sich an beliebige ListView's anhängen. Zugegben das geht mit Attached Properties auch. Vorteil ist an dieser Umsetzung jedoch, dass man Zugriff auf die konkreten ListView Event's hat und diese behandeln kann. In einem statischen Kontext ist das nicht möglich / schwierig.

Nachteil an dieser Lösung. Es gibt UI Elemente die sich vom Visual Tree lösen und dadurch das Detach aufgerufen wird ( z.b. bei Popup Elementen / Flyouts ). Hier muss meine eine Mechanik einbauen die ein "Re Attach" ermöglich sobald die UI Elemente erneut an den Visual Tree angehangen werden.

5.299 Beiträge seit 2008
vor 6 Jahren

@TE: Hmm - wie gesagt, ich kann nix testen, aber deine Codebehind-Lösung hätteste doch ebensogut jederzeit anpassen können, ohne iwie in die eigliche Funktionalität des Viewmodels einzugreifen?

Sogar wenn im nachhinein die Anforderung auftritt, das Verhalten auch auf annere ListViews zu übertragen - dann kann man ja immer noch die Codebehind-Logik als Behavior extrahieren (Prinzip "Yagni: du brauchst es vlt garnet")

Der frühe Apfel fängt den Wurm.

D
985 Beiträge seit 2014
vor 6 Jahren

@ErfinderDesRades

Jupp, da war ein Fehlerchen - ist korrigiert

5.299 Beiträge seit 2008
vor 6 Jahren

Vielen Dank - nu gehts, und nu kann ich auch was einfaches basteln, wasses auch tut:
Das mit der attached-bool-Property, wo man damm in einem ListboxItem-Style eine Farbe triggern kann, das gefällt mir sehr gut, und das habe ich stark verallgemeinert, nämlich ich hab eine AttachedProp, mit der man nu jedem FrameworkElement IsHovered zuordnen kann, und Trigger können dann das Element hervorheben - ob nu durch BackColor, ForeColor, Fonts etc. ist ja egal.
Den Rest habich wie gesagt im Codebehind belassen, und ist auch nicht viel:


   public partial class MainWindow : Window {
      public MainWindow() { InitializeComponent(); }

      private void ListBox_MouseLeave(object sender, MouseEventArgs e) { SetHoveredUpto((ListBox)sender, null); }

      private void ListBox_MouseMove(object sender, MouseEventArgs e) {
         var listBox = (ListBox)sender;
         var itm = (FrameworkElement)e.OriginalSource;
         var target = listBox.ItemContainerGenerator.ContainerFromItem(itm.DataContext);
         SetHoveredUpto(listBox, target);
      }
      private static void SetHoveredUpto(ListBox listBox, DependencyObject target) {
         var genr = listBox.ItemContainerGenerator;
         var hover = false;
         for (var i = listBox.Items.Count; i-- > 0; ) {
            var itm = genr.ContainerFromIndex(i);
            if (itm == target) hover = true;
            itm.SetValue(FweAttaches.IsHoveredProperty, hover); // setzen der attachten Prop
         }
      }
   }

Die IsHovered-Attached-Prop:


   public static class FweAttaches { // (evtl fällt mir auch noch mehr ein, was an FWEs zu attachen sinnvoll ist)
      public static readonly DependencyProperty IsHoveredProperty = DependencyProperty.RegisterAttached("IsHovered", typeof(bool), typeof(FweAttaches), new PropertyMetadata(false));
      public static bool GetIsHovered(FrameworkElement d) { return (bool)d.GetValue(IsHoveredProperty); }
      public static void SetIsHovered(FrameworkElement d, bool value) { d.SetValue(IsHoveredProperty, value); }
   }

Meine Xaml-Listbox sieht komplizierter aus als die von Sir Rufo, dafür entfällt der externe Style:


    <ListBox Grid.Row="2"  Grid.Column="3" ItemsSource="{Binding Persons}"  DisplayMemberPath="Name" Padding="4" MouseLeave="ListBox_MouseLeave" MouseMove="ListBox_MouseMove">
      <ItemsControl.ItemContainerStyle>
        <Style  TargetType="ListBoxItem">
          <Style.Triggers>
            <Trigger Property="local:FweAttaches.IsHovered" Value="true">
              <Setter Property="Background" Value="{x:Static SystemColors.InactiveSelectionHighlightBrush}"/>
            </Trigger>
          </Style.Triggers>
        </Style>
      </ItemsControl.ItemContainerStyle>
    </ListBox>

Wie gesagt: Solange man dieses Verhalten nur in einer Listbox braucht, ists deutlich einfacher, auf das Behavior-Brimborium zu verzichten - sei es nun im Blend-Style oder im AttachProp-Style umgesetzt.
Auslagern, wenns erforderlich wird, kann man ja immer noch (eben wenns erforderlich wird).
Und was daran mehrfach verwendbar ist - habe ich sogar schon ausgelagert: Diese fabelhafte IsHovered-Prop, die werd ich zukünftig noch öfter bemühen, wenn ich mit ListViews, Listboxen, Treeviews zu tun kriege.

Der frühe Apfel fängt den Wurm.