Benutzer mit den meisten Antworten
WPF_ListBox Selektierter Index ermitteln?

Frage
-
Hallo zusammen!
Poste hier zum ersten Mal :). Bin Anfänger in WPF (Umsteiger).
Problem: wie kann ich ein einer ListBox (Multiple) den Index des aktuellen Eintrages ermitteln????
Bsp. in einer ListBox mit Fernsehserien befinden sich 20 Einträge. Davon sind selektiert: 1, 3, 7, 13. Bei Durchlauf mit folgender Routine bleibt der Index immer auf 0 stehen. Ich brauche aber 1, 3, 7, 13. (Die Texte (Content) werden korrekt geliefert).
foreach (object item in lst_ListBoxSerien.SelectedItems)
{
if (item is ListBoxItem)
{
Item = item as ListBoxItem;
txt_Serien.Text = txt_Serien.Text + " " + Item.Content.ToString() + "\n";
Index = lst_ListBoxSerien.SelectedIndex;
}wohlgemerkt: ich brauche den Index! des selektieren Items aus der ListBox.
auch der Umweg über:
Index = lst_ListBoxSerien.Items.IndexOf(lst_ListBoxSerien.SelectedItem);
liefert ebenfalls bei jedem Durchgang immer 0.
vielen Dank
Lenny
- Bearbeitet Bolligru Freitag, 18. Mai 2018 16:45
Antworten
-
Hi Lenny,
ein Index der Anzeigefolge ist keine gute Idee. Ggf. kann die Anzeige sortiert und gefiltert werden und dann hat ein Element u.U. in unterschiedlichen Situationen einen anderen Index. Besser ist es, mit Datenobjekten zu arbeiten, wobei jedes Datenobjekt seinen eigenen Index hat. Dieser ist dann unabhängig von der Darstellung. Dazu hier mal eine kleine WPF-Demo mit MVVM Entwurfsmuster:XAML:
<Window x:Class="WpfApp1CS.Window58" 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:local="clr-namespace:WpfApp1CS" mc:Ignorable="d" Title="Window58" Height="300" Width="300"> <Window.Resources> <local:Window58VM x:Key="vm"/> </Window.Resources> <Grid DataContext="{StaticResource vm}"> <Grid.RowDefinitions> <RowDefinition/> <RowDefinition Height="Auto"/> </Grid.RowDefinitions> <ListBox ItemsSource="{Binding View}" SelectionMode="Multiple"> <ListBox.ItemContainerStyle> <Style TargetType="{x:Type ListBoxItem}"> <Setter Property="IsSelected" Value="{Binding IsSelected}"/> </Style> </ListBox.ItemContainerStyle> <ListBox.ItemTemplate> <DataTemplate> <Label Content="{Binding Info}"/> </DataTemplate> </ListBox.ItemTemplate> </ListBox> <Button Grid.Row="1" Content="Anzeige Selected" Command="{Binding Cmd}" Margin="5"/> </Grid> </Window>
Dazu der ViewModel und die Datenklasse:
using System; using System.Collections.ObjectModel; using System.ComponentModel; using System.Linq; using System.Windows; using System.Windows.Data; using System.Windows.Input; namespace WpfApp1CS { public class Window58VM { ObservableCollection<Window58Data> col = new ObservableCollection<Window58Data>(); CollectionViewSource cvs = new CollectionViewSource(); public ICollectionView View { get { if (cvs.Source == null) { for (int i = 1; i < 20; i++) col.Add(new Window58Data() { ID = i, IsSelected = false, Info = $"Zeile {i}" }); cvs.Source = col; } return cvs.View; } } public ICommand Cmd { get { return new RelayCommand(CmdExec); } } private void CmdExec(Object obj) { string msg = "Selected"; foreach (Window58Data item in from d in col where d.IsSelected select d) msg += Environment.NewLine + $"{item.ID} {item.Info}"; MessageBox.Show(msg); } } public class Window58Data { public int ID { get; set; } public bool IsSelected { get; set; } public string Info { get; set; } } }
--
Viele Grüsse
Peter Fleischer (ehem. MVP)
Meine Homepage mit Tipps und Tricks- Bearbeitet Peter Fleischer Samstag, 19. Mai 2018 12:17
- Als Antwort vorgeschlagen Peter Fleischer Samstag, 19. Mai 2018 20:13
- Als Antwort markiert Dimitar DenkovMicrosoft contingent staff, Administrator Freitag, 1. Juni 2018 13:33
-
Hi,
das geht auf mehreren Wegen:
...mit einer Schleife in eine Liste...
List<int> list = new List<int>(); foreach (var item in ListBoxTest.SelectedItems) { list.Add(ListBoxTest.Items.IndexOf(item)); }
...mit LINQ in eine Liste...
List<int> list2 = (from object obj in ListBoxTest.SelectedItems select ListBoxTest.Items.IndexOf(obj)).ToList();
...oder so wie du es vor hast, in eine TextBox...
string s = ""; foreach (var item in ListBoxTest.SelectedItems) { s += ListBoxTest.Items.IndexOf(item).ToString() + Environment.NewLine; } TextBox_Test.Text = s;
Allerdings ist die Vorgehensweise von Peter optimal.
Gruß
Freiberufler im Bereich Softwareentwicklung Von der PLC und Robotik zu VB.NET & C#, vorrangig WPF und UWP
- Als Antwort vorgeschlagen Dimitar DenkovMicrosoft contingent staff, Administrator Mittwoch, 23. Mai 2018 07:01
- Als Antwort markiert Dimitar DenkovMicrosoft contingent staff, Administrator Freitag, 1. Juni 2018 13:33
Alle Antworten
-
Hi Lenny,
ein Index der Anzeigefolge ist keine gute Idee. Ggf. kann die Anzeige sortiert und gefiltert werden und dann hat ein Element u.U. in unterschiedlichen Situationen einen anderen Index. Besser ist es, mit Datenobjekten zu arbeiten, wobei jedes Datenobjekt seinen eigenen Index hat. Dieser ist dann unabhängig von der Darstellung. Dazu hier mal eine kleine WPF-Demo mit MVVM Entwurfsmuster:XAML:
<Window x:Class="WpfApp1CS.Window58" 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:local="clr-namespace:WpfApp1CS" mc:Ignorable="d" Title="Window58" Height="300" Width="300"> <Window.Resources> <local:Window58VM x:Key="vm"/> </Window.Resources> <Grid DataContext="{StaticResource vm}"> <Grid.RowDefinitions> <RowDefinition/> <RowDefinition Height="Auto"/> </Grid.RowDefinitions> <ListBox ItemsSource="{Binding View}" SelectionMode="Multiple"> <ListBox.ItemContainerStyle> <Style TargetType="{x:Type ListBoxItem}"> <Setter Property="IsSelected" Value="{Binding IsSelected}"/> </Style> </ListBox.ItemContainerStyle> <ListBox.ItemTemplate> <DataTemplate> <Label Content="{Binding Info}"/> </DataTemplate> </ListBox.ItemTemplate> </ListBox> <Button Grid.Row="1" Content="Anzeige Selected" Command="{Binding Cmd}" Margin="5"/> </Grid> </Window>
Dazu der ViewModel und die Datenklasse:
using System; using System.Collections.ObjectModel; using System.ComponentModel; using System.Linq; using System.Windows; using System.Windows.Data; using System.Windows.Input; namespace WpfApp1CS { public class Window58VM { ObservableCollection<Window58Data> col = new ObservableCollection<Window58Data>(); CollectionViewSource cvs = new CollectionViewSource(); public ICollectionView View { get { if (cvs.Source == null) { for (int i = 1; i < 20; i++) col.Add(new Window58Data() { ID = i, IsSelected = false, Info = $"Zeile {i}" }); cvs.Source = col; } return cvs.View; } } public ICommand Cmd { get { return new RelayCommand(CmdExec); } } private void CmdExec(Object obj) { string msg = "Selected"; foreach (Window58Data item in from d in col where d.IsSelected select d) msg += Environment.NewLine + $"{item.ID} {item.Info}"; MessageBox.Show(msg); } } public class Window58Data { public int ID { get; set; } public bool IsSelected { get; set; } public string Info { get; set; } } }
--
Viele Grüsse
Peter Fleischer (ehem. MVP)
Meine Homepage mit Tipps und Tricks- Bearbeitet Peter Fleischer Samstag, 19. Mai 2018 12:17
- Als Antwort vorgeschlagen Peter Fleischer Samstag, 19. Mai 2018 20:13
- Als Antwort markiert Dimitar DenkovMicrosoft contingent staff, Administrator Freitag, 1. Juni 2018 13:33
-
Hi,
das geht auf mehreren Wegen:
...mit einer Schleife in eine Liste...
List<int> list = new List<int>(); foreach (var item in ListBoxTest.SelectedItems) { list.Add(ListBoxTest.Items.IndexOf(item)); }
...mit LINQ in eine Liste...
List<int> list2 = (from object obj in ListBoxTest.SelectedItems select ListBoxTest.Items.IndexOf(obj)).ToList();
...oder so wie du es vor hast, in eine TextBox...
string s = ""; foreach (var item in ListBoxTest.SelectedItems) { s += ListBoxTest.Items.IndexOf(item).ToString() + Environment.NewLine; } TextBox_Test.Text = s;
Allerdings ist die Vorgehensweise von Peter optimal.
Gruß
Freiberufler im Bereich Softwareentwicklung Von der PLC und Robotik zu VB.NET & C#, vorrangig WPF und UWP
- Als Antwort vorgeschlagen Dimitar DenkovMicrosoft contingent staff, Administrator Mittwoch, 23. Mai 2018 07:01
- Als Antwort markiert Dimitar DenkovMicrosoft contingent staff, Administrator Freitag, 1. Juni 2018 13:33
-
Hi Peter, Stefan
Vielen Dank für Eure Antworten. Die Lösung von Stefan habe ich sofort verstanden und kann das sicher aus umsetzen. Die Lösung von Peter muss ich mir erst noch in Ruhe ansehen. Ich schätze, da brauche ich noch einiges an Programmiererfahrung, bis ich das wirklich verstanden habe. Jedenfalls habe ich mir beide Lösung erst mal kopiert, um sie in Ruhe auszuprobieren.
@Peter: Missverständnis: habe mich nicht klar ausgedrückt. Ich hatte vermutet, dass die selektierten Einträge eine temporäre Liste bilden (im Beispiel 4 Einträge aus 20) und in dieser temporären Liste einen eindeutigen Index erhalten: 0,1,2,3 (unabhängig von den Einträgen der Original-ListBox). Dann sollte man mit foreach (object item in lst_ListBoxSerien.SelectedItems) diese temporäre Liste durchlaufen und den temporären Index erhalten können. Die Originalindizierung der Einträge in der ListBox bleibt davon unberührt und könnte auch weiterhin nach belieben verändert werden. Wie dem auch sei, ich probiere Eure Lösungen.
vielen Dank für die schnelle Antwort. Wird bestimmt nicht meine letzte Frage gewesen sein :)
schöne Feiertage
Lenny
- Bearbeitet Bolligru Samstag, 19. Mai 2018 14:48
-
Hi Lenny,
Oberflächen-Design (XAML) und Hintergrund-Code (CodeBehind) zu mixen ist zwar einfacher, schneller erstellt und für Ungeübte in kleinen Projekten, die gewohnt sind, linear und nicht objektorientiert zur programmieren, scheinbar übersichtlicher. Es kann aber bei zukünftigen Wartungen bzw. Erweiterungen zu erhöhtem Aufwand und Fehleranfälligkeit führen.Mein Beispiel dagegen nutzt das MVVM Entwurfsmuster, in welchem Oberflächen-Design (XAML) von der Programmlogik total getrennt sind. Die Programmlogik ist im ViewModel (im Beispiel Klasse Window58VM) vollständig enthalten. Der ViewModel braucht die Oberfläche nicht zu kennen. Ihm ist es egal, wie die einzelnen Steuerelemente für die Darstellung heißen. Wichtig sind lediglich die Bezeichner der öffentlichen Eigenschaften, die die Oberfläche "konsumieren" kann. In der Oberfläche selbst wird der ViewModel benötigt (im Beispiel mit dem Schlüssel "vm" im Ressourcen-Wörterbuch des Windows eingetragen). Mit der Nutzung des Eintrages als DataContext (im äußeren Grid) wird automatisch eine Instanz des ViewModels erzeugt und als DataContext im Grid und in den darin eingebetteten Steuerelementen genutzt. Die Steuerelemente selbst binden dann einzelne Eigenschaften an die Eigenschaften des ViewModels.
Der ViewModel stellt in der Eigenschaft "View" eine Sicht auf eine Liste von Datenobjekten bereit. Die Datenobjekte selbst sind vom Typ einer Klasse (im Beispiel Window58Data), die eine ID, eine IsSelected-Eigenschaft und einen Anzeigetext ("Info") enthalten. Die iD wird zur eindeutigen Identifizierung jedes Datenobjektes genutzt. Die Info wird zur Anzeige genutzt. Dier IsSelected-Eigenschaft des Datenobjektes ist über den Style an die IsSelected Eigenschaft der angezeigten Zeile gebunden. Damit braucht der ViewModel die ListBox nicht zu kennen. Wenn eine Zeile in der ListBox selektiert wird, wird automatisch die Eigenschaft IsSelected im Datenobjekt auf "true" gesetzt. Damit kann der ViewModel ohne Kenntnis der ListBox alle selektierten Datenobjekte ermittelt.
Damit im ViewModel die Auswertung der selektierten Objekte gestartet wird, ist in der Oberfläche eine Befehlsschaltfläche (Button) enthalten. Beim Button_Click wird über die gebunden Command-Eigenschaft die Methode CmdExec im ViewModel gestartet. Diese durchläuft einfach die Liste der Datenobjekte und holt sich mittels eines LinQ die Datenobjekte mit IsSelected=true. Diese werden dann einfach mit einer MessageBox angezeigt.
Damit die Command-Bindung so funktioniert, wird die Hilfsklasse "RelayCommend" genutzt, die so aussehen kann:
using System; using System.Windows.Input; namespace WpfApp1CS { public class RelayCommand : ICommand { private readonly Predicate<object> _canExecute; private readonly Action<object> _execute; public event EventHandler CanExecuteChanged; public RelayCommand(Action<object> execute) : this(execute, null) { } public RelayCommand(Action<object> execute, Predicate<object> canExecute) { _execute = execute; _canExecute = canExecute; } public bool CanExecute(object parameter) => (_canExecute == null) ? true : _canExecute(parameter); public void Execute(object parameter) => _execute(parameter); public void RaiseCanExecuteChanged() => CanExecuteChanged?.Invoke(this, EventArgs.Empty); } }
--
Viele Grüsse
Peter Fleischer (ehem. MVP)
Meine Homepage mit Tipps und Tricks