none
Binding mit Index funktioniert nicht RRS feed

  • Frage

  • Hallo,

    Ich habe eine Property (vereinfacht)

    	Private arrText(19) As String
    	Public Property Text(index%) As String
    		Get
    			Return arrText(index)
    		End Get
    		Set(value As String)
    			arrText(index) = value
    		End Set
    	End Property
    
    


    und 20 TextBlocks, die ich im Code lade und an diese Property binde:

    		For n = 0 To 19
    			Dim tbl As New TextBlock
    
    			pathStr = String.Format("Text[{0}]", n)
    			'pathStr = String.Format("Text[(sys:Int32){0}]", n)
    			Dim b As New Binding(pathStr)
    			tbl.SetBinding(TextBlock.TextProperty, b)
    
    			Panel.Children.Add(tbl)
    		Next
    
    


    Ich habe beide Versionen von pathStr ausprobiert, funktioniert aber nicht.

    Was mache ich falsch?

    Freitag, 29. Juli 2011 19:45

Antworten

  • Hier mal eine Demo, wie man das Binding mit Index per Code lösen kann.

    Zuerst der XAML:

    <Window x:Class="Window19"
       xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
       xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
       Title="Window19" Height="300" Width="300">
     <StackPanel Name="Panel"/>
    </Window>
    

    Dazu der Codebehind und der ViewModel:

    Public Class Window19
    
     Private Sub Window19_Loaded(sender As Object, e As 
    System.Windows.RoutedEventArgs) Handles Me.Loaded
      Me.Panel.DataContext = New Window19VM
      For n = 0 To 19
       Dim tbl As New TextBlock
       Dim b As New Binding(String.Format("[{0}]", n))
       tbl.SetBinding(TextBlock.TextProperty, b)
       Me.Panel.Children.Add(tbl)
      Next
     End Sub
    End Class
    
    Public Class Window19VM
    
     Private arrText(19) As String
    
     Public Sub New()
      arrText(0) = "<leer>"
      For i = 1 To arrText.GetUpperBound(0)
       arrText(i) = String.Format("Text {0:00}", i)
      Next
     End Sub
    
     Private _item As String
     Default Public ReadOnly Property Item(ByVal index As String) As String
      Get
       Dim ind As Integer = 0
       Integer.TryParse(index, ind)
       Return Me.arrText(ind)
      End Get
     End Property
    
    End Class
    

    --
    Viele Gruesse
    Peter

    Samstag, 30. Juli 2011 09:26
  • Hallo,

    das geht deutlich einfacher in dem Du das Array selbst zur Verfügung stellst,
    dann greift der Abschnitt aus Deinem Link (deutsch):

    Indexer einer Eigenschaft können in eckigen Klammern nach dem Eigenschaftennamen angegeben werden,
    auf den der Indexer angewendet wird.

    Arrays wie Du wie verwendest sind aber der kleinste gemeinsame Nenner,
    denn sie sind in der Größe unverländerlich. Besser wäre schon eine List(Of T),
    denn dort könnten Elemente hinzugefügt oder gelöscht werden.
    Das Optimum kriegst Du aber, wenn Du eine ObserverableCollection(Of T) verwendest.
    Die ist speziell für die Datenbindung-Möglichkeiten von WPF entworfen worden und
    unterstützt Änderungsbenachrichtigungen.

    Zur Illustration ein kleines Beispiel - wobei ich XAML verwendet habe
    und mich der Kürze halber auf 3 Elemente beschränkt habe:

    <Window x:Class="ElmarBoye.Samples.Wpf.TextWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Bindungen" Height="300" Width="300">
      <StackPanel>
        <StackPanel Orientation="Horizontal" Height="200">
        <StackPanel Margin="8,8">
            <TextBlock x:Name="TextBlock1" Width="120" Text="{Binding Path=Text[0]}"/>
            <TextBlock x:Name="TextBlock2" Width="120" Text="{Binding Path=Text[1]}"/>
            <TextBlock x:Name="TextBlock3" Width="120" Text="{Binding Path=Text[2]}"/>
        </StackPanel>
          <ListBox x:Name="TextListBox" 
               IsSynchronizedWithCurrentItem="True" 
               ItemsSource="{Binding Path=Text}">
            <ListBox.ItemTemplate>
              <DataTemplate>
                <Border Margin="2,2" BorderBrush="Silver" BorderThickness="1">
                  <TextBlock Width="120" Text="{Binding}"/>
                </Border>
              </DataTemplate>
            </ListBox.ItemTemplate>
          </ListBox>
        </StackPanel>      
        <StackPanel Orientation="Horizontal" HorizontalAlignment="Right">
          <Button Margin="8,8" Width="80" x:Name="AddButton" Content="Hinzufügen" Click="AddButton_Click"/>
          <Button Margin="8,8" Width="80" x:Name="RemoveButton" Content="Löschen" Click="RemoveButton_Click"/>
        </StackPanel>
      </StackPanel>
    </Window>
    
    
    dazu der Code-Behind:
    Imports System.Collections.Generic
    Imports System.Collections.ObjectModel
    Imports System.Windows
    
    Namespace ElmarBoye.Samples.Wpf
    	Public Partial Class TextWindow
    		Inherits Window
    
    		Private _text As New ObservableCollection(Of String)() From { _
    			"7. Text", "8. Text", "9. Text" }
    
    '		Private _textArray As String() = New String() {"1. Text", "2. Text", "3. Text"}
    '		Private _textList As New List(Of String)() From { _
    '			"4. Text", "5. Text", "6. Text" }
    
    		Public Sub New()
    			InitializeComponent()
    
    			' Bindung an sich selbst
    			Me.DataContext = Me
    		End Sub
    
    
    		Public ReadOnly Property Text() As ObservableCollection(Of String)
    			Get
    				Return Me._text
    			End Get
    		End Property
    
            ' Array ist unveränderlich und ohne Benachrichtigung
    '		Public ReadOnly Property Text() As String()
    '			Get
    '				Return Me._textArray
    '			End Get
    '		End Property
    
            ' List ist ohne Benachrichtigung
    '		Public ReadOnly Property Text() As List(Of String)
    '			Get
    '				Return Me._textList
    '			End Get
    '		End Property
    
    		Private Sub AddButton_Click(sender As Object, e As RoutedEventArgs)
    			Me.Text.Add("Neue Zeile " & Me.Text.Count.ToString())
    		End Sub
    
    		Private Sub RemoveButton_Click(sender As Object, e As RoutedEventArgs)
    			If Me.Text.Count > 0 Then
    				Me.Text.RemoveAt(0)
    			End If
    		End Sub
    	End Class
    End Namespace
    

    Oben auskommentiert sind die Abschnitte, die für die reine Bindung funktionieren würden.
    Nur das man bei einem Array nicht ohne weiteres hinzufügen/oder löschen kann.
    Und verwendest Du die List(Of) Variante passiert (ohne Nachhilfe) nichts auf dem Bildschirm.

    Daneben habe ich eine ListBox gestellt. Denn damit wird man die (20) Textblöcke los,
    und kann sich (sofern die Voraussetzungen passen) das Programmierer-Leben weiter vereinfachen.

    Gruß Elmar

    • Als Antwort markiert ubsch Samstag, 30. Juli 2011 09:36
    Samstag, 30. Juli 2011 08:29
    Beantworter

Alle Antworten

  • Hi,

    ich persönlich würde eher eine List( Of String ) anstelle des Array nehmen. Sollte aber hier keine Rolle spielen.

    Ich bin nicht so der WPF Mensch, daher rate ich einfach mal :) Es fehlt:

      b.Source = arrText

     


    Gruß, Stefan
    Microsoft MVP - Visual Developer ASP/ASP.NET
    http://www.asp-solutions.de/ - Consulting, Development
    http://www.aspnetzone.de/ - ASP.NET Zone, die ASP.NET Community
    Freitag, 29. Juli 2011 20:39
    Moderator
  • Hallo Stefan,

    danke für deine Antwort. Ich habe vorher natürlich den DataContext entsprechend festgelegt. Ich habe zur Probe, ob die Bindung funktioniert, eine zusätzliche Property gebildet:

    Public ReadOnly Property Text0 As String
    	Get
    		Return Text(0)
    	End Get
    End Property
    


    und in meinem Beispiel pathStr so gebildet:

    pathStr = String.Format("Text{0}", n)

    Dann funktioniert die Bindung an den einen Textblock bestens. Entsprechend könnte ich natürlich auch Properties Text1 bis Text19 bilden. Aber das kann ja wohl nicht die Lösung sein.

    Es muss an dem Indexer der Text-Property liegen.

    Gruß ubsch

    Freitag, 29. Juli 2011 22:21
  • Hallo,

    ich habe jetzt eine Lösung gefunden, indem ich an das Objekt mit der Text-Property binde, einen Converter bilde und den Index an den ConverterParameter übergebe:

    Public Class TextConverter
    	Implements IValueConverter
    
    	Public Function Convert(value As Object, targetType As System.Type, parameter As Object, culture As System.Globalization.CultureInfo) As Object Implements System.Windows.Data.IValueConverter.Convert
    		Dim cls = CType(TryCast(value, TextClass), TextClass)
    		If cls Is Nothing Then Return Nothing
    		Dim ix% = CInt(parameter)
    		Return cls.Text(ix)
    	End Function
    	Public Function ConvertBack(value As Object, targetType As System.Type, parameter As Object, culture As System.Globalization.CultureInfo) As Object Implements System.Windows.Data.IValueConverter.ConvertBack
    		'
    	End Function
    End Class <br/>
    

    und die Bindung:
    		For n = 0 To 19
    			Dim tbl As New TextBlock
    
    			Dim b As New Binding(".")
    			b.Converter = New TextConverter
    			b.ConverterParameter = n
    			tbl.SetBinding(TextBlock.TextProperty, b)
    
    			Panel.Children.Add(tbl)
    		Next
    
    


    Dann ist die Bindung nur ReadOnly, was in meinem Fall reicht, aber alles funktioniert.

    Trotzdem wüsste ich gerne, wie man eine Bindung mit Indexer erstellt. Ich meine, es so gemacht zu haben, wie hier beschrieben:

    http://msdn.microsoft.com/en-us/library/ms752300.aspx#Path_Syntax

    nur in Code statt in XAML

    Ich hoffe noch auf Antwort.

    Gruß, ubsch

    Freitag, 29. Juli 2011 22:49
  • Hallo,

    das geht deutlich einfacher in dem Du das Array selbst zur Verfügung stellst,
    dann greift der Abschnitt aus Deinem Link (deutsch):

    Indexer einer Eigenschaft können in eckigen Klammern nach dem Eigenschaftennamen angegeben werden,
    auf den der Indexer angewendet wird.

    Arrays wie Du wie verwendest sind aber der kleinste gemeinsame Nenner,
    denn sie sind in der Größe unverländerlich. Besser wäre schon eine List(Of T),
    denn dort könnten Elemente hinzugefügt oder gelöscht werden.
    Das Optimum kriegst Du aber, wenn Du eine ObserverableCollection(Of T) verwendest.
    Die ist speziell für die Datenbindung-Möglichkeiten von WPF entworfen worden und
    unterstützt Änderungsbenachrichtigungen.

    Zur Illustration ein kleines Beispiel - wobei ich XAML verwendet habe
    und mich der Kürze halber auf 3 Elemente beschränkt habe:

    <Window x:Class="ElmarBoye.Samples.Wpf.TextWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Bindungen" Height="300" Width="300">
      <StackPanel>
        <StackPanel Orientation="Horizontal" Height="200">
        <StackPanel Margin="8,8">
            <TextBlock x:Name="TextBlock1" Width="120" Text="{Binding Path=Text[0]}"/>
            <TextBlock x:Name="TextBlock2" Width="120" Text="{Binding Path=Text[1]}"/>
            <TextBlock x:Name="TextBlock3" Width="120" Text="{Binding Path=Text[2]}"/>
        </StackPanel>
          <ListBox x:Name="TextListBox" 
               IsSynchronizedWithCurrentItem="True" 
               ItemsSource="{Binding Path=Text}">
            <ListBox.ItemTemplate>
              <DataTemplate>
                <Border Margin="2,2" BorderBrush="Silver" BorderThickness="1">
                  <TextBlock Width="120" Text="{Binding}"/>
                </Border>
              </DataTemplate>
            </ListBox.ItemTemplate>
          </ListBox>
        </StackPanel>      
        <StackPanel Orientation="Horizontal" HorizontalAlignment="Right">
          <Button Margin="8,8" Width="80" x:Name="AddButton" Content="Hinzufügen" Click="AddButton_Click"/>
          <Button Margin="8,8" Width="80" x:Name="RemoveButton" Content="Löschen" Click="RemoveButton_Click"/>
        </StackPanel>
      </StackPanel>
    </Window>
    
    
    dazu der Code-Behind:
    Imports System.Collections.Generic
    Imports System.Collections.ObjectModel
    Imports System.Windows
    
    Namespace ElmarBoye.Samples.Wpf
    	Public Partial Class TextWindow
    		Inherits Window
    
    		Private _text As New ObservableCollection(Of String)() From { _
    			"7. Text", "8. Text", "9. Text" }
    
    '		Private _textArray As String() = New String() {"1. Text", "2. Text", "3. Text"}
    '		Private _textList As New List(Of String)() From { _
    '			"4. Text", "5. Text", "6. Text" }
    
    		Public Sub New()
    			InitializeComponent()
    
    			' Bindung an sich selbst
    			Me.DataContext = Me
    		End Sub
    
    
    		Public ReadOnly Property Text() As ObservableCollection(Of String)
    			Get
    				Return Me._text
    			End Get
    		End Property
    
            ' Array ist unveränderlich und ohne Benachrichtigung
    '		Public ReadOnly Property Text() As String()
    '			Get
    '				Return Me._textArray
    '			End Get
    '		End Property
    
            ' List ist ohne Benachrichtigung
    '		Public ReadOnly Property Text() As List(Of String)
    '			Get
    '				Return Me._textList
    '			End Get
    '		End Property
    
    		Private Sub AddButton_Click(sender As Object, e As RoutedEventArgs)
    			Me.Text.Add("Neue Zeile " & Me.Text.Count.ToString())
    		End Sub
    
    		Private Sub RemoveButton_Click(sender As Object, e As RoutedEventArgs)
    			If Me.Text.Count > 0 Then
    				Me.Text.RemoveAt(0)
    			End If
    		End Sub
    	End Class
    End Namespace
    

    Oben auskommentiert sind die Abschnitte, die für die reine Bindung funktionieren würden.
    Nur das man bei einem Array nicht ohne weiteres hinzufügen/oder löschen kann.
    Und verwendest Du die List(Of) Variante passiert (ohne Nachhilfe) nichts auf dem Bildschirm.

    Daneben habe ich eine ListBox gestellt. Denn damit wird man die (20) Textblöcke los,
    und kann sich (sofern die Voraussetzungen passen) das Programmierer-Leben weiter vereinfachen.

    Gruß Elmar

    • Als Antwort markiert ubsch Samstag, 30. Juli 2011 09:36
    Samstag, 30. Juli 2011 08:29
    Beantworter
  • Hier mal eine Demo, wie man das Binding mit Index per Code lösen kann.

    Zuerst der XAML:

    <Window x:Class="Window19"
       xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
       xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
       Title="Window19" Height="300" Width="300">
     <StackPanel Name="Panel"/>
    </Window>
    

    Dazu der Codebehind und der ViewModel:

    Public Class Window19
    
     Private Sub Window19_Loaded(sender As Object, e As 
    System.Windows.RoutedEventArgs) Handles Me.Loaded
      Me.Panel.DataContext = New Window19VM
      For n = 0 To 19
       Dim tbl As New TextBlock
       Dim b As New Binding(String.Format("[{0}]", n))
       tbl.SetBinding(TextBlock.TextProperty, b)
       Me.Panel.Children.Add(tbl)
      Next
     End Sub
    End Class
    
    Public Class Window19VM
    
     Private arrText(19) As String
    
     Public Sub New()
      arrText(0) = "<leer>"
      For i = 1 To arrText.GetUpperBound(0)
       arrText(i) = String.Format("Text {0:00}", i)
      Next
     End Sub
    
     Private _item As String
     Default Public ReadOnly Property Item(ByVal index As String) As String
      Get
       Dim ind As Integer = 0
       Integer.TryParse(index, ind)
       Return Me.arrText(ind)
      End Get
     End Property
    
    End Class
    

    --
    Viele Gruesse
    Peter

    Samstag, 30. Juli 2011 09:26
  • Hallo Elmar,

    danke für deine ausführliche Antwort.

    Habe ich richtig verstanden, dass eine Bindung mit Indexer nur funktioniert, wenn direkt an eine Auflistung (IEnumerable) gebunden wird?

    Ansonsten ist meine Frage damit beantwortet.

    Gruß ubsch

    Samstag, 30. Juli 2011 09:36
  • Hi Elmar,
    so, wie ich es aber verstanden haben, will der OP dynamisch per Code die Elemente in der Oberfläche erzeugen, mit einer Indexkennung versehen und so binden, dass der Index über eine Eigenschaft das gewünschte Element aus einer Menge holt. Ob da ein Array oder eine Liste genutzt wird, ist sekundär. In meinem parallel gepostetem Beispiel habe ich es mal mit einem Array gezeigt.
     
    --
    Viele Gruesse
    Peter
    Samstag, 30. Juli 2011 09:50
  • Nein,
    das hast Du falsch verstanden. Schau Dir mal mein Beispiel an.
     
    --
    Viele Gruesse
    Peter
    Samstag, 30. Juli 2011 09:51
  • Hallo,

    unter Indexer wird in der Beschreibung verstanden, was C# darunter versteht:
    Indexer (C#-Programmierhandbuch)-

    Peter zeigt es in seinem Beispiel in Visual Basic, wo es durch Default als Standard-Eigenschaft gekennzeichnet wird
    (und wo C# this verwendet schreibt man in Visual Basic Item (was C# im Hintergrund daraus macht).

    Visual Basic unterstützt Parameter bei beliebigen Eigenschaften - wie Du sie verwendet hast,
    nur die werden von anderen Sprachen (wie C#) nicht unterstützt.
    Und man sollte sie aus Gründen Interoperabilität (vor allem in Klassenbibliotheken) vermeiden.

    Gruß Elmar

    Samstag, 30. Juli 2011 10:10
    Beantworter
  • Hallo Peter,

    die "Dynamik" hatte ich weggelassen, um Alternativen in einem Beispiel zeigen zu können.

    Das Verwenden einer Indexer-Eigenschaft (mit oder ohne ViewModel) ist möglich aber nicht zwingend.

    WPF ist nunmal flexibel bis zum geht nicht mehr.

    Gruß Elmar

     


    Samstag, 30. Juli 2011 10:14
    Beantworter
  • Hallo Peter, Elmar,

    danke für eure Beiträge. Waren sehr hilfreich!

    ubsch

    Samstag, 30. Juli 2011 12:52