none
Von einer ComboBox den Index von dem Eintrag, wo IsHighlighted = True ist RRS feed

  • Frage

  • Hallo,
    in einem MVVM-Projekt benötige ich von einer ComboBox den Index von dem Eintrag, wo IsHighlighted = True ist.
    Nicht SelectedItem oder SelectedIndex, da der Eintrag nicht ausgewählt ist!

    Da es dafür eine Eigenschaft gibt hatte ich die Idee, den Index vom ComboBox-Eintrag mit einem Trigger in die Tag-Eigenschaft zu schreiben und diese dann im ModelView auszulesen.

    <ComboBox.Resources>
        <Style TargetType="ComboBoxItem">
            <Style.Triggers>
                <Trigger Property="IsHighlighted" Value="True">
                    <Setter Property="Tag" Value="ComboBoxItem.Index"/>
                </Trigger>                               
            </Style.Triggers>
        </Style>
    </ComboBox.Resources>

    Funktioniert aber nicht. Der Wert wird in Tag nicht gesetzt.
    Wo könnte der Fehler liegen? Gibt es den Value "ComboBoxItem.Index"?
    Oder gibt es eine andere Lösung?

    Alexander

    Dienstag, 5. November 2013 15:04

Antworten

  • Hallo,
    um dein Vohaben zu realisieren, kannst du dir das hier mal ansehen. Dort wird es über eine Ableitung von ComboBox gemacht, die eine Eigenschaft für die "Live-Preview" hat.

    Koopakiller [kuːpakɪllɐ] (Tom Lambert)
    Webseite | Code Beispiele | Facebook | Twitter | Snippets   C# ↔ VB.NET Konverter
    Markiert bitte beantwortende Posts als Antwort und bewertet Beiträge. Danke.

    • Als Antwort markiert AlexanderRi Montag, 11. November 2013 09:17
    Dienstag, 5. November 2013 16:11
    Moderator
  • Hallo,
    man muss natürlich beachten, das LivePreviewItem keinen Index liefert. Mein Fehler. Ich habe es gerade nochmal getestet und es funktioniert mit folgendem Code:
    <Window x:Class="WpfApplication44.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:l="clr-namespace:WpfApplication44"
            Title="MainWindow" Height="350" Width="525">
        <Window.CommandBindings>
            <CommandBinding Command="l:MainWindow.myCmd" Executed="CommandBinding_Executed" />
        </Window.CommandBindings>
        <Grid>
            <Grid.Resources>
                <BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter"/>
            </Grid.Resources>
            <l:LivePreviewComboBox LivePreviewItem="{Binding LivePreviewItemIndex, Mode=TwoWay}"  ItemsSource="{Binding Items}" VerticalAlignment="Top" Margin="10">
                <l:LivePreviewComboBox.ItemTemplate>
                    <DataTemplate>
                        <Grid Tag="{Binding }" HorizontalAlignment="Stretch" Name="grid" >
                            <Grid.ColumnDefinitions>
                                <ColumnDefinition Width="*" SharedSizeGroup="Item"/>
                                <ColumnDefinition Width="Auto"/>
                            </Grid.ColumnDefinitions>
                            <StackPanel>
                                <TextBlock Text="{Binding Caption}"/>
                                <TextBlock Text="{Binding Description}" Foreground="#FF505050" FontStyle="Italic"/>
                            </StackPanel>
                            <Button Command="l:MainWindow.myCmd" Height="20" Width="20" Content="X" Grid.Column="1"  />
                        </Grid>
                    </DataTemplate>
                </l:LivePreviewComboBox.ItemTemplate>
            </l:LivePreviewComboBox>
        </Grid>
    </Window>
        public partial class MainWindow : Window
        {
            public MainWindow()
            {
                this.Items = new ObservableCollection<Item>();
                this.Items.Add(new Item() { Caption = "Caption 1", Description = "Description 1", });
                this.Items.Add(new Item() { Caption = "Caption 2", Description = "Description 2", });
                this.Items.Add(new Item() { Caption = "Caption 3", Description = "Description 3", });
                this.InitializeComponent();
                this.DataContext = this;
            }
            public ObservableCollection<Item> Items { get; set; }
    
    
            public class Item
            {
                public string Caption { get; set; }
                public string Description { get; set; }
            }
    
            public static RoutedUICommand myCmd = new RoutedUICommand("Mein Befehl", "myCmd", typeof(MainWindow));
    
            private void CommandBinding_Executed(object sender, ExecutedRoutedEventArgs e)
            {
                Items.Remove(LivePreviewItemIndex);
            }
    
            public Item LivePreviewItemIndex { get; set; }
        }
    Das sollte auch alles mithilfe von MVVM funktionieren.

    Koopakiller [kuːpakɪllɐ] (Tom Lambert)
    Webseite | Code Beispiele | Facebook | Twitter | Snippets   C# ↔ VB.NET Konverter
    Markiert bitte beantwortende Posts als Antwort und bewertet Beiträge. Danke.

    • Als Antwort markiert AlexanderRi Montag, 11. November 2013 09:16
    Donnerstag, 7. November 2013 19:15
    Moderator

Alle Antworten

  • Hallo,
    um dein Vohaben zu realisieren, kannst du dir das hier mal ansehen. Dort wird es über eine Ableitung von ComboBox gemacht, die eine Eigenschaft für die "Live-Preview" hat.

    Koopakiller [kuːpakɪllɐ] (Tom Lambert)
    Webseite | Code Beispiele | Facebook | Twitter | Snippets   C# ↔ VB.NET Konverter
    Markiert bitte beantwortende Posts als Antwort und bewertet Beiträge. Danke.

    • Als Antwort markiert AlexanderRi Montag, 11. November 2013 09:17
    Dienstag, 5. November 2013 16:11
    Moderator
  • Hallo,

    die Lösung kenne ich bereits. Sie ist für mein Vorhaben zu aufwendig. Deshalb auch meine obige Frage. Wenn ich den Index erhalte, funktioniert der Rest.

    Alexander

    Dienstag, 5. November 2013 17:01
  • Zu aufwendig? Der Code des Controls ist mit den Kommentaren 92 Zeilen lang. Alle öffentlichen Methoden sind sogar XML-Dokumentiert.

    Was ist daran bitte schön aufwendig?


    Koopakiller [kuːpakɪllɐ] (Tom Lambert)
    Webseite | Code Beispiele | Facebook | Twitter | Snippets   C# ↔ VB.NET Konverter
    Markiert bitte beantwortende Posts als Antwort und bewertet Beiträge. Danke.

    Dienstag, 5. November 2013 17:08
    Moderator
  • Hallo Koopakiller,
    ich setze die Lösung jetzt ein. Funktioniert auch.
    Jetzt stehe ich vor dem nächsten Problem: Wie kann ich den Eintrag der ComboBox - wo IsHighlighted = True ist - dem ModelView übergeben?

    Folgendes soll erreicht werden:
    Die ComboBox hat neben jedem Eintrag auch einen ToggleButton zum Löschen in einer Zeile. 
    Dazu habe ich das ItemTemplate geändert:

    <ComboBox.ItemTemplate>
        <DataTemplate>
            <DockPanel LastChildFill="True" >
                <ToggleButton Command="{Binding ElementName=Root, Path=DataContext.RemoveCommand}"   DockPanel.Dock="Right"
    
                 Tag="{Binding Path=TagValue, UpdateSourceTrigger=PropertyChanged}"/>
                <TextBlock Text="{Binding Data}"/>
            </DockPanel>
        </DataTemplate>
    </ComboBox.ItemTemplate>
    
    Der ToggleButton weiß nicht, welcher ComboBox-Eintrag IsHighlighted = True ist.

    Die von Dir vorgeschlagene Lösung funktioniert sehr gut dafür.
    Der ToggleButton hat einen Trigger, welcher das ComboBox-Item mit IsHighlighted = True in die Eigenschaft Tag schreiben soll:

    <Trigger Property="IsChecked" Value="True">
      <Setter Property="Tag" Value="{Binding LivePreviewItem, ElementName=ComboBox_1}" />
    </Trigger> 
    Mit dem ModelView will ich dann Tag auslesen und den Eintrag löschen.

    Das funktioniert nicht, da Tag vom Binding überschrieben wird. Das heißt Binding hat Vorrang gegenüber dem Trigger.

    Geprüft habe ich das ganze mit der Eigenschaft Content:

    <Setter Property="Content" Value="{Binding LivePreviewItem, ElementName=ComboBox_1}" />
    Dann wird im Content vom Button die Zeile mit IsHighlighted = True angezeigt.

    Wenn ich dann auf die Eigenschaft Content ein Binding setze, dann ist auch Content null.

    Was muss ich ändern bzw. wie kann ich das umgehen?

    Alexander


    Mittwoch, 6. November 2013 14:43
  • Die höchste Priorität beim Zuweisen haben Bindungen und direkte zuweisungen. Setter haben die niedrigste. Darum funktioniert das so auch nicht.

    Mir fallen jetzt drei Lösungsansätze ein:

    1. Binde den Index des highlighted-Items an eine Eigenschaft (im ViewModel), die du dann vom Command aus auslesen kannst.
    2. Binde die Tag-Eigenschaft an den Index des highlighted-Items.
    3. Binde die Tag-Eigenschaft an das jeweilige Element der ItemsSource:
      <ToggleButton Tag="{Binding }" ...
      Dadurch erhählst du im Tag das Element, das gelöscht werden soll.

    PS: Reicht zum Löschen eines Items nicht auch ein normaler Button?


    Koopakiller [kuːpakɪllɐ] (Tom Lambert)
    Webseite | Code Beispiele | Facebook | Twitter | Snippets   C# ↔ VB.NET Konverter
    Markiert bitte beantwortende Posts als Antwort und bewertet Beiträge. Danke.


    Mittwoch, 6. November 2013 15:33
    Moderator
  • Hallo Koopakiller,
    danke für Deine Hilfe, aber irgendwie stehe ich auf dem Schlauch.

    zu 1. und 2.
    Wie komme ich an den Index des highlighted-Items?

    zu 2. und 3.
    Das Tag erhält ein Binding, wie von Dir beschrieben. Wie soll ich dann Tag über das ViewModel auslesen?

    Wie schon geschrieben, stehe ich wohl auf dem Schlauch.
    Vielleicht hilft mir eine etwas ausführliche Hilfe.

    Alexander

    Mittwoch, 6. November 2013 16:34
  • zu 1. und 2.
    Die ComboBox aus dem Projekt von Christian Moser hat eine bindbare LivePreviewItem-Eigenschaft. Also würde ich Tag an diese Eigenschaft der ComboBox binden.

    zu 2./3.
    Ich dachtemir das so, das das CommandBinding im Grid innerhalb des DataTemplates registriert wird und die Tag-Eigenschaft des Grids das Element übernimmt:

    <Grid Tag="{Binding }" >
        <Grid.CommandBindings>
            <CommandBinding Command="l:MainWindow.myCmd" Executed="CommandBinding_Executed" />
        </Grid.CommandBindings>
    Im CommandBinding_Executed-Eventhandler kannst du dann das Item vom sender-Parameter bekommen:
          private void CommandBinding_Executed(object sender, ExecutedRoutedEventArgs e)
          {
              Items.Remove((sender as Grid).Tag as Item);
          }
    Es kann aber auch sein, das ich gerade einen Denkfehler in Sachen MVVM habe...



    Koopakiller [kuːpakɪllɐ] (Tom Lambert)
    Webseite | Code Beispiele | Facebook | Twitter | Snippets   C# ↔ VB.NET Konverter
    Markiert bitte beantwortende Posts als Antwort und bewertet Beiträge. Danke.

    Mittwoch, 6. November 2013 17:06
    Moderator
  • Hallo Koopakiller,
    finde ich toll, dass Du mich unterstützt.

    zu 1.
    Wenn ich nicht wie oben beschrieben das highlighted Item über einen Trigger der Eigenschaft Tag dem Button zuweise sondern direkt:
    <ToggleButton Command="{Binding ElementName=Root, Path=DataContext.RemoveCommand}" Tag="{Binding LivePreviewItem, ElementName=ComboBox_1}" />
    Wie kann ich dann aber die Eigenschaft Tag vom Button im ViewModel (also ohne Code-Behind) wieder auslesen?
    Gibt es einen Trick? Muss ich der Eigenschaft Tag außer "{Binding LivePreviewItem, ElementName=ComboBox_1}" noch etwas hinzufügen?

    zu 2. und 3.
    So wie ich das sehe, funktioniert diese Lösung auch nur mit Code-Behind.
    Dann kann ich LivePreviewItem dem Tag vom Button zuweisen und das Click-Handling im Code-Behind auswerten. Das funktioniert auch.
    Nur das will ich, da MVVM verwendet wird.

    Es muss doch eine Möglichkeit geben, dem Tag einen Wert zuzuweisen (wie sieben Zeilen weiter oben beschrieben oder mit einem Trigger) und dies im ViewModel auszuwerten. Also die Priorität vom Binding und direktem Zuweisen umzudrehen.

    Alexander

    Mittwoch, 6. November 2013 17:58
  • Ich habe gerade noch eine etwas andere Idee dazu:
    Im ViewModel erzeugst du eine weitere Eigenschaft P2. Vom View aus bindest du dieLivePreviewItem-Eigenschaft an P2. Beim auslösen des Commands ist die Bindung bereits aktualisiert, weswegen du im ViewModel denn Index des Highlighted Items bekommen kannst.

    Also so hier:

    <l:LivePreviewComboBox LivePreviewItem="{Binding LivePreviewItemIndex}" 
    ItemsSource="{Binding Items, Mode=TwoWay}"
    VerticalAlignment="Top" Margin="10">
    ViewModel:
          private void CommandBinding_Executed(object sender, ExecutedRoutedEventArgs e)
          {
              Items.RemoveAt(LivePreviewItemIndex);
          }
    
          public int LivePreviewItemIndex { get; set; }



    Koopakiller [kuːpakɪllɐ] (Tom Lambert)
    Webseite | Code Beispiele | Facebook | Twitter | Snippets   C# ↔ VB.NET Konverter
    Markiert bitte beantwortende Posts als Antwort und bewertet Beiträge. Danke.

    Mittwoch, 6. November 2013 20:15
    Moderator
  • Hallo Koopakiller,
    die Idee ist sehr gut. Aber sie funktioniert nicht. LivePreviewItemIndex hat immer den Wert 0.
    Sobald ItemsSource auch gesetzt werden kann, scheint es nicht zu funktionierten ("{Binding LivePreviewItemIndex}").
    Im Beispiel "ComboBox with Live Preview" wird LivePreviewItem nur abgefragt.

    Hast Du Deinen Vorschlag mal ausprobiert?

    Alexander

    Donnerstag, 7. November 2013 17:53
  • Hallo,
    man muss natürlich beachten, das LivePreviewItem keinen Index liefert. Mein Fehler. Ich habe es gerade nochmal getestet und es funktioniert mit folgendem Code:
    <Window x:Class="WpfApplication44.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:l="clr-namespace:WpfApplication44"
            Title="MainWindow" Height="350" Width="525">
        <Window.CommandBindings>
            <CommandBinding Command="l:MainWindow.myCmd" Executed="CommandBinding_Executed" />
        </Window.CommandBindings>
        <Grid>
            <Grid.Resources>
                <BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter"/>
            </Grid.Resources>
            <l:LivePreviewComboBox LivePreviewItem="{Binding LivePreviewItemIndex, Mode=TwoWay}"  ItemsSource="{Binding Items}" VerticalAlignment="Top" Margin="10">
                <l:LivePreviewComboBox.ItemTemplate>
                    <DataTemplate>
                        <Grid Tag="{Binding }" HorizontalAlignment="Stretch" Name="grid" >
                            <Grid.ColumnDefinitions>
                                <ColumnDefinition Width="*" SharedSizeGroup="Item"/>
                                <ColumnDefinition Width="Auto"/>
                            </Grid.ColumnDefinitions>
                            <StackPanel>
                                <TextBlock Text="{Binding Caption}"/>
                                <TextBlock Text="{Binding Description}" Foreground="#FF505050" FontStyle="Italic"/>
                            </StackPanel>
                            <Button Command="l:MainWindow.myCmd" Height="20" Width="20" Content="X" Grid.Column="1"  />
                        </Grid>
                    </DataTemplate>
                </l:LivePreviewComboBox.ItemTemplate>
            </l:LivePreviewComboBox>
        </Grid>
    </Window>
        public partial class MainWindow : Window
        {
            public MainWindow()
            {
                this.Items = new ObservableCollection<Item>();
                this.Items.Add(new Item() { Caption = "Caption 1", Description = "Description 1", });
                this.Items.Add(new Item() { Caption = "Caption 2", Description = "Description 2", });
                this.Items.Add(new Item() { Caption = "Caption 3", Description = "Description 3", });
                this.InitializeComponent();
                this.DataContext = this;
            }
            public ObservableCollection<Item> Items { get; set; }
    
    
            public class Item
            {
                public string Caption { get; set; }
                public string Description { get; set; }
            }
    
            public static RoutedUICommand myCmd = new RoutedUICommand("Mein Befehl", "myCmd", typeof(MainWindow));
    
            private void CommandBinding_Executed(object sender, ExecutedRoutedEventArgs e)
            {
                Items.Remove(LivePreviewItemIndex);
            }
    
            public Item LivePreviewItemIndex { get; set; }
        }
    Das sollte auch alles mithilfe von MVVM funktionieren.

    Koopakiller [kuːpakɪllɐ] (Tom Lambert)
    Webseite | Code Beispiele | Facebook | Twitter | Snippets   C# ↔ VB.NET Konverter
    Markiert bitte beantwortende Posts als Antwort und bewertet Beiträge. Danke.

    • Als Antwort markiert AlexanderRi Montag, 11. November 2013 09:16
    Donnerstag, 7. November 2013 19:15
    Moderator
  • Hallo Koopakiller,
    es funktioniert.
    Das LivePreviewItem keinen Index liefert, hatte ich am Ende beachtet.
    Vielen Dank für Deine tolle Unterstützung!

    Alexander

    Montag, 11. November 2013 09:16